How do I whitelist a directory in .gitignore and blacklist a file inside it in ~/.gitignore?

763 Views Asked by At

There are many questions on this topic, but none involve ~/.gitignore.


I have a strange project with a top-level project directory—call it project/—full of garbage, but with at least one important subdirectory—say project/feedme/. I'd like to .gitignore everything in project/, but un.gitignore project/feedme/ and its contents. This is easy with a negated pattern:

project$ cat .gitignore
/*
!/feedme/

I have a file project/feedme/.exrc which I would like to ignore. Being a vi user, I use .exrc files for local editor settings often, and so I have an entry in my ~/.gitconfig (specified by the core.excludesFile Git setting):

project$ cat ~/.gitignore
.exrc

Unfortunately git seems not to want to reignore .exrc:

project$ git status -s
?? feedme/
project$ git add feedme/
project$ git status -s
A  feedme/.exrc

Why does Git not ignore project/feedme/.exrc?


Edit: This section is wrong, as per prosoitos' answer. See my edits at the end of this question, as it has not been completely answered.

Why do I think this should work? The manual page gitignore(5) says so:

Each line in a gitignore file specifies a pattern. When deciding whether to ignore a path, Git normally checks gitignore patterns from multiple sources, with the following order of precedence, from highest to lowest (within one level of precedence, the last matching pattern decides the outcome):

  • […]

  • Patterns read from a .gitignore file in the same directory as the path, or in any parent directory, with patterns in the higher level files (up to the toplevel of the work tree) being overridden by those in lower level files down to the directory containing the
    file. […]

  • […]

  • Patterns read from the file specified by the configuration variable core.excludesFile.

Since patterns from ~/.gitignore are read last, I would have thought that the .exrc would be ignored in the end regardless of local .gitgnores.

A possible explanation is that this (again from the gitignore(5) manual page, emphasis mine)

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

also applies in its dual form (not in the manual page)

It is not possible to exclude a file if a parent directory of that file is re-included.

I would consider this a bug however if this is the case; at least it should be stated clearly in the documentation.


P.S.: My ~/.gitignore works under normal circumstances, so the problem is not there.


Edits

Prosoitos is right, the global ~/.gitignore is read first, so the project/feedme/ subdirectory is re-included after .exrcs are globally ignored. However I still face another oddity.

As I understand the documentation now, putting .exrc on the last line of my ~/.gitignore is equivalent (if project/.git/info/excludes is empty) to putting .exrc on the first line of project/.gitignore. Yet if I do this, project/feedme/.exrc is ignored this time!

project$ cat ~/.gitignore
project$ cat .gitignore
.exrc
/*
!/feedme/
project$ git ls-files --other --ignored --exclude-standard
.gitignore
feedme/.exrc

What is happening here?

2

There are 2 best solutions below

1
On

Since patterns from ~/.gitignore are read last [...]

No. This is where you got it wrong. The section of the Git manual that you quote:

Git normally checks gitignore patterns from multiple sources, with the following order of precedence, from highest to lowest [...]

means that the global .gitignore gets overridden by your local one, not the other way around.

So !/feedme/ in your local .gitignore overrides .exrc in your global one. Consequently, feedme/.exrc is not excluded.

An easy solution would be to add .exrc in your local .gitignore file and to make sure that it is below the line !/feedme/ since:

(within one level of precedence, the last matching pattern decides the outcome)


Note:

Admittedly, the expression "order of precedence" can be confusing.

Here, "higher precedence" means that it is "more important than" (so it overrides).

You seem to have understood it in the sense of operator precedence, where "higher precedence" would mean "executed before", which would lead to the opposite result. Hence the confusion.


Edit after your edit to the question:

Now that you have made corrections to your situation, I think that you are correct.

I reproduced your situation, then ran:

git check-ignore -v feedme/.exrc

and I get:

.gitignore:1:.exrc  feedme/.exrc

showing that the exclusion of .exrc did not get negated by !/feedme/.

I tried to google this and I am also unable to find any documentation on it.

0
On

As I understand the documentation now, putting .exrc on the last line of my ~/.gitignore is equivalent (if project/.git/info/excludes is empty) to putting .exrc on the first line of project/.gitignore.
Yet if I do this, project/feedme/.exrc is ignored this time!

project$ cat ~/.gitignore
project$ cat .gitignore
.exrc
/*
!/feedme/
project$ git ls-files --other --ignored --exclude-standard
.gitignore
feedme/.exrc

You have excluded the folder /feedme/ from the ignore rules, but not its content.
You would need !/feedme/.exrc from that file to not be ignored.