如何修复损坏的 Git 存储库?

我试着将我保存在 Ubuntu One文件夹中的存储库复制到一台新机器上,我得到了这样的结果:

cd ~/source/personal
git clone ~/Ubuntu\ One\ Side\ Work/projects.git/


Cloning into 'projects'...
done.
fatal: unable to read tree 29a422c19251aeaeb907175e9b3219a9bed6c616

所以我试着看了很多类似这样的问题,他们中的大多数都说运行 git fsck --full,然后我得到了这个,当我尝试这个。

cd ~/Ubuntu\ One\ Side\ Work/projects.git
git fsck --full


Checking object directories: 100% (256/256), done.
Checking objects: 100% (447/447), done.
broken link from  commit 235ae1f48701d577d71ebd430344a159e5ba4881
to  commit 984c11abfc9c2839b386f29c574d9e03383fa589
broken link from    tree 632a9cf0ef9fccea08438b574e2f1c954f4ff08b
to    blob 25a742dff0a403b2b3884f2ffddf63eb45721fac
broken link from    tree 632a9cf0ef9fccea08438b574e2f1c954f4ff08b
to    blob dd4e97e22e159a585b20e21028f964827d5afa4e
broken link from    tree 632a9cf0ef9fccea08438b574e2f1c954f4ff08b
to    tree 29a422c19251aeaeb907175e9b3219a9bed6c616
broken link from    tree 632a9cf0ef9fccea08438b574e2f1c954f4ff08b
to    tree 8084e8e04d510cc28321f30a9646477cc50c235c
broken link from    tree 774b5b4157b4caae1c6cad96c8eaf5d4eba2c628
to    blob a0daa0c1567b55d8de2b4d7a3bc010f58c047eab
broken link from    tree 774b5b4157b4caae1c6cad96c8eaf5d4eba2c628
to    blob e9052d35bfb6d30065b206fc43f4200a04d5281b
broken link from    tree 774b5b4157b4caae1c6cad96c8eaf5d4eba2c628
to    blob 1a3a5e4dd2502ac121c22f743c4250e254a94eeb
broken link from    tree 4aa336dc1a5838e8918e03b85580069d83f4ad09
to    tree 8cc55ec952dc192a233e062201d1e7e873ac3db0
broken link from    tree e5674a91a53e15575a1f3bf5786bc5cc719fb483
to    blob 4a994e1e7bb7ce28dcec98bad48b9a891d7dec51
broken link from    tree e5674a91a53e15575a1f3bf5786bc5cc719fb483
to    blob ac033bf9dc846101320c96a5ce8aceb8c96ec098
broken link from    tree 252ab84542264e1589576b6ee51e7a31e580a0e2
to    tree 2069041cd5950e529e2991d37b7290ec021d90d4
broken link from    tree 2d4964aa4d4f5d8c7228518ce72ef6a63f820c6d
to    blob d83690e1b9a6bdd8a08754b38231799acefcb2ab
broken link from    tree c7192e82fc581bd6448bda1a25e8729bdac5f4ff
to    blob 30d54d47ae82add1917ca173d42e58b396df580b
broken link from    tree 7c66306901fc71389623286936cef172d4ffe408
to    blob bc7e05d705401273b1df4e939de0f540597c0931
broken link from    tree 0940f5fd227d4c84d6e6749d872db50a4522ae3a
to    tree 923767594ac22023e824948d65622fe5b407d1a1
broken link from    tree 8eadcd2a971e8357d24f0d80f993d2963452209f
to    blob 2598bde3dc8cb80ee49510b8159344004b88645f
broken link from    tree ffa302dd0d969172ef23caeefe856ab2f57a4e4d
to    blob d6925fa431be1ac585bf9a481e98f75107a6e6fb
broken link from    tree 7045b8870a49ce30a2027537a96d73d162bda773
to    blob 25688652dea26f61f576ca1b52b9d1a18fbfd01d
broken link from    tree 37e4705d34bd440ce681ae32ae9a180a13256d72
to    tree 246f564d4cee53339b8a4244f3173b61caa518eb
missing blob d6925fa431be1ac585bf9a481e98f75107a6e6fb
missing blob ac033bf9dc846101320c96a5ce8aceb8c96ec098
missing tree 29a422c19251aeaeb907175e9b3219a9bed6c616
missing tree 8084e8e04d510cc28321f30a9646477cc50c235c
missing blob 30d54d47ae82add1917ca173d42e58b396df580b
missing tree 8cc55ec952dc192a233e062201d1e7e873ac3db0
missing blob e9052d35bfb6d30065b206fc43f4200a04d5281b
dangling tree 4b26e95db542c72ac4a22ec25abe38fb2de79752
missing blob d83690e1b9a6bdd8a08754b38231799acefcb2ab
missing blob 25a742dff0a403b2b3884f2ffddf63eb45721fac
missing tree 923767594ac22023e824948d65622fe5b407d1a1
missing blob 25688652dea26f61f576ca1b52b9d1a18fbfd01d
missing blob 2598bde3dc8cb80ee49510b8159344004b88645f
dangling tree 3a683869f1bb0c1634de75700c316b3b36570dbd
dangling blob 4098d30843380d798a811f1aa9a02994f0dbbb27
missing tree 2069041cd5950e529e2991d37b7290ec021d90d4
missing blob 4a994e1e7bb7ce28dcec98bad48b9a891d7dec51
missing blob 1a3a5e4dd2502ac121c22f743c4250e254a94eeb
missing blob a0daa0c1567b55d8de2b4d7a3bc010f58c047eab
dangling tree 6c7b5162aa7a303fa3fe8dc393c5da564e309521
missing commit 984c11abfc9c2839b386f29c574d9e03383fa589
missing blob bc7e05d705401273b1df4e939de0f540597c0931
missing blob dd4e97e22e159a585b20e21028f964827d5afa4e
missing tree 246f564d4cee53339b8a4244f3173b61caa518eb
dangling commit a01f5c1e5315dc837203d6dee00d3493be9c5db9

