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
- App configuration files (i.e.
- Build artifacts (i.e.
- Installed dependencies (i.e.
- Non-documenation and personal text files (i.e.
- Application data and logs (i.e.
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.
- Generated or minified source code (i.e.
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.
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
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
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:
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
node_modules are now gone from the "Untracked files" list, but we still have a few that we want to get rid of,
.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 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:
Free eBook: Git Essentials
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
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:
- 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.
- Use the
-fflag) when staging your files, i.e.
git add .env --force
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.