Make Git Ignore the Right Files, and Prove Which Rule Won
You will set up a small, predictable ignore policy for a Git repository, keep personal rules out of the project, and diagnose a path that is unexpectedly ignored. The examples use Git 2.43.0 from the Ubuntu git-man package installed on this machine. Allow about 10 minutes for a first setup. You need a shell, Git, and a repository where you can create test files.
The route
Jump straight to the step you need, or tick off Done means at the end.
Checkpoint: choose where each rule belongs
Put a pattern in the repository's .gitignore when every clone should use it: build output, test artefacts and editor directories shared by the team. Put a repository-only rule in .git/info/exclude when it is useful on your checkout but should not be committed, such as a local scratch directory. Put a personal rule in the file named by core.excludesFile when it should apply across your repositories, such as editor backup files.
The installed manual lists these sources in precedence order: command-line patterns supported by a particular command, nested .gitignore files, $GIT_DIR/info/exclude, then core.excludesFile. Within one level, the last matching pattern wins. This ordering is the reason a shared rule belongs in the shared file rather than being patched with a personal exception.
1. Add shared project rules
From the top level of your repository, create or edit .gitignore. Keep comments and blank lines: they make a pattern file easier to review.
# Build output anywhere below this repository
build/
# Compiled object and archive files
*.[oa]
# Local environment files at the repository root
/.env.local
A trailing slash restricts build/ to directories. A pattern without a slash, such as *.o, can match at any level below the directory containing the ignore file. The leading slash in /.env.local anchors that name at the repository root.
Check the file before moving on:
$ sed -n '1,80p' .gitignore
build/
*.[oa]
/.env.local
2. Keep checkout-specific rules local
Use .git/info/exclude for files that should stay out of this checkout but should not affect colleagues. This file is inside the repository metadata and is not normally committed.
$ cat >> .git/info/exclude <<'EOF'
# Personal scratch files for this checkout
/scratch/
EOF
Do not use this file for a rule that the project depends on. A fresh clone will not receive it, so the same generated files may appear as untracked elsewhere.
3. Set a global personal exclude file
For a rule that belongs to your workstation rather than a project, configure an explicit file. This example creates a directory under your XDG configuration directory and makes Git use it.
$ mkdir -p "$HOME/.config/git"
$ touch "$HOME/.config/git/ignore"
$ git config --global core.excludesFile "$HOME/.config/git/ignore"
$ printf '%s\n' '*~' '.DS_Store' >> "$HOME/.config/git/ignore"
$ git config --global --get core.excludesFile
/home/your-user/.config/git/ignore
Replace /home/your-user in the displayed result with your actual home directory; Git prints the path configured on your machine. If XDG_CONFIG_HOME is set, Git's documented default is $XDG_CONFIG_HOME/git/ignore; otherwise it is $HOME/.config/git/ignore. An explicit setting avoids guessing which location is active.
No elevated privileges are needed. Avoid using sudo: a root-owned global file is usually the wrong policy for your user account.
4. Understand matching before adding exceptions
A pattern beginning with ! reverses an earlier match. This is useful for keeping one file while ignoring a class of files.
# Ignore generated HTML, except the hand-maintained index
*.html
!index.html
The exception must be reachable. If a parent directory is excluded, Git does not search inside it, so a later negation cannot bring a file back. To keep one path under a mostly ignored tree, leave the route open with directory patterns:
/*
!/docs
/docs/*
!/docs/README.md
Here, /* excludes top-level entries, !/docs keeps the directory reachable, /docs/* excludes its contents, and the final rule restores the selected file. Test a structure like this before committing a broad policy. An accidental pattern such as docs/ can make every apparent exception ineffective.
Wildcards do not all cross directories. * and ? match characters except /. Thus foo/* matches foo/test.json and the directory foo/bar, but not foo/bar/file.txt. Use the documented ** forms when you mean arbitrary directory depth, for example cache/** for everything below a repository-relative cache directory.
5. Verify an ignored path and its rule
git status --short --ignored gives a useful overview, but it does not explain the winning pattern. Use git check-ignore with --verbose for that. The command exits successfully when a path is ignored, so the output can be used in scripts.
$ mkdir -p build scratch
$ touch build/output.o scratch/notes.txt
$ git check-ignore --verbose -- build/output.o scratch/notes.txt
.gitignore:2:build/ build/output.o
.git/info/exclude:2:/scratch/ scratch/notes.txt
The columns are the matching source, line number and pattern, followed by the path. Your line numbers will differ if your files have different comments. If the command prints nothing and returns a non-zero status, the path is not ignored by the sources Git considered.
$ git check-ignore --verbose --no-index -- tracked-file.txt
$ printf 'exit status: %s\n' "$?"
exit status: 1
--no-index is useful when checking a path that is already tracked. Normally an ignored rule does not affect tracked files, which is a common source of confusion.
6. Stop tracking a file safely
Adding a tracked filename to .gitignore does not remove it from Git. If the file should remain on disk but no longer be versioned, inspect the path first, add the rule, then remove only its index entry.
$ git ls-files --error-unmatch .env.local
.env.local
$ printf '%s\n' '/.env.local' >> .gitignore
$ git rm --cached -- .env.local
rm '.env.local'
$ git status --short --ignored -- .env.local
D .env.local
!! .env.local
This stages a deletion from the next commit while leaving the working-tree file in place. The !! line confirms that the remaining file is ignored. This is a state-changing command: do not run it on a shared or sensitive file until you have confirmed that removing it from version control is intended.
To undo before committing, restore the index entry and remove the rule you just added:
$ git restore --staged -- .env.local
$ git restore --source=HEAD --staged --worktree -- .gitignore
$ git status --short -- .env.local .gitignore
The second command restores the whole tracked .gitignore, so do not use it if that file contains other uncommitted edits. In that case, edit out only the new line and keep the staged state under review.
Common traps
- Ignored does not mean deleted. Git will not remove an ignored file, and it will not hide a file already in the index.
- A leading slash is relative to the directory containing that
.gitignore. A nested ignore file has a different anchor from the root file. - A trailing slash matches directories, not a regular file or symbolic link with the same name.
- Use a backslash before a leading
#or!when those characters are literal pattern text. Trailing spaces are ignored unless escaped. - Git does not follow symbolic links when reading a working-tree
.gitignore. Store the real rules in the repository file, not behind a symlink.
Done means
- Shared generated files are covered by the committed
.gitignore. - Checkout-only and workstation-only files are in their separate exclude sources.
git check-ignore --verbose -- PATHidentifies the rule for each surprising match.- No tracked file is being mistaken for an ignored file.
- Any untracking operation was reviewed with
git statusbefore committing.