误删了分支、reset --hard 冲掉了暂存的文件、stash drop 了,内容还能找回吗?这篇逐个造出这些误操作,用 git fsck 找出悬空对象并恢复,说明 dangling 与 unreachable 的区别,最后看 fsck 怎么报告损坏的对象。
$ git branch -D feature Deleted branch feature (was cf1a7af). $ git branch -a; git log --oneline --all * main a9b2bc9 主线 1 fsck 找悬空对象(--no-reflogs 忽略 reflog 的保护,等于“假设 reflog 已经过期”): $ git fsck --no-reflogs --lost-found 2>&1 dangling commit cf1a7af242a9d48843721356ad1435445eb40a93 悬空的 commit 就是被删分支的最后一个提交 f3: True
git branch -D 只删除引用。三个只被它引用的提交变成不可达的对象,仍留在对象库里,git fsck --no-reflogs 把没有任何东西指向的末端列为 dangling commit。
$ git show -s --format="%h %s" cf1a7af242a9d48843721356ad1435445eb40a93 cf1a7af 功能 3(还没合并) $ git branch rescued cf1a7af242a9d48843721356ad1435445eb40a93 && git log --oneline rescued cf1a7af 功能 3(还没合并) c9f466c 功能 2 99e3446 功能 1 a9b2bc9 主线 1
用 git branch 新名字 <hash> 重新挂上引用,parent 链把整段历史都带回来:只需要找到那个末端。
$ git fsck --no-reflogs 2>&1 | sort | uniq -c | sed -E 's/[0-9a-f]{40}/<sha>/g'
1 dangling commit <sha>
$ git fsck --no-reflogs --unreachable 2>&1 | sed "s/ [0-9a-f]\{40\}//" | sort | uniq -c | sed -E 's/[0-9a-f]{40}/<sha>/g'
4 unreachable blob
3 unreachable commit
3 unreachable tree
(3 个提交只有最后一个是 dangling,前两个被它的 parent 链引用着;它们的 tree 和 blob 同理)dangling 是不可达对象的“顶端”:没有任何引用或对象指向它。unreachable 则包括所有走不到的对象——被丢掉的 3 个提交里只有最后一个是 dangling,但 3 个 commit、3 个 tree、4 个 blob 都是 unreachable。
$ git reset --hard -q; ls; git status --short
a.txt
$ git fsck --lost-found --no-reflogs 2>&1 | sed -E "s/[0-9a-f]{40}/<sha>/"
dangling blob <sha>
$ ls .git/lost-found/other/ | sed -E "s/[0-9a-f]{40}/<sha>/"; cat .git/lost-found/other/*
<sha>
写了一整天的重要内容
没提交,只做了 git add
fsck --lost-found 把悬空 blob 写成文件(文件名就是 hash),没有路径信息:
文件名里的 hash 就是原来暂存的 blob: True只要 git add 过,文件内容就已经作为 blob 写进了对象库。之后 git reset --hard 把暂存区和工作区冲掉,这个 blob 变成悬空对象;git fsck --lost-found 把它们写到 .git/lost-found/other/,文件名是 hash,没有路径。从来没有 add 过的修改没有进对象库,无法找回。
$ git stash -q; git stash list; git stash drop
stash@{0}: WIP on main: 2ef4622 起点
Dropped refs/stash@{0} (8b6cfd0bd100f83582de78ed93bef1529415951e)
$ git fsck --no-reflogs --unreachable 2>&1 | grep commit | sed -E 's/[0-9a-f]{40}/<sha>/'
unreachable commit <sha>
unreachable commit <sha>
$ git show -s --format="%s | parents=%p" 8b6cfd0bd100f83582de78ed93bef1529415951e | sed -E "s/[0-9a-f]{7}/<sha>/g"
WIP on main: <sha> 起点 | parents=<sha> <sha>
$ git stash apply -q 8b6cfd0bd100f83582de78ed93bef1529415951e && cat a.txt
改了没提交git stash drop 会打印被丢弃的 stash 的 hash。stash 是带多个 parent 的 commit,变成不可达之后同样可以用 fsck 找到,git stash apply <hash> 恢复。
$ git fsck 2>&1; echo "退出码 $?"
退出码 0
$ { git fsck 2>&1; echo "退出码 $?"; } | sed -E 's/[0-9a-f]{40}/<sha>/g'
error: inflate: data stream error (incorrect data check)
error: unable to unpack header of .git/objects/1b/b77240495ec07c912bf8696093f20726108add
error: <sha>: object corrupt or missing: .git/objects/1b/b77240495ec07c912bf8696093f20726108add
missing blob <sha>
退出码 3
$ { git fsck 2>&1; echo "退出码 $?"; } | sed -E 's/[0-9a-f]{40}/<sha>/g'
error: inflate: data stream error (incorrect data check)
error: unable to unpack header of .git/objects/1b/b77240495ec07c912bf8696093f20726108add
error: <sha>: object corrupt or missing: .git/objects/1b/b77240495ec07c912bf8696093f20726108add
missing blob <sha>
missing blob <sha>
退出码 3git fsck 遍历对象、校验 zlib 与 hash。改坏一个字节会报告 inflate 错误和对象损坏,删掉文件会报告 missing,退出码都是 3。它只负责发现,不负责修复。