多个分支同时开发时,合并提交并不难,难的是合完后还能看出原来每条分支走了哪些提交。Git 的 merge 和 rebase 会形成两种完全不同的提交图,前者保留分叉,后者把分叉拉直,但保留分叉并不等于所有分支都适合直接 merge。先判断分支类型和依赖关系,再决定操作方式,否则后面排查问题时会多花很多时间。
先确认分支类型,再决定用 merge 还是 rebase
这里有一个判断顺序。在决定是否让多个分支共享提交历史前,先检查各分支的合入目标。如果目标分支是长期维护的主干线,比如 main 或 release,且各功能分支之间没有耦合,那么通过 merge --no-ff 保留各自分叉历史是安全的。如果目标分支是临时集成分支,且后续会被删除,则更适合用 rebase 拉直历史。判断时可以用 git log --graph --oneline --all 查看当前分支网络,观察是否存在明显的分叉点以及各分支的最近共同祖先。若分支间有重复提交,merge 会识别并自动跳过,无需手动剔除。
上述检查的关键是看目标分支是否长期存在。长期分支上的提交图会被反复查看,保留分叉有助于了解每次合入的上下文;临时分支则更看重整洁,拉直后更容易阅读。“没有耦合”也很重要,如果两个功能分支修改了同一个模块的同一处,用 merge 会在合并时留下冲突,而这个冲突的解决记录会被当作一次新的合并提交,但不影响原有分叉结构。
操作路径:用 merge --no-ff 保留分叉
当确认目标分支确实需要保留历史线时,按下面的方式操作。要保留各分支的独立历史线,最直接的方式是使用 git merge --no-ff。该参数会强制创建一个合并提交,即使分支可以快进合并,也会保留两个分支的原有提交轨迹。执行时先切换目标分支,例如 git checkout main,然后依次合并各特性分支 git merge --no-ff feature-a 和 git merge --no-ff feature-b。若遇到冲突,建议逐个文件手动解决后 git add 对应文件,再继续 git merge --continue。合并完成后,用 git log --graph --oneline 查看提交图,确认各分支的历史线仍然清晰可见。
这里有一个容易忽略的点:--no-ff 只在合并动作发生时有效。如果之前已经用 fast-forward 方式合并过,历史线已经被拉直,再执行 --no-ff 只能从当前状态生成新分叉,无法恢复原有分支轨迹。所以合并前先确认目标分支没有被快进。可以用 git log --oneline --graph 查看当前 HEAD 和待合并分支的关系。
分支依赖关系要先排好
多个分支并不总是相互独立,有些分支是基于另一条分支拉出来的,这时合并顺序会影响最终提交图。如果各分支之间存在依赖关系,比如 feature-b 基于 feature-a 开发,那么合并时先合并 feature-a 再合并 feature-b,这样 feature-b 的提交会自动基于 feature-a 的合并结果,历史线上两个分叉会叠在一起,而不是各自独立。若强行同时合并,可能出现大量冲突,即使解决也无法体现前后依赖。此时更稳妥的方式是让 feature-a 先合入后,feature-b 再变基或重新合并,但这样会改变 feature-b 的提交哈希。所有保留历史线的操作都需要团队达成共识,并且建议在合并前把所有分支的最新状态拉取到本地,避免因远端更新导致合并结果与预期不符。
如果依赖关系不明确,可以用 git merge-base --is-ancestor feature-a feature-b 检查 feature-a 是否已经包含在 feature-b 的历史中。该命令返回 0 表示有依赖,返回 1 表示没有。这样可以在合并前决定顺序,而不是等到冲突出现再倒推。如果 feature-b 确实依赖 feature-a,但 feature-a 尚未合入,那么先合并 feature-a 是唯一能避免大量冲突的选择。
合并后看这几个信号
合并完成不等于历史线一定保住了,需要验证。合并后需要验证各分支历史是否被正确保留。在目标分支中运行 git log --graph --oneline --decorate --all,观察是否有多个分叉点指向不同分支的顶端。再用 git merge-base --is-ancestor feature-a main 检查原 feature-a 分支顶端是否为当前分支的祖先,若返回正确表示该分支的提交已完整包含。同时,若想确认某个提交属于哪个原始分支,可用 git branch --contains 查看,但注意该命令只返回包含该提交的分支,不会显示原分支名,因为提交本身不记录所属分支。
实际看 graph 输出时,如果看到多条竖线最后汇入一个带 “Merge branch” 字样的提交,说明分叉保留成功。git merge-base --is-ancestor 的退出码是 0 或空值表示成功,返回 1 表示不是祖先,这时需要检查 feature-a 是否还有未合入的提交。
注意容易翻车的边界
合并后不要急于删除特性分支。删除前可以先运行下面的命令,查看哪些分支已经完整合入主分支:
git branch --merged main对于列出的分支,用 git branch -d 可以安全删除;对于未列出的分支,说明还有提交未合入,需要先处理。如果此时用 git branch -D 强制删除,历史就真的丢了。
另外,如果分支在合并前已经推送到远端且其他成员在其上提交过,直接合并会把那些额外提交也带进来。这时先 git pull --rebase 把本地分支同步到最新,再执行合并。合并顺序也要固定,比如先合并改动范围小的分支,再合并范围大的分支,因为不同合并顺序会生成不同的合并提交,祖先不同,后续二分排查时结果也会不同。
最后,保留历史线不是目的,目的是让以后有人翻提交记录时能快速理解每次合入的上下文。只要团队对合并策略达成一致,操作本身并不复杂。