Wiki LogoWiki - The Power of Many

Week 07: 调试、恢复与仓库维护

掌握 Bisect 二分查找, Reflog 恢复, fsck 验证, GC 机制与仓库优化技术.

1. Bisect (二分查找)

1.1 什么是 Bisect

Bisect 通过二分搜索在提交历史中快速定位引入 Bug 的提交.

原理: 每次测试将搜索范围减半, 对于 1000 个提交, 最多只需约 10 次测试.

1.2 手动 Bisect

# 1. 开始 bisect
git bisect start

# 2. 标记当前版本 (有 Bug)
git bisect bad

# 3. 标记已知正常的版本
git bisect good v1.0.0

# Git 会检出中间提交

# 4. 测试并标记
git bisect good   # 如果正常
git bisect bad    # 如果有问题

# 5. 重复直到找到
# abc1234 is the first bad commit

# 6. 结束 bisect
git bisect reset

1.3 自动 Bisect

提供一个测试脚本, Git 自动运行:

git bisect start
git bisect bad HEAD
git bisect good v1.0.0

# 自动测试
git bisect run ./test.sh
# 脚本返回 0 = good, 非 0 = bad

测试脚本示例

#!/bin/bash
# test.sh

# 编译项目
make || exit 125  # 125 = 跳过此提交

# 运行测试
./run_tests || exit 1  # 1 = bad

exit 0  # good

1.4 跳过提交

# 无法测试当前提交时
git bisect skip

# 跳过某个范围
git bisect skip abc1234..def5678

1.5 查看 Bisect 日志

# 查看 bisect 历史
git bisect log

# 保存日志
git bisect log > bisect.log

# 从日志恢复
git bisect replay bisect.log

2. Reflog (引用日志)

2.1 什么是 Reflog

Reflog 记录本地仓库中所有引用的变更历史, 包括:

  • 分支移动
  • HEAD 变化
  • reset、rebase、checkout 等操作
git reflog
# abc1234 HEAD@{0}: commit: Latest commit
# def5678 HEAD@{1}: checkout: moving from feature to main
# ghi9012 HEAD@{2}: commit: WIP
# jkl3456 HEAD@{3}: reset: moving to HEAD~2

2.2 Reflog 是恢复的利器

几乎任何"丢失"的提交都可以通过 Reflog 找回:

找回被 reset 的提交

# 不小心 reset 丢失了提交
git reset --hard HEAD~5

# 查看 reflog
git reflog

# 恢复到 reset 之前
git reset --hard HEAD@{1}

找回删除的分支

# 删除了分支
git branch -D feature

# 查看 reflog
git reflog

# 重新创建分支
git branch feature abc1234

找回 rebase 之前的状态

# rebase 后发现问题
git reflog

# 恢复到 rebase 之前
git reset --hard ORIG_HEAD
# 或
git reset --hard HEAD@{2}

2.3 Reflog 配置与清理

# 查看 reflog 过期时间
git config gc.reflogExpire           # 默认 90 天
git config gc.reflogExpireUnreachable # 默认 30 天

# 手动清理
git reflog expire --expire=now --all
git gc

2.4 Reflog 的局限

  • 只存在于本地: 不会 push 到远程
  • 有过期时间: 默认 90 天后被清理
  • 依赖 gc: gc 运行后, 过期的 reflog 条目会被删除

3. fsck (文件系统检查)

3.1 什么是 fsck

fsck (File System Check) 验证 Git 对象的完整性:

# 完整检查
git fsck

# 静默模式 (只报告错误)
git fsck --no-full

# 检查不可达对象
git fsck --unreachable

# 检查悬空对象
git fsck --dangling

3.2 常见问题

悬空对象 (Dangling Objects)

不被任何引用指向的对象:

git fsck
# dangling commit abc1234
# dangling blob def5678

恢复悬空提交

# 查看悬空提交内容
git show abc1234

# 创建分支指向它
git branch recovered abc1234

损坏的对象 (Corrupt Objects)

git fsck
# error: sha1 mismatch abc1234
# missing blob def5678

恢复选项:

  1. 从备份恢复 .git/objects/
  2. 从远程 fetch 丢失的对象
  3. 使用 reflog 和其他引用重建

4. 垃圾回收 (GC)

4.1 GC 的作用

  • 打包松散对象为 Packfile
  • 删除不可达对象
  • 压缩仓库
  • 清理过期的 reflog

4.2 GC 命令

# 运行 gc
git gc

# 激进模式 (更彻底的压缩)
git gc --aggressive

# 立即删除不可达对象
git gc --prune=now

4.3 自动 GC

Git 在某些操作后自动运行 gc:

# 配置自动 gc 阈值
git config gc.auto 256                # 松散对象数量
git config gc.autoPackLimit 50        # pack 文件数量

# 禁用自动 gc
git config gc.auto 0

