Git rebase -r底层工作原理解析:保留合并提交的实现逻辑
理解git rebase -r的底层提交挑选与关联逻辑
git rebase -r(全称--rebase-merges)的核心是保留提交的拓扑结构,不会像普通rebase那样丢弃合并提交、扁平化分支关系。它的底层逻辑不是简单的逐个cherry-pick,而是通过重建合并提交来维持原有的分支关联。
先明确你的场景与命令含义
你的提交结构:
* bfgh894 (master) I * b9d9a21 Merge |\ | * 0542684 (test) H | * 78fg123 G * | 7474ecd F |/ * 4d49fcc E
执行的命令git rebase G I,本质是把I所在的master分支,以G为上游进行重定向。
普通rebase的行为(无-r参数)
普通rebase的核心是扁平化处理:
- 找出
G..I范围内的所有非合并提交(这里会跳过Merge提交,因为普通rebase默认忽略合并提交的拓扑)。 - 把这些提交逐个cherry-pick到G上,生成全新的提交链。
在你的场景中,普通rebase会直接cherry-pick提交I到G上,生成新的I',最终形成G → I'的线性链,原有的Merge、F、H分支关系被完全丢弃。
git rebase -r的底层逻辑
-r模式会完整保留原提交的分支与合并拓扑,底层分为三个关键步骤:
1. 确定需要迁移的提交拓扑
Git首先通过git rev-list G..I找出所有在master分支(I路径)但不在G路径中的提交,包括:
- 提交F(因为F基于E,不在G的路径上)
- Merge提交(b9d9a21)
- 提交I
同时,Git会记录这些提交之间的拓扑关系:Merge的父提交是F和H,I的父提交是Merge。
2. 生成拓扑重建脚本
Git会生成一个类似交互式rebase的todo脚本,但加入了合并指令,用来还原原有的分支结构。针对你的场景,脚本大致如下:
pick 7474ecd F # 将F重放到G的分支上(生成F') pick 0542684 H # H已经在G的路径上,无需修改 merge -C b9d9a21 HEAD~1 HEAD # 重建合并提交,保留原Merge的提交信息,父提交为F'和H pick bfgh894 I # 将I重放到新的合并提交上(生成I')
3. 执行拓扑重建
Git按照脚本逐步执行:
- 先把F cherry-pick到G的末端,生成F'(因为F和G原本就基于同一个E,这里不会有冲突)
- 保留H的原有位置(因为H已经是G的子提交)
- 创建新的合并提交Merge',以F'和H为父提交,完全复用原Merge的提交信息
- 最后把I cherry-pick到Merge'上,生成I'
但在你的场景中,执行git rebase -r G I后没有生成新提交,原因是Git检测到原有的拓扑已经可以直接基于G存在:Merge提交本身就包含G作为父分支,F与G并行的关系也无需调整,整个master分支的拓扑已经符合"基于G"的要求,因此不需要生成新提交,仅让HEAD指向master即可。
核心区别总结
| 普通rebase | git rebase -r |
|---|---|
| 丢弃合并提交,扁平化分支 | 保留合并提交,维持原分支拓扑 |
| 仅迁移单个提交的变更内容 | 迁移整个提交树的结构关系 |
| 底层是逐个cherry-pick非合并提交 | 底层是重建提交拓扑+cherry-pick+重建合并 |
内容的提问来源于stack exchange,提问作者teo77
相关产品推荐
相关产品推荐

