重新认识 .gitignore:目录级规则与 .git/info/exclude

2192 字
11 分钟
重新认识 .gitignore:目录级规则与 .git/info/exclude

我以前理解的 .gitignore 比较简单:用户级忽略文件处理个人习惯,项目根目录里的 .gitignore 处理团队共用的规则。

直到最近排查一个项目中的本地运行目录时,我才注意到,项目的子目录里也可以放 .gitignore。例如,.trellis/.gitignore 里有这样一条规则:

.runtime/

它实际忽略的是 .trellis/.runtime/,不是仓库根目录下的 .runtime/.gitignore 并不只有根目录那一份,而是可以随着目录层级分开维护。

.gitignore 到底在解决什么问题#

.gitignore 告诉 Git,哪些尚未被跟踪的文件或目录不必作为提交候选。它会影响这些操作:

Terminal window
git status
git add .
git ls-files --others --exclude-standard

它不会删除文件,也不会阻止程序生成文件,更不是安全保护。文件被忽略后,Git 默认不会把它作为待提交内容展示或加入索引。

这里最容易踩坑:已经被 Git 跟踪的文件,不会因为后来新增 .gitignore 规则就自动消失。

如果要停止跟踪一个已经提交过的文件,需要先把它从索引中移除,同时保留工作区文件:

Terminal window
git rm --cached path/to/file

然后再把对应路径写入 .gitignore,后续它才不会重新进入提交候选。

目录级 .gitignore 的作用域#

一个仓库可以有这样的结构:

repo/
├── .gitignore
├── src/
│ └── .gitignore
└── tools/
└── .gitignore

判断 src/generated/cache.json 是否被忽略时,Git 会考虑仓库根目录、src/ 以及更靠近目标文件的目录中的 .gitignore。每份文件的规则都从它所在的目录开始,向下覆盖子目录。

例如,仓库根目录的 .gitignore

# 只匹配仓库根目录下的 dist
/dist/
# 没有斜杠,匹配当前目录及所有子目录中的日志文件
*.log

src/.gitignore

# 只匹配 src/generated,不匹配仓库根目录的 generated
/generated/

这里的 / 是相对于当前 .gitignore 所在目录的。于是,src/.gitignore 中的 /generated/ 对应的就是 src/generated/

这也是我看到 .trellis/.gitignore 后才真正意识到的地方。子目录里的忽略文件可以把该目录自己的构建产物、缓存目录或工具运行时文件放在离它们更近的地方,不必把所有细节都堆进根目录的 .gitignore

Git 会从哪些地方读取忽略规则#

Git 通常按下面的优先级读取排除规则,前面的来源优先级更高:

优先级来源适合放什么
1命令行传入的排除参数某一次命令的临时过滤条件
2各级目录中的 .gitignore应随项目共享的规则,以及某个子目录自己的规则
3$GIT_COMMON_DIR/info/exclude当前本地仓库使用、但不想提交给团队的规则
4core.excludesFile 指向的文件当前用户在所有仓库中都适用的规则

对于目录中的 .gitignore,Git 会同时考虑目标文件所在目录和父目录中的规则;更靠近目标文件的规则可以覆盖父目录中的规则。同一层级中,如果多个模式都匹配,后出现的模式决定最终结果。

所以,“全局忽略”“根目录忽略”和“子目录忽略”虽然都提供排除模式,但作用域、共享方式和优先级并不一样。

常见模式怎么解释#

不带斜杠的模式#

*.log

只要文件名匹配,就可以命中当前 .gitignore 目录及其子目录中的日志文件,例如:

app.log
logs/app.log
src/test/app.log

以斜杠开头的模式#

/tmp/

它只匹配当前 .gitignore 所在目录下的 tmp 目录。

在仓库根目录的 .gitignore 中,它匹配 repo/tmp/;在 tools/.gitignore 中,它匹配 repo/tools/tmp/

以斜杠结尾的模式#

cache/

末尾的斜杠表示只匹配目录,不匹配同名普通文件。

使用 ** 跨越目录#

**/generated/

这类模式可以匹配不同层级中的 generated 目录。比如,**/foo/bar 可以匹配 foo/bara/foo/bar 或更深层级的路径。

! 取消忽略#

*.log
!keep.log

这表示先忽略日志文件,再把 keep.log 重新纳入 Git 的候选范围。

但这里有一个限制:如果 keep.log 的父目录本身已经被忽略,Git 不会继续进入这个目录寻找可以恢复的文件。例如:

build/
!build/keep.log

通常不能达到预期,因为 build/ 已经让 Git 跳过了整个目录。可以先保留目录的可访问性,再排除其中不需要的内容:

