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

100

我遇到了一次硬盘故障,导致Git存储库中的某些文件损坏。在运行git fsck --full命令时,我得到以下输出:

error: .git/objects/pack/pack-6863e0a0e4b4ded6090fac5d12eba6ca7346b19c.pack SHA1 checksum mismatch
error: index CRC mismatch for object 6c8cae4994b5ec7891ccb1527d30634997a978ee from .git/objects/pack/pack-6863e0a0e4b4ded6090fac5d12eba6ca7346b19c.pack at offset 97824129
error: inflate: data stream error (invalid code lengths set)
error: cannot unpack 6c8cae4994b5ec7891ccb1527d30634997a978ee from .git/objects/pack/pack-6863e0a0e4b4ded6090fac5d12eba6ca7346b19c.pack at offset 97824129
error: inflate: data stream error (invalid stored block lengths)
error: failed to read object 0dcf6723cc69cc7f91d4a7432d0f1a1f05e77eaa at offset 276988017 from .git/objects/pack/pack-6863e0a0e4b4ded6090fac5d12eba6ca7346b19c.pack
fatal: object 0dcf6723cc69cc7f91d4a7432d0f1a1f05e77eaa is corrupted

我有仓库的备份,但唯一包含pack文件的备份已经损坏。所以我认为我必须找到一种方法从不同的备份中检索单个对象,并以某种方式指示Git生成一个只包含正确对象的新包。

请问您能给我修复我的存储库的提示吗?


2
这只是刚刚发生在我身上的事。 我不想与git对象搞混...所以我将项目从远程存储库重新克隆到一个新文件夹中,然后仅将我有问题的存储库中的所有文件(当然要排除.git文件夹)复制到刚刚克隆的存储库中...然后在新的存储库中执行 git status 命令...git正确检测到了我文件的所有更改,我现在可以开始我的工作了。 - Rosdi Kasim
8个回答

