"熟练使用这些工具后,你将拥有‘重写过去’和‘精准剪裁代码’的能力。"
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)
- 改变父节点:当你
rebase时,你生成父关系不同的新提交,原提交对象并不被原地修改。- 联动反应:由于哈希是由内容(含父哈希)生成的,父哈希变了,该提交的哈希值也会彻底改变(变成
D')。- 历史断裂:如果你把
D'推送到远程公共分支,其他协作者本地仍保留旧的D历史,需要协调共同基础。- 灾难现场:当同事执行
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 恢复查找前位置。
参考与延伸
根据技术笔记重新整理,示例使用通用名称。