4.4 Prune (修剪)

删除不可达对象:

# 检查会删除什么
git prune -n

# 执行删除
git prune

# 删除所有过期对象
git prune --expire=now

4.5 Repack (重新打包)

优化 pack 文件:

# 基本重新打包
git repack

# 生成单个 pack
git repack -a -d

# 激进模式
git repack -A -d --depth=250 --window=250

5. 仓库瘦身

5.1 分析仓库大小

# 查看仓库大小
du -sh .git

# 查看 pack 文件大小
du -sh .git/objects/pack

# 查看最大的对象
git rev-list --objects --all | \
  git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
  sort -k3 -n -r | head -20

5.2 清理大文件

# 使用 git-filter-repo 删除大文件
git filter-repo --strip-blobs-bigger-than 10M

# 使用 BFG
bfg --strip-blobs-bigger-than 10M

# 运行 gc
git gc --prune=now --aggressive
git reflog expire --expire=now --all

# 强制推送
git push origin --force --all
git push origin --force --tags

5.3 shallow clone (浅克隆)

# 只克隆最近 N 个提交
git clone --depth=1 url
git clone --depth=10 url

# 只克隆特定分支
git clone --single-branch --branch main url

# 后续获取更多历史
git fetch --unshallow
git fetch --depth=100

5.4 partial clone (部分克隆)

Git 2.22+ 支持:

# 只克隆 commit 和 tree, 按需获取 blob
git clone --filter=blob:none url

# 限制 blob 大小
git clone --filter=blob:limit=1m url

# 稀疏检出
git clone --filter=blob:none --sparse url
cd repo
git sparse-checkout set src/

6. 超大规模仓库优化: Monorepo & Scalar

对于 GB 级或百万文件的超大仓库 (如 Windows, Office), 标准 Git 会变慢.

6.1 核心加速技术

  • Commit Graph: 缓存提交图遍历结果, 加速 log/merge.
  • Multi-Pack Index (MIDX): 减少对象查找的 pack 文件扫描数.
  • Filesystem Monitor (FSMonitor): 避免扫描工作区 100k+ 文件, 仅处理 OS 变更通知.
# 写入 commit-graph
git commit-graph write --reachable --changed-paths

# 写入 MIDX
git multi-pack-index write --bitmap

6.2 Scalar

Microsoft 开源 (现合并入核心) 的大仓库管理工具.

# 注册大仓库 (自动配置所有优化参数)
scalar register /path/to/repo

# 克隆大仓库
scalar clone https://github.com/microsoft/git

Scalar 会自动配置:

  • Partial Clone (不下载 Blob)
  • Sparse Checkout (只检出根目录)
  • FSMonitor
  • 后台维护任务

6.3 Git Maintenance

Git 的后台任务调度器 (cron/launchd):

# 开启后台维护
git maintenance start

它会定期运行:

  • gc
  • commit-graph
  • prefetch (预取远程提交)

7. 备份与迁移

6.1 Clone 备份

# 裸仓库备份
git clone --mirror origin-url backup.git

# 更新备份
cd backup.git
git fetch origin +refs/*:refs/*

6.2 Bundle 备份

# 创建完整备份
git bundle create backup.bundle --all

# 增量备份
git bundle create incremental.bundle main ^v1.0.0

# 验证
git bundle verify backup.bundle

6.3 仓库迁移

# 1. 裸克隆原仓库
git clone --bare old-url

# 2. 推送到新地址
cd repo.git
git push --mirror new-url

# 3. 或直接更新 remote
git remote set-url origin new-url
git push --mirror

8. 练习

7.1 Bisect 练习

  1. 创建一个包含 Bug 的提交历史.
  2. 使用 bisect 找到引入 Bug 的提交.
  3. 尝试自动化 bisect.

7.2 Reflog 恢复

  1. 创建几个提交.
  2. 使用 reset --hard 丢失提交.
  3. 使用 reflog 恢复.

7.3 仓库瘦身

  1. 分析仓库大小.
  2. 找出最大的对象.
  3. 清理并压缩仓库.

9. 思考题

  1. Bisect 的时间复杂度是多少?
  2. Reflog 能跨仓库使用吗?
  3. 为什么 gc --prune=now 通常不建议使用?
  4. shallow clone 有什么缺点?
  5. git clone --mirrorgit clone --bare 有什么区别?

10. 本周小结

  • Bisect: 二分查找定位 Bug.
  • Reflog: 本地引用变更历史, 恢复利器.
  • fsck: 验证仓库完整性.
  • GC/Prune/Repack: 仓库清理和优化.
  • 瘦身: 清理大文件, shallow/partial clone.
  • 备份: mirror, bundle, 增量备份.

Reflog 是本地仓库的"后悔药". 只要 Reflog 还在, 几乎没有不可挽回的操作.

On this page