如何恢复HEAD^的树?

5

总结一下:如果HEAD^的树在之前没有被推送,而且其他内容都完好无损,是否有可能恢复?

我不小心删除了部分.git内容,但并不确定删除了哪些内容。

在发现git push不能正常工作后,我运行了git fsck

Checking object directories: 100% (256/256), done.
Checking objects: 100% (1265/1265), done.
broken link from  commit f3419f630546ba02baf43f4ca760b02c0f4a0e6d
              to    tree 29616dfefd2bff59b7fb3177e99b4a1efc7132fa
broken link from  commit ccfe9502e24d2b5195008005d83155197a2dca25
              to    tree 0580c3675560cbfd3f989878a9524e35f53f08e9
broken link from  commit ccfe9502e24d2b5195008005d83155197a2dca25
              to  commit 0bca9b3a9f1dd9106922f5b4ec59cdc00dd6c049
broken link from    tree 6d33d35870281340c7c2f86c6d48c8f133b836bb
              to    blob 226d8a10a623acd943bb8eddd080a5929f3ccb2c
broken link from  commit db238d4a52ee8f18a04c038809bc6587d7643438
              to    tree 0b69ab3f6940a04684ee8c0c423ae7da89de749c
missing tree 0580c3675560cbfd3f989878a9524e35f53f08e9
dangling commit 05512f9ac09d932e7d9a11d490c8a2f117c0ca11
missing tree 29616dfefd2bff59b7fb3177e99b4a1efc7132fa
dangling commit 578464dde7d7b8628f77e536b4076cfa491d7602
missing blob 5d351b568abb734605ca4bf446e13cfd87ca9ce8
missing tree 0b69ab3f6940a04684ee8c0c423ae7da89de749c
missing commit 0bca9b3a9f1dd9106922f5b4ec59cdc00dd6c049
dangling blob d53a9d0f3364b648edbc4beede022e4594a84c35
missing blob 23db34f729a88c5f5f7fe6e281921f1334f493d1
dangling commit 8dcbde55462ca0c29e0ca339a49db95b43188ef1
dangling blob e59b25b9675625d0e6b8abfa37e955ab46493fd9
missing blob 226d8a10a623acd943bb8eddd080a5929f3ccb2c
dangling commit 85fdaaa579cf1ae2a8874e3e1f3c65d68b478179
dangling commit 075e9d72e90cc8bf3d960edd8376aaae0847f916
missing blob 83fec2ff8cfcaaa06c96917b6973ace96301e932
dangling commit a88e18e1c102d909361738fd70137b3f4a1c7496
dangling blob 9c6f61e0acffe2a1f5322cd2b72c181e95e9de75
dangling commit ca9fe0dd3123a731fc310b2a2285b00ef673de79

我的假设是我仅仅缺少一些可以从GitHub中恢复的信息。我的第一反应是运行git fetch,但是它没有输出任何内容,因为它认为没有新内容需要获取。

我尝试了几种方法解压.git/objects/pack/pack-ea43d1db155e4502c2250ec1d4608843715c8b1f.pack文件,但都没有成功。例如:

% git clone --mirror git://github.com/strugee/dots.git # returns bare repo
Cloning into bare repository 'dots.git'...
remote: Counting objects: 1331, done.
remote: Compressing objects: 100% (23/23), done.
remote: Total 1331 (delta 12), reused 0 (delta 0)
Receiving objects: 100% (1331/1331), 402.31 KiB | 197.00 KiB/s, done.
Resolving deltas: 100% (454/454), done.
Checking connectivity... done.
% ls dots.git
config  description  HEAD  hooks  info  objects  packed-refs  refs
% mkdir git-tmp; cd git-tmp
% git init
% git unpack-objects < ../dots.git/objects/pack/pack-ea43d1db155e4502c2250ec1d4608843715c8b1f.pack
error: inflate: data stream error (incorrect data check)
error: inflate returned -3

我每次都收到这个错误。(请记住:这是一个 --mirror,所以它是 GitHub 的精确副本 - 对吧?那么怎么会损坏呢?)
最终我意识到我实际上并不需要解压 packfile。我只需将其复制回原始仓库,Git 就可以很好地识别它。因此:
% cd ../configs
% cp ../dots.git/objects/pack/pack-ea43d1db155e4502c2250ec1d4608843715c8b1f.* .git/objects/pack/

这似乎起到了作用。大部分情况下是这样。

% git fsck
Checking object directories: 100% (256/256), done.
Checking objects: 100% (2596/2596), done.
broken link from  commit db238d4a52ee8f18a04c038809bc6587d7643438
              to    tree 0b69ab3f6940a04684ee8c0c423ae7da89de749c
