.gitignore 的匹配规则

2026-02-25 前端

.gitignore 是 Git 项目中用来告诉 Git 哪些文件不需要跟踪的配置文件。看似简单,但它的匹配规则有不少细节——理解这些规则可以让你写出更精确的忽略配置,避免不该提交的文件被误提交,或者该忽略的没忽略。

一、基本语法

注释

以 # 开头的行是注释,会被 Git 忽略。

# 这是注释,不会生效
# 下面是忽略 node_modules
node_modules/

空行

空行同样被忽略,用于分隔不同的规则组,提高可读性。

# 依赖目录
node_modules/
vendor/

# 构建输出
dist/
build/

二、基础匹配模式

逐行规则

.gitignore 文件中,每一行是一条独立的匹配规则。

# 忽略 .log 文件
*.log

# 忽略 node_modules 目录
node_modules/

忽略特定文件

直接写文件名,匹配任意目录下的同名文件:

# 匹配任意目录下的 .env 文件
.env

# 这样会忽略 a/.env、b/.env、.env 等

通配符 *

* 匹配任意数量的任意字符(/ 除外)。

# 匹配所有 .log 文件
*.log

# 匹配以 test 开头的任意文件/目录
test*

# 注意:*.log 会匹配 a.log、b/test.log,但不会匹配 a/b/c.log 吗?
# 实际上会匹配任意层级的 .log 文件!因为 * 不匹配 /,但规则本身是递归的。

这里有个很多人搞混的点:*.log 实际上会匹配任意目录下的 .log 文件,因为 Git 的规则默认是递归的。后面会讲如何限制到当前目录。

单字符通配符 ?

? 匹配任意一个字符(/ 除外)。

# 匹配 a.txt、b.txt,但不匹配 ab.txt
?.txt

# 匹配 1.log、a.log,但不匹配 10.log
??.log

字符集 []

匹配方括号中列出的任意一个字符。

# 匹配 a.txt、b.txt,但不匹配 c.txt
[ab].txt

# 匹配 1.txt、2.txt、3.txt
[123].txt

# 范围:匹配 a.txt ~ f.txt
[a-f].txt

# 取反:匹配不是 a 或 b 的 .txt 文件
[!ab].txt

三、路径匹配

目录分隔符 /

/ 在 .gitignore 中有特殊含义:

# 规则开头的 / —— 仅匹配仓库根目录
/build       # 仅忽略根目录下的 build 文件/目录
             # src/build 不会受影响

# 规则结尾的 / —— 仅匹配目录
dist/        # 仅忽略 dist 目录(及其内容)
             # dist.zip 文件不会被忽略

# 规则中间的 / —— 匹配相对于 .gitignore 位置的路径
docs/out/    # 仅忽略 docs/out 这个具体路径
             # src/docs/out 不受影响

绝对路径 vs 相对路径

规则开头的 / 表示相对于 .gitignore 文件的路径,而不是从系统根开始。

# 只忽略项目根目录下的 dist 目录
/dist

# 以下会忽略任意层级下的 dist 目录(默认递归行为)
dist

深度限制

如果没有 /,规则会递归匹配所有层级。如果有限制 /,只匹配指定路径。

# 递归匹配(默认)
*.log     # 匹配 a.log、src/a.log、src/a/b.log……

# 仅根目录
/*.log    # 只匹配项目根目录下的 .log 文件

# 仅某目录下
build/*.log  # 匹配 build/a.log,但不匹配 src/build/a.log

四、** 高级模式

** 是 Git 对标准 glob 的扩展,匹配任意层级的目录。

** 匹配零个或多个目录

# 匹配 a 目录下任意层级的 .log 文件
a/**/*.log    # 匹配 a/b.log、a/b/c.log、a/b/c/d.log……

# 匹配所有层级下的 temp 目录
**/temp/      # 匹配 temp/、a/temp/、a/b/temp/……

** 的常见用法

# 忽略所有 test 目录
**/test/

# 忽略 a 目录下的所有内容
a/**

# 从根目录开始的任意深度匹配
**/node_modules/    # 和 node_modules/ 效果一样

