“我明明写了 .gitignore,node_modules 怎么还在提交?“——这是 Git 新手最常见的困惑之一。问题几乎都出在同一个认知误区上。理解它,再配一张语法速查,gitignore 就不再玄学。
核心规则:只对”未跟踪”的文件生效
.gitignore 的作用是阻止 Git 开始跟踪一个还没被跟踪的文件。它管不了已经被跟踪的文件。
所以如果某文件之前已经 git add / commit 过,它已进入索引,事后再往 .gitignore 里加规则,Git 不会回头忽略它——这就是”加了 gitignore 却不生效”的真相。
已提交的文件怎么忽略:git rm —cached
正确做法是先把它从索引里移除,再让 gitignore 接管:
# 单个文件
git rm --cached <文件>
# 整个目录(如已误提交的 node_modules)
git rm -r --cached node_modules
# 然后确认 .gitignore 里有对应规则,提交
git commit -m "stop tracking ignored files"
--cached 的关键作用:只从 Git 索引移除,磁盘上的文件原样保留。少了 --cached,git rm 会把本地文件也删掉,别搞错。
gitignore 语法速查
| 写法 | 含义 |
|---|---|
*.log | 当前及各层级所有 .log(* 不跨目录分隔符) |
build/ | 任意层级名为 build 的目录 |
/build | 只匹配仓库根的 build |
**/foo | 任意层级的 foo |
logs/**/*.tmp | logs 下任意深度的 .tmp |
!keep.txt | 否定规则,取消对 keep.txt 的忽略 |
# 注释 | 注释行 |
否定规则(!)的父目录陷阱
想”忽略整个目录但留一个文件”是常见需求:
.vscode/*
!.vscode/settings.json
但如果你写成了 .vscode/(带尾斜杠的整体排除),Git 根本不会进入该目录,里面的 ! 否定就失效了。规则是:要保留子文件,父目录不能用 dir/ 形式整体排除,得用 dir/* 这种”排除内容但目录可进入”的写法。
全局 gitignore:别在每个项目重复写
.DS_Store、Thumbs.db、.idea/、.vscode/ 这类系统和编辑器产物,和具体项目无关,写进每个仓库既啰嗦又容易漏。配一份全局的:
git config --global core.excludesfile ~/.gitignore_global
把系统类、编辑器类规则放进 ~/.gitignore_global,项目内的 .gitignore 就只需要关注本项目的构建产物(dist/、target/、*.pyc 等)。
排查神器:git check-ignore
不确定某文件被哪条规则命中时,别肉眼对规则:
git check-ignore -v path/to/file
它会直接打印命中的规则文件、行号和内容。“为什么这个文件被忽略了/为什么没被忽略”,一条命令见分晓。
小结:gitignore 不生效,先想”是不是已经被跟踪了”,用 git rm --cached 解决;多技术栈项目用生成器把语言、框架、编辑器、系统模板一次合并好放进仓库根,系统/编辑器类则沉淀到全局 gitignore。