协作时最常遇到的”惊吓”就是:合并冲突(conflict)。两条分支改了同一文件的同一处,Git 不知道听谁的,就把皮球踢给你。这一篇先把 merge 和 rebase 这对兄弟讲清,再手把手教你怎么拆冲突这个雷。
把冲突想成两个编辑同时改一句话:A 改成”今天晴天”,B 改成”今天下雨”,合并时总得有人拍板用哪句,Git 不会替你猜。
merge:保留真实的”分叉史”
git merge 会把两条分支的历史真正交汇成一个新的”合并提交”:
git switch main
git merge feature-x
优点是历史真实可溯——你能清楚看到某天某功能是从哪条分支合进来的。缺点是如果功能分支很多,历史图会变成一团乱麻(很多交叉的线)。
rebase:把历史”梳成一条直线”
rebase 的思路不同:它把当前分支的提交”摘下来”,先快进到目标分支最新处,再一颗颗重新接上去:
git switch feature-x
git rebase main
# 之后切回 main 再 merge,就会是干净的快进合并
git switch main
git merge feature-x
rebase 后历史变成一条漂亮的直線,没有多余的合并节点,review 时很清爽。但代价是它改写了提交历史(原来的提交被换成新哈希的新提交)。
merge 还是 rebase?一句话准则
- 未推送的本地分支:想让历史干净,用
rebase。 - 已推送到远程、别人也在用的分支:只用
merge,绝不rebase——改写公共历史会让协作者崩溃。 - 团队里常约定:“个人功能分支用 rebase 整理,合进 main 用 merge 保留轨迹。“
冲突长什么样
合并遇到冲突时,Git 会在文件里插入标记:
<<<<<<< HEAD(当前分支 main 的内容)
function hello() { return "你好"; }
=======(要合并进来的 feature-x 的内容)
function hello() { return "hello"; }
>>>>>>> feature-x
<<<<<<< HEAD到=======之间:你当前分支的内容。=======到>>>>>>>之间:对方分支的内容。- 你的任务:删掉标记,留下正确的代码。
一步步解决冲突
# 1. 合并触发冲突后,先看哪些文件冲突
git status
# 2. 打开冲突文件,手动编辑,删掉 <<< === >>> 标记,保留正确内容
# 3. 改完后,把解决好的文件加入暂存区
git add 冲突文件.js
# 4. 所有冲突都解决完,提交这次合并
git commit
现代编辑器(如 VS Code)会提供”采用当前/采用传入/两端都保留”的按钮,点几下就能解决,比手改标记省事。
用工具辅助
# 图形化解决冲突(装了 VS Code 的话)
git config --global merge.tool vscode
git mergetool
遇到复杂冲突,git mergetool 会打开对比界面,左右两边分别是两个版本,中间是结果,直观很多。
常见新手坑
- 看到冲突就
git merge --abort溜了:能中止,但问题没解决,下次还得面对。勇敢拆掉标记才是正道。 - rebase 已推送的分支:改写公共历史,队友
pull时一团乱。记住”已公开的历史别 rebase”。 - 解决冲突时把
<<<<<<<标记也留进代码:提交后代码里出现奇怪符号,运行时报错。解决完务必确认标记全删了。 - 冲突没全解决就
commit:git status还有未解决的就提交,半截代码进库。
小测验
- 问题1:merge 和 rebase 最大的观感区别?答案:merge 保留分叉交汇(有合并提交),rebase 把历史梳成一条直线。
- 问题2:为什么已推送到远程的分支不建议 rebase?答案:rebase 改写提交历史(换哈希),会让协作者拉取时历史对不上。
- 问题3:冲突标记
=======上下分别代表什么?答案:上是当前分支内容,下是要合入的分支内容。
更多实战:rebase 整理后再合并
你在本地的 feature-x 上提交了三次,期间 main 被同事推了两次新提交。你想让历史干净:
git switch feature-x
git rebase main # 把你的三次提交"挪"到 main 最新之后
# 若有冲突,逐个解决后 git add + git rebase --continue
git switch main
git merge feature-x # 此时是快进合并,没有多余合并节点
这样 main 的历史是一条直线,review 时清爽。记住:rebase 只用于你自己的、还没推送到远程的分支。
中止与后悔
git rebase --abort # rebase 中途想放弃,回到操作前
git merge --abort # 合并冲突搞不定,放弃这次合并
这两个命令是”逃生舱”,任何时候觉得乱了,先 abort 回到干净状态再想。
自测:你真的懂了吗
- rebase 中途冲突解决了,下一步?答案:
git add后git rebase --continue。 - 为什么已推送的分支别 rebase?答案:改写历史会让协作者拉取时对不上。
- 合并搞砸了想放弃用?答案:
git merge --abort。
常见认知误区
合并与变基最容易被误解的是”该用哪个”。误区一是”为了历史干净无脑用 rebase”——却忘了 rebase 会改写提交历史(生成新哈希),如果那条分支已经推送到远程、别人也在用,你一 rebase 再强推,协作者拉取时历史就对不上了,轻则冲突重则混乱。铁律:已公开的远程分支只用 merge,本地未推送的分支才用 rebase 整理。误区二是”看到冲突就 panic”——冲突不是错误,只是 Git 把选择权交给你,按标记删掉不要的那段、留正确的即可。误区三是”rebase 中途卡住就放弃整条分支”——其实 git rebase --abort 能一键回到操作前的干净状态,随时能撤。误区四是”解决冲突时把 <<<<<<< 标记也留进代码”——提交后代码里出现奇怪符号,运行时才报错,解决完务必确认标记全删了。
这一篇你该记住的
merge保留真实分叉史,rebase把历史梳成直线但改写历史。- 准则:本地分支可 rebase 整理,已公开的远程分支只用 merge。
- 冲突标记
<<<<<<</=======/>>>>>>>三块,手动删标记留正确代码。 - 解决流程:
git status看冲突 → 编辑文件 →git add→git commit。 - 善用编辑器按钮或
git mergetool降低手改出错率。
下一篇我们讲 进阶救命命令:stash 暂存、cherry-pick 摘提交、reset 回退——这些是你偶尔”翻车”时的安全绳。