[Git 黑魔法:高级技巧与故障恢复]
100 天认知提升计划 | Day 5
目录
第一部分:Git 高级命令原理
Git 对象模型回顾
概念说明
理解 Git 高级命令的基础是理解 Git 的对象存储模型。Git 是一个内容寻址文件系统,所有数据都以对象形式存储。
Git 对象类型
| 对象类型 | 说明 | 存储内容 |
|---|---|---|
| blob | 文件内容 | 文件数据,不含文件名 |
| tree | 目录 | 文件名到 blob/tree 的映射 |
| commit | 提交 | tree 指针、父提交、作者信息 |
| tag | 标签 | 特定 commit 的引用 |
Git 对象关系图:
commit (abc1234)
├─ tree: 项目根目录快照
├─ parent: 父提交
├─ author: 作者信息
└─ committer: 提交者信息
tree (def5678)
├─ blob: src/main.py (文件内容)
├─ tree: src/utils/ (子目录)
└─ blob: README.md查看 Git 对象
# 查看对象类型和内容
git cat-file -t <hash> # 查看对象类型
git cat-file -p <hash> # 查看对象内容
# 查看提交的树
git ls-tree <commit-hash>
# 查看所有对象
git rev-list --all --objects引用与 reflog 机制
概念说明
引用(refs) 是指向 Git 对象的指针,如分支、标签等。reflog(引用日志) 是 Git 的安全网,记录所有引用的移动历史。
引用类型
refs/
├── heads/ # 本地分支
│ ├── main
│ └── feature-xxx
├── tags/ # 标签
│ ├── v1.0
│ └── v2.0
├── remotes/ # 远程分支
│ └── origin/main
└── stash # stash 记录reflog 工作原理
# reflog 记录每次 HEAD 的移动
git reflog
# 输出示例:
# abc1234 HEAD@{0}: commit: 修复登录bug
# def5678 HEAD@{1}: commit: 添加新功能
# 9012345 HEAD@{2}: reset: moving to main
# ...关键理解:reflog 记录的是"引用的移动"而非"提交的创建"。即使删除了分支或 reset,reflog 仍然保留历史。
reflog 保留期限
| 引用类型 | 保留期限 |
|---|---|
| HEAD | 90 天 |
| 分支 | 90 天 |
| 其他引用 | 30 天 |
核心命令深度解析
1. git bisect - 二分查找
原理说明
bisect 使用二分查找算法定位引入 bug 的提交:
有效区间 二分位置
├──────●──────●──────●──────●────●────>
good bad
时间复杂度:O(log n)
100个提交 → 最多7次查找
1000个提交 → 最多10次查找bisect 工作流程
# 1. 启动 bisect
git bisect start
# 2. 标记当前状态(坏的)
git bisect bad HEAD
# 3. 标记已知好的版本
git bisect good v1.0
# Git 自动切换到中间提交
# 4. 测试当前版本
./run_tests.sh # 手动测试
git bisect good # 或 git bisect bad
# 5. 重复步骤4直到定位
# Bisect: abc1234 is the first bad commit
# 6. 结束 bisect
git bisect reset自动化 bisect
# 创建测试脚本
cat > test.sh << 'EOF'
#!/bin/bash
make # 编译
./run_tests # 运行测试
# 返回0表示good,非0表示bad
EOF
chmod +x test.sh
# 运行自动化 bisect
git bisect start
git bisect bad HEAD
git bisect good v1.0
git bisect run ./test.sh2. git reflog - 引用日志
核心用途
| 场景 | 命令 |
|---|---|
| 查看操作历史 | git reflog |
| 恢复误删的分支 | git branch recovered HEAD@{n} |
| 撤销 reset | git reset --hard HEAD@{1} |
| 撤销 rebase | git reset --hard ORIG_HEAD |
reflog 实战场景
# 场景1: 误删分支后恢复
git branch -D feature-xxx # 删除分支
git reflog # 找到分支最后的commit
git checkout -b feature-xxx HEAD@{5} # 恢复分支
# 场景2: reset后悔了
git reset --hard HEAD~3 # 回退3个commit
# ... 发现回退错了
git reset --hard HEAD@{1} # 回到reset前的状态
# 场景3: rebase混乱后
git rebase -i HEAD~10 # 交互式rebase
# ... 搞得一团糟
git reflog # 找到rebase前
git reset --hard HEAD@{n} # ORIG_HEAD 也可以
# 场景4: 查看分支在某时刻的状态
git reflog show feature-xxx
git checkout feature-xxx@{yesterday}reflog 高级用法
# 按时间查看
git reflog --date=local
# 查看特定分支的reflog
git reflog show main
# 限制输出行数
git reflog -10
# 按日期过滤
git reflog --since="2 days ago"3. git worktree - 多工作树
原理说明
传统 Git 仓库每个工作目录只能检出一个分支。worktree 允许同一仓库拥有多个工作目录,各自检出不同分支。
worktree vs stash
| 特性 | worktree | stash |
|---|---|---|
| 并行工作 | ✅ 同时修改多个分支 | ❌ 必须切换分支 |
| 隔离性 | ✅ 完全独立目录 | ❌ 共享工作区 |
| 持久化 | ✅ 一直存在 | ✅ 持久化 |
| 适用场景 | 长期并行开发 | 临时切换 |
worktree 基本操作
# 添加新的 worktree
git worktree add ../feature-branch feature-branch
# 目标位置 分支名
# 列出所有 worktree
git worktree list
# 删除 worktree
git worktree remove ../feature-branch
# 移动 worktree
git worktree move ../old-path ../new-path
# prune 清理已删除的 worktree 记录
git worktree pruneworktree 实战场景
# 场景1: 紧急修复 bug
# 当前正在开发 feature,突然需要修复 hotfix
git worktree add ../hotfix main
cd ../hotfix
# ... 修复 bug,提交
cd ../original-project # 回到原来的工作
# 场景2: 代码审查
git worktree add ../review pr-123
cd ../review
# ... 审查代码
# 场景3: 并行测试多个版本
git worktree add ../test-v1.0 v1.0
git worktree add ../test-v2.0 v2.0
# ... 在不同目录测试不同版本
# 场景4: CI/CD 部署
git worktree add /tmp/build-deploy main
cd /tmp/build-deploy
./deploy.sh
git worktree remove /tmp/build-deploy4. git rerere - 重用冲突解决
原理说明
rerere (Reuse Recorded Resolution) 记录冲突解决方案,当相同冲突再次出现时自动应用。
rerere 工作流程
第一次遇到冲突:
┌─────────────────────────────────────┐
│ 合并冲突 │
│ ↓ │
│ 手动解决 │
│ ↓ │
│ git rerere记录解决方案 │
│ ↓ │
│ git commit │
└─────────────────────────────────────┘
再次遇到相同冲突:
┌─────────────────────────────────────┐
│ 合并冲突 │
│ ↓ │
│ git rerere自动应用之前记录 │
│ ↓ │
│ 检查自动解决是否正确 │
│ ↓ │
│ git commit │
└─────────────────────────────────────┘启用和配置 rerere
# 启用 rerere
git config --global rerere.enabled true
# 查看 rerere 状态
git rerere status
# 查看记录的解决方案
git rerere diff
# 清除旧记录
git rerere forget <path>rerere 实战价值
# 场景: 长期功能分支与主分支频繁合并
feature-xxx ─┐
├─→ conflict1 ─┐
main ─┘ │
│
feature-xxx ─┐ ↓
├─→ conflict1 (rerere自动解决)
main ─┘5. git range-diff - 版本范围对比
原理说明
range-diff 用于对比两个版本范围(如两个分支的提交系列),显示每个提交的变化。
使用场景
# 对比两个分支的差异
git range-diff main..feature-branch
# 对比 rebase 前后的变化
git rebase -i HEAD~5
git range-diff HEAD@{1} HEAD
# 对比 PR 的更新
git range-diff origin/main...pr-v1 origin/main...pr-v2range-diff 输出解读
1: acaa63b = 1: ef56789 feat: add login
2: bcb234c ! 2: gh67890 fix: handle error
@@ Context: changed due to upstream
3: cdc345d < 3: (removed)
4: - > 4: ij90123 feat: add logout符号含义:
=提交相同!内容变化<被删除>新增
第二部分:实战场景与技巧
故障恢复场景
场景1: 误删重要分支
# 问题:误删分支
git branch -D important-feature
# Branch deleted.
# 解决:使用 reflog 恢复
git reflog | grep "important-feature"
# abc1234 HEAD@{5}: checkout: moving from main to important-feature
git checkout -b important-feature abc1234
# Switched to a new branch 'important-feature'场景2: 错误的 rebase
# 问题:rebase 导致提交混乱
git rebase -i HEAD~10
# ... 手动编辑后保存
# 发现出错了,想回退
# 方法1: 使用 reflog
git reflog
git reset --hard HEAD@{n}
# 方法2: 使用 ORIG_HEAD
git reset --hard ORIG_HEAD
# 方法3: 创建备份分支后再操作
git branch backup-before-rebase
git rebase -i HEAD~10
# 如果出错
git reset --hard backup-before-rebase场景3: hard reset 后后悔
# 问题:hard reset 丢失了提交
git reset --hard HEAD~3
# ... 发现重要的提交丢失了
# 解决:reflog 恢复
git reflog
# abc1234 HEAD@{0}: reset: moving to HEAD~3
# def5678 HEAD@{1}: commit: important change
# 9012345 HEAD@{2}: commit: another important change
# 恢复到 reset 前
git reset --hard HEAD@{1}
# 或创建新分支指向丢失的提交
git branch recovered def5678场景4: 合并冲突后的后悔
# 问题:合并产生大量冲突,想放弃
git merge feature-branch
# Auto-merging file.txt
# CONFLICT (content): Merge conflict in file.txt
# 解决:放弃合并
git merge --abort
# 如果已经 commit 了
git reset --hard ORIG_HEAD场景5: 误 git commit --amend
# 问题:amend 覆盖了原来的提交
git commit -m "WIP"
git add forgotten-file.txt
git commit --amend
# ... 发现需要原来的提交
# 解决:reflog 中查找
git reflog
# abc1234 HEAD@{0}: commit (amend): ...
# def5678 HEAD@{1}: commit: WIP
git show def5678 # 查看原提交内容高效工作流
使用 fixup 和 autosquash 整理历史
传统方式 vs autosquash
# 传统方式:手动编辑 rebase todo
git rebase -i HEAD~5
# 手动将 fixup 提交移动到对应提交下
# 手动标记为 fixup
# autosquash:自动整理
git commit -m "feat: add user login"
# ... 发现有个bug
git commit -m "fix: typo" --fixup HEAD~1
# ... 又发现一个问题
git commit -m "fix: validation" --fixup HEAD~2
# 自动 rebase 整理
git rebase -i --autosquash HEAD~3
# Git 自动将 fixup 提交放到对应位置fixup 类型对比
| 类型 | 说明 | 使用场景 |
|---|---|---|
--fixup | 合并到目标提交,丢弃 fixup 的提交信息 | 小修复 |
--squash | 合并到目标提交,保留提交信息 | 重要修改 |
# fixup 示例
git commit -m "feat: add feature"
# ... 发现小bug
git add .
git commit --fixup HEAD
# squash 示例
git commit -m "feat: add feature"
# ... 发现重要问题需要说明
git add .
git commit --squash HEAD
# 编辑器打开,合并提交信息交互式 rebase 高级技巧
rebase 操作说明
| 命令 | 说明 |
|---|---|
pick | 使用该提交 |
reword | 修改提交信息 |
edit | 停在该提交,允许修改 |
squash | 合并到前一个提交 |
fixup | 合并到前一个提交(丢弃信息) |
drop | 删除该提交 |
exec | 在该提交后执行命令 |
实用 rebase 技巧
# 1. 压缩多个小提交
git rebase -i HEAD~5
# 将多个 pick 改为 squash/fixup
# 2. 重新排序提交
git rebase -i HEAD~10
# 调整提交顺序
# 3. 修改特定提交
git rebase -i HEAD~5
# 将目标改为 edit
# ... 修改文件
git add .
git rebase --continue
# 4. 在 rebase 过程中执行命令
git rebase -i HEAD~10
pick abc1234 commit 1
exec make test # 在该提交后运行测试
pick def5678 commit 2
exec ./deploy.sh # 在该提交后部署团队协作技巧
分支策略最佳实践
# 1. 功能分支命名规范
feature/ticket-123-user-login
bugfix/ticket-454-memory-leak
hotfix/critical-security-fix
# 2. 提交信息规范
feat: add user authentication
fix: resolve memory leak in parser
docs: update API documentation
refactor: simplify error handling
test: add unit tests for auth module
# 3. 使用 worktree 进行代码审查
git worktree add ../review-pr-123 pr-123
cd ../review-pr-123
# ... 审查完成后删除
git worktree remove ../review-pr-123安全的 rebase 操作
# 1. rebase 前创建备份
git branch backup-$(date +%Y%m%d)
git rebase main
# 2. 使用 --onto 精确 rebase
git rebase --onto new-base old-base branch
# 将 branch 从 old-base 重新应用到 new-base
# 3. 只 rebase 本地提交
git rebase @{u} # 只 rebase 本地未推送的提交清理历史
# 1. 清理大文件
git rev-list --objects --all |
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
awk '/^blob/ {print substr($0,6)}' |
sort -n -k2 |
tail -10
# 使用 BFG Repo-Cleaner
bfg --delete-files unwanted-file.txt
git reflog expire --expire=now --all
git gc --prune=now --aggressive
# 2. 清理无用分支
git branch -merged | grep -v "\*" | xargs git branch -d
# 3. 清理无用 reflog
git reflog expire --expire=now --all
git gc --prune=now第三部分:实践与思考
实践记录
环境准备
- [ ] 创建测试仓库
- [ ] 准备测试数据(多个提交、分支)
- [ ] 安装必要工具
基础练习
- [ ] 使用
git bisect定位引入 bug 的提交 - [ ] 使用
git reflog恢复误删的分支 - [ ] 创建并管理多个
git worktree - [ ] 使用
git rerere自动解决重复冲突 - [ ] 使用
git range-diff对比两个分支
进阶场景
- [ ] 模拟 rebase 灾难并恢复
- [ ] 使用
--fixup和--autosquash整理提交历史 - [ ] 清理仓库中的大文件
- [ ] 建立团队的 Git 规范文档
疑问与思考
已解答
✅ reflog 和 git log 有什么区别?
git log显示提交历史(不可变)git reflog显示引用移动历史(本地操作记录)
✅ 什么时候用 worktree vs stash?
- 长期并行工作用 worktree
- 临时切换上下文用 stash
✅ rerere 记录存在哪里?
.git/rr-cache/目录- 可以纳入版本控制(团队共享冲突解决方案)
待探索
❓ 如何让 rerere 记录在团队中共享?
- 考虑将
.git/rr-cache/加入版本控制 - 需要评估安全性和适用性
- 考虑将
❓ 大型仓库(如 Linux 内核)如何高效使用 bisect?
- 研究并行 bisect
- 考虑使用 CI 自动化测试脚本
❓ 如何防止团队成员误用 rebase?
- 使用 pre-receive hook
- 设置受保护分支规则
❓ git worktree 有什么限制?
- 不能在同一个仓库中创建嵌套 worktree
- 需要管理多个工作目录的存储
更新日期:2026-02-19
参考资料
- Git Flight Rules - Git 紧急情况处理手册
- Oh Shit, Git!?! - 常见 Git 问题解决
- Pro Git 中文版
- Git bisect 官方文档
- Git worktree 官方文档