build/*
!build/keep.log

复杂的 ! 规则最好用 git check-ignore 验证,不要只凭肉眼判断。

.git/info/exclude 是什么#

.git/info/exclude 也是 Git 的忽略规则文件。它位于仓库的 .git 目录中,不会被提交,也不会随着普通的 git clone 复制给其他人。

它适合存放当前仓库的本地忽略配置:

  • 规则只对当前仓库生效;
  • 规则不会出现在提交记录中;
  • 规则不会影响其他协作者;
  • 它比全局忽略更具体,又不会污染项目公共的 .gitignore

比如,只想在当前仓库忽略本地笔记和某个临时目录,可以编辑:

Terminal window
$EDITOR "$(git rev-parse --git-path info/exclude)"

加入:

# 只属于我在这个仓库中的本地文件
notes-local/
review-draft.md

如果当前仓库使用了 worktree,直接写死 .git/info/exclude 不一定稳妥。git rev-parse --git-path info/exclude 会返回当前仓库实际使用的 exclude 文件路径。

规则放在哪里,可以按下面的边界判断:

文件是否提交典型用途
项目根目录或子目录的 .gitignore团队都应该遵守的项目规则
.git/info/exclude仅当前仓库、仅本机工作流需要的规则
core.excludesFile 指向的文件所有仓库都适用的个人规则,如 .DS_Store

所有开发者都会生成的构建目录,应该写进项目 .gitignore;个人使用的编辑器临时文件,放进 .git/info/exclude;所有仓库都要忽略的 macOS .DS_Store,再考虑全局忽略文件。

如何查出究竟是哪条规则生效#

遇到“为什么这个文件没有出现在 git status 中”时,先运行:

Terminal window
git check-ignore -v -- path/to/file

输出通常会包含规则来源、行号、具体模式和目标路径,例如:

.trellis/.gitignore:8:.runtime/ .trellis/.runtime

这比只盯着根目录 .gitignore 猜测可靠得多。

如果目标文件已经被跟踪,默认情况下 git check-ignore 可能不会把它当作普通的 ignored 文件展示。这时可以加上 --no-index,只按工作区的忽略规则检查:

Terminal window
git check-ignore -v --no-index -- path/to/file

其他有用的排查命令:

Terminal window
# 查看被忽略的文件
git status --ignored
# 查看全局忽略文件配置及其来源
git config --show-origin --get core.excludesFile
# 查看当前仓库实际的 info/exclude 路径
git rev-parse --git-path info/exclude

几个容易误判的地方#

忽略不是删除#

.gitignore 不会删除已经存在的文件,也不会清理工作区。它只是改变 Git 对未跟踪内容的判断。

忽略不是安全措施#

.env 写进 .gitignore,只能降低它被误提交的概率。如果密钥已经提交过,应该立即轮换密钥,并按仓库历史清理方案处理;不能把“已经写入 .gitignore”当成泄露风险已经解决。

规则写对了,文件也可能仍然显示#

常见原因包括:文件已经被跟踪、模式相对于错误的 .gitignore 目录、父目录被排除导致 ! 无法生效,或者后面又有一条规则覆盖了前面的规则。

.git/info/exclude 不是团队配置#

它很适合处理我的本地文件,但新同事重新克隆仓库后不会自动拥有这些规则。如果规则对所有人都重要,就应该提交到项目中的 .gitignore

排查时按这个顺序看#

处理 Git 忽略问题时,我通常按下面的顺序判断:

  1. 这个文件是否已经被跟踪?
  2. 目标文件所在目录及父目录中,是否存在 .gitignore
  3. 规则中的 / 是相对于哪一份 .gitignore
  4. 是否有更靠近目标文件的规则或后出现的 ! 规则覆盖它?
  5. 是否还有 .git/info/exclude 或全局 core.excludesFile 在生效?
  6. git check-ignore -v 查看 Git 给出的证据。

这样排查,通常很快就能定位规则的来源:它可能来自某一级目录的 .gitignore,也可能来自当前仓库的 .git/info/exclude 或用户级配置。

顺带一提,目录级忽略文件并不是 Git 2.x 才增加的能力。Git 的早期实现中,v0.99.2 已经出现了按目录读取 exclude 文件的支持,早于 Git 1.0.0。只是我最近才真正把它用起来。

参考:

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

重新认识 .gitignore:目录级规则与 .git/info/exclude
https://blog.sephy.top/posts/gitignore-mechanism-and-info-exclude/
作者
虾米
发布于
2026-07-26
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
虾米
coder
分类
标签
站点统计
文章
69
分类
11
标签
73
总字数
79,999
运行时长
0
最后活动
0 天前
站点信息
构建平台
GitHub Actions
博客版本
Firefly v6.14.3
文章许可
CC BY-NC-SA 4.0