首页 > 解决方案 > 如何修复包含大量悬空对象的 git 存储库,导致 git gc --prune=now 失败并出现致命:坏对象:

问题描述

我有一个远程在 bitbucket 上的存储库。在某些时候,我将一个分支推送到服务器,并错误地要求推送所有本地标签。

一段时间后该操作因错误而失败(无法准确回忆 - 但我认为我不允许更改服务器上的标签)。

然而——从那以后我就有了这些问题。

  1. 大量 (~130) 悬空 blob、提交和标签

  2. 任何摆脱这些“悬空”对象的尝试 - 都会失败并出现很多错误,所有错误都指向一个特定的“坏对象”

     mini-mac7:myRepo $ git fsck
     Checking object directories: 100% (256/256), done.
     Checking objects: 100% (16253/16253), done.
     broken link from  commit c521a9214898aac031831d97e6a8329342b788c4
                   to  commit a2df338e00be26da2f0b84543668ef29b52df5c3
     broken link from  commit 19f55b6374e7adcc42046821c1659bb47df933a8
                   to  commit 0d862ff1507038edf4c26129abcc11ce8c00bef3
     dangling blob ab0129187ed300114d5d34501add4dc05feaeee0
     dangling commit 5a04331eb72123423888a4fac9922355588f138f
     dangling commit 350da8fc761b2b41d923ccad97cf77e320b0cf34
     dangling commit 8c0e4e66c891f4bffc23c1ee35f4d5eaca2bdb78
     dangling tag 9611c319d012589ea451ce56495f4c9673292615
     .
     . (130 more dangling objects... blobs commits and tags)
     .
     mini-mac7:myRepo $ 
    

mini-mac7:itx-mac-agent-broken $ git gc
error: Could not read a2df338e00be26da2f0b84543668ef29b52df5c3
error: Could not read a2df338e00be26da2f0b84543668ef29b52df5c3
error: Could not read a2df338e00be26da2f0b84543668ef29b52df5c3
.
. (40 more times - same object)
.
error: Could not read a2df338e00be26da2f0b84543668ef29b52df5c3
fatal: bad object a2df338e00be26da2f0b84543668ef29b52df5c3
fatal: failed to run repack
mini-mac7:itx-mac-agent-broken $ 

谁能帮我恢复这个回购?首先 --- 如何解决“坏对象”问题?我已经直接fetching --all从远程尝试过deleting .git/objects/96/11c319d012589ea451ce56495f4c9673292615(实际上只有少数存在,但是当 rm'ed --- 悬空的物体并没有消失。

这个 repo 很重要——我已经从服务器上重新克隆了它,但是那里的许多本地存储和分支包含我需要的工作。由于这些坏的和悬空的对象,任何拉/取等尝试都会失败并出现奇怪的错误。

标签: git

解决方案


几个背景点:

  1. 推送失败与本地问题无关(无论本地问题到底是什么)。

  2. 悬空提交和 blob 是正常的。实际问题出在其他地方(请注意,实际问题可能会导致“额外的”悬空提交和/或 blob,因此人们不想在这里丢弃所有这些 danglers:这意味着不要git gc在此存储库上运行,尽管在您的情况下幸运的是git gc失败了,可能什么也没做)。

现在,实际的问题开始了——也许也结束了——在这里:

error: Could not read a2df338e00be26da2f0b84543668ef29b52df5c3

git fsck输出中,我们知道a2df338e00be26da2f0b84543668ef29b52df5c3在它被损坏之前是或者至少是一个提交:

 broken link from  commit c521a9214898aac031831d97e6a8329342b788c4
               to  commit a2df338e00be26da2f0b84543668ef29b52df5c3

提交c521a9214898aac031831d97e6a8329342b788c4本身是可以的,但是它是指a2df338e00be26da2f0b84543668ef29b52df5c3通过哈希ID提交,并且对象a2df338e00be26da2f0b84543668ef29b52df5c3丢失或损坏。

我们还看到:

 broken link from  commit 19f55b6374e7adcc42046821c1659bb47df933a8
               to  commit 0d862ff1507038edf4c26129abcc11ce8c00bef3

这表明该对象0d862ff1507038edf4c26129abcc11ce8c00bef3也丢失了(也许git repack运行时git gc从未尝试读取它,因此它不会出现在git gc输出中)。

这个 repo 很重要——我已经从服务器上重新克隆了它,但是那里的许多本地存储和分支包含我需要的工作。

如果提交对象a2df338e00be26da2f0b84543668ef29b52df5c3并且0d862ff1507038edf4c26129abcc11ce8c00bef3在新克隆中可用,您可以将这些对象安装到损坏的克隆中,看看它是否可以工作。过去,我自己在这种技术上取得了成功。跳转到“复制丢失的对象”部分了解详细信息。

如果这些提交对象不可用,那么您可能做的最好的事情就是git fsck --lost-found在错误的存储库中运行。对于 Git 找到的各种 dangler(通过正常方式无法访问的提交和文件),这将把这些提交/文件写入.git/lost-found/. 坏处是这些文件丢失了它们的名字。您将在.git/lost-found/other/<hash-name>(对于每个哈希名称)中找到文件的内容;弄清楚该文件的“有用名称”是什么将是您的工作。

(文件的名称可能会或可能不会通过对象找到,但git fsck不会将这些树对象保存在任何地方。)

首先弄清楚存储库是如何损坏的也是一个非常好的主意。通常的情况是电源故障或系统崩溃。其他更令人担忧的情况包括磁盘/ SSD 故障:如果您遇到这些故障,可能是时候购买新硬件了。

复制丢失的对象

首先,我们将测试某个对象是否存在于某个克隆中。就我而言,我将使用ebf3c04b262aa27fbb97f8a0156c2347fecafafb,我希望将其作为提交找到:

$ git -C path/to/clone cat-file -t ebf3c04b262aa27fbb97f8a0156c2347fecafafb
commit

如果该对象作为松散对象存在,我们可以直接将其复制到目标 Git:

$ cp path/to/clone/.git/objects/eb/f3c04b262aa27fbb97f8a0156c2347fecafafb path/to/original/.git/objects/eb/f3c04b262aa27fbb97f8a0156c2347fecafafb

(使用通常的技巧,哈希 ID 的前两个字符成为目录名称,其余字符成为该目录中的文件)。

在这个特定的存储库中,这是行不通的,ebf3c04b262aa27fbb97f8a0156c2347fecafafb仅在某些包文件中。可能有一些更聪明的方法可以只解压一个对象,但我不会这样做,而是这样做:git cat-file -p将对象变成“人类可读”的副本,并将git hash-object -t <type> -w --stdin人类可读的对象变成松散的对象在.git/objects/目录中并报告其 ID。所以:

$ git -C path/to/good-clone cat-file -p ebf3c04b262aa27fbb97f8a0156c2347fecafafb | git -C path/to/bad-clone hash-object -t commit -w --stdin
ebf3c04b262aa27fbb97f8a0156c2347fecafafb

转移提交后,您现在可能会发现一堆tree和/或blob丢失的对象,当然可能还有更多提交;这个过程可以一次次重复,有点痛苦。

可能还有一个简单的蛮力方法:将正确的包文件转储到位并运行git index-pack,或将包文件和索引文件转储到位。不过,这不是我过去在其中一种恢复期间所做的。(我很幸运,我只破坏了两三个对象,并且在另一个 repo 中都松散了。我运行了一个 bad find ... -remove,未能跳过.git目录。)


推荐阅读