跳到主要内容
AZ Tools

.gitignore 测试器

.gitignore 是一份有顺序的规则表,对同一条路径,最后一条匹配的规则获胜。所以「这个文件为什么被忽略」的答案,往往不在你正盯着看的那一行。本工具接收该文件和一组路径,用 `git check-ignore -v` 的方式作答:逐条给出是否被忽略、做出决定的行号、以及那条规则的原文;如果决定来自上级目录而不是文件本身,还会把该目录一并标出。 最后这一点正是大多数 .gitignore 缺陷的藏身之处。git 从不进入被排除的目录,所以一旦 `build/` 被忽略,`!build/keep.txt` 就是一行死代码:文件是通过父目录判定的,那条取反规则根本没有被读到。本工具会专门把这种情况标出来,而不是让你自己猜;它还会列出没有匹配到任何粘贴路径的规则,多年前改过名的 coverage/ 或祖传的 Thumbs.db 往往就是这样浮出水面的。 匹配遵循 git 自己的解释,而不是 shell 通配:不含斜杠的规则在任意深度与文件名比较;含斜杠的规则锚定在 .gitignore 所在目录;结尾的斜杠把它限制为只匹配目录;`*` 不跨越斜杠,而独占一整段路径的 `**` 可以跨越;[0-9]、[!a-z] 这类字符类可用;行尾空格会被丢弃,除非用反斜杠转义;开头的 # 和 ! 可以转义;UTF-8 BOM 与 CRLF 换行会在解析前去掉。由于粘贴的路径不带类型信息,请给目录加上结尾斜杠:`build/` 按目录判定,`build` 按文件判定,仅这一点就会让所有「仅目录」规则的结论反转。 有两件事它无法告诉你。它只读一份 .gitignore,因此子目录里的 .gitignore、.git/info/exclude 以及 core.excludesFile 指定的全局文件都不在考虑之内,而它们都可能推翻这里的结果;它按大小写敏感比较,与 Linux 上的 git 一致,所以在 macOS 或 Windows 上开启了 core.ignorecase 的仓库可能忽略得更多。所有计算都在浏览器内完成,文件与路径都不会上传。

被忽略

5

未被忽略

3

规则数

7

死规则

1

不起作用的取反规则

