Git 合并分支:手把手教你 3 种做法,附选择建议与失败排查

猿友 2026-09-28 10:41:09 浏览数 (29)
反馈

Git 合并分支报错最多,原因常常不是命令写错,而是工作区没清理干净或冲突没处理完。本文用最小示例讲清三种合并做法:普通 merge、保留合并记录的 --no-ff、以及压缩历史的 squash,并说明各自的适用场景。核心结论是:功能分支合并到主干用 squash 让历史干净,已发布分支合并用 --no-ff 保留来源,本地小修复用普通 merge 就够。下面每段代码都给出逐行说明、预期结果和排错清单。先确认当前分支和工作区状态,再开始合并。

Git 合并分支三种做法对比示意图

一、先看结论:三种做法怎么选

下面这张表给出三种做法的适用场景和注意点。选择依据是「你希望提交历史长什么样」。

做法 提交历史形态 适用场景
git merge 分支 Fast-forward,无新提交 快速提一下的小修复,本地分支
git merge --no-ff 多一条合并提交 保留分支来源,团队协作主干合并
git merge --squash 压缩成一条提交 功能分支合并,历史保持干净

三种合并做法历史形态对照图

需要提醒的是,--squash 不会自动提交,它只是把改动暂存起来,提交信息要自己写,漏掉这一步会让人以为合并失败。想在线对比不同做法产生的历史,可用 在线代码实例 验证实际结果。

二、环境确认与最小示例

先确认分支和状态,再执行合并。示例基于 Git 2.x,命令行为一致;Git 版本差异对合并语义没有影响。以下为预期结果说明,未在本机执行,按 Git 官方文档推断。

# 查看当前分支与工作区状态,合并前必做这两步
git branch
git status

下面模拟一次完整合并:从主干切出功能分支,提交两个改动,再合并回主干。

# 在项目目录执行,先切回主干并确认干净
git checkout main
git status            # 预期输出 nothing to commit, working tree clean

# 切出功能分支并提交两个改动
git checkout -b feature/login
echo \"console.log(\'login\')\" > login.js
git add login.js
git commit -m \"feat: 添加登录入口\"

echo \"console.log(\'login ok\')\" >> login.js
git add login.js
git commit -m \"feat: 补齐登录日志\"

到这里功能分支有两条提交。合并前先确认工作区干净,这是合并失败最常见的原因:有未提交改动时 git merge 会直接拒绝执行。

三、做法一:普通 merge _fast-forward

普通 merge 在两条分支没有分叉时可快进,直接把指针移过去,不产生新提交。

# 切回主干,合并功能分支
git checkout main
git merge feature/login

# 无冲突时输出类似:
# Updating 1a2b3c4..5d6e7f8
# Fast-forward
#  1 file changed, 2 insertions(+)

Fast-forward 意味着历史是一条直线,看不出这个功能曾经在分支上做过。判定标准是「这个分支会不会长期存在」,短期分支Fast-forward 很清爽,长期分支Fast-forward就丢了来源信息。如果希望无论能否快进都留下一条合并记录,就用做法二。

四、做法二:--no-ff 保留合并记录

--no-ff 强制生成一条合并提交,即便可以快进,历史里也能看出这次改动来自哪个分支。

# --no-ff:即使可以快进,也创建一条合并提交
git merge --no-ff feature/login -m \"合并登录功能分支到主干\"

# 没有冲突时输出类似:
# Merge made by the \'ort\' strategy.
#  1 file changed, 2 insertions(+)

这里的判定标准是「半年后能不能一眼看出这个改动来自哪次分支合并」。团队协作中通常要求功能分支合并到主干时保留记录,便于追溯。修复方向的判断依据是团队规范,没有统一规范时,建议主干一律用 --no-ff。判定标准也可以反过来用一次:如果你希望半年后还能查到这个改动来自哪个分支,那就是 --no-ff;如果你只关心主干最后长什么样,快进更省事。把这条规则写进团队的提交规范,能避免每次合并都要临时讨论一句。要提醒的是,--no-ff 会让历史多出合并节点,看 git log 时会多一条记录,这是预期结果而不是出错。

五、做法三:--squash 压缩历史

--squash 把分支上的多条提交合并成一次提交,历史保持线性干净。注意它不自动提交。

# --squash:把分支改动压缩进暂存区,不产生合并提交
git merge --squash feature/login

# 关键:squash 不会自动提交,必须自己写提交信息
git commit -m \"feat: 新增登录功能(合并自 feature/login)\"

# 合并后确认一下日志形态,预期只有一条 feat 提交
git log --oneline -3

最容易漏的正是那句 git commit。跳过它时暂存区里还有改动,下一次操作容易被误当成没合并完。判定标准很直接:功能分支提交了很多条小改动,合并到主干后你希望主干上只留一条,就用 squash。分支上的提交条数越多,压缩的收益越明显;原本只有两三条提交的分支,压与不压差别不大。

现象 常见原因 修复方向
报 Your local changes would be overwritten 工作区有未提交改动 先 git stash 或提交后再合并
报 You have not concluded your merge 冲突解决后没执行 git add 改完冲突文件后 git add,再 git commit
报切换到主干失败 目标分支不存在或名字写错 用 git branch 核对分支名
合并后历史没变化 分支名写错,合并的是空分支 核对 git branch,确认合并的是预期分支
squash 后暂存区还有残留 漏了 git commit 确认提交后再看 git status

以上失败与修复均来自真实边界场景,未在本机执行,按 Git 官方文档推断。想系统补齐 Git 基础,可以看 Git 教程;合并前养成先看一眼 git status 的习惯,能挡掉绝大多数本来不必发生的失败。

总结

Git 合并分支按这个顺序选就行:只是本地 quick fix、不留痕迹,用普通 merge;要保留分支来源、便于追溯,用 merge --no-ff;功能分支合主干、想让历史干净,用 merge --squash 并记得自己补 git commit。执行前先确认工作区干净和分支名正确,这两件事挡掉了绝大多数合并失败。合并后跑一次 git log --oneline 确认历史形态符合预期,比事后回滚省事得多。

延伸学习

想在编程狮系统学 Git 分支操作,可以顺着下面三篇深入:

常见问题

Q:合并时说本地改动会被覆盖,怎么办?

A:先处理工作区。有用的改动用 git stash 存起来,或先提交;确认不需要的就丢弃,再执行合并。不要带着未提交改动硬合并,这会直接失败,也可能覆盖掉你的编辑。

Q:冲突解决完了为什么还提示没结束合并?

A:因为改完冲突文件后还没让 Git 知道。执行 git add 文件名 标记冲突已解决,再 git commit 完成合并。只改文件不 add,合并状态不会推进。

Q:--squash 和 --no-ff 该用哪个?

A:看想不想要分支历史。--squash 把多条提交压成一条,主干历史最干净;--no-ff 保留合并节点,能看出这次改动来自哪个分支。多数项目的主干合并偏向 --no-ff,功能模块合并偏向 squash。

Q:合并完发现合错了分支怎么办?

A:先别急着改代码。如果已经提交,可用 git revert 生成一条反向提交来撤销;如果还没提交想直接放弃,用 git merge --abort 回到合并前的状态。此时不要强推历史,会影响协作者。

0 人点赞