.gitignore 里的星号、双星号、斜杠到底怎么匹配?取反为什么有时无效?多个 ignore 文件谁优先?这篇不背规则,直接用 git check-ignore -v 对每条模式逐路径验证,看匹配的文件、行号和结果。
a.log .gitignore:1:*.log → 忽略 sub/deep/b.log .gitignore:1:*.log → 忽略 todo.txt .gitignore:2:/todo.txt → 忽略 sub/todo.txt (没有匹配的模式 → 不忽略) build .gitignore:3:build/ → 忽略 sub/build .gitignore:3:build/ → 忽略 x/tmp .gitignore:4:**/tmp → 忽略 tmp .gitignore:4:**/tmp → 忽略 doc/a.md .gitignore:5:doc/*.md → 忽略 doc/sub/b.md (没有匹配的模式 → 不忽略) logs/a.txt .gitignore:6:logs/**/*.txt → 忽略 logs/x/y/a.txt .gitignore:6:logs/**/*.txt → 忽略 foo1.c .gitignore:7:foo?.c → 忽略 foo12.c (没有匹配的模式 → 不忽略) ax.c .gitignore:8:[ab]x.c → 忽略 cx.c (没有匹配的模式 → 不忽略)
*、?、[...] 不跨目录分隔符,** 才能跨任意多级目录。模式里没有斜杠(除结尾)时匹配任意深度,开头或中间有斜杠就锚定到 .gitignore 所在目录;结尾的斜杠表示只匹配目录。
a.log .gitignore:1:*.log → 忽略
important.log .gitignore:2:!important.log → 不忽略(最后匹配的是取反模式)
build/keep.txt .gitignore:3:build/ → 忽略
build/o.o .gitignore:3:build/ → 忽略
cache/keep.txt .gitignore:6:!cache/keep.txt → 不忽略(最后匹配的是取反模式)
cache/o.o .gitignore:5:cache/* → 忽略
$ git status --short -uall | sed -E "s/^/ /"; echo "(上面是 git status 看到的未跟踪文件)"
?? .gitignore
?? cache/keep.txt
?? important.log
(上面是 git status 看到的未跟踪文件)! 重新包含前面忽略的路径,但 git 不会进入被忽略的目录,所以不能救回里面的文件。想保留目录里的少数文件,要忽略目录内容(dir/*)而不是目录本身。git check-ignore -v 也会打印取反模式,看到 ! 开头就是“不忽略”。
a.txt .gitignore:1:/a.txt → 忽略 sub/a.txt (没有匹配的模式 → 不忽略) b.txt .gitignore:2:b.txt → 忽略 sub/b.txt .gitignore:2:b.txt → 忽略 c/f .gitignore:3:c/ → 忽略 sub/c (没有匹配的模式 → 不忽略) d/e.txt .gitignore:4:d/e.txt → 忽略 sub/d/e.txt (没有匹配的模式 → 不忽略)
/a.txt 只管根目录,b.txt 管所有层,c/ 只管目录,d/e.txt 因为中间有斜杠也被锚定。
a.tmp .gitignore:1:*.tmp → 忽略 sub/a.tmp .gitignore:1:*.tmp → 忽略 sub/keep.tmp sub/.gitignore:1:!keep.tmp → 不忽略(最后匹配的是取反模式) keep.tmp .gitignore:1:*.tmp → 忽略 sub/x.dat sub/.gitignore:2:*.dat → 忽略 x.dat (没有匹配的模式 → 不忽略) a.secret .git/info/exclude:1:*.secret → 忽略 a.bak /work/_home/global_ignore:1:*.bak → 忽略
离文件越近的 .gitignore 优先级越高,同一个文件里后面的行覆盖前面。此外还有 .git/info/exclude(仓库私有,不入库)和 core.excludesFile(全局)。
$ git check-ignore -v secret.env; echo "(退出码 $?:对已跟踪的文件,check-ignore 默认直接说“不忽略”)"; git check-ignore -v --no-index secret.env; echo "(加 --no-index 才忽略 index 里的跟踪状态,看到匹配的模式)"; git status --short; echo "(status 仍然显示它被修改:已跟踪的文件不受 .gitignore 影响)" (退出码 1:对已跟踪的文件,check-ignore 默认直接说“不忽略”) .gitignore:1:secret.env secret.env (加 --no-index 才忽略 index 里的跟踪状态,看到匹配的模式) M secret.env ?? .gitignore (status 仍然显示它被修改:已跟踪的文件不受 .gitignore 影响) $ git rm --cached -q secret.env; git status --short; git ls-files --others --ignored --exclude-standard D secret.env ?? .gitignore secret.env $ git add secret.env 2>&1 | head -3; git add -f secret.env; git status --short The following paths are ignored by one of your .gitignore files: secret.env hint: Use -f if you really want to add them. M secret.env ?? .gitignore
.gitignore 只作用于未跟踪的文件。已经提交的文件事后被加进去,仍然会被跟踪、修改仍显示在 status 里;git rm --cached 取消跟踪后才算被忽略。被忽略的文件要 git add -f 强制添加。
$ git status --short --ignored; git ls-files -o -i --exclude-standard; git clean -n -X | sed -E "s/^/ /"; git clean -n -X -d | sed -E "s/^/ /"
?? .gitignore
?? a.c
!! a.o
!! sub/
a.o
sub/b.o
Would remove a.o
Would remove a.o
Would remove sub/git status --ignored 用 !! 标出,git ls-files -o -i --exclude-standard 列出,git clean -X 只清理被忽略的(-n 先预览,-d 包含目录)。