首页 > 解决方案 > 修复从提交到提交的 git 断开链接

问题描述

我有一个git fsck调用返回几个断开的链接的情况。这是因为,对于此存储库,rm运行了一个命令并删除了几个写保护文件(发生了错误)。该存储库也没有最近的备份(同样,犯了错误)。因为 Git 被使用了,所以存储库并没有完全丢失,但是一些历史已经被打乱了。直到最近要重新同步到源时,这才引起注意,并且由于历史记录损坏,这失败了。

我想修复此历史记录(如果可能),以便它可以与上游源合并。我认识到我将无法取回完整的历史记录,因为一些文件刚刚消失,但我想尽可能多地保留它,以确保一切正常。

我查看了 Linus 的电子邮件“如何恢复损坏的 blob 对象”(麻省理工学院托管副本),还查看了:

如何恢复因硬盘故障损坏的 Git 对象?

修复损坏的 Git 存储库

与许多其他人一起,但我没有看到太多关于从提交到提交错误的断开链接的建议。请注意,我确实复制了这个存储库,所以我没有擦除任何东西。

结果git fsck

    $ git fsck
    broken link from commit <SHA1>
                  to commit <SHA2>
    broken link from   tree <SHA3>
                  to   blob <SHA4>
    ...
    dangling blob <SHA5>
    missing commit <SHA2>
    missing blob <SHA4>
    ...

当我最终通过 git 历史记录时,git log我得到了错误

error: Could not read <SHA2>
fatal: Failed to traverse parents of commit <SHA1>

它靠近(ish)最后一个备份存在的位置,但不完全在那里,所以我没有重叠的覆盖范围。我想尝试反向遍历历史,认为我可以将我的日志从最旧的提交移动到最新的提交,但是

$ git log --reverse
error: Could not read <SHA2>
fatal: Failed to traverse parents of commit <SHA1>

所以我不能尝试绑定双方的提交(除非有人知道如何做到这一点)。我尝试使用git repair它似乎能够解决一些问题,但不是全部。从现在开始,它似乎也正在破坏事物git log

$ git log
...
error: Could not read <SHA6>
fatal: Failed to traverse parents of commit <SHA7>

这在历史上比问题发生得更早。有趣的是,这个提交确实存在于我原来未修复的存储库中。复制 sha 文件让我摆脱了失败,只是因为另一个同样存在的文件突然出现。

它建议我运行git repair --force,但最终完全重新初始化了存储库,这也不是我真正想要的。

我该怎么做才能将此存储库恢复到工作状态?

标签: gitcommitcorruptionrecovery

解决方案


@LeGEC 为我提供了最后的部分以将其组合在一起,但我认为值得展示我使用的完整方法。注意:我希望我能做的很多事情都是针对我的案例的,但有些事情可以概括。

在查看结果时,git fsck我发现有几个悬空提交。当我检查这些哈希时,我发现了一些好的提交。所以一个具有原始结构的存储库

(a)->(b)->(c)->(d)->(e)->(f)->(g)->(h)->(i)->(j)

在之后,我们称之为“不明智”,rm命令可能会处于类似的状态

(b)->(c) (e)->(f) (h)->(i)->(j)

如问题所述,备份非常旧并且具有

(a)->(b)

但就是这样。能做的就是用它git replace来尝试解决这个问题。BE WARNEDgit replace似乎是真正摧毁你的存储库的绝佳工具。我是在原始存储库的副本上执行此操作的,我很高兴这不是真正的交易!

我们将在一个新的(良好的)基础上构建我们的新存储库。我们首先从我们拥有的备份中初始化一个新的存储库。

$ mkdir my/new/fixed/repository
$ cd my/new/fixed/repository
$ git init

现在,从我们的备份(它没有覆盖损坏存储库的全部空间)中,我们将解压缩现有的结构。

$ git remote add origin /path/to/backup/repository
$ get remote fetch
$ get checkout --track my-broken-branch # This may not be necessary

为了避免弄乱我们损坏的存储库,我们制作了一个副本

