.gitignore怎么写?语法规则+已提交文件如何忽略
git status 一敲,node_modules/ 里几万个文件全冒出来;不小心把 .env 提交了,密钥泄露在仓库历史里;明明写了 .gitignore,那个文件还是被跟踪着。这三件事,几乎每个开发者都遇到过。这篇文章把 .gitignore 的规则和坑一次讲清。
.gitignore 的作用与生效范围
.gitignore 告诉 Git 哪些文件不需要纳入版本控制。典型的忽略对象:
- 编译产物:
target/、dist/、build/、*.class - 依赖目录:
node_modules/、vendor/ - IDE 配置:
.idea/、.vscode/、*.iml - 系统文件:
.DS_Store、Thumbs.db - 敏感配置:
.env、*.key、credentials.json - 日志与临时文件:
*.log、*.tmp、*.swp
最关键的一条规则,先说在前面:
.gitignore 只对未被跟踪(untracked)的文件生效。已经被 Git 跟踪的文件,无论你怎么写规则,都会继续被跟踪。
这是"gitignore不生效"问题的唯一原因,后面会给出完整解决方案。
匹配优先级
Git 按以下顺序查找规则,后面的覆盖前面的:
- 命令行指定的规则
- 同目录下的
.gitignore(子目录的规则优先级高于父目录) - 上层目录的
.gitignore .git/info/exclude- 全局
core.excludesFile指定的文件
在同一个 .gitignore 文件内部,写在后面的规则优先级更高。这是 ! 反向规则能生效的基础。
完整语法规则表
| 语法 | 含义 | 示例 | 匹配结果 |
文件名 | 匹配任意目录下的同名文件/目录 | debug.log | 任意位置的 debug.log |
* | 匹配0或多个字符(不跨目录) | *.log | error.log、app.log |
? | 匹配单个字符 | file?.txt | file1.txt、fileA.txt,不匹配 file10.txt |
[] | 匹配字符集合中的一个 | file[0-9].txt | file1.txt ~ file9.txt |
[!] | 匹配不在集合中的字符 | file[!0-9].txt | fileA.txt,不匹配 file1.txt |
** | 匹配任意层级目录 | logs/**/debug.log | logs下任意深度的 debug.log |
/ 开头 | 只匹配仓库根目录 | /config.json | 根目录的 config.json,不匹配 src/config.json |
/ 结尾 | 只匹配目录 | build/ | build 目录,不匹配名为 build 的文件 |
! 开头 | 反向规则,取消忽略 | !important.log | 即使 *.log 被忽略,此文件仍跟踪 |
# 开头 | 注释行 | # 依赖目录 | 不参与匹配 |
| 空行 | 分隔用,无作用 | ||
\ | 转义特殊字符 | \#file.txt | 匹配名为 #file.txt 的文件 |
* 和 ** 的区别(高频困惑点)
# * 不跨越目录分隔符
logs/*.log # 匹配 logs/a.log,不匹配 logs/2024/a.log
# ** 可跨越任意层级
logs/**/*.log # 匹配 logs/a.log 和 logs/2024/01/a.log
# ** 在开头表示任意路径前缀
**/temp # 匹配任意深度的 temp 目录
# ** 在结尾表示目录内所有内容
logs/** # 匹配 logs 目录下的一切
前导斜杠的重要性
config.json # 匹配所有目录下的 config.json(含 src/config.json)
/config.json # 只匹配根目录的 config.json
如果你只想忽略根目录的某个文件,一定要加前导 /,否则会连带忽略掉子目录里的同名文件——这类问题往往要到很久以后才被发现。
反向规则 ! 的一个大坑
# ✅ 正确写法
logs/*
!logs/.gitkeep
# ❌ 错误写法:不生效
logs/
!logs/.gitkeep
原因:如果一个目录被忽略了,Git 不会再进入这个目录扫描,因此内部的 ! 规则永远不会被求值。
解决方法:忽略目录内容(logs/*)而不是目录本身(logs/),这样 Git 仍会进入目录,! 规则才有机会生效。
同理,这种写法也不行:
# ❌ 父目录被忽略,子级例外无效
build/
!build/keep/output.txt
# ✅ 逐层放行
build/*
!build/keep/
build/keep/*
!build/keep/output.txt
已提交的文件如何忽略:git rm --cached
这是本文最实用的部分。
问题场景
你在项目跑了一周后才想起来加 .gitignore,此时 node_modules/ 已经被提交了。加规则后 git status 依然显示它的变更。
解决方案:清除索引缓存
方案A:只处理特定文件/目录(推荐,影响面小)
# 单个文件
git rm --cached config/secret.properties
# 整个目录(-r 递归)
git rm -r --cached node_modules/
# 提交
git commit -m "chore: 从版本控制中移除 node_modules"
git push
--cached 参数的含义是:只从 Git 索引中删除,保留本地磁盘上的文件。不加 --cached 会连本地文件一起删掉,这是最危险的误操作之一。
方案B:全量刷新(.gitignore 改动较大时)
# 1. 先确保工作区干净,所有改动已提交
git status
# 2. 清空索引中的所有文件记录(本地文件不受影响)
git rm -r --cached .
# 3. 按新的 .gitignore 重新添加
git add .
# 4. 查看即将提交的变更,确认只有预期的删除
git status
# 5. 提交
git commit -m "chore: 应用 .gitignore 规则"
执行前务必确认:
- 工作区没有未提交的改动(否则
git add .会一并提交进去) - 第4步的
git status要仔细看,确保没有误删该保留的文件
验证规则是否生效
Git 提供了专门的调试命令,能告诉你某个文件被哪条规则、在哪个文件的第几行忽略了:
# 检查单个文件
git check-ignore -v node_modules/react/index.js
# 输出:.gitignore:3:node_modules/ node_modules/react/index.js
# ↑规则来源文件 ↑行号 ↑规则内容
# 检查多个文件
git check-ignore -v src/a.log dist/index.js
# 列出所有被忽略的文件
git status --ignored
排查"为什么这个文件被忽略了"或"为什么这个文件没被忽略",git check-ignore -v 是最快的手段,比逐行读 .gitignore 高效得多。
如果只是想快速生成一份符合项目技术栈的规则文件,.gitignore 生成器 支持按语言和框架组合生成,比手写完整。
三级忽略机制
Git 提供三个层级的忽略配置,用途完全不同。
| 层级 | 文件位置 | 是否提交到仓库 | 适用场景 |
| 项目级 | .gitignore | 会提交,团队共享 | 编译产物、依赖目录等全员通用规则 |
| 仓库本地级 | .git/info/exclude | 不提交,仅本地 | 个人临时文件,不想影响他人 |
| 全局级 | ~/.gitignore_global | 不提交,跨所有仓库 | 操作系统和编辑器文件 |
配置全局忽略
# 1. 创建全局忽略文件
touch ~/.gitignore_global
# 2. 告诉 Git 使用它
git config --global core.excludesFile ~/.gitignore_global
# 3. 验证配置
git config --global core.excludesFile
全局文件里适合放与项目无关、与个人环境有关的内容:
# macOS
.DS_Store
.AppleDouble
._*
# Windows
Thumbs.db
Desktop.ini
$RECYCLE.BIN/
# Linux
*~
.directory
# 编辑器(个人偏好,不应强加给团队)
.idea/
.vscode/
*.swp
*.swo
.history/
一条团队协作的最佳实践:.idea/、.vscode/ 这类 IDE 配置应该放全局而不是项目的 .gitignore。因为团队成员用什么编辑器是个人自由,把它写进项目文件相当于替别人做决定。当然,如果团队统一了 IDE 并且要共享代码风格配置,那就另当别论。
各语言必备忽略项
Java / Maven / Gradle
# 编译输出
target/
build/
out/
*.class
# 打包文件
*.jar
*.war
*.ear
# Gradle
.gradle/
gradle-app.setting
# IDE
*.iml
.classpath
.project
.settings/
# 日志
*.log
Node.js
# 依赖(体积最大的元凶)
node_modules/
# 构建产物
dist/
build/
.next/
.nuxt/
out/
# 日志
npm-debug.log*
yarn-error.log*
pnpm-debug.log*
# 环境变量(重要!)
.env
.env.local
.env.*.local
# 缓存
.cache/
.parcel-cache/
.eslintcache
# 注意:lock 文件应当提交,不要忽略
# package-lock.json / yarn.lock / pnpm-lock.yaml
Python
# 字节码
__pycache__/
*.py[cod]
*$py.class
# 虚拟环境
venv/
env/
.venv/
# 分发打包
build/
dist/
*.egg-info/
# 测试与覆盖率
.pytest_cache/
.coverage
htmlcov/
.tox/
# Jupyter
.ipynb_checkpoints/
# 环境变量
.env
Go
# 二进制
*.exe
*.dll
*.so
*.test
# 覆盖率
*.out
# 依赖目录(Go Modules 时代通常不提交 vendor)
vendor/
通用必备
# 操作系统
.DS_Store
Thumbs.db
# 敏感信息
.env
*.pem
*.key
secrets.yml
credentials.json
# 临时文件
*.tmp
*.bak
*.swp
日常开发中常用命令记不住,可以对照 Git 命令速查表 查找;容器化项目还需要 .dockerignore,语法与 .gitignore 类似但作用不同,可参考 Docker 命令速查表。
误提交 .env 怎么补救
这是最紧急的一类事故。分三步处理,顺序不能颠倒。
第一步:立即轮换所有泄露的密钥(最优先)
在做任何 Git 操作之前先做这件事:
- 数据库密码:立即修改
- API Key / Secret:到服务商后台吊销并重新生成
- OAuth Client Secret:重置
- 私钥文件:重新生成密钥对,更新所有部署点
原因:一旦推送到远程(尤其是公开仓库),必须假定密钥已经泄露。GitHub 上有大量爬虫在实时扫描新提交中的密钥格式,从提交到被利用的时间可能不到一分钟。清除历史只能防止未来被看到,不能收回已经被抓走的内容。
第二步:从当前版本移除并忽略
# 从索引移除,保留本地文件
git rm --cached .env
# 确保 .gitignore 里有规则
echo ".env" >> .gitignore
# 提交
git add .gitignore
git commit -m "chore: 移除 .env 并加入忽略规则"
git push
做到这一步,新的提交里不再包含 .env,但历史提交中仍然存在,任何人 git log 都能翻出来。
第三步:清除历史记录(可选,有风险)
如果确实需要从历史中彻底抹掉,推荐使用 git-filter-repo(官方推荐,比 filter-branch 快且安全):
# 安装
pip install git-filter-repo
# 从所有历史提交中移除该文件
git filter-repo --path .env --invert-paths
# 重新添加远程(filter-repo 会移除 origin 作为安全措施)
git remote add origin <你的仓库地址>
# 强制推送(危险操作)
git push origin --force --all
git push origin --force --tags
执行前必须知道的风险:
| 风险 | 说明 |
| 所有 commit hash 改变 | 相当于重写了整个历史 |
| 团队成员本地仓库失效 | 所有人必须重新 clone,未推送的工作可能丢失 |
| 已有的 PR / MR 会混乱 | 需要关闭重开 |
| Fork 出去的副本仍有记录 | 无法清除他人 fork 中的历史 |
| 平台缓存可能残留 | 部分平台需联系官方彻底清除 |
强烈建议:执行前先完整备份仓库(git clone --mirror),并提前通知所有协作者。多人协作的仓库,这类操作应由仓库管理员统一执行,不要个人擅自 force push。
更好的做法:从一开始就防住
# 提交 .env.example 作为模板(不含真实值)
cp .env .env.example
# 手动把 .env.example 里的值改成占位符
git add .env.example
配合 pre-commit 钩子做敏感信息扫描,可以在提交前就拦住。这比事后补救成本低几个数量级。
小结
| 要点 | 结论 |
| 核心限制 | .gitignore 只对未跟踪文件生效 |
| 已跟踪文件 | git rm -r --cached <路径> 后提交 |
--cached | 只删索引,保留本地文件,务必带上 |
* vs ** | * 不跨目录,** 跨任意层级 |
前导 / | 只匹配根目录 |
尾随 / | 只匹配目录 |
! 失效原因 | 父目录被整体忽略,要用 dir/* 而非 dir/ |
| 调试规则 | git check-ignore -v <文件> |
| 三级忽略 | 项目 / .git/info/exclude / 全局 |
| IDE 配置 | 建议放全局,不强加给团队 |
| 误提交密钥 | 先轮换密钥,再移除,最后才考虑清历史 |
新建项目的第一件事,就是用 .gitignore 生成器 生成对应技术栈的规则文件再执行 git init——比事后清理省事得多。