Skip to content

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 路径仍生效

bash
$ 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 会清晰显示各自命中的来源:

bash
# 准备:全局、仓库本地、.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.mda/b/CONTEXT.md 全命中
*/CONTEXT.md 仅一层深 one/CONTEXT.md 命中;a/b/CONTEXT.md
**/CONTEXT.md 任意层级(等价无前缀) CONTEXT.md

验证 */ 只匹配一层的坑:

bash
$ 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 后它的改动照常显示。要让它真正脱离跟踪:

bash
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 才能停止跟踪。

基于 VitePress 构建