Nikita Bobko
Nikita Bobko

Reputation: 634

Git ignore all files except particular ones

I have a lot of files in directory which i wouldn't like them to be tracked by git. So i want to ignore all files except .bashrc, .gitignore and .config/terminator/config for example.

I'm trying to do something like this in .gitignore:

*
!.gitignore
!.bashrc
!.config/terminator/config

.bashrc and .gitignore files are unignored now but still cannot get .config/terminator/config unignored. How can i do this in git?

I have one possible solution but is there more elegant one without specifying subdirectories?

*
!.gitignore
!.bashrc
!.gitconfig

!.config/
!.config/terminator/
!.config/terminator/config

Upvotes: 1

Views: 594

Answers (1)

torek
torek

Reputation: 489638

The short version of the answer is that once Git has ignored some directory, it proceeds not to bother to look inside that directory unless something else already forces it to do so.1 To stop this, you must explicitly list the directory or directories that you don't want ignored, with ! entries:

*
!/.bashrc
!/.config
!.config/terminator
!.config/terminator/config
!/.gitignore

That is:

*

tells Git: ignore everything unless overridden. Then the eventual:

!.bashrc

overrides the ignore rule for .bashrc, and so on. But what Git does is that it starts by reading . to find all entries. Suppose it finds them in alphabetical order: .bashrc, .config, .editorthing, .gitignore, .history, and so on; and suppose we have your original .gitignore file:

  1. Hm, I see .bashrc, let's check on it... ah, line 1 says ignore but 3 says don't, so don't ignore. Result: not ignored.
  2. Now I see .config, let's check on that. Line 1 says ignore, line 2 is about .gitignore, line 3 is about .bashrc, and line 4 says to keep .config/terminator/config. It's not any of those, so line 1 applies! Result: ignored!
  3. Repeat as above.

Arguably, the action in step 2 is idiotic: it's clearly impossible to apply the long rule if the shorter .config is ignored, so the presence of .config/<something> as un-ignored ought to trigger Git to un-ignore .config. And in fact, the newest versions of Git try to be more clever about this, but older ones don't: you must explicitly un-ignore .config (or /.config, which is better here for reasons I won't get into, but that's why I wrote it that way above2).

There is a more elegant trick but it causes Git to be much slower, so you may not want to use it. The trick is to explicitly un-ignore all directories:

*
!*/
!/.bashrc

and so on. But now if you have a directory a with millions of sub-directories and files within a, Git will scan all a and all its subdirectories, whereas before, it would skip it.


1Having a file within .config in the index forces Git to scan .config. However, you'd then run into the same issue with .config/terminator unless the index contained a file within .config/terminator as well.

2OK, I will get into it a little bit. It does not apply in this particular case, but assuming you are un-ignoring .bashrc, if Git were for some reason to scan a/, and a/ contained a file named .bashrc, Git would not ignore a/.bashrc. We do not need the leading slash on .config/terminator, though, because that contains an embedded slash (one not at the end). This makes the match on that name rooted, while matches on .bashrc are non-rooted and hence match a/.bashrc.

Upvotes: 4

Related Questions