bigywater.tech
Git · 43

.gitignore 的匹配规则与 check-ignore

.gitignore 里的星号、双星号、斜杠到底怎么匹配?取反为什么有时无效?多个 ignore 文件谁优先?这篇不背规则,直接用 git check-ignore -v 对每条模式逐路径验证,看匹配的文件、行号和结果。

父目录
父目录已被忽略时,里面的 !取反 无效(build/ + !build/keep.txt);改用 build/* 才有效
check-ignore -v
显示每条路径被哪个文件第几行的哪条模式匹配;对已跟踪的文件需要 --no-index
已跟踪不受影响
事后把已提交的文件加进 .gitignore,git 仍然跟踪它,要 git rm --cached 才取消

通配符

实测 · 本机执行输出
  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 包含目录)。

分步演示

.gitignore 规则
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。所有结果来自 git check-ignore -v 与 git status;core.excludesFile 指向实验专用目录里的文件;输出里 a.bak 的来源路径显示为简化后的路径
    • 没有验证:核心的 gitignore 实现细节(如 trailing space、转义 \# 与 \!,没有逐项试);忽略大小写的文件系统上 core.ignoreCase 的影响;符号链接是否被忽略;子模块目录与 .gitignore;.gitattributes 的 export-ignore;git check-ignore --stdin 批量模式
    京ICP备2026067502号-1京公网安备11010502063487号