git 不会进入被排除的目录,因此它下面的 ! 规则永远不会被读取。改为排除内容(dir/*)而不是目录(dir/),取反才会生效。

  • .vscode/settings.json — !.vscode/settings.json (行号 10) · 来自被排除的父目录 .vscode/
路径结论行号决定性规则
src/index.ts未被忽略没有规则匹配
node_modules/react/index.js被忽略2node_modules/来自被排除的父目录 node_modules/
dist/被忽略3dist/
dist/app.js被忽略3dist/来自被排除的父目录 dist/
debug.log未被忽略6!debug.log
logs/debug.log未被忽略6!debug.log
server.log被忽略5*.log
.vscode/settings.json被忽略9.vscode/来自被排除的父目录 .vscode/这条 ! 规则无法生效
没有匹配到任何路径的规则

上面的路径都没有匹配这些行。如果路径清单有代表性,它们就是可以删除的候选。

  • 行号 4 coverage/

按大小写敏感的文件系统、仓库根目录下的单个 .gitignore 进行判定。

使用方法

  1. 把 .gitignore 原样粘贴到第一个框里。注释、空行、CRLF 换行和 BOM 都会按 git 的方式处理。
  2. 在第二个框里逐行写出要测试的路径,相对于仓库根目录。目录请在行尾加斜杠,`logs/` 这类仅目录规则的判定完全取决于它。
  3. 看表格。每一行给出是否被忽略、做出决定的行号和规则原文,也就是 `git check-ignore -v` 打印的三项信息,再加上被排除的父目录。
  4. 留意琥珀色提示框。当一条 `!` 规则确实匹配了路径,却因为父目录已被排除而无法生效时,它就会出现;这正是取反规则看起来没错却毫无作用的常见原因。
  5. 查看底部的死规则清单:没有匹配到任何粘贴路径的行。若路径清单具有代表性,它们就是可以删掉的候选。

常见问题

为什么写了 ! 规则,文件还是回不来?
因为 git 根本没有看到那个文件。遍历目录树时一旦发现某个目录被排除,git 就在那里停下,不再往里走,于是关于该目录内部的任何规则都不会被求值。官方文档说得很直接:如果一个文件的父目录被排除,就无法把它重新包含进来。写了 `logs/` 再写 `!logs/important.log`,日志依旧被忽略,本工具会显示决定来自那条目录规则。正确做法是排除内容而不是目录:先写 `logs/*` 再写 `!logs/important.log` 就可以,因为 `logs/*` 只匹配 logs 里的条目,并不排除 logs 本身。若要恢复整棵子树,标准写法是 `/logs/**`、`!/logs/keep/`、`!/logs/keep/**` 三行。
build 和 /build 有什么区别?
规则里一个斜杠都没有时,它会在任意深度与路径的最后一段名字比较,所以 `build` 会同样忽略 /build、/src/build 和 /a/b/c/build。只要规则里出现了不是最后一个字符的斜杠,它就被锚定在 .gitignore 所在的目录,`/build` 和 `docs/output` 只从那个根往下匹配。这也解释了为什么在根目录 .gitignore 里只写 `node_modules`,连嵌套包里的 node_modules 也一并隐藏了:多数时候这正是大家想要的,但很少是有意写成这样的。想说「只在这里」,就在开头加一个斜杠。
`logs/` 会匹配名为 logs 的文件吗?
不会。结尾的斜杠表示这条规则只匹配目录,因此名为 logs 的普通文件安然无恙,而任意深度的 logs 目录会连同里面的一切被忽略。这一点在本工具里尤其重要,因为粘贴的路径清单不携带类型信息:git 是查看真实文件系统来判断的,而本工具需要你告诉它。目录写 `logs/`,文件写 `logs`;同一个字符串加不加斜杠可能得到完全相反的结论。这不是工具的怪癖,而正是当路径尚不存在时 `git check-ignore` 常常给出意外答案的原因。
** 的三种形式究竟是什么意思?
只有当 `**` 独占一整段路径时才有特殊含义。开头的 `**/foo` 匹配任意深度的 foo,它是不含斜杠的规则本来就有的行为的显式写法。结尾的 `foo/**` 匹配 foo 里的一切,但不匹配 foo 本身,前面那套重新包含的写法之所以成立,靠的正是这一点。位于中间的 `a/**/b` 匹配 a/b、a/x/b 和 a/x/y/b,也就是零个或多个目录。放在其他位置就只是普通的星号:`a**b` 等同于 `a*b`,而单个 `*` 不会跨越斜杠,所以 `src/*.c` 匹配 src/main.c,却不匹配 src/lib/main.c。
规则看起来没错,git 却仍然显示文件被修改。
.gitignore 只对 git 尚未跟踪的文件起作用。文件一旦被提交过,后来再加规则也不会改变什么:git 依然报告它的改动,依然把它提交进去。要先用 `git rm --cached 路径` 取消跟踪(工作区里的文件会保留),并提交这次删除;从下一次提交开始,忽略规则才说了算。本工具回答的是未跟踪文件的问题,与 `check-ignore` 相同,所以一条在这里显示为被忽略的路径,只要还在索引里,就仍会出现在 `git status` 中——两者不一致时,这是第一个该检查的地方。
有多个 .gitignore 时以哪个为准?
git 按固定顺序读取多个来源,越具体的越优先:先是命令行给出的规则,然后是各个 .gitignore 文件,其中更深目录里的文件会覆盖上层的文件,接着是不会被共享的仓库本地规则 .git/info/exclude,最后是 core.excludesFile 指定的全局文件。在同一个文件内部,由最后一条匹配的行决定。本工具只模拟一份 .gitignore,因此如果它的结论与你的仓库不一致,多半是子目录里的 .gitignore、info/exclude 中的某一条或你的全局文件在起作用;在那里运行 `git check-ignore -v`,来源那一列会告诉你是哪个文件。

相关工具