如何从损坏的 Git 仓库中恢复

发布: (2026年3月30日 GMT+8 05:20)
11 分钟阅读
原文: Dev.to

Source: Dev.to

如何从损坏的 Git 仓库中恢复

在日常开发中,Git 仓库偶尔会出现损坏的情况——比如磁盘故障、意外中断的 git push、或者不恰当的文件系统操作。下面的步骤帮助你诊断并尽可能恢复受影响的仓库。

1. 确认仓库真的损坏了

首先运行 git fsck(文件系统检查)来检测对象库中的错误:

git fsck --full

如果看到类似以下的输出,说明仓库已经出现损坏:

error: object <sha1> is missing
error: broken link from <sha1> to <sha1>

2. 使用 reflog 找回最近的提交

即使对象库受损,Git 的 reflog 仍然可能保存对最近 HEAD、分支以及其他引用的记录。查看 reflog:

git reflog

挑选一个看起来完整且没有错误的提交哈希值(SHA‑1),然后将 HEAD 重置到该提交:

git reset --hard <good-sha1>

提示:如果你只想恢复某个分支,而不是整个仓库,可以使用 git checkout <branch> 再执行 git reset --hard <good-sha1>

3. 从本地备份或镜像仓库恢复

如果你有本地备份(例如使用 git clone --mirror 创建的裸仓库),可以直接把对象库复制过去:

# 假设备份位于 /backups/myrepo.git
rm -rf .git
cp -r /backups/myrepo.git .git

随后再次运行 git fsck 确认恢复成功。

4. 重新克隆远程仓库(如果本地恢复不可行)

当本地所有尝试都失败时,最可靠的办法是重新克隆远程仓库:

git clone <remote-url> new-repo

如果你在损坏的仓库中还有未推送的本地更改,可以把这些更改打包为补丁,然后在新克隆的仓库中应用:

# 在旧仓库中生成补丁
git format-patch -o /tmp/patches <base-commit>..HEAD

