【问题标题】:Repair git broken link from commit to commit修复从提交到提交的 git 断开链接
【发布时间】:2021-03-17 16:11:43
【问题描述】:

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

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

我查看了 Linus 的电子邮件“如何恢复损坏的 blob 对象”(MIT hosted copy),还查看了:

How to recover Git objects damaged by hard disk failure?

Repair corrupted Git repository

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

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 log 浏览 git 历史时,最终我得到了错误

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,但最终完全重新初始化了存储库,这也不是我真正想要的。

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

【问题讨论】:

  • “断开的链接”投诉实际上只是意味着git fsck 无法将给定的哈希 ID 检索为必要类型的有效对象。这就是为什么您在那之后不久就将它们视为“丢失”的原因:Git 根本无法检索它们。 fsck 可能会抱怨类似(但略有不同),例如,如果提交声称其父提交是 hashX 并且 hashX 存在但不是提交对象(是树、blob 或带注释的标签对象)。
  • 我不太确定 git repair 究竟做了什么,但阅读 this page 上的顶级声明表明,当且仅当它能够从它通过在配置中找到的各种远程 Git URL 找到相关的 Git 存储库。
  • @torek 好的,这对git repair 更有意义。我应该更彻底地阅读文档。我已经取得了一些进展,git log 现在可以完全构建,但git fsck 仍然显示丢失的提交,git gc 也会阻塞。

标签: git commit corruption recovery


【解决方案1】:

@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来尝试解决这个问题。警告git 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>
...

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

$ git show -s <SHAX>

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

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

所以我们需要用一个实际上很好的提交替换父级。这种模式称为嫁接,但由于新的(呃)最佳实践git replace,不再将其视为最佳实践(How do git grafts and replace differ? (Are grafts now deprecated?))。

所以我现在把

$ 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

历史又一次干净了。

【讨论】:

  • 感谢您的帖子。对my similar problem 感到沮丧,我听从了您的建议。似乎他们改变了git replace --graft 的行为,因为在git fsck 之后我没有得到任何更改,因为它不使用这些替换。尽管如此,我还是让我的其他命令运行(日志、责备等),这些命令使用了替换。
【解决方案2】:

(来自您的 cmets :我假设您设法建立了一个具有提交历史的分支,您认为这很满意)

您可以在损坏的克隆旁边创建一个新克隆,然后在 fresh 之上迭代地从 broken 提取您可以做的内容,以检查您是否提取了有效对象,并处理有效的 repo .

从一个新的克隆开始:

# next to your broken 'myproject' directory :
git clone <url> fresh
cd fresh
git remote add broken ../myproject

看看您是否可以感染您在原始myproject 目录中创建的分支:

# from fresh :
git fetch broken my/branch

如果此操作有效,这意味着您只拉入了有效的提交,指向有效的树和有效的 blob,并且您处于稳定状态。

另一方面,如果此操作不起作用:您将需要找出哪些提交具有有效内容。

对于树:对从“远程中的最后一个”到分支头部的所有提交运行 git ls-tree -r &lt;commit&gt;。如果树是无效的,git ls-tree -r 会报错。

对于 blob:在上面 git ls-tree -r 命令提到的所有 blob 上运行 git cat-file -p。同样:如果缺少 blob,您将遇到错误。

【讨论】:

  • 这对我没有用,因为即使在git replace --graft 之后,我仍然看到与git fsck 的旧链接断开。我不知道为什么它没有被清理
  • git fsck 扫描所有对象,因此预计它仍会检查那些损坏的对象。我找到了正确的命令来检查现有提交(而不是“我的仓库中的所有对象”):git rev-list --objects my/branch
  • git replace 单独不会重写对象,它只是将它们从诸如git log 之类的命令中“隐藏”起来。要真正重写提交以使它们与这个新历史记录匹配,您可以使用 git-filter-repo :运行 git replace --graft 命令后,运行 git filter-repo --force(请参阅 this example
  • 我想更清楚一点,我发出了git replace --graft &lt;SHA1&gt; &lt;good_SHA&gt;,但我仍然在git fsck 中收到broken link from commit &lt;SHA1&gt; to commit &lt;SHA2&gt; 错误。 git rev-list --objects my/branch 似乎很干净(反正没有错误)。 不会出现在 git rev-list 调用中。
  • 好的,git-filter-repo 是我需要的灵丹妙药。在运行它并清理 blob 之后,我已经使存储库处于工作状态。如果您想更新您的答案以包含git-filter-repo,我会接受它作为答案。我还将上传我自己的答案,详细说明。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-18
  • 2018-01-24
  • 2016-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-02
相关资源
最近更新 更多