Git 合并分支有哪几种方式?3 种常见做法一次讲清

编程狮(w3cschool.cn) 2026-10-12 17:38:21 浏览数 (99)
反馈

把功能分支的改动合回主分支时,你至少有三种常见做法:git merge 默认合并、git rebase 先把提交变基到最新基底再合并、git merge --no-ff 强制保留一个合并提交。三种写法都能把代码合进来,差别只在提交历史长什么样、是否保留分叉线索,以及日后审计时能否一眼看出“这是一次功能合并”。下面先用一张决策表看清边界,再逐个给出可复现的命令与典型输出。理解这些差异后,你就能在 merge、rebase 与 --no-ff 之间做出更稳妥的选择。

git merge 三种方式对比封面

先看结论

你的场景 首选命令 历史形态 风险等级
把 feature 合入 main,保留合并节点 git merge 保留分叉与合并提交 低
想让历史呈一条直线、无合并提交 git rebase + git merge 变基后快进,历史线性 中,改写本地提交
禁止快进、必须留痕审计 git merge --no-ff 强制生成合并提交 低

记住一条铁律:已经推送到远端、别人可能拉取过的分支不要做 rebase;公开历史一律用 merge 或 --no-ff,只有本地独占分支才允许 rebase 整理。

一、git merge 默认到底合并了什么

真正动手 Git 合并分支之前,先弄清“合并”在动什么。Git 里每个分支名只是一个指向最新提交的轻量指针,HEAD 指向你当前所在的分支。git merge 做的大事只有一件:把目标分支的提交“接”到当前分支上,并根据两个分支指针的位置决定要不要新建一个合并提交。

当你的当前分支指向的提交,正好是目标分支的祖先时(也就是目标分支没有新东西,或者当前分支落后),Git 会直接“快进”(fast-forward),把指针顺着目标分支移动到最新提交,不生成任何新提交。这种情况看上去就像没发生过合并,历史是一条直线。

当两边都有对方没有的提交时,Git 才会真正做三路合并(3-way merge),并自动新建一个“合并提交”(merge commit),它的父节点同时指向原来两条分支的最新提交。这个合并提交就是“分叉又合拢”的可见痕迹。关于指针和提交对象的底层机制,建议先过一遍 Git 教程。

git merge、rebase 与 --no-ff 怎么选?

git merge 的适用边界很清晰:它从不改写任何已有提交,只新增内容,因此对已经推送的公开分支也安全。但同时也正因为它“不碰旧历史”,如果团队成员众多、功能分支频繁,主干历史会出现大量交叉的合并提交,图看起来很乱。对比之下,rebase 能把这些分叉“抹平”,代价是改写了提交。性能上两者差异可以忽略;安全上 merge 更稳;版本上 2.x 与 1.x 行为基本一致。理解这一层,后面两种写法才好对照着选。

二、git merge 默认与 --no-ff 的真实输出

日常合并最稳的就是直接用 git merge,必要时加 --no-ff 强制留痕。假设你在 main 分支,要把 feature 合进来:

# 切到接收改动的分支
git checkout main

# 默认合并:能快进就快进,不能才建合并提交
git merge feature

# 无论如何都生成一个合并提交(禁止快进)
git merge --no-ff feature

如果当前分支落后且能快进,git merge feature 执行后终端输出类似:

Updating a1b2c3d..e5f6a7b
Fast-forward
 src/app.js | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

如果两边都有新提交、走三路合并,会输出:

Merge made by the 'recursive' strategy.
 src/app.js | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

失败分支最常出现在两种场景。第一种是工作区有未提交的修改且与合并内容冲突,Git 会报 error: Your local changes to the following files would be overwritten by merge,此时先运行 git stash 暂存改动,合并完再 git stash pop 即可修复。第二种是出现内容冲突,文件里出现 <<<<<<< 标记,需要手动编辑后 git add,再 git commit 完成合并。命令执行后预期当前分支指针前进,可用 git log --graph --oneline 验证是否成功。更多实战细节与冲突处理可参考 Git Magic。

实际项目里,合并前最好先运行 git status 检查工作区是否干净,再运行 git fetch 把远端最新提交拉到本地对照,避免把别人的改动漏掉。确认无误后再执行上面的 merge 命令。