看起来很糟糕,我做 git log | head的时候会得到这个

git log | head


error: Could not read 984c11abfc9c2839b386f29c574d9e03383fa589
fatal: Failed to traverse parents of commit 235ae1f48701d577d71ebd430344a159e5ba4881
commit 2fb0d2d0643b445440f01b164f11ee9ee71fca48
Author: christopher <christopher@christopher.christopher>
Date:   Wed Aug 7 15:51:42 2013 -0400


finishing chapter 7

这里的其他问题已经说看看 ./git/refs/heads/master。这是一个赤裸裸的回购和 refs/heads/存在,但 refs/heads/master不存在。但是,存储库中的 HEAD 显示为 ref: refs/heads/master

packed-refs确实这么说

# pack-refs with: peeled
2fb0d2d0643b445440f01b164f11ee9ee71fca48 refs/heads/master

还有其他问题建议运行 git reflog,当我运行它时没有显示任何输出。

所以我真的不知道该怎么办。应该采取什么策略?有没有可能在8月7日重置头部到最后一次提交?

执行 git log并转到屏幕输出的底部显示如下:

commit 996e03b949aea176238e3c7a8452700bbb987ac9
Author: christopher <christopher@christopher>
Date:   Wed Jul 3 23:00:44 2013 -0400


many many changes
error: Could not read 984c11abfc9c2839b386f29c574d9e03383fa589
fatal: Failed to traverse parents of commit 235ae1f48701d577d71ebd430344a159e5ba4881

这似乎阻止了 Git prune 的工作。

221926 次浏览

DR

Git 并不像你想的那样存储历史。它在运行时基于祖先链的 计算历史记录。如果您的祖先缺少斑点、树或提交,那么您可能无法完全恢复您的历史。

从备份中还原丢失的对象

您可以尝试的第一件事情是从备份中还原丢失的项。例如,查看是否有存储为 .git/objects/98/4c11abfc9c2839b386f29c574d9e03383fa589的提交的备份。如果是这样,你可以恢复它。

如果提交已经打包,并且希望将其返回到一个松散的对象,那么您可能还需要查看 Git-確認-packGit-unpack-Objects,以便进行存储库操作。

手术切除

如果无法从备份中替换丢失的项,则可以删除丢失的历史记录。例如,您可以检查历史记录或 reflog 以查找提交984c11abfc9c2839b386f29c574d9e03383fa589的祖先。如果你发现一个完好无损的,那么:

  1. 将你的 Git 工作目录复制到某个临时目录。
  2. 对未损坏的提交执行硬重置。
  3. 将当前文件复制回 Git 工作树,但要确保不将. Git 文件夹复制回来!
  4. 提交当前的工作树,并尽最大努力将其视为所有缺失历史的压缩提交。