$ cd /path/to/repository/root
$ mkdir repository-copy
$ cp -R /path/to/broken/repository /path/to/repository-copy
$ cd /path/to/repository-copy

首先,让我们尝试使用我们以前的存储库来修复我们可以解决的问题:

git remote add backup /path/to/backup/repository
git unpack-objects < /path/to/backup/repository/.git/objects/pack/pack-*.pack

好吧,让我们看看伤害是什么:

$ git fsck
broken link from  commit <SHA1>
              to  commit <SHA2>
broken link from    tree <SHA3>
              to    blob <SHA4>
...
dangling commit <SHA5>
...
missing commit <SHA2>
...
missing blob <SHA4>
...
dangling commit <SHA6>
...

有趣的是悬空提交,因为它们很可能是我们想要尝试并重新组合在一起的小子分支。请注意,这些提交并不总是按时间顺序排列。对我来说,顺序恰好是(从最旧到最新)<SHA5>-<SHA6>,但你可能会有自己的结要解开。您可以通过运行检查提交日期/时间

$ git show -s <SHAX>

此时需要注意的一点是,如果您在损坏的存储库副本中,然后运行该命令git log,您将能够遍历存储库,直到您遇到错误时:

error: Could not read <SHA2>
fatal: Failed to traverse parents of commit <SHA1>

所以我们需要用一个实际上很好的提交替换父级。这种模式被称为嫁接,但由于新的(呃)最佳实践,不再将其视为最佳实践(git 嫁接和替换有何不同?(现在不推荐使用嫁接吗?)git replace ) 。

所以我现在让父母

$ git replace --graft <SHA1> <SHA6>
$ git fsck
broken link from  commit <SHA1>
              to  commit <SHA2>
broken link from    tree <SHA3>
              to    blob <SHA4>
...
broken link from  commit <SHA7>
              to  commit <SHA8>

所以出现了一个新的中断提交。如果我使用我调查该提交,git log我发现上一个提交在剩余悬空提交的提交时间之前结束。所以我要把这两个嫁接在一起。请注意,如果您有很多人在此存储库上工作,这可能不是一件安全的事情,但在这种情况下,我相信它是可以的。

$ git replace --graft <SHA7> <SHA5>
$ git fsck
broken link from  commit <SHA1>
              to  commit <SHA2>
broken link from    tree <SHA3>
              to    blob <SHA4>
...
broken link from  commit <SHA7>
              to  commit <SHA8>

没有新的悬空提交,就我而言,能够连接到我的备份存储库。在其他情况下,我想这并不总是正确的。如果是这样,您最终可以将远程存储库的头部移植为剩余的错误提交链接。

现在我们必须处理丢失的 blob。您可以尝试按照 Linus 的方法修复它们,或者,如果您愿意接受丢失的历史记录,您可以再次使用 git replace 从历史记录中删除它们。一般的做法是

$ git ls-tree <SHA3>
...
100644 blob <SHA4>  my-magic-file
...
$ git log --raw --all --full-history -- subdirectory/my-magic-file | grep -B 20 -A 20 "<SHA4>" # May just need to use first few values from SHA4
# commit information after missing blob
# commit information for missing blob
# commit information before missing blob
$ git replace --graft <commit-after-missing-blob> <commit-before-missing-blob>

重复此操作,直到git rev-list --objects my/branch运行完成。

现在,您需要删除无关的提交。幸运的是,已经开发出一种新工具来做到这一点:git-filter-repo. 该工具将提交我们的移植并重构历史。

$ git filter-repo --force
$ git fsck
Checking object directories: 100%...
Checking objects: 100%...

现在让我们看看我们是否可以从损坏的分支中成功获取我们的存储库。

$ cd /path/to/my/new/fixed/repository
$ git fetch broken my/branch
...
From /path/to/my/broken/repository
 * branch            my/branch        -> FETCH_HEAD
 * [new branch]      my/branch        -> broken/my/branch

而且,因为我们与遥控器有共同的历史记录,我们现在可以与之前损坏的分支合并

$ git merge broken/my/branch

历史再次清白。


推荐阅读