OFFSET偏移
← 文章目录OFFSET / FIELD NOTESNO. 096

Git 进阶:变基、拣选与二分排错

在理解历史改写边界后使用 rebase、cherry-pick、reflog 和 bisect。

所在专题Git:从快照到协作与恢复 ↗
约 6 分钟阅读开始阅读

"熟练使用这些工具后,你将拥有‘重写过去’和‘精准剪裁代码’的能力。"

1. 变基:git rebase (衍合)#

rebase 的本质是:改变分支的根基 (Base)。

哲学对比:Merge vs. Rebase

graph TD
    subgraph Merge_Flow
    A1((A)) --> B1((B))
    B1 --> C1((C))
    B1 --> D1((D))
    C1 --> M1((Merge Commit))
    D1 --> M1
    end

    subgraph Rebase_Flow
    A2((A)) --> B2((B))
    B2 --> C2((C))
    C2 --> D2((D prime))
    end

解析:

  • Merge (合并):保留了分支的“祖先”关系。产生一个新的合并点 M1。历史是真实的,但如果分支很多,拓扑图会像蜘蛛网一样乱。
  • Rebase (变基):把 feature 分支上的提交 D 摘下来,以 master 分支的最新提交 C 为新底座,重新“应用”一遍。生成的 D' 重放相同补丁,冲突处理和新基础可能改变最终内容;哈希值也会改变(因为它的父指针变了)。历史变成了一条笔直的线。

场景:保持功能分支最新

当你正在 feature 分支开发,而 master 已经往前走了。

  • Merge 做法:保留分叉关系,可产生合并提交。
  • Rebase 做法:
    git switch feature/example
    git rebase master

黄金法则:不要在公共分支上执行 Rebase!

技术原理:哈希裂变 (Hash Fission)

  1. 改变父节点:当你 rebase 时,你生成父关系不同的新提交,原提交对象并不被原地修改。
  2. 联动反应:由于哈希是由内容(含父哈希)生成的,父哈希变了,该提交的哈希值也会彻底改变(变成 D')。
  3. 历史断裂:如果你把 D' 推送到远程公共分支,其他协作者本地仍保留旧的 D 历史,需要协调共同基础。
  4. 灾难现场:当同事执行 git pull 时,Git 会发现本地有 D 而远程有 D',由于它们哈希不同,Git 会认为这是两条完全不同的历史。如果他尝试合并,会产生大量重复的提交和乱七八糟的冲突。

总结:只在“属于你自己的”分支上进行 Rebase,在公共分支上请老老实实地使用 Merge。

2. 拣选:git cherry-pick#

场景:你在 dev 分支写了 10 个提交,但由于紧急上线需求,你只需要其中一个特定的功能(比如 Commit X)进入 master。

# 1. 切换到目标分支
git switch master

# 2. 将特定的提交“拣选”过来
git cherry-pick <commit-hash>

Git 通常将该提交引入的变化应用到当前分支并创建新提交,可能冲突;拣选合并提交需明确 -m 主线父提交。

3. 交互式变基:git rebase -i#

这是重写历史的“外科手术”。它允许你合并、重命名、甚至删除过去的提交。

# 修改最近 3 个提交
git rebase -i HEAD~3

进入编辑器后的常用指令:

  • pick:保留该提交。
  • reword:保留该提交,但修改提交信息。
  • edit:停下来让你修改代码。
  • squash:合并提交。将这个提交合入前一个提交中(常用于清理乱七八糟的 WIP 提交)。
  • drop:在新历史中跳过该提交;旧对象是否保留取决于引用、reflog 与 GC。

4. 引用日志:git reflog (终极救命)#

场景:你不小心执行了 git reset --hard,或者在 rebase 过程中把代码搞丢了。

本地 reflog 记录引用更新,HEAD reflog 也可记录切换;历史会过期,不随普通 fetch/push 自动传到远端。未提交内容不能保证通过它恢复。

# 1. 查看所有历史轨迹 (即使是被 reset 掉的)
git reflog

# 2. 找到你想找回的那个时刻的哈希值
# 3. 先建立恢复分支保住该提交,不覆盖当前工作区
git branch recovery/saved-tip <reflog-hash>
git show --stat <reflog-hash>

5. 查找 Bug:git bisect (二分查找)#

场景:项目突然报了个错,你不知道是哪天、哪次提交引入的。

git bisect start             # 开始查找
git bisect bad               # 标记当前版本是坏的
git bisect good <hash>       # 标记很久以前的一个已知好版本

# Git 会自动切换到中间的 commit,请你测试:
git bisect good              # 如果这个版本是好的
# ... 循环往复,直到 Git 锁定那个引起 Bug 的 commit
git bisect reset             # 结束查找,回到原点

操作中断与验收#

变基或拣选发生冲突时,检查 status,编辑冲突文件并暂存后,分别使用 git rebase --continue 或 git cherry-pick --continue;决定放弃时对应使用 --abort。rebase -i HEAD~3 要求存在该祖先,初始提交范围可用 --root。

bisect 需要可重复的好/坏判定,环境无法测试的提交可用 git bisect skip;多个跳过结果可能让最终原因不唯一。结束时 git bisect reset 恢复查找前位置。

参考与延伸

  1. Git 变基 ↗
  2. Git 拣选 ↗
  3. Git 本地引用日志 ↗
  4. Git 二分查找 ↗

根据技术笔记重新整理,示例使用通用名称。

沿专题继续阅读

Git:从快照到协作与恢复

  1. 01Git 的底层模型:对象、快照与引用
  2. 02从第一次 add,到撤回一次修改。
  3. 03Git 分支:合并、冲突与现场保存
  4. 04Git 远程协作:抓取、追踪与双远端
  5. 05Git 进阶:变基、拣选与二分排错
  6. 06Git 场景手册:修正、撤销与恢复
CONTINUE READING / 下一篇Git 场景手册:修正、撤销与恢复

按当前操作和数据保存状态选择恢复路径,避免扩大丢失。