Git 全局 ignore 配置原理
背景
不想把某类文件(如 CONTEXT.md、.codegraph)提交到任何仓库时,每个 repo 的 .gitignore 各写一遍太麻烦。Git 提供了跨仓库生效的全局 ignore,但「全局」到底配在哪、core.excludesFile 和网上常说的 XDG 路径是什么关系,容易搞混。本文把原理和容易踩的坑讲清楚。
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 文件所在目录的相对层级;
*只跨一层。不含斜杠的裸文件名才会「在任意层级匹配」。所以忽略各级同名文件,直接写裸名最省事,别加*/。
已跟踪文件不受 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 config。- 验证忽略来源用
git check-ignore -v <path>,输出会指明命中的是哪个文件的哪一行。 - 忽略各级同名文件写裸名(
CONTEXT.md),不要写*/CONTEXT.md,后者只匹配一层深。 - ignore 不影响已跟踪文件,需
git rm --cached才能停止跟踪。