Find the Ignore Rule Hiding Your File with git check-ignore

Use git check-ignore to find out whether Git is ignoring a path and exactly which rule is doing it. The examples use a disposable test repository first, then show the same checks for a real working tree. Allow about ten minutes. You need Git and a repository containing the path you want to inspect.

Checkpoint: this command diagnoses ignore behaviour. It does not edit .gitignore, remove files, untrack files or change the index. No command in this guide needs elevated privileges.

1. Confirm the installed Git version

The local command and manpage are from Git 2.43.0. Check your own installation before copying output into a script, because diagnostic wording can vary between releases:

$ git --version
git version 2.43.0
$ git check-ignore -h
usage: git check-ignore [options] <pathname>...

The options used below are documented by the installed Git 2.43.0 manpage. The current upstream documentation describes the same core interface.

2. Check one path in an existing repository

Run the command from the repository, or give it a path that Git can resolve from your current repository:

$ git check-ignore path/to/build/output.o
path/to/build/output.o

Seeing the path means at least one exclude rule matches it. Seeing no output means Git did not find a matching rule. In a shell check, trust the exit status over the output:

$ git check-ignore path/to/build/output.o
$ printf 'status: %s\n' "$?"
status: 0
$ git check-ignore path/to/source/main.c
$ printf 'status: %s\n' "$?"
status: 1

Warning: do not treat an empty line of output as proof that a path is safe to add unless you also understand whether it is tracked.

3. Show the source file, line and pattern

Add --verbose when the result needs an explanation:

$ git check-ignore --verbose path/to/build/output.o
.gitignore:4:build/\tpath/to/build/output.o

The fields are the rule source, the line number, the pattern and the queried path. A repository-local .git/info/exclude or per-directory exclude file is reported relative to the repository root. A rule from core.excludesFile is reported with its absolute source path. The separator between the pattern and path is a hard tab, shown above as \t for readability.

This is usually the fastest route to the fix: open the named source file at the named line, then decide whether the rule is intentional. If you are changing a shared ignore rule, review the effect on other contributors first. This command itself makes no change, so there is no undo step.

4. Build a small test repository for confusing rules

When a real repository has many rules, reproduce the pattern in a temporary directory. These commands create files under /tmp only. They do not touch your project or its index:

$ TEST_DIR=$(mktemp -d /tmp/check-ignore.XXXXXX)
$ cd "$TEST_DIR"
$ git init -q demo
$ cd demo
$ printf 'build/\n*.secret\n!important.secret\n' > .gitignore
$ mkdir -p build src
$ : > build/output.o
$ : > src/config.secret
$ : > src/important.secret
$ : > src/main.c
$ git add .gitignore src/important.secret
$ git commit -qm init

The build/ rule ignores the build directory, the wildcard ignores secret files, and the later negation exempts important.secret.

Warning: the explicit git add is safe here only because this is the disposable repository. Do not copy a broad git add command into a valuable worktree without reviewing its paths.

Query all four paths with one input stream:

$ printf '%s\n' build/output.o src/config.secret src/important.secret src/main.c \
    | git check-ignore --stdin --verbose --non-matching
.gitignore:1:build/\tbuild/output.o
.gitignore:2:*.secret\tsrc/config.secret
::\tsrc/important.secret
::\tsrc/main.c

With --non-matching, unmatched paths are included. Their source, line and pattern fields are empty, so you can tell "not matched" from delayed output in a long-running process. The exact file path and line numbers will change if you edit the test file.

5. Investigate a tracked file with --no-index

By default, tracked files are not reported, because ignore rules apply to untracked paths. This often explains an apparent contradiction: a file matches a pattern, but git check-ignore prints nothing because the file is already in the index.

$ git check-ignore src/important.secret
$ printf 'normal status: %s\n' "$?"
normal status: 1
$ git check-ignore --no-index --verbose src/important.secret
.gitignore:3:!important.secret\tsrc/important.secret

--no-index tells the diagnostic to ignore the index and evaluate the exclude rules as though the path were not tracked. Use it to check patterns after git add -f, or to understand why a path became tracked after a broad add. It does not untrack the file and it does not rewrite any Git state.

6. Make scripts safe for unusual path names

--stdin reads one pathname per line. That suits ordinary paths, but newline characters are legal in Unix file names. Use -z when another command supplies NUL-separated names, and keep the output NUL-separated too:

$ printf 'build/output.o\0src/config.secret\0' \
    | git check-ignore --stdin --verbose --non-matching -z \
    | od -An -tx1c
 2e 67 69 74 69 67 6e 6f 72 65 00 31 00 62 75 69
  .  g  i  t  i  g  n  o  r  e  \0  1  \0  b  u  i

With both options, each output field is NUL-delimited: source, line number, pattern, pathname and a final NUL. Do not parse verbose output by splitting on spaces or colons, because patterns and paths can contain characters that make that ambiguous. Use a NUL-aware reader in production tooling.

7. Avoid the common diagnostic traps

Warning: do not respond to an unwanted ignore rule by deleting files or running a forced add blindly. First identify the rule, decide whether it should change, and review the resulting status with git status --short.

Tip: if you created the disposable repository above, it is safe to leave it in /tmp or to remove that specific directory after checking its path. Do not use a broad recursive deletion target.

Done means