2.3 变基与拣选:改写历史的搬运术


2.3 变基与拣选:改写历史的搬运术

本节摘要:rebase 不"移动"提交——提交不可变,它把每个旧提交"复印"成新提交排到新基线上,最后只挪分支指针。理解这一点,变基的一切行为(哈希全变、reflog 救场、黄金法则)都不再神秘。cherry-pick 则是同一套复印术的单点应用。

学习目标

  1. 能描述 rebase 逐步执行的"复印"过程并预判新哈希
  2. 能说出变基后旧提交的下落与找回方法
  3. 能解释"不要变基已推送的公共分支"这条黄金法则的对象模型依据
  4. 能区分 merge 与 rebase 的历史形态并按场景选择

一、rebase 到底做了什么

沿用 2.2 的分叉现场:main 与 dev 都从公共祖先 B 前进。执行变基前先看形态:

git switch dev git rebase main # Successfully rebased and updated refs/heads/dev. git log --oneline --graph --all # * 5k6l7m8 (dev) dev edit 被复印的新提交 # * 4j9i8h7 (main) main edit # * ...公共祖先

变基内部发生的序列,用侦探的语言逐帧还原:

  1. 逐个找出 dev 上不在 main 的提交(从旧到新)。
  2. 在心里把它们摘下来,把 dev 指针临时指向 main 顶点。
  3. 对每个摘下的提交,用 2.2 讲过的三方合并机理(base=原 parent,ours=新基线,theirs=该提交的变更)生成一个**全新的提交**——内容可能一样,但 parent 变了、committer 时间变了,哈希必然全变。
  4. 最后把 refs/heads/dev 指向最后一个新提交。

所以"变基"这个名字有误导性:没有任何旧提交被移动,它们原地不动,只是 dev 指针改挂到一串新复印的提交上。旧提交此刻成了悬空对象,仍在对象库待两周以上,reflog 里也有记录——这就是"变基翻车还能救"的物理保证:

git reflog # 5k6l7m8 dev@{0}: rebase (finished): returning to refs/heads/dev # 3f4g5h6 dev@{1}: rebase (started) # e5f6a7b dev@{2}: commit: dev edit <- 变基前的旧顶点 git reset --hard dev@{2} # 一句话回到变基前

二、黄金法则的对象模型依据

"不要对已推送到公共仓库的提交变基"——这条法则常被当成教条背诵,其实它的依据完全来自对象模型:变基生成的新提交与旧提交内容可能相同但哈希不同,在协作者眼里这是两个不同的提交。当变基者 force push 之后,同事本地既有旧链又有拉下来的新链,Git 会把它们当作分叉的历史,下次合并时把同一段变更合并两遍,冲突与混乱随之而来。

那 force push 什么时候可用?仅当这条分支确认为你个人私有(比如你自己的功能分支,团队规范明确"该分支可强推")。Git 也提供了护栏:git push --force-with-lease 只在远端引用与你上次看到的一致时才允许强推,避免误删别人刚推的提交。团队规范里应当把"允许强推的分支名单"写清楚(第 5 章 5.2 节展开)。

三、交互式变基:整理卷宗

git rebase -i <基线> 打开提交清单编辑器,是整理功能分支历史的瑞士军刀:

git rebase -i main # pick 1a2b3c4 dev-1 # pick 5e6f7a8 dev-2 # pick 9c0d1e2 dev-3

每行一个提交,改动词再保存退出即可:squash 把该提交并进前一个(合并说明)、fixup 同样并入但丢弃说明、reword 只改说明、drop 删除、调换行序重排提交。执行时 Git 依然是在做"逐个复印",只是复印的内容与顺序按你的指示重组。合并零碎提交、拆分大提交、把"fix typo"类噪音清出历史,都靠它。代价是每一步都可能冲突——机理与 2.2 完全相同,解决后 git rebase --continue,反悔 git rebase --abort

四、cherry-pick:单点搬运

拣选是变基机理的最小应用:把某一个提交复印到当前位置。

git switch main git cherry-pick 5e6f7a8 # [main 8h9i0j1] dev-2 的内容 哈希是新的

新提交内容与 dev-2 相同、哈希不同,author 保留原作者、committer 是你——2.1 讲过的双字段在此派上用场。典型场景:main 上发现一个只存在于 dev 分支的修复,等不起整条 dev 合并;或维护多个发布分支时把关键补丁逐个"点射"到各版本。它也支持区间(git cherry-pick A..B)与不提交模式(-n 只改工作区与 index,让你整理后再提交)。

merge 与 rebase 的历史形态对比

上面是 merge 形态(真实分叉拓扑,历史保真);rebase 形态则是一条直线:C1→C2→M1→D1'→D2'(D1、D2 的复印品)。merge 保留"发生过什么",rebase 呈现"希望发生过什么"。我的实践倾向:个人功能分支合入主干前先 rebase 主干,让主线历史可线性阅读;主干本身永远只接受 merge 或 fast-forward,绝不改写。这也是后面第 5 章多数工作流的共同底座。

⚠️ 常见坑:交互式变基中途有冲突时,git status 会同时显示"rebase in progress"与冲突文件,新手常在此连环 abort。记住序列:解决冲突 → add → git rebase --continue。abort 永远是可选项,但每次 abort 都丢掉已完成的整理进度。

💡 关键直觉:判断一条命令"危不危险",看它改写的是不是已被别人引用的哈希。本地未推送的提交随便 rebase(没人引用);已推送的公共提交一根手指都别碰。黄金法则不是礼仪,是引用计数。

本节要点回顾

  • 复印不移动:rebase 生成新提交串、挪分支指针,旧提交悬空待回收,reflog 可回。
  • 黄金法则:改写已被他人引用的哈希必然制造双链混乱,force-with-lease 是护栏。
  • 交互式整理:squash/fixup/reword/drop 重组私有分支历史的复印指令集。
  • cherry-pick:单提交复印,author 保留、committer 更新,适合补丁点射。
  • 形态选择:个人分支先 rebase 后 merge 进主干;主干不可变。

第 2 章结案。现在你已经掌握了 Git 引用系统的全部核心机制——第 3 章回到日常,把高频命令逐一还原成对象与指针操作。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U