三、git rebase 再 merge 得到线性历史

如果你嫌合并提交太多、想让历史是一条干净的线,可以先 rebase 再 merge。思路是:把 feature 上的提交“摘下来”,重新以最新 main 为父节点重放,这样 feature 就像从最新 main 直接长出来,之后再 merge 就能快进,不留合并提交。

# 把 main 更新到最新
git checkout main
git pull origin main

# 回到 feature,以最新 main 为基底重放提交
git checkout feature
git rebase main

# 再回到 main 做快进合并
git checkout main
git merge feature

rebase 正常运行时会在每个提交上重放;若在某个提交上冲突,Git 会暂停并提示:

Auto-merging src/app.js
CONFLICT (content): Merge conflict in src/app.js
error: could not apply 3f4e5d6... 增加分页逻辑

此时编辑冲突文件,解决后 git add,再执行 git rebase --continue;想放弃就 git rebase --abort,不会留下半成品。rebase 的核心坑是:千万不要在已经推送、别人基于它工作的分支上做 rebase,否则对方本地历史会和你的新历史对不上。rebase 只在本地独占分支使用。分支合并策略上,rebase 适合“私人整理”,merge 适合“公开收口”。关于变基与合并的取舍与底层原理,Pro Git 中文版 讲得更系统。

四、反例与失败排查

最常见的错误做法是:在已经 push 的 feature 分支上 rebase,然后强制 push,导致队友拉取时历史错乱。正确边界是——公开分支只用 merge 或 --no-ff,绝不 rebase。

另一个容易踩的坑是误以为 --no-ff 会改变代码内容。实际上 --no-ff 只影响“是否生成合并提交”,最终代码状态和普通 merge 完全一致,区别仅在历史图里多一个节点。想确认某次合并是不是快进,运行 git log --graph --oneline -10 检查有没有独立的合并节点即可。

如果合并后想反悔,还没推送就可以 git reset --hard ORIG_HEAD 回到合并前;已经推送则应当用 git revert -m 1 <合并提交> 生成一个反向提交,而不是 reset 抹掉公开历史。命令执行失败时优先看终端输出里的 CONFLICT 或 fatal 行,那是排错入口。关于两种合并模式的字段与参数对照,git merge 与 rebase 的速查可看 Git 速查手册。遇到 merge 冲突时,先读懂冲突标记里的 ours 与 theirs 两段,再决定保留哪一侧,切勿整段删除。

本文示例基于 Git 官方文档与 2.x 版本实际运行(实际运行),命令输出均为典型结果。

总结

Git 合并分支常见有三种做法:git merge 默认合并,能快进就快进、不能就建合并提交,历史保留分叉痕迹,对公开分支最安全;git rebase 先把 feature 的提交变基到最新 main 再 merge,能得到一条线性历史,但会改写提交,只适合本地独占分支;git merge --no-ff 强制生成一个合并提交,即使能快进也留痕,适合需要审计、要一眼看出“这是一次功能合并”的场景。一句话记忆:公开合并用 merge 或 --no-ff,本地整理才用 rebase,分支合并策略按“是否要审计留痕”来定。

延伸学习

常见问题

Q:git merge 和 git merge --no-ff 合并出来的代码有区别吗?

没有区别,两者最终的工作区文件内容完全一致。--no-ff 只影响历史图:即使能快进,它也强制创建一个合并提交,让你在日志里清楚看到“这里发生过一次合并”。如果你关心审计留痕,就用 --no-ff;如果只想让历史干净,普通 merge 即可。

Q:为什么已经推送到远端的分支不能用 rebase?

因为 rebase 会重新生成一批新提交(新的哈希),旧提交被“替换”掉。如果别人已经基于旧提交工作,你强制推送新历史后,对方拉取时会出现历史对不上的冲突,甚至需要全员重新 rebase。公开分支请用 merge 或 --no-ff 保持历史只增不减。

Q:怎么判断一次合并到底是快进还是生成了合并提交?

运行 git log --graph --oneline -10 查看历史图。如果能看到一条分叉又合拢、带有两个父节点的提交,说明生成了合并提交(或用了 --no-ff);如果 main 指针只是顺着 feature 平滑前进、没有分叉节点,那就是快进合并。

0 人点赞