dangling commit 05512f9ac09d932e7d9a11d490c8a2f117c0ca11
dangling commit 578464dde7d7b8628f77e536b4076cfa491d7602
missing blob 5d351b568abb734605ca4bf446e13cfd87ca9ce8
missing tree 0b69ab3f6940a04684ee8c0c423ae7da89de749c
dangling blob d53a9d0f3364b648edbc4beede022e4594a84c35
dangling commit 8dcbde55462ca0c29e0ca339a49db95b43188ef1
dangling commit 85fdaaa579cf1ae2a8874e3e1f3c65d68b478179
dangling commit 075e9d72e90cc8bf3d960edd8376aaae0847f916
missing blob 83fec2ff8cfcaaa06c96917b6973ace96301e932
dangling commit a88e18e1c102d909361738fd70137b3f4a1c7496
dangling commit ca9fe0dd3123a731fc310b2a2285b00ef673de79

如您所见,这修复了所有缺失的链接,除了一个。事实证明,db238d是一个提交的id(恰好是HEAD^),我还没有推送它。我是否正确地假设这个仓库中的最后两个提交是无法恢复的,我需要重新创建这些提交的内容?在这种情况下,我做出了正确的决定吗?


下一次(Git 2.10,2016年第三季度),不要忘记 git fsck --name-objects:请参见我的答案 - VonC
2个回答

8

尝试使用git fetch-pack命令从另一个存储库中恢复缺失的对象。以下为详细说明。

如果要恢复未推送的提交,特别是HEAD^1,可以从以下步骤开始:

git diff-tree -r HEAD~2^{tree} HEAD^{tree}

你会得到一个列出所有已更改的树/ blob及其SHA(其中包括HEAD和HEAD ^ 1的更改)的列表。 根据可用的信息,您可以重新创建一些或所有缺失的树。 缺少的blob则更加棘手。
使用git fetch-pack
故意损坏存储库
me@myvm:/scratch/corrupt/.git  (GIT_DIR!)$ cd objects/
me@myvm:/scratch/corrupt/.git/objects  (GIT_DIR!)$ ll
total 20
drwxrwxr-x 2 andrewc warp 4096 Oct  7 06:03 20
drwxrwxr-x 2 andrewc warp 4096 Oct  7 06:03 22
drwxrwxr-x 2 andrewc warp 4096 Oct  7 06:03 25
drwxrwxr-x 2 andrewc warp 4096 Oct  7 06:03 info
drwxrwxr-x 2 andrewc warp 4096 Oct  7 06:03 pack
me@myvm:/scratch/corrupt/.git/objects  (GIT_DIR!)$ rm -rf 22

验证头处于错误状态

me@myvm:/scratch/corrupt/.git/objects  (GIT_DIR!)$ cd ../../
me@myvm:/scratch/corrupt  (master)$ git status
fatal: bad object HEAD

恢复丢失的对象

me@myvm:/scratch/corrupt  (master)$ git fetch-pack --all $(git config --get remote.origin.url)
error: refs/heads/master does not point to a valid object!
error: refs/remotes/origin/HEAD does not point to a valid object!
error: refs/remotes/origin/master does not point to a valid object!
error: refs/heads/master does not point to a valid object!
error: refs/remotes/origin/HEAD does not point to a valid object!
error: refs/remotes/origin/master does not point to a valid object!
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
22ecde746be79c65b27a5cf1dc421764d8ff6e17 HEAD
22ecde746be79c65b27a5cf1dc421764d8ff6e17 refs/heads/master
me@myvm:/scratch/corrupt  (master)$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

丢失的对象已恢复。
me@myvm:/scratch/corrupt  (master)$ ll .git/objects/
total 20
drwxrwxr-x 2 andrewc warp 4096 Oct  7 06:03 20
drwxrwxr-x 2 andrewc warp 4096 Oct  7 06:05 22
drwxrwxr-x 2 andrewc warp 4096 Oct  7 06:03 25
drwxrwxr-x 2 andrewc warp 4096 Oct  7 06:03 info
drwxrwxr-x 2 andrewc warp 4096 Oct  7 06:03 pack
me@myvm:/scratch/corrupt  (master)$ 


me@myvm:/scratch/corrupt  (master)$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

