gitignore exceptions: The Good, the Bad and the Ugly
As you know, gitignore files allow us to make ignore exceptions for files and directories using the the exclamation mark !. For multi-level directory structures adding gitignore exceptions can become really tricky.
Let’s take an example. Say we have the following structure on our local:
my-app/
βββ .gitignore
βββ dir1/
βββ foo.txt
βββ bar.txt
βββ baz.txtWe want to ignore everything in my-app directory except the file baz.txt. We simply add to our .gitignore:
*
!baz.txtEasy, right? Pfff… π€
Β
What if we have the following structure, instead:
my-app/
βββ .gitignore
βββ dir1/
βββ foo.txt
βββ bar.txt
βββ baz.txtNow we want to ignore everything in dir1 except foo.txt.
- Ah, cmon…, that’s too easy, here, catch it:
dir1
!dir1/foo.txtSurprise! It will not work. Git will still ignore everything inside the dir1 directory. Don’t believe me? Check it yourself:
As you can see, only baz.txt from the root folder will be added to the index.
Fortunately, the solution is pretty simple here:
dir1/*
!dir1/foo.txt
With an asterisk added, we say: Ignore everything in the folder. While, without an asterisk, we say: Ignore the folder.
When we are not dealing with gitignore exceptions, there’s no difference between using dir/* or just dir. This happens, because, if everything is ignored in the directory, then git will find no meaning in adding it to the index.
But things are changing when we have exceptions in gitignore. And the reason why it’s not working when we simply use dir is that:
It is not possible to re-include a file if a parent directory of that file is excluded.
Git doesnβt list excluded directories for performance reasons, so any patterns on contained files
have no effect, no matter where they are defined.Β
- A-ha… I get it now. π
Are you sure? If you are, then let’s take a look at another example. This time we have the following structure:
my-app/
βββ .gitignore
βββ dir1/
βββ foo.txt
βββ dir2
βββ bar.txt
βββ baz.txt
βββ qux.txtAnd now, we want to ignore everything in dir1 but keep the baz.txt inside dir2. So, in the end, we only want to have qux.txt and dir1/dir2/baz.txt in the index.
- Easy, here you go, boomer!
dir1/*
!dir1/dir2/baz.txtWait, mmm… this will not work because dir1/* will ignore the dir2, and since it’s ignored we can’t re-include baz.txt. Give me a minute… π€ Maybe…
dir1/dir2/*
!dir1/dir2/baz.txt
( ... π€ )
dir1/* ... ( No, this will ignore the dir2 again... π )Ok, I give up. What can we do? You, paranoid! π€¬
__________
Don’t panic! It has a solution, even if it’s ugly. We need to add .gitignores inside dir1 and dir2.
my-app/
βββ .gitignore
βββ dir1/
βββ .gitignore
βββ foo.txt
βββ dir2
βββ .gitignore
βββ bar.txt
βββ baz.txt
βββ qux.txtIn .gitignore inside dir2 we need to have:
*
!baz.txtAnd, in .gitignore inside dir1:
*
!dir2Don’t forget to empty any directives you had for dir1 and dir2 in .gitignore in the root directory.
And, --dry-run again, to check π
This solution works, however, I don’t think using multiple gitignores in different tree levels is a good sign. I suggest doing the maximum on your behalf to avoid such situations. Cheers! πΎ
Β Β