如果成功了,你当然会失去中间的历史。此时,如果您有一个工作历史记录日志,那么最好对所有无法访问的提交和对象进行删除历史记录和重新日志记录。

完全还原和重新初始化

如果您的存储库仍然损坏,那么希望您有一个未损坏的备份或克隆,您可以从中恢复。如果没有,但是你当前的工作目录包含有效的文件,那么你可以重新初始化 Git。例如:

rm -rf .git
git init
git add .
git commit -m 'Re-initialize repository without old history.'

这是极端的,但是如果您的存储库历史是真正不可恢复的,那么这可能是您唯一的选择。

如果你绝望了,你可以试试这个:

git clone ssh://me@my.git.server/path/to/project destination --depth=1

它会得到你的数据,但你会失去历史记录。我在回购的过程中反复试验,--depth=10成功了,但是 --depth=50给了我失败。

我当时也面临同样的问题,所以我把。“ git”文件夹的备份版本,但仍然无法工作,因为 。 gitconfig文件已损坏。我笔记本电脑上的 BSoD损坏了它。我用以下代码替换了它,并且 源头树还原了我的所有存储库。

[user]
name = *your username*
email = *your email address*
[core]
autocrlf = true
excludesfile = C:\\Users\\*user name*\\Documents\\gitignore_global.txt

我不知道这对谁有帮助,但这只是另一个对我有效的解决方案。

在我的例子中,我正在用我的电脑中的源代码创建存储库,然后出现了这个错误。我删除了。Git 文件夹并重新做了所有事情,它工作了:)

这里有一个脚本(Bash) ,通过@CodeGnome 自动化第一个解决方案,从备份中恢复(从损坏的存储库的顶级运行)。备份不需要完成; 它只需要有丢失的对象。

git fsck 2>&1 | grep -e missing -e invalid | awk '{print $NF}' | sort -u |
while read entry; do
mkdir -p .git/objects/${entry:0:2}
cp ${BACKUP}/objects/${entry:0:2}/${entry:2} .git/objects/${entry:0:2}/${entry:2}
done

如果你有一个远程配置,并且你不在乎丢失一些未推送的代码,你可以这样做:

git fetch && git reset --hard

在尝试这个页面上描述的任何修复之前,我建议您复制一个存储库,并且只处理这个副本。然后在最后,如果你可以修复它,比较它与原来的,以确保你没有丢失任何文件在修复过程中。

另一个对我有效的替代方法是使用以下方法将 Git 头部和索引重置为以前的状态:

git reset --keep

你也可以通过打开 Git GUI 并选择每个“分阶段更改”,然后点击“取消分阶段更改”来手动完成同样的操作。当所有内容都非暂存时,您现在应该能够压缩数据库、检查数据库并提交。

我还尝试了以下命令,但它们对我不起作用。但是他们可能会帮助你,这取决于你所面临的具体问题:

git reset --mixed
git fsck --full
git gc --auto
git prune --expire now
git reflog --all

最后,为了避免这种损害 Git 索引的同步问题(Dropbox蜘蛛橡树或任何其他云磁盘都可能出现这种情况) ,您可以执行以下操作:

  1. 使用: git bundle create my_repo.git --all将你的 .git文件夹转换成一个“捆绑”Git 文件 ,然后它应该和以前一样工作,但是因为所有东西都在一个文件中,你不会再冒同步破坏你的 Git repo 的风险。
  2. 禁用瞬时同步 : SpiderOak 允许您将检查更改的调度设置为“自动”(这意味着由于操作系统通知,它将尽快监视文件更改)。这是不好的,因为它会在您进行更改时立即开始上载更改,然后下载更改,因此它可能会删除您刚刚进行的最新更改。修复此问题的解决方案是将更改监视延迟设置为5分钟或更长时间。这也解决了即时保存笔记应用程序(如 记事本 + + )的问题。

作为 Todd 最后一个选项(完全还原和重新初始化)的替代方案,如果只有本地存储库损坏,并且你知道远程的 URL,你可以使用这个来重置你的 .git以匹配远程(用远程 URL 替换 ${url}) :

mv -v .git .git_old &&            # Remove old Git files
git init &&                       # Initialise new repository
git remote add origin "${url}" && # Link to old repository
git fetch &&                      # Get old history
# Note that some repositories use 'master' in place of 'main'. Change the following line if your remote uses 'master'.
git reset origin/main --mixed     # Force update to old history.

