gitignore exceptions: The Tricky Parts
Sometimes, when pushing our changes to remote, we want to ignore everything in the directory except some files. In .gitignore
we must use the exclamation mark ! to do that.
Β
Let’s say we have the following structure in our local:
my-app/
βββ .gitignore
βββ dir1/
βββ file1
βββ file2
βββ file3
And, we want to, for example, ignore everything in my-app
except file3
. We simply add to our .gitignore
:
*
!file3
Easy, right? Pfff… π€
Β
Let’s take another example. This time we have the following structure:
my-app/
βββ .gitignore
βββ dir1/
βββ file1
βββ file2
βββ file3
And, we want to ignore everything in dir1
except file1
.
- Ah, cmon…, that’s too easy, here, catch it:
dir1
!dir1/file1
π₯ Padam! And… I don’t want to disappoint you but this will not work. Don’t believe me? Check it yourself:
As you can see, only file3
from the root folder will be added to commit, above.
Fortunately, the solution is pretty simple here:
dir1/*
!dir1/file1
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 commit.
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 the final example. This time we have the following structure:
my-app/
βββ .gitignore
βββ dir1/
βββ file1
βββ dir2
βββ file2
βββ file3
βββ file4
And now, we want to ignore everything in dir1
but keep the file3
inside dir2
. So, in the end, we only want to have file4
and dir1/dir2/file3
in the commit.
- Easy, here you go, boomer!
dir1/*
!dir1/dir2/file3
Wait, mmm… this will not work because dir1/*
will ignore the dir2
, and since it’s ignored we can’t re-include file3
. Give me a minute… π€ Maybe…
dir1/dir2/*
!dir1/dir2/file3
( ... π€ )
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 .gitignore
s inside dir1
and dir2
.
my-app/
βββ .gitignore
βββ dir1/
βββ .gitignore
βββ file1
βββ dir2
βββ .gitignore
βββ file2
βββ file3
βββ file4
In .gitignore
inside dir2
we need to have:
*
!file3
And, in .gitignore
inside dir1
:
*
!dir2
Don’t forget to empty any directives you had for dir1
and dir2
in .gitignore
in the root directory.
And, --dry-run
again, to check π
Although this solution works fine, I don’t think using multiple .gitignore
s in different tree levels is a good sign. I suggest doing as maximum as you can to avoid this kind of situation. Cheers! πΎ
Β Β
Icons made by DinosoftLabs and smalllikeart from flaticon.com.