关于怎么从Git历史里删除文件
0. Motivation
今天写博客的时候遇到一个问题
我之前把一张图片加进了 commit
后来发现图片不清晰,想换成另一张新的图片
如果我只是删掉旧图片,再 commit 一张新图片
那旧图片是不是还会永远留在 Git 历史里?
1. Overall
Git 保存的每个 commit 都记录了当时仓库的一个快照
所以只要某个文件进入过某个 commit,它就属于仓库历史的一部分
即使后面的 commit 把这个文件删掉了,它也只是从“当前版本”里消失
但在之前的 commit 里,它仍然存在
比如:
1 | |
在最新的 commit B 里看不到 old.jpg
但是切回 commit A,还是能看到它
1 | |
这也是为什么如果不小心提交了大文件、隐私文件、token,单纯 git rm 是不够的
2. Case 1: 还没有 push
如果这个 commit 只是存在本地,还没有 push 到 GitHub
那就比较好办
可以直接修改最后一个 commit
假设旧文件是:
1 | |
新文件是:
1 | |
先从 Git 当前版本中删除旧文件:
1 | |
然后加入新文件和修改过的文章:
1 | |
把这些修改合并进上一个 commit:
1 | |
这样新的最后一个 commit 里就只有 new.png
从正式历史来看,old.jpeg 就没有出现过
不过本地 .git 里可能还会暂时有旧对象,比如 reflog 还记着旧 commit
一般不需要管,后面 Git 自己会清理
3. Case 2: 已经 push
如果已经 push 到远端仓库了
那就要分情况
如果只是普通的小文件,比如一张几 KB 的图片
那其实问题不大
直接新建一个 commit 删除它就行:
1 | |
这样最新版本里不会再有旧图片
只是 Git 历史里仍然会保留它
如果是大文件,或者隐私文件,比如密码、token、私钥
那就不能只靠 git rm
需要重写历史
常见工具是:
1 | |
或者:
1 | |
重写历史之后还需要 force push:
1 | |
这个操作会影响已经 clone 过仓库的人
所以如果是多人协作项目,要非常谨慎
4. Check
可以用下面命令看看某个文件有没有出现在历史里:
1 | |
如果有输出,说明这个文件至少出现在某个 commit 里
也可以看当前仓库状态:
1 | |
如果显示:
1 | |
说明本地比远端多了 1 个 commit
也就是这个 commit 还没 push
这时候用 git commit --amend 修改最后一个 commit,一般是很舒服的选择
5. Summary
简单总结:
git rm只是让文件从当前版本消失- 只要文件进入过 commit,它就会存在于历史里
- 还没 push 的 commit,可以用
git commit --amend改掉 - 已经 push 的普通小文件,通常直接新 commit 删除即可
- 已经 push 的大文件或隐私文件,需要重写历史
所以 Git 里真正需要紧张的不是普通废图
而是大文件和敏感信息