这使您的工作树保持完整,并且只影响 Git 的簿记。

我最近还为此目的编写了一个 Bash 脚本(附录 A) ,它包装了这个操作的一些安全性。

注:

  • 如果存储库有子模块,这个过程会以某种方式把它们搞乱,到目前为止我找到的唯一解决方案是删除它们,然后使用 git submodule update --init(或者重新克隆存储库,但这似乎是极端的 也是)。
  • 这会根据本地配置设置来确定“ main”和“ master”之间的正确选择,但是如果在使用“ master”的存储库上使用,在使用“ main”作为默认分支的机器上使用,可能会出现一些问题。
  • 这使用 wget在执行任何操作之前检查 URL 是否可达。这不一定是确定站点是否可访问的最佳操作,如果没有 wget 可用,则可以使用 ping -c 1 "${url_base}"(linux)、 ping -n 1 "${url_base}"(windows)或 curl -Is "${url_base}"替换它

附录 A-完整脚本

也作为 大意出版,虽然现在已经过时了。

#!/bin/bash


# Usage: fix-git [REMOTE-URL]
#   Must be run from the root directory of the repository.
#   If a remote is not supplied, it will be read from .git/config
#
# For when you have a corrupted local repo, but a trusted remote.
# This script replaces all your history with that of the remote.
# If there is a .git, it is backed up as .git_old, removing the last backup.
# This does not affect your working tree.
#
# This does not currently work with submodules!
# This will abort if a suspected submodule is found.
# You will have to delete them first
# and re-clone them after (with `git submodule update --init`)
#
# Error codes:
# 1: If a URL is not supplied, and one cannot be read from .git/config
# 4: If the URL cannot be reached
# 5: If a Git submodule is detected




if [[ "$(find -name .git -not -path ./.git | wc -l)" -gt 0 ]] ;
then
echo "It looks like this repo uses submodules" >&2
echo "You will need to remove them before this script can safely execute" >&2
echo "Then use \`git submodule update --init\` to re-clone them" >&2
exit 5
fi


