问题背景
有时候我们会把某个本来不应该提交的文件 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 以后不再跟踪它,常见做法是:
- 把文件路径加入
.gitignore - 用
git rm --cached把它从 Git 索引中移除,但保留本地文件 - commit 并 push 这次变更
正确处理方式:远程删除,本地保留
假设误提交的文件是:
| |
先把它加入 .gitignore:
| |
然后执行:
| |
这个命令的意思是:
- 从 Git 索引中删除这个文件
- 不删除你本地工作区里的真实文件
接着提交并推送:
推送完成后,结果是:
- 远程仓库中这个文件会在新提交里被删除
- 本地的
config/local.yml仍然存在 - 因为
.gitignore已经包含它,后续 Git 不会再把它作为未跟踪文件提示出来
如果是目录怎么办?
如果误提交的是整个目录,比如:
| |
.gitignore 中写:
| |
然后执行:
这里需要加 -r,因为 logs/ 是目录。
为什么 .gitignore 不会影响已跟踪文件?
Git 判断一个文件是否要纳入版本管理,大致可以分成两类:
- 已经被 Git 跟踪的文件
- 还没有被 Git 跟踪的文件
.gitignore 管的是第二类,也就是 untracked file。
比如你新建了一个 debug.log,它还没有被 git add,这时 .gitignore 里写了:
| |
那么 git status 就不会再提示这个文件。
但如果 debug.log 之前已经被提交过,Git 的索引里已经有它了。此时 .gitignore 不会让 Git 自动忘记它。你必须显式告诉 Git:
| |
也就是把它从索引里移除。
如何判断文件是否已经被 Git 跟踪?
可以用:
| |
如果有输出,说明它已经被 Git 跟踪。
也可以查看当前状态:
| |
如果某个文件修改后显示在 Changes not staged for commit 里,通常说明它已经是 tracked file。
如果显示在 Untracked files 里,说明它还没被跟踪,这时 .gitignore 才能直接让它消失在状态列表中。
常见误区
误区一:提交 .gitignore 后,远程文件会自动消失
不会。
.gitignore 只是忽略规则,不是删除命令。远程文件是否删除,取决于后续提交里有没有把这个文件从 Git 索引中移除。
需要配合:
| |
误区二:git rm –cached 会删除本地文件
不会。
git rm 文件路径 会删除本地文件,并把删除动作加入索引。
git rm --cached 文件路径 只从索引中移除文件,本地工作区文件会保留。
这也是处理本地配置文件时最常用的方式。
误区三:.gitignore 能阻止所有人提交这个文件
不完全能。
.gitignore 能让正常情况下的 git add . 不再添加这个文件,但别人仍然可以强制添加:
| |
所以如果是敏感文件,比如密钥、token、数据库密码,不能只依赖 .gitignore,还要配合权限控制、密钥扫描、CI 检查等手段。
如果已经把敏感信息 push 出去了怎么办?
如果误提交的是普通文件,比如日志、编译产物、本地配置模板,用 git rm --cached 通常就够了。
但如果误提交的是敏感信息,比如:
- API token
- SSH 私钥
- 数据库密码
- 云厂商访问密钥
- 生产环境配置
那就不能只删除文件了,因为敏感内容仍然存在于 Git 历史提交中。
正确处理顺序应该是:
- 立刻废弃或轮换已经泄露的密钥
- 从当前版本中删除敏感文件
- 把敏感文件加入
.gitignore - 如有必要,使用
git filter-repo或 BFG 清理历史记录 - 强制推送清理后的历史
- 通知所有协作者重新拉取或重新 clone 仓库
也就是说:删除当前版本里的文件,不等于删除历史里的泄露内容。
如果我想远程保留,本地以后不提交改动呢?
这是另一种需求。
比如仓库里有一个默认配置文件:
| |
团队希望远程继续保留这个文件,但你本地会根据自己的环境改它,而且不想每次都在 git status 里看到它。
这种情况不适合靠 .gitignore,因为文件已经是 tracked file。
可以考虑:
| |
恢复跟踪时执行:
| |
还有一个类似命令:
| |
两者区别可以简单理解为:
assume-unchanged更偏性能优化,告诉 Git 暂时假设这个文件没变skip-worktree更偏本地覆盖场景,告诉 Git 尽量不要关心工作区里的这个文件
日常如果是“仓库保留默认配置,本地想长期改成自己的配置”,一般优先考虑 skip-worktree。
不过这个方案只影响你本地,不会影响其他人,也不会改变远程仓库。
推荐实践
对于不应该提交的本地文件,推荐一开始就做好这几件事:
- 把真实本地配置加入
.gitignore - 提供一个可提交的模板文件,比如
.env.example - 文档里说明如何复制模板并填写本地值
例如:
仓库里提交:
| |
这样既不会泄露本地敏感配置,也能让新人知道需要哪些环境变量。
总结
已经 commit 或 push 过的文件,再加入 .gitignore 并不会自动停止跟踪。
如果你的目标是“远程删除,本地保留,以后不再跟踪”,使用:
如果是目录,加上 -r:
| |
如果是敏感信息泄露,要先轮换密钥,再考虑清理 Git 历史。
记住一句话就够了:.gitignore 只负责忽略未跟踪文件,不能让 Git 自动忘记已经跟踪过的文件。