Git 全局 ignore 配置原理
想让某类文件(如 AI 编码工具生成的 CONTEXT.md、.codegraph)不出现在任何仓库里,为每个 repo 的 .gitignore 各写一遍并不现实。Git 提供跨仓库生效的全局 ignore,但「全局」到底配在哪、core.excludesFile 与网上常说的 XDG 路径是什么关系,容易混淆。本文说明三个忽略来源的分工、全局 ignore 不配置时的默认行为、ignore 模式中的路径深度陷阱,以及用 git check-ignore 验证规则来源的方法。
gitignore 的三种来源
Git 读取 ignore 模式共有三个来源,按「作用范围」和「是否共享」区分:
| 来源 | 作用范围 | 是否提交到仓库 | 典型用途 |
|---|---|---|---|
.gitignore | 当前项目 | 是(团队共享) | 项目约定的忽略文件 |
.git/info/exclude | 当前仓库本地 | 否(仅本地) | 个人的、不想写进项目的 |
core.excludesFile | 全部仓库 | 否(个人全局) | 跨所有 repo 的个人偏好 |
关键认知:三者是叠加关系,不是互斥。三个文件里的模式都会生效,文件只要命中其中任意一条就被忽略。
core.excludesFile 与 XDG 默认路径
这里常被误解。core.excludesFile 不是「要么显式配置、要么不生效」——它是一个带默认值的配置项:
- 显式配置:
git config --global core.excludesFile ~/.gitignore_global,指向指定的文件。 - 不配置时,Git 回退到 XDG 默认路径:
$XDG_CONFIG_HOME/git/ignore;若$XDG_CONFIG_HOME未设置或为空,则使用~/.config/git/ignore。
也就是说,哪怕从未执行过 git config --global core.excludesFile,只要 ~/.config/git/ignore 这个文件存在,Git 就会自动把它当作全局 ignore 文件读取(Git 2.0+ 的行为)。
验证:未配置时 XDG 路径仍生效
$ git config --global core.excludesFile # 查询,无输出
$ echo $?
1 # exit 1 = 未设置
# 但 check-ignore 证明 ~/.config/git/ignore 仍被读取
$ mkdir -p a/b && touch a/b/CONTEXT.md a/b/keep.txt
$ git check-ignore -v a/b/CONTEXT.md
/home/user/.config/git/ignore:5:CONTEXT.md a/b/CONTEXT.md # 命中全局文件第5行
$ git check-ignore -v a/b/keep.txt
[未命中] # 符合预期 check-ignore -v 的输出格式是 来源文件:行号:模式 被检查的路径,是验证「某个文件被哪条规则忽略」的标准手段。
结论:如果已经在用 ~/.config/git/ignore,就不需要再执行 git config --global core.excludesFile。一旦显式配置了别的路径,XDG 默认路径就不再被读取(配置覆盖默认)。
三种来源叠加生效
把同一个 repo 里分别由三个来源忽略的文件混在一起,check-ignore -v 会清晰显示各自命中的来源:
# 准备:全局、仓库本地、.gitignore 各忽略一个,再留一个不忽略的
# ~/.config/git/ignore -> global.bin
# .git/info/exclude -> local.txt / proj/repo.txt
# .gitignore -> shared.log
$ for f in global.bin local.txt proj/repo.txt shared.log keep.md; do
git check-ignore -v "$f" || echo " [未忽略]"
done
/home/user/.config/git/ignore:6:global.bin global.bin # 来自全局
.git/info/exclude:1:local.txt local.txt # 来自仓库本地
.git/info/exclude:2:proj/repo.txt proj/repo.txt # 来自仓库本地
.gitignore:1:shared.log shared.log # 来自项目 .gitignore
keep.md [未忽略] 三个来源各司其职、互不干扰,都会生效。
匹配语法:路径深度是个坑
往全局 ignore 里添加模式时,前缀写法直接决定匹配深度。以忽略各级目录下的 CONTEXT.md 为例:
| 写法 | 匹配范围 | 示例命中 |
|---|---|---|
CONTEXT.md | 任意层级(推荐) | CONTEXT.md、a/b/CONTEXT.md 全命中 |
*/CONTEXT.md | 仅一层深 | one/CONTEXT.md 命中;a/b/CONTEXT.md 不命中 |
**/CONTEXT.md | 任意层级(等价无前缀) | 同 CONTEXT.md |
验证 */ 只匹配一层的问题:
$ printf '*/CONTEXT.md\n' > /tmp/star-ignore
$ git -c core.excludesFile=/tmp/star-ignore check-ignore -v one/CONTEXT.md
/tmp/star-ignore:1:*/CONTEXT.md one/CONTEXT.md # 一层深:命中
$ git -c core.excludesFile=/tmp/star-ignore check-ignore -v two/three/CONTEXT.md
[未命中] # 两层深:漏掉
$ printf 'CONTEXT.md\n' > /tmp/plain-ignore
$ git -c core.excludesFile=/tmp/plain-ignore check-ignore -v two/three/CONTEXT.md
/tmp/plain-ignore:1:CONTEXT.md two/three/CONTEXT.md # 无前缀:任意深度都命中 原理:模式里只要含斜杠(不在末尾),就锚定到 ignore 文件所在目录的相对层级,而 * 不匹配斜杠;不含斜杠的裸文件名才会「在任意层级匹配」。所以要忽略各级同名文件,直接写裸名即可,不要写 */CONTEXT.md——它只覆盖一层深度的目录。
已跟踪文件不受 ignore 影响
ignore 规则只对未跟踪的文件生效。如果一个 CONTEXT.md 之前已被 git add 跟踪过,加进 ignore 后它的改动照常显示。要让它真正脱离跟踪:
git rm --cached <path/to/CONTEXT.md> # 从索引移除,保留本地文件
git commit -m "stop tracking CONTEXT.md" 要点
- 全局 ignore 有三个层次:
.gitignore(项目共享)、.git/info/exclude(仓库本地)、core.excludesFile(跨仓库个人),三者叠加生效。 core.excludesFile不显式配置时,默认读 XDG 路径~/.config/git/ignore——文件存在就生效,无需额外配置。- 验证忽略来源用
git check-ignore -v <path>,输出会指明命中的是哪个文件的哪一行。 - 忽略各级同名文件写裸名(
CONTEXT.md),不要写*/CONTEXT.md,后者只匹配一层深。 - ignore 不影响已跟踪文件,需要
git rm --cached才能停止跟踪。
更多 Git 常用操作与历史整理,见 Git 常用命令速查。