# 在新仓库中应用补丁
git am /tmp/patches/*.patch

5. 防止以后再次出现损坏

  • 定期备份:使用 git clone --mirror 或者 git bundle 创建完整的仓库镜像。
  • 启用文件系统检查:在服务器上运行定期的磁盘健康检查(如 fsck、SMART)。
  • 避免中断的网络操作:在网络不稳定时,尽量使用 git push --no-verify 或者手动重试。

通过上述步骤,你应该能够诊断并恢复大多数因对象库损坏导致的 Git 问题。即使恢复失败,重新克隆并手动迁移未推送的更改也能让你快速回到正常的开发流程。祝你好运!

1. 实际出了什么问题?

Git 将所有内容存储为位于 .git/objects/ 目录下的对象:

  • blobs – 文件内容
  • trees – 目录结构
  • commits – 快照
  • tags – 引用

每个对象都以其 SHA‑1 哈希命名,并使用 zlib 进行压缩。

常见的损坏原因

  • 磁盘故障或断电导致对 .git/objects/ 的写入中断
  • 在执行 git gcgit repack 或推送/拉取时进程被杀死
  • 文件系统错误(尤其是在网络共享驱动器上)
  • 过于激进的杀毒软件或备份软件锁定或修改 .git/ 中的文件

由于 Git 的存储是 内容可寻址 的,只要对象的内容仍然与其哈希匹配,就说明该对象是完整的。损坏通常仅限于少数几个对象。

Never panic‑delete anything. First, figure out exactly what’s broken.

检查整体仓库完整性

git fsck --full --no-dangling

典型输出:

error: object file .git/objects/a1/b2c3... is empty
error: sha1 mismatch for .git/objects/d4/e5f6...
missing blob a1b2c3d4e5f6...
broken link from tree f7a8b9... to blob a1b2c3...

记录下损坏的 SHA‑1 哈希值——稍后会用到。

验证 HEAD

cat .git/HEAD          # 应显示: ref: refs/heads/main
cat .git/refs/heads/main
git cat-file -t $(cat .git/refs/heads/main)   # 应输出: commit

如果 HEAD 或分支引用已损坏,这就是你的起点。否则问题出在更深层(blob、tree 等)。

3. 使用 Reflog —— 你的最佳伙伴

Reflog 记录了分支指针的历史位置,并且因为它是独立于对象数据库存储的,所以能在大多数损坏情况下存活下来。

# 显示所有引用的最近 reflog 条目
git reflog show --all

# 如果 HEAD 损坏,直接读取 reflog
cat .git/logs/HEAD

找到最后一次已知的良好提交哈希并重置到该提交:

git reset --hard <commit‑hash>

Reflog 默认保留 90 天 的历史记录,因此在最近的损坏中几乎总能找到可恢复的有效提交哈希。

4. 从远程拉取缺失的对象

如果你曾经向远程仓库(GitHub、自托管服务器、同事的机器)推送过代码,你已经拥有了备份。

# 1️⃣ Move the broken objects out of the way
mkdir -p .git/objects-broken
# Example for a broken object a1b2c3...
mv .git/objects/a1/b2c3d4e5f6* .git/objects-broken/

# 2️⃣ Fetch missing objects from the remote
git fetch origin

# 3️⃣ Verify again
git fsck --full --no-dangling

由于 Git 对象是不可变的,远程仓库中的同一提交拥有完全相同的 SHA‑1 和字节。获取操作会恢复所有缺失的对象。

5. 重新创建损坏的 Blob(文件)

如果损坏的对象是 blob(文件的特定版本),并且你仍然拥有工作树中的该文件版本,则可以将其重新构建:

# 查找损坏的 blob 所属的文件
git ls-tree -r HEAD | grep <blob‑sha>
# 示例输出:
# 100644 blob a1b2c3d4e5f6    path/to/file.js

# 将当前文件重新哈希并写入对象库
git hash-object -w path/to/file.js

*仅当工作树中的文件与损坏的 blob 完全匹配时才有效。*否则请从远程仓库或备份中获取正确的版本。

6. 从远程重新开始(当损坏严重时)

如果仓库严重损坏,但你有远程副本:

# Rename the broken repo
mv my-project my-project-broken

# Clone a fresh copy
git clone <remote‑url> my-project

# Copy over any uncommitted work
diff -rq my-project-broken/ my-project/ --exclude='.git'
# (Manually copy any files that differ)

你不会失去任何已推送的内容;唯一的风险是未提交的本地工作,diff 可以帮助你定位这些文件。

7. 处理损坏的 Packfile

Git 会定期将松散对象打包到 .git/objects/pack/ 中。损坏的 pack 可能隐藏数百个对象。

# Try to unpack what you can
git unpack-objects .pack

# Verify the packfile
git verify-pack -v .git/objects/pack/pack-*.idx

git unpack-objects 将在遇到损坏的对象时失败,但会恢复完整的对象。提取完成后,从远程获取以填补缺失的对象。

8. 预防 – 让损坏变得罕见

  1. 频繁推送 – 每个远程仓库都是完整备份。即使是杂乱的功能分支也要推送;以后可以再压缩(squash)。

  2. 绝不要中断 Git 操作 – 让 git gcgit repack 等完成。中途杀掉它们是我见过的导致仓库损坏的头号原因。

  3. 避免使用共享/网络文件系统 – NFS、CIFS、Dropbox、OneDrive 等都极易导致仓库损坏。如果必须使用,请使用 git bundle 创建可移植的备份。

  4. 为对象文件启用 fsync – 可防止断电导致的数据丢失:

    git config --global core.fsyncObjectFiles true
  5. 定期运行 git fsck,在重要仓库上尽早发现问题。

  6. 备份 .git 目录(或整个仓库)到外部硬盘或云存储,并保持定期执行。

TL;DR

  1. 运行 git fsck → 记录损坏的 SHA‑1。
  2. 检查 HEAD 和分支引用。
  3. 使用 reflog 找到一个良好的提交并执行 git reset --hard
  4. 如果有远程仓库,先把损坏的对象移走,git fetch,然后再次验证。
  5. 重新计算任何可恢复的 blob 哈希,或全新克隆后复制未提交的工作。
  6. 对于 packfile 问题,尽可能解包、验证,然后获取缺失的对象。

按照这些步骤,大多数 “fatal: bad object HEAD” 场景都可以完全恢复。祝编码愉快!

Git (2.36+) – 更细粒度的 core.fsync

git config core.fsync objects,derived-metadata,reference

定期完整性检查

  • 定期运行 git fsck
  • 将其添加到 cron 任务pre‑push 钩子 中。
  • 及早检测到损坏可以为您提供更多恢复选项。

为什么 Git 的设计有助于恢复

  • 内容可寻址的对象存储意味着 每个克隆都是完整备份
  • 每个对象都是 自我验证 的。
  • 损坏通常是 局部的,而非灾难性的。

实际案例

我遇到过的最严重的 Git 损坏,在我了解对象模型后大约用了 20 分钟就修复了。第一次遇到时,我在慌乱中耗费了三小时才学会这些。希望这能为你省下那三小时。

想进一步了解?

  • 阅读官方 Git 书中的 Git Internals 章节。
  • 了解 .git/objects/ 的工作原理可以把 “我的仓库损坏了” 从危机转变为 15 分钟即可修复
0 浏览
Back to Blog

相关文章

阅读更多 »

更多关于版本控制

更新:令人惊讶且高兴的是,我关于版本控制的上一篇文章 https://bramcohen.com/p/manyana 被 Hacker News 采纳并获得了大量浏览。谢谢……

西班牙立法作为 Git 仓库

Legalize — 西班牙 将西班牙立法作为 Git 仓库。每部法律是一个 Markdown 文件,每次改革是一次 commit。超过 8,600 部法律来自开放数据 API。