我遇到git fsck
调用返回几个断开的链接的情况。这是因为,对于此存储库,运行了rm
命令并删除了几个写保护文件(犯了错误)。此存储库最近也没有备份(再次犯了错误)。因为使用了 Git,所以存储库并没有完全丢失,但一些历史记录已被打乱。直到最近要重新同步到源时,这才被注意到,并且由于历史记录中断,这失败了。
我想修复此历史记录(如果可能),以便它可以与上游源合并。我认识到我将无法恢复完整的历史记录,因为有些文件刚刚消失,但我想尽可能多地保留它,以便正常工作。
我已经查看了Linus的电子邮件"如何恢复损坏的blob对象"(MIT托管副本),并且还查看了:
如何恢复因硬盘故障损坏的 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 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
但最终完全重新初始化存储库,这也不是我真正想要的。
我该怎么做才能将此存储库恢复到正常工作状态?
@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>
...
令人感兴趣的是悬而未决的提交,因为这些可能是我们想要尝试重新拼接在一起的小分支。请注意,这些提交并不总是按时间顺序排列。对我来说,顺序恰好是(从最旧到最新)<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>
没有新的悬空提交,就我而言,能够连接到我的备份存储库。在其他情况下,我想这并不总是正确的。如果是这样,您最终可以达到可以将远程仓库的头部移植为剩余的错误提交链接的地步。
现在我们必须处理丢失的斑点。您可以尝试按照Linus的方法修复它们,或者,如果您愿意接受丢失的历史记录,则可以再次使用git place将它们从历史记录中删除。一般方法是
$ 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
历史再次干净。
(来自您的评论:我假设您设法构建了一个具有提交历史记录的分支,您认为这令人满意)
您可以在损坏的克隆旁边创建一个新的克隆,并在fresh
之上迭代地从broken
中提取内容,以检查是否拉入了有效的对象,并处理有效的存储库。
从一个新的克隆开始:
# 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 <commit>
。如果树无效,git ls-tree -r
将提及错误。
对于 blob :在上述git ls-tree -r
命令提到的所有 blob 上运行git cat-file -p
。再次:如果缺少斑点,您将遇到错误。