.gitignore does not unignore my file!
Tags: git, programming, ignore, rails
Published: 2022-09-12 13:35:09

---
title: .gitignore does not unignore my file!
published: true
date: 2022-09-12 13:17:11 UTC
tags: git,programming,ignore,rails
canonical_url: https://blog.narnach.com/blog/2022/dot-gitignore-does-not-unignore-my-file/
---

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.

## What’s .gitignore?

Anyone using [git](https://git-scm.com/) 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 status`, `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.


```bash
# Lets git NOT ignore .gitkeep files, i.e. always check them in:
echo "!.gitkeep" >> .gitignore
touch tmp/pids/.gitkeep
git status
# ... tmp/pids/.gitkeep is NOT listed as new file
```

## 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!)

```
/tmp/*
```

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](https://git-scm.com/docs/gitignore), 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

Let’s update `.gitignore` using our new knowledge. The `git diff` after my changes:

```diff
 /tmp/*
+!/tmp/pids
+/tmp/pids/*.pid
+!.gitkeep
```

The order of operations here is:

- Ignore `/tmp/*` and all its children
- Add `/tmp/pids` back
- Ignore all `.pid` files in `tmp/pids` (because those are the only ones generated there)
- Globally enable `.gitkeep`

## 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](http://twitter.com/narnach) or [email](mailto:wes@narnach.com). I’m an experienced [Ruby software developer](https://www.narnach.com/) with a focus on back-end systems and an obsession with code quality. I even have a few Ruby on Rails [maintenance services](https://www.rormaas.com/) that I offer. If you still need to upgrade to Rails 6, then grab the handy free [checklist](https://www.rormaas.com/rails_upgrades.html#our-rails-upgrade-process) or reach out to have me do it for you.