一次本地 git 仓库损坏后的补救

服务器上有个仓库,由于正在开发中,所以在 commit 后都没有 push 到远端。

服务器上该仓库所在分区是 nfs,远端 nfs 下面是一个 ext4,之前 nfs 服务器掉电过,所以该 ext4 已经存在损坏。

恰逢机房要停电倒闸,大约是今天早上八点的事情,但我工作到了四点,忘了这回事(即忘记断电)就去睡觉了。

一起床,一上机器,一列目录

文件损坏

1
2
ls: cannot access 'flash.h': Structure needs cleaning
ls: cannot access '.littlefs_api.h.swp': Structure needs cleaning

瞬间醒了,开始救文件

Git 损坏

首先是 git status 看看情况,一看

1
fatal: .git/index: index file open failed: Input/output error

怎么 git 本体也损坏了,赶快拷贝走

1
2
3
4
5
$ tar --ignore-failed-read -czf ~/backup.tgz ./
tar: build/CMakeFiles/usbbootrom.dir/usbd_conf.c.obj.d: Warning: Cannot stat: Input/output error
tar: flash.h: Warning: Cannot stat: Input/output error
tar: .littlefs_api.h.swp: Warning: Cannot stat: Input/output error
tar: .git/index: Warning: Cannot stat: Input/output error

一看这个 log 似乎还不错,我感觉还能从 ref 里面找 commit 把东西拿回来。把文件丢到好机器上一看

1
2
3
4
5
6
7
8
9
10
11
12
$ git fsck
error: inflate: data stream error (unknown compression method)
error: unable to parse header of .git/objects/2c/0e5af1cb6b6265aaf26fc418684e5952e0522a
error: 2c0e5af1cb6b6265aaf26fc418684e5952e0522a: object corrupt or missing: .git/objects/2c/0e5af1cb6b6265aaf26fc418684e5952e0522a
error: inflate: data stream error (unknown compression method)
error: unable to parse header of .git/objects/99/65ff7cece036f6fb5cf1e2945d85a40e4615d0
error: 9965ff7cece036f6fb5cf1e2945d85a40e4615d0: object corrupt or missing: .git/objects/99/65ff7cece036f6fb5cf1e2945d85a40e4615d0
Checking object directories: 100% (256/256), done.
error: refs/heads/master: invalid sha1 pointer 0000000000000000000000000000000000000000
notice: HEAD points to an unborn branch (master)
notice: No default references
fatal: .git/index: index file open failed: Input/output error

我……只能尝试去找一个 commit 尝试恢复,首先看看 master 指向哪,一看:

文件错位

1
2
3
4
$ cat .git/refs/heads/master
0000000000000000000000000000000000000000 6cab02d8393382761f1a3bbc770ff32fcd771afc Zenithal <i@zenithal.me> 1658677567 +0800 commit (initial): Initial working example
6cab02d8393382761f1a3bbc770ff32fcd771afc d8e3d6b41613f1295f656fb5d21f344924358279 Zenithal <i@zenithal.me> 1658677603 +0800 commit (amend): Initial working example
...

草,这是 reflog,最底端的还不是最新的 commit(我记得之后还有个几个 commit)

再一看 reflog 是啥

1
2
$ cat .git/logs/refs/heads/master
#include "mmio.h"

草草草,是 worktree 文件

我尝试去 objects 里面找最新的 commit,找不到。

我只能先保存现有 worktree,然后从 reflog 里面指向的最后一个 commit 恢复 worktree,然后再逐渐 apply 回来

1
2
$ git checkout 1825d7ca .
Updated 37 paths from 6796640

谢天谢地,这棵树还没坏

然后看看现有 worktree

1
2
3
4
5
6
7
$ cat main.c
#include <stddef.h>
#include "mmio.h"

#include "flash.h"

int flash_init() {

怎么是 flash.c 的内容……

仔细翻了一遍以后发现文件全错位了,只找到一个好的新文件,刚好对应下一个 commit。

没 commit 的内容就全部没了,只能重写,不过还好都是些测试,问题不大。