0

I am using the official Visual Studio .gitignore.

https://github.com/github/gitignore/blob/main/VisualStudio.gitignore

I have projects in a nested folder, say:

/MySolution/src/MyProject1/SqlServerTypes/...
/MySolution/src/MyProject2/SqlServerTypes/...
/MySolution/src/MyProject3/SqlServerTypes/...
/MySolution/src/MyProject4/SqlServerTypes/...
...
/MySolution/src/MyProjectN/SqlServerTypes/...

There can be multiple projects but I need to whitelist SqlServerTypes folder and all its contents on any path. How is this possible?

!SqlServerTypes/*
!SqlServerTypes/**
!SqlServerTypes/**/*.dll
!SqlServerTypes/x64/*.dll
!SqlServerTypes/x86/*.dll

This did not work for me. It is placed at the bottom of the .gitignore file. When writing git rm -r --cached . and git add . in the root folder, the .dlls from my SqlServerTypes folders are not included.

enter image description here

The contents of the repository as seen on gitlab.com:

enter image description here

  • as you can see, the dlls and the x64 and x86 folders are not present.

This behavior is required for using SqlServerTypes NuGet package for projects because installing the package, the folder is automatically added to the project, but it is not refreshed after deleted. Because of this, when the files are ignored, the NuGet package does not work correctly, although "installed".

Thank you, any help is very appreciated

user3625699
  • 147
  • 1
  • 9

1 Answers1

0

There's not enough information in your question to be absolutely sure of this (e.g., you might have submodules), but most likely the problem is the usual one with .gitignore directives that is covered by this sentence (which, apparently, many people find confusing) from the documentation:

It is not possible to re-include a file if a parent directory of that file is excluded.

The way to understand this sentence is to realize that Git follows a simple algorithm when scanning a working tree's files and directories (folders):

  1. Read some level of the working tree (starting with the top, but we'll come back to step 1 below).
  2. For each file or directory name found here, e.g., README.txt, .git, file.ext, main.py, main.pyc, subdir, and so on, execute the "check ignore" algorithm.
    • For files:

      If the file isn't ignored, and isn't in Git's index right now, gripe that the file is untracked (git status) or go ahead and add it (git add with any en-masse style add that adds everything and sees this file).

      If the file is ignored and isn't in Git's index right now, be quiet (git status: shut up about untracked-ness) or do nothing (git add: ignore it).

      If the file is ignored but is in Git's index right now, it's not really ignored: check its status (git status) or add it (git add).

    • For directories (folders):

      If it's ignored, don't even read it. If it's not ignored, read it and execute the three rules on its contents.

(Any .git directory containing a repository is always either ignored entirely or converted to a submodule gitlink as appropriate. You cannot force a .git to be included in a repository: Git absolutely refuses to nest repositories.1)

Since rule 3b handles ignored directories by not looking inside them, if Git ever reaches:

/MySolution/src/MyProject1/SqlServerTypes/

and, say, SqlServerTypes itself is ignored, Git won't bother to read the /MySolution/src/MyProject1/SqlServerTypes/ directory.

Now, looking at https://github.com/github/gitignore/blob/main/VisualStudio.gitignore (which you should not just refer to—you should include any important lines from this in your question) I find that lines 24 and 25 read:

x64/
x86/

The trailing slash here tells Git that x64 and x86 should be ignored when they're directories, so Git won't bother to look inside /MySolution/src/MyProject1/SqlServerTypes/x64. You attempted to override this with:

!SqlServerTypes/*

Unfortunately for you, this line means:2

!/SqlServerTypes/*

and not:

!**/SqlserverTypes/*

What you might want here is:

!**/SqlServerTypes/x64/

for instance, so that Git will look inside /MySolution/src/MyProject1/SqlServerTypes/x64/.

Alternatively, if it doesn't include too much, you can simply list !x64/ and/or !x86/ to override the rules from lines 24 and 25.


1The point of refusing nested repositories is security. Early on, Git accidentally allowed people to store a .GIT/ or .Git/, which was not a security hole on typical Linux setups which are case sensitive, but was a security hole on typical Windows and macOS setups which are not. Git has since been smartened-up to reject .git, .gIT, and so on, regardless of upper/lower-case mix.

2The reason for this is a bit complicated. Some .gitignore lines are applied to every name component and some are applied to the full path name achieved so far in the scanning process outlined above. The full-path-name restriction applies if:

  • the name in .gitignore starts with a slash, or
  • the name in .gitignore contains a slash after removing a single trailing slash if present.

That second rule is the one biting you here. The "after removing a single trailing slash if present" is simply because .gitignore entries can be marked as "applies only to a directory name" by appending a slash:

foo/

means do ignore anything whose last name component is foo and is a directory, while:

foo

means do ignore anything whose last name component is foo, directory or not. So that last slash gets stripped. But:

foo/bar

or:

foo/bar/

still contains at least one slash after stripping any trailing slash, so it's treated the same as:

/foo/bar

or:

/foo/bar/

Since .gitignore files can appear at any level within the working tree, these "anchored" rules, as I call them, don't necessarily apply only at the top level. Instead, they apply at the level at which the .gitignore was found. For instance, a working tree containing subdir and subdir/.gitignore at its top level, where subdir/.gitignore lists foo/bar, ignores /subdir/foo/bar/baz if that exists, but not /foo/bar or /foo/bar/baz if those exist.

Note that the scanning process here is heavily involved in this whole directory / file distinction that OSes force upon Git. However, after an en-masse git add .—or in fact, any git add—only files, not any directories, appear in Git's index. You cannot get a directory into Git's index, no matter what you do here.3 And, since Git makes the next commit from whatever is in its index, the absence of directories in Git's index means that no Git commit ever contains anything except files.

3There's one trick that sort-of-works, except that the empty directories eventually turn into gitlinks, which act as half-assed submodules.4 Meanwhile, submodules—which use gitlinks, or index entries with mode 160000—also allow you to store an empty directory in a repository, sort of: the empty directory may wind up with a .git subdirectory. See How can I add a blank directory to a Git repository?

4The gitlink provides the path; a .gitmodules file completes the submodule, and without a .gitmodules file, you end up with a broken submodule, or as I sometimes call it, a half-assed submodule. By fully-assing-up the submodule, we fix that problem, only to find a .git present after running git submodule update --init or using recursive clone. So you might as well just create an empty .gitignore or other dot-file.

torek
  • 448,244
  • 59
  • 642
  • 775