Git: Ignore Files with .gitignore

Git is a great tool for tracking all of the files in a project, whether you have only a few to track or thousands. But just because a file exists in your project doesn't mean you automatically want to keep track of it and its changes over the lifetime of the project.

In this article we'll see a bit of background on why it's important to tell Git to ignore certain files, how to actually do it, and some best practices.

Why use .gitignore?

Manually ignoring files that you don't want to track is manageable on a small-scale project, but as soon as your project starts to grow and include tens or hundreds of non-version controlled files, it can then become a big problem. These files will start to clutter the "Untracked files" list, which may cause you to overlook legitimate files that need to be in the repo.

To help with this problem, Git has an "ignore" mechanism in the form of a file called .gitignore. With this file and very simple pattern matching, you can tell Git which types of files you want it to ignore and not track in your repo. If a filename in your project matches one of the patterns in the .gitignore file then Git won't attempt to track the file and it won't show up in the "Untracked files" list.

What Files to Ignore

A lot of this comes down to personal preference, but in general I tend to follow these general rules on which files to not track:

  • System files (i.e. Mac's .DS_Store)
  • App configuration files (i.e. app.config, .env, etc.)
  • Build artifacts (i.e. *.pyc)
  • Installed dependencies (i.e. node_modules)
  • Non-documenation and personal text files (i.e. todo.txt)
  • Application data and logs (i.e. *.log, *.sqlite, etc.)

There are quite a few other files types that are often ignored, but a lot of this comes down to personal preference, like the following:

  • Dev configuration files (i.e. .jshintrc)
  • Generated or minified source code (i.e. *.min.js)
  • .gitignore

Yes, even tracking of the .gitignore file itself is debated.

The advice given here may change depending on who you talk to, so you may want to take some of this with a grain of salt. Everyone has their opinions on what should or shouldn't be tracked, so your best option is to review both sides of the debate and then make a decision for yourself based on what's best for your project.

Using .gitignore

The basics of using this ignore feature are pretty straight forward, which is what we'll go over in this section.

For the sake of our example, let's say we have a new project with the following untracked files:

$ git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .DS_Store
    .env-dev
    .env-prod
    index.js
    node_modules/
    package-lock.json
    package.json

It's a bit pointless to track the .DS_Store and node_modules/ files in our repo, so we'll want to ignore these. To do so, we'll first create a file in the project's root directory named .gitignore:

$ touch .gitignore

The simplest way to ignore a file, and most common, is to simply add the full filename to the ignore file. So to ignore the above files, for example, we'll want to add the following:

.DS_Store
node_modules

Once saved, Git will now show us the following untracked files:

$ git status

...

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .env-dev
    .env-prod
    .gitignore
    index.js
    package-lock.json
    package.json

Both .DS_Store and node_modules are now gone from the "Untracked files" list, but we still have a few that we want to get rid of, .env-dev and .env-prod. To avoid having to explicitly add each to .gitignore (especially if we have to add more of these files for test and staging environments), we'll use a wildcard:

.DS_Store
node_modules
.env-*

And now our "Untracked files" list has been reduced even further:

$ git status

...

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .gitignore
    index.js
    package-lock.json
    package.json

The wildcard character (*) will match anything except for a slash. And using two wildcard characters in a row (**) followed by a slash will match your pattern in all directories. So, for example, public/**/*.min.js will ignore all minified JavaScript files in the public directory, regardless of how many directories deep they are.

The Git ignore mechanism also supports some simple regular expression-like syntax, with some extra syntax of its own:

  • [a-zA-Z]: Match a range of characters
  • ?: Match 0 or 1 occurrences of the preceding element
  • !: Negates the match or preceding character

Git also allows you to add comments to this file, which start with a #. This is very useful for organizing the file or adding explanations about why certain patterns were added.

Hierarchy of .gitignore Files

Git actually checks more than just the local .gitignore file for which files it should ignore. As we've seen so far, the most common location is to place a .gitignore file in the root directory of your project. Another option is to have a .gitignore file nested in a directory in your project. While less common in practice, this can be useful for applying an entire ignore file on a subdirectory that has many rules of its own.

Another useful feature is a global ignore file. This is typically a .gitignore file placed in your home directory:

$ touch ~/.gitignore

If needed, you can change the location of this global file using the following command:

$ git config --global core.excludesFile ~/.gitignore

Any patterns placed in this file should be for file types that you are positive you'll never want to track, like the .DS_Store file for you Mac users. It's easy to forget about this global ignore file, which can cause a lot of confusion or problems when you miss committing a file because it was ignored globally.

Committing Ignored Files

Let's say you have an exception you want to make for a file that's usually ignored, but for whatever reason this project in particular needs it. In cases like this you have a few options:

  1. Tell Git to not ignore this file by prefixing the file name with a ! in .gitignore, i.e. !.env. This will override any global ignore files or ignore files in parent directories.
  2. Use the --force option (or -f flag) when staging your files, i.e. git add .env --force

Conclusion

In this article we saw how Git provides a mechanism for us to tell it which files should not be tracked in our repo, preventing us from having to manually prevent files from being added. This is a powerful feature that provides a rich syntax, as well as a hierarchy to better control which files are ignored and which aren't.