📝 博客 · · ⏱ 18 分钟

.gitignore怎么写?语法规则+已提交文件如何忽略

写了.gitignore却不生效?本文详解匹配语法规则表、已跟踪文件的清除方法、三级忽略机制,以及各语言必备忽略项和误提交.env的补救方案。

Git版本控制开发工具

.gitignore怎么写?语法规则+已提交文件如何忽略

git status 一敲,node_modules/ 里几万个文件全冒出来;不小心把 .env 提交了,密钥泄露在仓库历史里;明明写了 .gitignore,那个文件还是被跟踪着。这三件事,几乎每个开发者都遇到过。这篇文章把 .gitignore 的规则和坑一次讲清。

.gitignore 的作用与生效范围

.gitignore 告诉 Git 哪些文件不需要纳入版本控制。典型的忽略对象:

最关键的一条规则,先说在前面

.gitignore 只对未被跟踪(untracked)的文件生效。已经被 Git 跟踪的文件,无论你怎么写规则,都会继续被跟踪。

这是"gitignore不生效"问题的唯一原因,后面会给出完整解决方案。

匹配优先级

Git 按以下顺序查找规则,后面的覆盖前面的

  1. 命令行指定的规则
  2. 同目录下的 .gitignore(子目录的规则优先级高于父目录)
  3. 上层目录的 .gitignore
  4. .git/info/exclude
  5. 全局 core.excludesFile 指定的文件

在同一个 .gitignore 文件内部,写在后面的规则优先级更高。这是 ! 反向规则能生效的基础。

完整语法规则表

语法含义示例匹配结果
文件名匹配任意目录下的同名文件/目录debug.log任意位置的 debug.log
*匹配0或多个字符(不跨目录*.logerror.log、app.log
?匹配单个字符file?.txtfile1.txt、fileA.txt,不匹配 file10.txt
[]匹配字符集合中的一个file[0-9].txtfile1.txt ~ file9.txt
[!]匹配不在集合中的字符file[!0-9].txtfileA.txt,不匹配 file1.txt
**匹配任意层级目录logs/**/debug.loglogs下任意深度的 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 提供了专门的调试命令,能告诉你某个文件被哪条规则、在哪个文件的第几行忽略了

# 检查单个文件
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 操作之前先做这件事:

原因:一旦推送到远程(尤其是公开仓库),必须假定密钥已经泄露。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——比事后清理省事得多。