上周在公司群里,产品在催版本上线,我们在合并分支,结果 Git 一通冲突,整个人都傻了。小李一边 merge,一边骂:“我靠谁又 force push 啦!” 我在旁边看着 log,一堆奇奇怪怪的提交历史,一条线分岔成八条蛇。那一刻我就想说一句:merge 和 rebase 真是孪生兄弟,一个乱写历史,一个改历史。
merge 到底干了啥?
咱先不整术语,用最直白的比喻:merge 就是“结婚登记”,两个人原来的生活都保留,结婚之后加个共同签名。
比如你有一个feature/login分支,别人有一个dev主干。你写完登录功能后,想合并回主干。
git checkout dev
git merge feature/login
Git 会生成一个新的“合并提交”,这玩意是一个特殊节点,有两个父节点。提交历史像这样:
A---B---C---D (dev)
\
E---F (feature/login)
执行 merge 后变成:
A---B---C---D------G (merge commit)
\ /
E------F
你看,原来的历史都还在,Git 没改你过去干过的事,只是额外加了一个“把他们俩合起来”的节点。
好处是啥?历史完整、可追溯。 坏处也明显:日志一旦多了,看 commit log 简直像走迷宫。
rebase 是改历史的狠人
rebase跟 merge 最大的区别就是:它不加节点,它直接让你“假装历史是线性的”。
举个栗子。 你写了feature/login,然后别人也在dev改了点别的。你不想看见那种“分叉”线条,就 rebase 一下:
git checkout feature/login
git rebase dev
Git 会“把你的提交从原来的历史上摘下来”,然后挨个重新应用到dev的末尾。结果是这样的:
A---B---C---D---E'---F' (feature/login)
注意那个',说明这是“新的”提交。 Git 实际上是重新造了一批 commit,把旧的扔了。你原来的 E、F 已经不是原来的 E、F 了。
那到底该用哪个?
这个问题就像“洗碗机和手洗哪个干净”,得看场景。
用 merge 的时候
团队协作开发,一个分支多人同时改。
需要保留完整历史,比如生产版本要追溯问题。
代码合并频繁,不想冒改历史的风险。
比如我们每次发版前,主干都会 merge 各个 feature 分支,确保线上可查:
git checkout main
git merge --no-ff release/v1.3.0
这行命令会强制生成一个 merge commit,方便以后回看是哪一版上线的。
用 rebase 的时候
你在自己本地写功能,还没推到远程。
想让提交历史更干净,像一条直线一样。
需要整理 commit 顺序、合并无意义提交。
比如你写功能写得太碎了,commit 一堆:
fix bug1
fix bug2
change color
try fix
final fix
你想上线时干净点,可以这么整:
git rebase -i HEAD~5
在弹出的编辑界面里,把没用的改成squash:
pick e1a23 fix bug1
squash e2b33 fix bug2
squash e3a12 change color
squash e4f98 try fix
squash e5c12 final fix
保存后,Git 会把五个 commit 合成一个新的,干净整齐。
那个“冲突地狱”又是怎么来的?
merge 和 rebase 都可能冲突,只不过 rebase 冲突的地方更“阴险”一点。 因为 rebase 是逐个 commit 应用的,一旦某个 commit 改动了别人已经改过的行,Git 就得停下来让你手动解决。
有一次我在本地 rebase 了半天,改了十几个文件,结果到第 8 个 commit 冲突了,我解决完之后才发现——前 7 个 commit 早就推不上去了,因为远程已经有新的分支。 最后的解决办法是这样的:
git rebase --abort
git pull --rebase origin dev
意思是:算了别改了,重新跟远程同步,再 rebase 一次。 结果还是冲突,不过这次我心态平和了。
小贴士:合并后保持整洁的日志
有些团队会这样配置:
git pull --rebase
这意味着每次拉代码的时候,不是直接 merge,而是先把本地提交摘下来,放到远程更新之后重新贴上去。 这样就不会出现那种“Merge branch 'dev' of origin/dev”的垃圾 commit。
最后的建议
如果你是新人,建议先学会 merge,再学 rebase。 merge 容错高,不会破坏历史; rebase 要心细,否则分分钟历史错乱。
我个人的习惯是:
在本地开发时用 rebase 整理;
推远程、多人协作时只用 merge。
最后给你看个我常用的命令组合,用来“优雅合并”:
# 整理提交
git fetch origin
git rebase origin/dev
# 确认无误后合并
git checkout dev
git merge --no-ff feature/login
# 删除分支
git branch -d feature/login
历史干净、逻辑清晰、回滚方便。 不过话说回来,只要团队约定一致,无论你是 merge 派还是 rebase 党,都没错。怕的就是——一个人 merge,一个人 rebase,那才是真正的修罗场。