如果您遇到了一个破损的树对象和一个破损的 blob(二进制大对象)对象,您可以手动恢复这些对象。您可以使用git cat-file -p BLOB_SHA命令来查看任何 blob 的内容,这将会显示其内容。如果您通过查看内容能够找出文件类型,这将有助于您恢复文件。同样的,git cat-file -p TREE_SHA命令将会显示该树形结构,其中包含了文件名和 blob SHA 值。此时,您需要尝试手动构建树形结构和提交对象,这通常需要利用部分数据进行操作。如果您的 HEAD 提交是正确的,那么您只需要补充历史记录,并且应该至少覆盖了最近的状态。

不错!但是这样行不通,因为缺失的对象不在GitHub上。我想知道Git是否在其他地方有它的副本。我怀疑没有,所以我想要一个回答,告诉我在哪里可以找到它,或者明确地说“它已经丢失了”。 - strugee
你从除了Github以外的地方获取过代码吗?如果没有,那么缺失的提交都是本地未推送(或无用)的。你注意到未推送的HEAD^有一个损坏的树。通过检查和比较HEAD^2^{tree}和HEAD^{tree},你可能可以恢复其中的一些blob(除非它们也是丢失的)。 - Andrew C
唯一缺失的是HEAD^树。您提到的差异化过程正是我正在寻找的。 - strugee
尝试运行 git diff-tree -r HEAD~2^{tree} HEAD^{tree} 命令。这里的 ^ 不像在 HEAD^ 中表示父级,而是表示 HEAD~2(我在第一条评论中打错了,本意是 ~2 而不是 ^2)。 - Andrew C
@AndrewC 所以如果我理解正确,就无法找出更改的内容,只能找到已更改的文件对吗? - strugee
树应该打印出对象的名称和 blob sha,blob sha 包含内容。我会更新答案。 - Andrew C

2

所以我的假设是,我仅仅缺少一些可以从GitHub中恢复的信息。

一般来说,如果您能够确定那个损坏的链接究竟来自哪里,那就更有帮助了。

这就是Git 2.10(2016年第三季度)将提出的内容:

git fsck --name-objects

请见提交记录 90cf590提交记录 1cd772c提交记录 7b35efd提交记录 993a21b (由 Johannes Schindelin (dscho) 于 2016 年 7 月 17 日提交)。
(由 Junio C Hamano -- gitster --提交记录 9db3979 中合并,于 2016 年 7 月 25 日)

fsck:可选地显示更多有关损坏链接的信息

当 "git fsck" 报告损坏的链接(例如,一个树对象包含一个不存在的 blob)时,同时报告包含对象和被引用的对象的 40 个十六进制对象名称。
该命令学会了 "--name-objects" 选项,以显示从现有引用到包含对象的路径(例如 "HEAD~24^2:file.txt")。


三年后,在 Git 2.25(2020 年第一季度)中重新设计了 git fsck:清理了围绕“git fsck”中使用的对象解析和低级对象访问的代码和逻辑的混乱。

这反过来修复了 fsck 如何装饰其条目。

请参考 commit b2f2039commit c5b4269commit 103fb6dcommit f648ee7commit cc57900commit 7854399commit b8b00f1commit 6da40b2commit 3837025commit f597937commit 5afc4b1commit 82ef89bcommit 7339029commit d40bbc1commit a59cfb3commit 23a173acommit 2175a0ccommit ec65231commit 1de6007commit 78d

fsck:统一对象名称代码

签名作者:Jeff King

提交90cf590f53("fsck: 可选显示更有用的损坏链接信息",2016-07-17,Git v2.10.0-rc0 - 在批次#7中列出的合并)添加了一个系统来装饰对象名称。 代码分散在builtin/fsck.c(提供初始名称)和fsck.c(扫描对象图时添加名称)之间。
这导致一些重复的地方,两个站点都有近乎相同的describe_object()函数(不同之处在于builtin/fsck.c中的函数使用循环缓冲区允许在单个printf中进行多次调用)。

我们提供一个统一的object_name API以供fsck使用。

这使我们可以消除重复,并使接口边界更清晰(这将让我们在将来的补丁中重构实现)。

我们将在builtin/fsck.c中保留describe_object()作为围绕新API的薄包装器,因为它依赖于静态全局变量使其许多调用者更短。

我们还将把builtin/fsck.c中的裸add_decoration()调用转换为put_object_name()

这修复了两个小错误

  1. 我们泄漏了许多小字符串。 add_decoration()采用最后一个覆盖策略:它将装饰更新为新字符串并返回旧字符串。 但是我们忽略了返回值,泄漏了旧字符串。
    这很常见,因为我们查看引用日志:任何引用的末尾都将通过查看实际引用以及最新的引用日志条目来描述。
    所以我们总是会泄漏其中一个字符串。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接