** 与单 * 的区别

模式 匹配的文件
a/*.log a/1.log ✅,a/b/1.log ❌
a/**/*.log a/1.log ✅,a/b/1.log ✅,a/b/c/1.log ✅

五、取反 !

以 ! 开头的规则表示不忽略匹配的文件,相当于"白名单"。

# 忽略所有 .log 文件,但保留重要日志
*.log
!important.log

# 忽略所有目录下的 config,但保留根目录的
config/
!config/
!/root-config/

取反的注意事项

取反只能对已经匹配的文件生效。

# ❌ 这不会生效,因为目录已被忽略
dist/
!dist/important.txt

# ✅ 要这样写
dist/*
!dist/important.txt
# 先忽略 dist 下所有内容,再保留特定文件

取反不能恢复被忽略的目录中的文件

如果目录被忽略了,取反其中的文件可能不会生效,除非 Git 能"看到"那个目录。

# 以下规则可能不会生效
src/
!src/keep.txt

# 正确做法:不忽略目录,只忽略目录中的文件
src/*
!src/keep.txt

六、匹配优先级

后定义的规则优先级更高

如果同一个文件匹配了多条规则,后定义的规则覆盖先定义的。

# 先忽略所有 .env 文件
.env

# 再保留 .env.example
!.env.example

# 等价于:忽略所有 .env 文件,除了 .env.example

就近原则

如果一个文件被父目录的 .gitignore 忽略,但被子目录的 .gitignore 不忽略,则子目录的规则优先。

project/
├── .gitignore            # 忽略 *.log
├── src/
│   ├── .gitignore        # 不忽略 important.log
│   ├── app.log           # 被 project/.gitignore 忽略
│   └── important.log     # 被 src/.gitignore 恢复
└── api/
    └── debug.log         # 被 project/.gitignore 忽略

七、常见场景示例

前端项目

后端项目

# 依赖
vendor/         # PHP
venv/           # Python
.env/

# 运行时
*.pyc
__pycache__/
*.pid

# 配置文件(保留模板)
.env
!.env.example

# 上传目录(保留目录结构)
public/uploads/*
!public/uploads/.gitkeep

# 日志
storage/logs/*
!storage/logs/.gitkeep

八、全局 .gitignore

除了项目级的 .gitignore,Git 还支持全局模式,适用于所有项目。

# 设置全局 gitignore
git config --global core.excludesFile ~/.gitignore_global

# 在 ~/.gitignore_global 中添加通用规则
echo ".DS_Store" >> ~/.gitignore_global
echo "Thumbs.db" >> ~/.gitignore_global
echo "*.swp" >> ~/.gitignore_global

这样系统文件和编辑器临时文件就不需要在每个项目中重复配置了。

九、验证 .gitignore 规则

查看哪些文件被忽略

# 查看所有被忽略的文件
git status --ignored

# 检查特定文件是否会被忽略
git check-ignore -v src/app.log
# 输出会显示匹配的规则和行号

# 检查多个文件
git check-ignore -v node_modules/package.json .env

调试 .gitignore

# 如果文件已经被跟踪,.gitignore 不会生效
# 需要先删除缓存
git rm --cached .env
git commit -m "停止跟踪 .env"

# 然后再添加 .gitignore 规则

十、总结

符号 含义 示例
* 匹配任意字符(不含 /) *.log
? 匹配单个字符 ?.txt
[abc] 匹配集合中任一字符 [abc].txt
[!abc] 匹配不在集合中的字符 [!abc].txt
** 匹配任意层级目录 a/**/*.log
/ 开头 仅匹配根目录 /dist
/ 结尾 仅匹配目录 dist/
! 开头 取反,不忽略 !important.log
# 开头 注释 # 这是注释

记住三条核心原则:

  1. .gitignore 规则默认是递归的——*.log 匹配所有层级
  2. 后定义的规则优先级更高——取反规则要放在被取反的规则之后
  3. 已跟踪的文件不受 .gitignore 影响——需要先 git rm --cached