合并保留 B 与 U,并创建以两者为父级的 M。变基在 U 后重建 B,得到身份不同的 B′。

merge 与 rebase 画出的历史线不同

merge 与 rebase 都能整合工作,却对提交身份和祖先关系产生不同影响。合并通常保留既有提交并连接历史,变基则把选定变化移到另一基线。选择不只是图形看起来是否整齐,还影响评审引用、冲突处理、签名,以及已经依赖旧提交的协作者。应依据仓库流程与真实分支关系决定,而不是把“线性一定更好”或“有合并提交就一定安全”当成通用判断。 图示已分叉历史上的普通变基;快进与保留合并结构的变基有所不同。箭头表达父级关系,不代表墙上时钟顺序。 合并记录什么 两个末端已经分叉时,普通双分支合并创建以这两个末端为父级的新提交,并记录结果目录树;既有提交保持原身份。图保留的是祖先关系,不是所有活动的完整时间排序。提交日期与界面展示顺序也不能替代父级关系,否则可能把并行工作误读成严格前后发生的一串事件,进而错误判断某项修改究竟基于什么版本。 如果当前末端是传入末端的祖先,合并可以直接快进分支,不创建额外合并提交。要求始终创建合并提交,是通过相应选项表达的另一项流程规则。反过来,即便结果保留两个父级,也不能证明双方预期行为都正确保留。仍需审阅内容并运行集成测试,不能用提交拓扑替应用接口、数据规则或发布依赖完成验收。 变基改变什么 普通变基识别选定提交,再把它们的变化重新应用到新基线。重新创建的提交只要父级不同,对象标识就不同,即便补丁看起来相同。原作者信息通常保留,但新对象仍需自己的验证,以及流程要求的签名。指向旧对象的评审链接,也不会自动变成指向新对象的链接;后续说明需要让读者能够找到实际接受审阅的结果。 并不是每次变基都会重写所有提交:分支已经更新时可能无需重放;上游等价变化或应用后的空结果,也会影响哪些提交留下。普通变基从重放序列中省略合并提交,让选定工作趋于线性;明确启用重建合并的模式,则会尝试恢复分支结构。因此,“变基永远得到直线”同样过度概括,具体结果取决于参数和输入历史。 合并协调的是分支末端,变基通常逐个处理提交,所以冲突出现的位置与次数可能不同,同一种取舍还可能需要多次执行。完成后应比较最终目录树及重放变化,并重新运行相关测试。补丁相似,不证明新基线上行为完全相同;依赖、接口和上下文已经改变时,文本能够落地只是审查的开始。 坏掉的样子 重写其他工作已经依赖的提交,会产生协调成本。私人且未发布的分支,通常比共享集成分支更适合变基。明确协商过的共享历史重写并非不可能,但必须识别受影响引用及消费者。偏好线性历史,不等于获得替换别人已发布工作的权限;是否应该执行,需要独立于工具是否支持来判断。 变基本身并不必然要求强制推送。新的远端分支可以普通创建;远端末端仍是本地结果祖先时,也可以正常快进。需要替换已被重写的远端提交,才属于非快进情况。即便使用租约,它也只是根据预期远端值作并发检查,不代表已经批准替换,也不能证明所有协作者都已被考虑。发布前应先确定真实引用关系,而不是机械套用强推命令。 变基冲突中,合并后端所称的本方,是目前已构建的变基历史,包含上游基线;对方则是正在重放的提交。这可能与开发者按原功能分支理解的“我的版本”相反。应检查实际文件版本和目标契约,再决定如何解决;暂存具体文件后继续。跳过会丢弃当前补丁,并不是自动修好这次冲突,不能为了让过程继续就忽略它的业务含义。 决定放弃操作时,中止变基会恢复起初的分支状态。--quit 则只停止管理变基,保留当前 HEAD、索引与工作文件,并不是等价的撤销命令。需要恢复起点时,不能因为两者都会结束流程就随意替换。从干净工作区开始,有助于明确恢复结果。不要把中止当成在过程中保存任意无关编辑的通用备份,也不要用强制清理作为常规冲突处理方式。应先理解当前序列、索引与文件中有哪些有效成果,再选择与操作意图匹配的恢复路径。 快速验证 在 POSIX 系统上使用 Python 3 与 Git 2.28 或更新版本运行下面实验。它在临时仓库中使用示例身份,关闭钩子与签名,没有远端,并排除继承的 Git 路由及个人配置。两个分支从同一个主题提交起步,以不同方式整合一项独立上游变化;第三个分支有意制造新增同名文件的冲突,检查中止恢复,而不碰当前项目的实际历史。 ...

