Today I ran into an interesting edge case that my colleague and I could not easily explain until we checked the
git manual. I’m sharing the details because even after using
git for 15 years, I was mildly surprised by the behavior. Luckily, it does make sense now that I understand the underlying reasons.
Anyone using git for version control is likely familiar with the
.gitignore file where you can specify which file name patterns
git will ignore by default when you perform various actions, such as
git diff and
git add. It’s very useful to ignore generated files and temporary files. You can add a line that starts with a
! to make it NOT ignore files with that pattern. Patterns are evaluated top to bottom, and the last pattern that matches a file will determine if it’s ignored or not.
What we did
We wanted to add a simple
.gitkeep file to preserve the
tmp/pids directory in our project on all machines that checked out the project. This arguably trivial operation did not work and took me a while to figure out why it did not work.
1 2 3 4 5
Why it did not work
So why is the file we just told
git we want to NOT ignore still getting ignored? It’s because our
.gitignore already contained this handy line that ignored all temporary files (because they have no place being checked in!)
This line makes
git ignores all files and directories inside
tmp. It’s also the direct reason why our earlier change did not give the result we want. After some reading of the manual, I found this relevant bit (emphasis mine):
An optional prefix “!” which negates the pattern; any matching file excluded by a previous pattern will become included again. 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.
It really makes sense from a performance perspective to not recurse into children of ignored directories to see if they happen to be not ignored.
TL;DR: you need to re-include an ignored parent directory if you want to customize rules for its contents.
How to make git really NOT ignore your file
.gitignore using our new knowledge. The
git diff after my changes:
1 2 3 4
The order of operations here is:
/tmp/*and all its children
- Ignore all
tmp/pids(because those are the only ones generated there)
- Globally enable
Alternative: use the –force
Alternatively, you can use
git add --force tmp/pids/.gitkeep to add it while ignoring
.gitignore rules. Arguably that’s faster than checking the manual and thinking about it, but it also prevents you from learning why it failed to work in the first place.
This worked for me ™, so I hope it helps you as well. If you run into issues, feel free to reach out to me via Twitter or email. I’m an experienced Ruby software developer with a focus on back-end systems and an obsession with code quality. I even have a few Ruby on Rails maintenance services that I offer. If you still need to upgrade to Rails 6, then grab the handy free checklist or reach out to have me do it for you.