Featured image of post 已经 push 到远程的文件,再加入 .gitignore 还会生效吗?

已经 push 到远程的文件,再加入 .gitignore 还会生效吗?

解释 .gitignore 对已跟踪文件为什么不生效,以及如何用 git rm --cached 保留本地文件、删除远程文件并停止后续跟踪。

问题背景

有时候我们会把某个本来不应该提交的文件 commit 并 push 到远程仓库,比如:

  • 本地配置文件:application-local.yml
  • IDE 配置文件:.idea/workspace.xml
  • 临时生成文件:debug.log
  • 编译产物:dist/target/
  • 机器相关文件:.env.local

等发现问题时,第一反应通常是:把这个文件写进 .gitignore,然后再提交一次 .gitignore,是不是远程仓库里的那个文件就不会有了?

答案是:不会。

.gitignore 只对还没有被 Git 跟踪的文件生效。只要某个文件已经被 commit 过,它就是 tracked file。即使后来把它加入 .gitignore,Git 仍然会继续跟踪它。

直接结论

如果文件已经被 commit 并 push 到远程:

  • 只修改 .gitignore,远程仓库里的文件不会自动删除
  • 只修改 .gitignore,本地文件也不会停止被 Git 跟踪
  • 后续继续修改这个文件,git status 里仍然可能看到它

想让 Git 以后不再跟踪它,常见做法是:

  1. 把文件路径加入 .gitignore
  2. git rm --cached 把它从 Git 索引中移除,但保留本地文件
  3. commit 并 push 这次变更

正确处理方式:远程删除,本地保留

假设误提交的文件是:

1
config/local.yml

先把它加入 .gitignore

1
config/local.yml

然后执行:

1
git rm --cached config/local.yml

这个命令的意思是:

  • 从 Git 索引中删除这个文件
  • 不删除你本地工作区里的真实文件

接着提交并推送:

1
2
3
git add .gitignore
git commit -m "stop tracking local config file"
git push

推送完成后,结果是:

  • 远程仓库中这个文件会在新提交里被删除
  • 本地的 config/local.yml 仍然存在
  • 因为 .gitignore 已经包含它,后续 Git 不会再把它作为未跟踪文件提示出来

如果是目录怎么办?

如果误提交的是整个目录,比如:

1
logs/

.gitignore 中写:

1
logs/

然后执行:

1
2
3
4
git rm --cached -r logs/
git add .gitignore
git commit -m "stop tracking logs directory"
git push

这里需要加 -r,因为 logs/ 是目录。

为什么 .gitignore 不会影响已跟踪文件?

Git 判断一个文件是否要纳入版本管理,大致可以分成两类:

  1. 已经被 Git 跟踪的文件
  2. 还没有被 Git 跟踪的文件

.gitignore 管的是第二类,也就是 untracked file。

比如你新建了一个 debug.log,它还没有被 git add,这时 .gitignore 里写了:

1
debug.log

那么 git status 就不会再提示这个文件。

但如果 debug.log 之前已经被提交过,Git 的索引里已经有它了。此时 .gitignore 不会让 Git 自动忘记它。你必须显式告诉 Git:

1
git rm --cached debug.log

也就是把它从索引里移除。

如何判断文件是否已经被 Git 跟踪?

可以用:

1
git ls-files config/local.yml

如果有输出,说明它已经被 Git 跟踪。

也可以查看当前状态:

1
git status

如果某个文件修改后显示在 Changes not staged for commit 里,通常说明它已经是 tracked file。

如果显示在 Untracked files 里,说明它还没被跟踪,这时 .gitignore 才能直接让它消失在状态列表中。

常见误区

误区一:提交 .gitignore 后,远程文件会自动消失

不会。

.gitignore 只是忽略规则,不是删除命令。远程文件是否删除,取决于后续提交里有没有把这个文件从 Git 索引中移除。

需要配合:

1
git rm --cached 文件路径

误区二:git rm –cached 会删除本地文件

不会。

git rm 文件路径 会删除本地文件,并把删除动作加入索引。

git rm --cached 文件路径 只从索引中移除文件,本地工作区文件会保留。

这也是处理本地配置文件时最常用的方式。

误区三:.gitignore 能阻止所有人提交这个文件

不完全能。

.gitignore 能让正常情况下的 git add . 不再添加这个文件,但别人仍然可以强制添加:

1
git add -f config/local.yml

所以如果是敏感文件,比如密钥、token、数据库密码,不能只依赖 .gitignore,还要配合权限控制、密钥扫描、CI 检查等手段。

如果已经把敏感信息 push 出去了怎么办?

如果误提交的是普通文件,比如日志、编译产物、本地配置模板,用 git rm --cached 通常就够了。

但如果误提交的是敏感信息,比如:

  • API token
  • SSH 私钥
  • 数据库密码
  • 云厂商访问密钥
  • 生产环境配置

那就不能只删除文件了,因为敏感内容仍然存在于 Git 历史提交中。

正确处理顺序应该是:

  1. 立刻废弃或轮换已经泄露的密钥
  2. 从当前版本中删除敏感文件
  3. 把敏感文件加入 .gitignore
  4. 如有必要,使用 git filter-repo 或 BFG 清理历史记录
  5. 强制推送清理后的历史
  6. 通知所有协作者重新拉取或重新 clone 仓库

也就是说:删除当前版本里的文件,不等于删除历史里的泄露内容。

如果我想远程保留,本地以后不提交改动呢?

这是另一种需求。

比如仓库里有一个默认配置文件:

1
config/dev.yml

团队希望远程继续保留这个文件,但你本地会根据自己的环境改它,而且不想每次都在 git status 里看到它。

这种情况不适合靠 .gitignore,因为文件已经是 tracked file。

可以考虑:

1
git update-index --skip-worktree config/dev.yml

恢复跟踪时执行:

1
git update-index --no-skip-worktree config/dev.yml

还有一个类似命令:

1
git update-index --assume-unchanged config/dev.yml

两者区别可以简单理解为:

  • assume-unchanged 更偏性能优化,告诉 Git 暂时假设这个文件没变
  • skip-worktree 更偏本地覆盖场景,告诉 Git 尽量不要关心工作区里的这个文件

日常如果是“仓库保留默认配置,本地想长期改成自己的配置”,一般优先考虑 skip-worktree

不过这个方案只影响你本地,不会影响其他人,也不会改变远程仓库。

推荐实践

对于不应该提交的本地文件,推荐一开始就做好这几件事:

  1. 把真实本地配置加入 .gitignore
  2. 提供一个可提交的模板文件,比如 .env.example
  3. 文档里说明如何复制模板并填写本地值

例如:

1
2
.env
.env.local

仓库里提交:

1
.env.example

这样既不会泄露本地敏感配置,也能让新人知道需要哪些环境变量。

总结

已经 commit 或 push 过的文件,再加入 .gitignore 并不会自动停止跟踪。

如果你的目标是“远程删除,本地保留,以后不再跟踪”,使用:

1
2
3
4
git rm --cached 文件路径
git add .gitignore
git commit -m "stop tracking ignored file"
git push

如果是目录,加上 -r

1
git rm --cached -r 目录路径

如果是敏感信息泄露,要先轮换密钥,再考虑清理 Git 历史。

记住一句话就够了:.gitignore 只负责忽略未跟踪文件,不能让 Git 自动忘记已经跟踪过的文件。

本文采用 CC BY-NC-SA 4.0 协议,转载请注明出处。
记录工程实践和排障过程。
使用 Hugo 构建
主题 StackJimmy 设计