一月 18, 2028 · 2 分钟 · XB Field Notes
合并 M 的第一父级是原当前末端 C,第二父级是传入末端 I。文本合并干净不证明行为正确。

git merge 用一个新的提交把分叉的两条线接起来

合并连接两段历史,并记录项目的结果快照。普通的双分支分叉合并,会创建同时把两个原末端作为父级的新提交。这些父级关系让更早的提交仍然可达,却不能证明最终文件保留了每一项预期行为。合并既是图上的连接,也是内容上的决定;只检查其中一面,会漏掉另一面真实存在的错误,不能把“两个父提交都还在”当作功能没有丢失的证据。 记录合并前先审阅行为 父级保留历史,不把它改写成单线 如果当前末端是传入末端的祖先,快进可以直接移动当前分支引用,不需要额外合并提交。当两侧各自拥有新提交后,普通合并才产生双父级结果:第一个父级是合并前的当前末端,第二个是传入末端。图中依然包含两条历史线,并没有把它们重新写成一串线性提交。阅读历史时,应区分分支名字的变化与提交图本身的形状。 Git 通常借助合并基点协调两侧末端;更复杂的历史可能涉及多个基点。相对共同祖先比较,可以区分某一侧确实修改的内容和保持不变的上下文。但文本结果干净,只表示选定合并机制找到了一种组合文件的方法,不代表 Git 理解了应用接口、业务规则或发布依赖。这些含义仍需通过源码关系、约定和测试来判断。 记录连接之前先审阅结果树 开始前应明确并保留当前工作状态。未提交修改可能阻止合并,也可能让失败后重建原状态变得复杂。不能假定中止操作能够恢复任意在合并前或合并期间发生的编辑。还应记录真正被连接的分支末端对象,让审阅和后续检查针对稳定对象,而不是继续变化的名称;否则两次观察可能根本不是同一组输入。 如果流程要求显式审阅合并提交,可以让 Git 在记录提交之前暂停。但不提交选项无法阻止快进,所以即使线性情况也要求生成合并提交时,需要同时关闭快进。暂停后检查暂存变化,把候选结果与两个父级分别比较,并运行任意一侧所影响的测试。下面的实验正是在这个暂停点发现行为不兼容,先修复再记录合并,而不是把有问题的组合直接当成成功。 压缩合并是另一种选择:--squash 准备组合内容,但不记录 MERGE_HEAD,之后普通提交也不会把传入末端记作父级。因此,即便最终文件看起来相同,祖先关系以及未来合并的判断基础也可能不同。需要保留双父级证据时,不能把压缩提交误认为普通合并的等价记录。 坏掉的样子 文本冲突不限于两边改动同一行。删除、重命名、二进制变化及其他不兼容操作,都可能需要人工决定。应使用工作区状态及索引中的未合并条目识别未解决路径。普通文本冲突中的标记方便查看双方内容,但没有发现这些字符串,不能证明所有冲突已经解决,更不能代替对二进制文件或路径结构的检查。 解决应遵循组合后程序的真实契约,再暂存具体路径并继续或提交。删除标记、执行暂存,只是在告诉 Git 已经提供了解法,不是在证明解法正确。生成文件应先协调权威输入,再通过生成器重建;不能把两个生成结果拼成看似合理的输出,否则下次构建还会覆盖人工拼接,甚至与输入表达的规则不一致。 更隐蔽的是文本干净却行为损坏。一条分支可能收紧函数参数形式,另一条同时增加仍按旧形式调用的代码。它们修改不同文件,各自测试都可以成功,组合后却失败。因此,集成测试必须覆盖这次组合新形成的调用关系,不能只是把两份分支测试摘要并排放在一起,就认为结果也已经通过同样验证。 策略选择还可能主动丢弃内容。偏向传入方的冲突选项,会优先采用传入方的冲突片段,同时保留其他非冲突变化;它不是自动保留双方意图的通用规则。另一种名为 ours 的策略则保留当前目录树,却仍把另一侧历史记录为已合并。因此,双父级图不能证明传入实现真的被采用。类似选择必须有明确依据并审查内容,不能只为让冲突提示消失而使用。 快速验证 在 POSIX 系统上使用 Python 3 和 Git 2.28 或更新版本运行下面实验。全部工作都在临时仓库,使用示例身份,没有远端,关闭钩子与签名,并排除继承的 Git 路由和个人配置。一条分支要求 Python 接口使用关键字参数,另一条分支新增位置参数调用。关闭字节码写入,是为了避免测试产生的缓存混入工作树状态,便于准确检查最终仓库是否干净。 ...

十二月 29, 2027 · 2 分钟 · XB Field Notes