90
在之前的某些备份中,您的坏对象可能被打包在不同的文件中或者仍然是散对象。因此,您的对象可能可以恢复。
看起来您的数据库中有一些坏对象。所以您可以手动处理。
由于git hash-objectgit mktreegit commit-tree没有写入对象,因为它们在打包中被找到,那么请开始执行以下操作:
mv .git/objects/pack/* <somewhere>
for i in <somewhere>/*.pack; do
  git unpack-objects -r < $i
done
rm <somewhere>/*

您的包已从仓库中移出,并在其中解压;现在只有好的对象在数据库中。

您可以执行以下操作:

git cat-file -t 6c8cae4994b5ec7891ccb1527d30634997a978ee

并检查对象的类型。

如果类型是blob:从之前的备份中检索文件内容(使用git showgit cat-filegit unpack-file),然后您可以使用git hash-object -w在当前存储库中重新编写该对象。

如果类型是tree:您可以使用git ls-tree从以前的备份中恢复树形结构;然后使用git mktree将其再次写入当前存储库。

如果类型是commit:与git showgit cat-filegit commit-tree相同。

当然,在开始此过程之前,我会备份您的原始工作副本。

另请参阅如何恢复损坏的Blob对象


2
谢谢,那救了我一命!我会将我的确切步骤作为单独的答案发布。 - Christian
只是一个更正:以“done”结尾的命令,而不是“end”。 - Felipe
在 git unpack-objects -r < $i 之后缺少了一个分号。 - mithrandir
1
如果您将“done”放在前一行:是的,您需要一个分号。如果您粘贴我所写的内容,则不需要。 - Daniel Fanjul
我遇到了这个问题:http://stackoverflow.com/q/38770736/575643 我尝试了你的解决方案,但是无法解决它,你有什么线索可以修复它吗? - Idemax
显示剩余2条评论

41

Banengusk给了我正确的方向。为了进一步参考,我想发布我采取的步骤来修复我的存储库损坏。我很幸运地在旧包或存储库备份中找到了所有所需的对象。

# Unpack last non-corrupted pack
$ mv .git/objects/pack .git/objects/pack.old
$ git unpack-objects -r < .git/objects/pack.old/pack-012066c998b2d171913aeb5bf0719fd4655fa7d0.pack
$ git log
fatal: bad object HEAD

$ cat .git/HEAD 
ref: refs/heads/master

$ ls .git/refs/heads/

$ cat .git/packed-refs 
# pack-refs with: peeled 
aa268a069add6d71e162c4e2455c1b690079c8c1 refs/heads/master

$ git fsck --full 
error: HEAD: invalid sha1 pointer aa268a069add6d71e162c4e2455c1b690079c8c1
error: refs/heads/master does not point to a valid object!
missing blob 75405ef0e6f66e48c1ff836786ff110efa33a919
missing blob 27c4611ffbc3c32712a395910a96052a3de67c9b
dangling tree 30473f109d87f4bcde612a2b9a204c3e322cb0dc

# Copy HEAD object from backup of repository
$ cp repobackup/.git/objects/aa/268a069add6d71e162c4e2455c1b690079c8c1 .git/objects/aa
# Now copy all missing objects from backup of repository and run "git fsck --full" afterwards
# Repeat until git fsck --full only reports dangling objects

# Now garbage collect repo
$ git gc
warning: reflog of 'HEAD' references pruned commits
warning: reflog of 'refs/heads/master' references pruned commits
Counting objects: 3992, done.
Delta compression using 2 threads.
fatal: object bf1c4953c0ea4a045bf0975a916b53d247e7ca94 inconsistent object length (6093 vs 415232)
error: failed to run repack

# Check reflogs...
$ git reflog

# ...then clean
$ git reflog expire --expire=0 --all

# Now garbage collect again
$ git gc       
Counting objects: 3992, done.
Delta compression using 2 threads.
Compressing objects: 100% (3970/3970), done.
Writing objects: 100% (3992/3992), done.
Total 3992 (delta 2060), reused 0 (delta 0)
Removing duplicate objects: 100% (256/256), done.
# Done!

3
此外,如果备份中有一个包含缺失文件的包,正确的方法是通过“git cat-file blob <SHA1> > file.dat”提取包中的blob,然后通过“git hash-object -w file.dat”将其恢复到损坏的仓库中,具体请参考Daniel的回答。 - Emil Styrke
你如何找到最后一个未损坏的包?谢谢。 - Romain Ourgorry

26

首先尝试以下命令(如有需要,请重新运行):

$ git fsck --full
$ git gc
$ git gc --prune=today
$ git fetch --all
$ git pull --rebase

如果您仍然遇到问题,可以尝试以下方法:

  • 删除所有损坏的对象,例如

    fatal: loose object 91c5...51e5 (stored in .git/objects/06/91c5...51e5) is corrupt
    $ rm -v .git/objects/06/91c5...51e5
    
    删除所有空对象,例如:
    error: object file .git/objects/06/91c5...51e5 is empty
    $ find .git/objects/ -size 0 -exec rm -vf "{}" \;
    
  • 检查“链接已损坏”消息的方法:

    git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
    

    这将告诉您损坏blob文件来自哪个文件!

  • 要恢复文件,您可能非常幸运,它可能是您在工作树中已经签出的版本:

  • git hash-object -w my-magic-file
    
  • 再次运行,如果输出缺失的SHA1(4b945..),那么现在就完成了!

  • 假设它是某个旧版本出了问题,最简单的方法是执行:

  • git log --raw --all --full-history -- subdirectory/my-magic-file
    

    你可以使用该命令查看文件的完整日志(请注意,您可能不在顶级目录下,因此需要自行确定子目录),然后您现在可以再次使用hash-object命令重新创建缺失的对象。

  • 获取所有缺失提交、树或blob的引用列表:

  • $ git for-each-ref --format='%(refname)' | while read ref; do git rev-list --objects $ref >/dev/null || echo "in $ref"; done
    

    在使用常规的分支 -d 或标签 -d 命令删除一些引用时,由于可能会发现损坏,因此无法删除。因此,请改用管道命令 git update-ref -d $ref。请注意,在本地分支的情况下,该命令可能会在 .git/config 中留下过时的分支配置。可以手动删除它们(搜索 [branch "$ref"] 部分)。

    在所有引用都已清除后,引用日志中仍可能存在损坏的提交。您可以使用 git reflog expire --expire=now --all 清除所有引用日志。如果不想失去所有引用日志,可以查找单个引用的损坏引用日志:

    $ (echo HEAD; git for-each-ref --format='%(refname)') | while read ref; do git rev-list -g --objects $ref >/dev/null || echo "in $ref"; done
    

    (请注意在 git rev-list 命令中添加了 -g 选项。) 然后,在每个受影响的引用上运行 git reflog expire --expire=now $ref。

    当所有损坏的引用和 reflog 被删除后,运行 git fsck --full 以检查存储库是否干净。悬空对象是可以的。

    以下是命令的高级用法,如果不明智地使用,可能导致数据丢失,请在意外造成更多损害之前备份您的git。如果您知道自己在做什么,请自行决定风险。


    在获取后将当前分支拉到上游分支的顶部:

    $ git pull --rebase
    

    你也可以尝试检出一个新分支并删除旧的分支:

    $ git checkout -b new_master origin/master
    

    要查找 Git 中的损坏对象以进行删除,请尝试以下命令:

    while [ true ]; do f=`git fsck --full 2>&1|awk '{print $3}'|sed -r 's/(^..)(.*)/objects\/\1\/\2/'`; if [ ! -f "$f" ]; then break; fi; echo delete $f; rm -f "$f"; done
    

    对于OSX,使用sed -E替代sed -r


    另一个想法是从打包文件中解压所有对象,以重新生成.git/objects内的所有对象,因此请尝试在您的存储库中运行以下命令:

    $ cp -fr .git/objects/pack .git/objects/pack.bak
    $ for i in .git/objects/pack.bak/*.pack; do git unpack-objects -r < $i; done
    $ rm -frv .git/objects/pack.bak
    
    如果上述方法不起作用,您可以尝试从另一个存储库中复制git对象,例如:
    $ rsync -varu git_server:/path/to/git/.git local_git_repo/
    $ rsync -varu /local/path/to/other-working/git/.git local_git_repo/
    $ cp -frv ../other_repo/.git/objects .git/objects
    
    尝试检出以下分支时,修复已损坏的分支:
    $ git checkout -f master
    fatal: unable to read tree 5ace24d474a9535ddd5e6a6c6a1ef480aecf2625
    

    尝试将其删除,然后再次从上游检出:

    $ git branch -D master
    $ git checkout -b master github/master
    

    如果Git将您置于分离状态,请切换到master并将分离的分支合并到其中。


    另一个想法是递归地重新设置现有的主分支:

    $ git reset HEAD --hard
    $ git rebase -s recursive -X theirs origin/master
    

    另请参见:


qnundrum.com的链接已经失效:“404.未找到。请求的URL在此服务器上未找到。”。可能www.seeques.com也是如此。 - Peter Mortensen
非常感谢!对我来说,需要运行 rm -v .git/objects/06/91c5...51e5 命令来修复我的代码库中的一个损坏对象。 - Nightmare_82

2

以下是我遵循的步骤来恢复损坏的 blob 对象。

1)确定损坏的 blob。

git fsck --full
  error: inflate: data stream error (incorrect data check)
  error: sha1 mismatch 241091723c324aed77b2d35f97a05e856b319efd
  error: 241091723c324aed77b2d35f97a05e856b319efd: object corrupt or missing
  ...

损坏的 blob 是 241091723c324aed77b2d35f97a05e856b319efd

2) 将损坏的 blob 移动到安全位置(以防万一)

mv .git/objects/24/1091723c324aed77b2d35f97a05e856b319efd ../24/

3) 获取损坏blob的父级

git fsck --full
  Checking object directories: 100% (256/256), done.
  Checking objects: 100% (70321/70321), done.
  broken link from    tree 0716831e1a6c8d3e6b2b541d21c4748cc0ce7180
              to    blob 241091723c324aed77b2d35f97a05e856b319efd

父级哈希值为0716831e1a6c8d3e6b2b541d21c4748cc0ce7180

4) 获取对应于损坏Blob的文件名

git ls-tree 0716831e1a6c8d3e6b2b541d21c4748cc0ce7180
  ...
  100644 blob 241091723c324aed77b2d35f97a05e856b319efd    dump.tar.gz
  ...

在备份或上游git仓库中找到这个特定的文件(在我的情况下是dump.tar.gz)。然后将其复制到本地仓库的某个位置。

5)将之前损坏的文件添加到git对象数据库中

git hash-object -w dump.tar.gz

6) 庆祝!


git gc
  Counting objects: 75197, done.
  Compressing objects: 100% (21805/21805), done.
  Writing objects: 100% (75197/75197), done.
  Total 75197 (delta 52999), reused 69857 (delta 49296)

这对我没用。第4步导致了 git ls-tree 9504a07fb803edfdf0c1dd99c5d561274af87982 error: Could not read 19505205fd1f219993da9b75846fff3cf432152d,我也尝试了没有第2步的所有步骤,结果是 git ls-tree 9504a07fb803edfdf0c1dd99c5d561274af87982 error: inflate: data stream error (invalid stored block lengths) fatal: failed to read object 19505205fd1f219993da9b75846fff3cf432152d: Invalid argument - Ryan

1
以下是两个函数,如果您的备份已损坏或者有一些部分损坏的备份(如果备份了损坏的对象,则可能会发生这种情况),则可以使用它们来帮助恢复。请在要恢复的存储库中运行这两个函数。
标准警告:仅在您真的非常绝望并且已经备份了(损坏的)存储库的情况下使用。这可能无法解决任何问题,但至少应该突出显示损坏的程度。
fsck_rm_corrupted() {
    corrupted='a'
    while [ "$corrupted" ]; do
        corrupted=$(                                  \
        git fsck --full --no-dangling 2>&1 >/dev/null \
            | grep 'stored in'                          \
            | sed -r 's:.*(\.git/.*)\).*:\1:'           \
        )
        echo "$corrupted"
        rm -f "$corrupted"
    done
}

if [ -z "$1" ]  || [ ! -d "$1" ]; then
    echo "'$1' is not a directory. Please provide the directory of the git repo"
    exit 1
fi

pushd "$1" >/dev/null
fsck_rm_corrupted
popd >/dev/null

并且

unpack_rm_corrupted() {
    corrupted='a'
    while [ "$corrupted" ]; do
        corrupted=$(                                  \
        git unpack-objects -r < "$1" 2>&1 >/dev/null \
            | grep 'stored in'                          \
            | sed -r 's:.*(\.git/.*)\).*:\1:'           \
        )
        echo "$corrupted"
        rm -f "$corrupted"
    done
}

if [ -z "$1" ]  || [ ! -d "$1" ]; then
    echo "'$1' is not a directory. Please provide the directory of the git repo"
    exit 1
fi

for p in $1/objects/pack/pack-*.pack; do
    echo "$p"
    unpack_rm_corrupted "$p"
done

1

Git checkout 命令可以从 revision 中提取单个文件。只需提供提交哈希和文件名即可。更详细的信息请点击此处

我想最安全的解决方法是回滚到最新的未提交备份,然后从较新的 commits 中选择不损坏的文件。祝你好运!


0

我通过添加一些更改来解决了这个问题,例如再次运行git add -Agit commit


0
Daniel Fanjul的解决方案看起来很有前途。我能够找到那个blob文件并提取它("git fsck --full --no-dangling","git cat-file -t {hash}","git show {hash} > file.tmp"),但是当我尝试使用"git hash-object -w file.tmp"更新包文件时,它显示了正确的哈希值,但错误仍然存在。
因此,我决定尝试不同的方法。我可以简单地删除本地存储库并从远程下载所有内容,但是本地存储库中的某些分支比远程版本领先8个提交,我不想失去这些更改。由于那个微小的6kb mp3文件,我决定彻底删除它。我尝试了许多方法,但最好的方法是从这里得到的:https://itextpdf.com/en/blog/technical-notes/how-completely-remove-file-git-repository 我通过运行以下命令获取了文件名"git rev-list --objects --all | grep {hash}"。然后我进行了备份(强烈建议这样做,因为我失败了3次),然后运行了以下命令:
"java -jar bfg.jar --delete-files {filename} --no-blob-protection ."

您可以从这里https://rtyley.github.io/bfg-repo-cleaner/获取bfg.jar文件,根据文档,接下来应该运行以下命令:

"git reflog expire --expire=now --all && git gc --prune=now --aggressive"

当我这样做时,最后一步出现了错误。因此,我从备份中恢复了所有内容,并在删除文件后,切换到分支(导致该错误的分支),然后再切换回主分支,然后依次运行命令:

"git reflog expire --expire=now --all" "git gc --prune=now --aggressive"

然后将我的文件添加回其位置并提交。但是,由于许多本地提交已更改,我无法将任何内容推送到服务器。因此,我在服务器上备份了所有内容(以防万一),检出受影响的分支并运行命令"git push --force"。

从这个案例中我理解到什么?GIT很棒,但是非常敏感...我应该有一个选项来简单地忽略一个6kb的文件,我知道我在做什么。我不知道为什么“git hash-object -w”也没有起作用=(吸取教训,推送所有提交,不要等待,定期备份存储库。此外,如果需要,我知道如何从存储库中删除文件=)。
我希望这能节省某人的时间。

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