前情提要
最近尝试 AI 编程工具 CladueCode 来做一个小项目,在 CladueCode 完成任务的最后,它还「好心」的为我提交 git,本着来都来了的心态,便让他提交了。但提交后就返现了不对,它将我本地暂存的文件都给提交了,这里有部分文件我是不打算提交或者不打算在这次提交的。于是我就开始用 git reset
进行回滚,于是乎重头戏来了。之前我要么用 git reset --soft
这样既能回滚 git 提交,也能将修改的文件回滚到暂存区保证不丢失,要么就是用 git reset --hard
直接回滚掉我确定不要了的提交。但这次,我看到了每次都一视而过的 git reset --keep
想着既然都叫 keep
了应该也是挺「稳妥」的,变尝试了,执行完回到暂存区一看,心率急升,暂存区啥都没了,说好的 keep 呢?
救命步骤
还好在 Reddit 看到类似的情况,然后底下有大佬推荐 git fsck --lost-found
和 git reflog
git fsck --lost-found
看着原理太繁琐了,本来就已经心慌得不行,没法静下心来操作,于是打算先尝试 git reflog
直接执行 git reflog
查看历史所有操作的 SHA-1 值
可以看到最新的一行就是我执行的 git reset --keep
将 HEAD 重置到 18f883f
然后第二行就是被弄丢的提交 bc8114e
现在要做的就是将 HEAD 重置到 bc8114e
,这样理论上来说就可以恢复了
1 | $ git reset bc8114e --hard |
YES,有惊无险💦,恢复了上次提交
查漏补缺
这里也是重新补补课,了解清楚 git reset 的四种模式
先初始化第一次提交,提交两个文件
然后在第二提交中修改 test_02.py
和新增一个 test_03.py
soft
执行 git reset --soft 0efa9be
可以看到 test_02.py
和 test_03.py
都回到了暂存区
mixed
执行 git reset --mixed 0efa9be
可以看到 mixed 比 soft 更加彻底,不仅回滚提交,还将 test_03.py
会滚到了原本的工作区,而不是暂存区
hard
执行 git reset --hard 0efa9be
可以看到暂存区和工作区都空了,完全回滚了到提交点状态
keep
重新修改 test_02.py
新增 test_03.py
并执行 git reset --keep 0efa9be
结果和上面的 hard 一致
hard 与 keep
那么 hard 和 keep 到底有什么区别呢?
这里在第二次提交之后,新增一个 test_04.py
到暂存区,然后执行 hard 与 keep 来看下差异
先执行
git reset --keep
可以看到
test_04.py
回到了工作区重新执行第二次提交并将
test_04.py
添加回暂存区,然后执行git reset --hard
可以看到
test_04.py
也丢失了!
所以 hard 会清空暂存区,而 keep 不会!
git fsck
前面有提到,git fsck
也可以找回 git reset --hard
丢失的 commit,但是这个命令比较底层,并且有很多作用:
- 检测对象是否损坏:Git 中 commit、tree、blob、tag 都是对象,
git fsck
会检查这些对象的 SHA-1 校验和 是否正确,文件是否损坏 - 检查引用关系:确认 commit 是否能正确引用到它的 parent、tree 等
- 找出悬空对象(dangling objects):通常在
reset --hard
、rebase
、amend
后,旧的 commit 就可能不被任何分支引用,这时这个提交就成为了「悬空」的,git fsck
会把这些孤立的对象列出来,这个就是找回丢失 commit 的关键 - 清理仓库体积:通过
git fsck --lost-found
把悬空的对象移到.git/lost-found
目录下,然后结合git gc
可以彻底清理掉无用的对象,减少仓库体积
找回悬空提交
找出悬空的 commit
1
git fsck --lost-found
查看 commit 内容,确认是需要恢复的
1
git show <commit_hash>
建个新分支来保存这个 commit
1
git branch restore-branch <commit_hash>
将恢复的提交合并回主分支
1
2git checkout main
git merge restore-branch
如果只是想把 commit 拿出来,不想新建分支,可以直接使用 cherry-pick
1 | git cherry-pick <commit_hash> |