修复从提交到提交的 git 损坏链接

Nat*_*teM 5 git recovery commit corruption

我遇到过这样的情况: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>
    ...
Run Code Online (Sandbox Code Playgroud)

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

error: Could not read <SHA2>
fatal: Failed to traverse parents of commit <SHA1>
Run Code Online (Sandbox Code Playgroud)

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

$ git log --reverse
error: Could not read <SHA2>
fatal: Failed to traverse parents of commit <SHA1>
Run Code Online (Sandbox Code Playgroud)

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

$ git log
...
error: Could not read <SHA6>
fatal: Failed to traverse parents of commit <SHA7>
Run Code Online (Sandbox Code Playgroud)

这在历史上比问题发生得早得多。有趣的是,这个提交确实存在于我原来的未修复的存储库中。复制 sha 文件让我克服了失败,但又出现了另一个也存在的失败。

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

我可以做什么来恢复这个存储库到工作状态?

Nat*_*teM 5

@LeGEC 为我提供了最终的部分,但我认为有必要展示我使用的完整方法。注意:我希望我能做的很多事情都是针对我的案例的,但有些事情是可以概括的。

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

(a)->(b)->(c)->(d)->(e)->(f)->(g)->(h)->(i)->(j)
Run Code Online (Sandbox Code Playgroud)

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

(b)->(c) (e)->(f) (h)->(i)->(j)
Run Code Online (Sandbox Code Playgroud)

正如问题中所述,备份非常旧并且具有以下形式

(a)->(b)
Run Code Online (Sandbox Code Playgroud)

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

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

$ mkdir my/new/fixed/repository
$ cd my/new/fixed/repository
$ git init
Run Code Online (Sandbox Code Playgroud)

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

$ git remote add origin /path/to/backup/repository
$ get remote fetch
$ get checkout --track my-broken-branch # This may not be necessary
Run Code Online (Sandbox Code Playgroud)

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

$ cd /path/to/repository/root
$ mkdir repository-copy
$ cp -R /path/to/broken/repository /path/to/repository-copy
$ cd /path/to/repository-copy
Run Code Online (Sandbox Code Playgroud)

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

git remote add backup /path/to/backup/repository
git unpack-objects < /path/to/backup/repository/.git/objects/pack/pack-*.pack
Run Code Online (Sandbox Code Playgroud)

好吧,让我们看看伤害有多大:

$ 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>
...
Run Code Online (Sandbox Code Playgroud)

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

$ git show -s <SHAX>
Run Code Online (Sandbox Code Playgroud)

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

error: Could not read <SHA2>
fatal: Failed to traverse parents of commit <SHA1>
Run Code Online (Sandbox Code Playgroud)

因此,我们需要用实际上很好的提交替换 的父项。这种模式称为移植,但由于新的最佳实践,纯移植不再被认为是最佳实践(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>
Run Code Online (Sandbox Code Playgroud)

所以出现了一个新的损坏的提交。如果我使用调查该提交,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>
Run Code Online (Sandbox Code Playgroud)

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

现在我们必须处理丢失的斑点。你可以尝试按照 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>
Run Code Online (Sandbox Code Playgroud)

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

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

$ git filter-repo --force
$ git fsck
Checking object directories: 100%...
Checking objects: 100%...
Run Code Online (Sandbox Code Playgroud)

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

$ 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
Run Code Online (Sandbox Code Playgroud)

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

$ git merge broken/my/branch
Run Code Online (Sandbox Code Playgroud)

历史再次被清白。


LeG*_*GEC 2

(根据您的评论:我假设您设法构建了一个具有提交历史的分支,您认为这是令人满意的)

您可以在损坏的克隆旁边创建一个新的克隆,并迭代地从broken顶部提取您可以提取的内容fresh,以检查您是否提取了有效的对象,并处理有效的存储库。

从一个新的克隆开始:

# next to your broken 'myproject' directory :
git clone <url> fresh
cd fresh
git remote add broken ../myproject
Run Code Online (Sandbox Code Playgroud)

看看是否可以影响您在原始目录中创建的分支myproject

# from fresh :
git fetch broken my/branch
Run Code Online (Sandbox Code Playgroud)

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

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

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

对于 blob :在上述命令git cat-file -p提到的所有 blob 上运行git ls-tree -r。再说一遍:如果缺少一个 blob,您将会收到错误。