if [[ $# -ge 1 ]] ;
then
url="$1"
else
if ! url="$(git config --local --get remote.origin.url)" ;
then
echo "Unable to find remote 'origin': missing in '.git/config'" >&2
exit 1
fi
fi


if ! branch_default="$(git config --get init.defaultBranch)" ;
then
# if the defaultBranch config option isn't present, then it's likely an old version of git that uses "master" by default
branch_default="master"
fi


url_base="$(echo "${url}" | sed -E 's;^([^/]*://)?([^/]*)(/.*)?$;\2;')"
echo "Attempting to access ${url_base} before continuing"
if ! wget -p "${url_base}" -O /dev/null -q --dns-timeout=5 --connect-timeout=5 ;
then
echo "Unable to reach ${url_base}: Aborting before any damage is done" >&2
exit 4
fi


echo
echo "This operation will replace the local repo with the remote at:"
echo "${url}"
echo
echo "This will completely rewrite history,"
echo "but will leave your working tree intact"
echo -n "Are you sure? (y/N): "


read confirm
if ! [ -t 0 ] ; # i'm open in a pipe
then
# print the piped input
echo "${confirm}"
fi
if echo "${confirm}"|grep -Eq "[Yy]+[EeSs]*" ; # it looks like a yes
then
if [[ -e .git ]] ;
then
# remove old backup
rm -vrf .git_old | tail -n 1 &&
# backup .git iff it exists
mv -v .git .git_old
fi &&
git init &&
git remote add origin "${url}" &&
git config --local --get remote.origin.url | sed 's/^/Added remote origin at /' &&
git fetch &&
git reset "origin/${branch_default}" --mixed
else
echo "Aborting without doing anything"
fi

这个命令对我很有效:

git reset --mixed

我尝试用0字节移动目标文件,然后再次从远程获取它们,结果奏效了:

find . -type f -size 0 -exec mv {} /tmp \;
git fetch

它从远程获取丢失的对象,允许我在不重新初始化整个存储库的情况下继续工作。

如果您对当前项目有更改并且不想丢失它,那么快速的方法是将当前项目移动到某个地方,将项目从 GitHub 复制到该文件夹,然后进行一些更改并尝试再次提交。

或者直接删除存储库,然后再克隆一次,对我来说很管用。

我想在上面的 Zoey Hewil 的绝妙回答下面添加这个评论,但是我现在没有足够的名气这样做,所以我必须在这里添加它,并给予她的工作表扬: P

如果您正在使用 波什吉特并且感到 非常特别懒惰,那么可以使用以下方法从 Git 配置中自动提取 URL,从而使简单的工作变得更加容易。对于在本地存储库的拷贝/备份上测试这个问题,需要注意的是,万一出现问题,首先要进行备份。

$config = get-content .git\config
$url = $config -match "    url = (?<content>.*)"
$url = $url.trim().Substring(6)
$url


move-item -v .git .git_old;
git init;
git remote add origin "$url";
git fetch;
git reset origin/master --mixed

我最近在 Ubuntu 18.04.3(Bionic Beaver)下使用 Git 2.7.1版时遇到了类似的问题:

sudo apt install git-repair
git-repair  # Fix a broken Git repository
or
git-repair --force  # Force repair, even if data is lost
git fsck  # To verify it was fixed

大多数情况下,恢复过程是成功的。

如果没有以上工作,这里有一个报告,用残疾检查修改 git 对我来说非常有效。我刚刚修复了它一个“不可恢复的”回购。

本质:

Git 二进制对象(在 .git/objects中)只是 gzip 压缩的 blobs,以二进制数据格式打包在一起。在大多数情况下,如果这些文件被损坏,您可以获得非常好的部分恢复——如果 git 支持的话。它有各种各样的检查(主要是: 无法打开对象文件,或解压缩 blob 结果错误) ,它停止处理,并出现致命错误。

它需要一点 C 技能,至少在您可以从源代码修补和重新编译 git 的级别上。您需要修改的内容总是相同的: git 的某个地方停止了处理,出现了一个致命错误,因此您将其注释掉,并用一个空的 mock 替换它。在此之后,/your/patched/git gc --aggressive --prune=now(常见的超积极重新分解器)修复它应该被修复的部分。例如,下面的补丁可以修复一些目标文件损坏的 git 回购。当然,恢复只是部分的: 损坏对象中的文件会丢失,甚至是追溯性的丢失。

这可能就是为什么 git 开发没有实现如此微小但极其有用的改进的原因。我认为,所需的补丁大小可能低于100行。

进一步发展这个想法,也许还可以创建一个 git fork,它可以恢复这种受损的回购协议。

diff -urNw orig/sha1-file.c patched/sha1-file.c
--- orig/sha1-file.c   2021-03-08 21:36:01.000000000 +0100
+++ patched/sha1-file.c 2021-08-19 18:32:44.019115545 +0200
@@ -1285,8 +1285,16 @@
else if (stream->avail_in)
error(_("garbage at end of loose object '%s'"),
oid_to_hex(oid));
+
+  error("error ignored");
+
+  /*
free(buf);
return NULL;
+  */
+
+  status = Z_OK;
+  return buf;
}
 

/*
@@ -1656,7 +1664,7 @@
oid_to_hex(repl), oid_to_hex(oid));
 

if (!stat_loose_object(r, repl, &st, &path))
-               die(_("loose object %s (stored in %s) is corrupt"),
+               error(_("loose object %s (stored in %s) is corrupt"),
oid_to_hex(repl), path);
 

if ((p = has_packed_and_bad(r, repl->hash)) != NULL)
@@ -1664,7 +1672,9 @@
oid_to_hex(repl), p->pack_name);
obj_read_unlock();
 

-       return NULL;
+  errno = 0;
+  error("error ignored");
+       return strdup("");
}
 

void *read_object_with_reference(struct repository *r,
@@ -2473,10 +2483,12 @@
}
git_inflate_end(stream);
 

+  /*
if (status != Z_STREAM_END) {
error(_("corrupt loose object '%s'"), oid_to_hex(expected_oid));
return -1;
-       }
+       }*/
+
if (stream->avail_in) {
error(_("garbage at end of loose object '%s'"),
oid_to_hex(expected_oid));

Git 修复 (sudo apt install git-repair)和一些额外的命令对我很有用:

  1. 创建损坏的存储库的备份副本。

  2. 删除中断的引用:
    find .git/refs -size 0 -delete -print

  3. 从远程修复存储库:
    git-repair --force

  4. 清理悬挂物承诺:
    git gc --prune=now

  5. 从远程获取最新状态:
    git fetch

可以选择切换到 师父并将其重置为 产地来源/主管:
git checkout master
git reset --hard origin/master

滚开确认:
git fsck