在我们平时合并代码的时候,你们用的是 merge 还是 rebase 呢?

为什么问这个问题呢?

简单来说,如果使用得当,你的代码合并就能清爽无比;

使用不当,不仅可能引发冲突,还会让整个提交记录流程变得混乱,甚至导致发布失败。

正确选择合并方式有很多讲究,涉及到各种不同的开发场景和需求:

有时候,项目单一,团队人数不多,这时候用 rebase 可能比较合适,以保持提交历史的清晰;
有时候,项目复杂,开发人员众多,这时候 merge 可能更合适,能记录每个人的工作;
有时候,项目需要频繁合并分支,那么就需要综合考虑 merge 和 rebase 的优劣......

我用 Git 很久了,也一直没听过 rebase 是什么,只知道合并分支就是用 merge。
直到有一天一个新同事问我:

“你们为什么不使用 rebase 来合并分支?我之前的公司都用 rebase,很少用 merge。”

那之后,我才第一次知道 rebase 这个命令的存在。

只有在需要合并分支的时候才会谈到 merge 和 rebase,如果没有合并需求,那怎么用都无所谓。

就像我自己独立开发的小项目,从头到尾就一个 main 分支,开发的也只有我自己,根本没有冲突这回事,有时候几天都不 push 一次代码。

分支合并主要是多人协作的团队项目才会用到,通常会有一个主分支,还有一些开发分支,有时候还会有临时的 feature 分支。

merge 合并分支

同一个分支也可能会出现 merge 的情况,比如我这边有一个老项目,平时基本上没人动。

所以我在改这个项目的时候经常忘记先 pull 一下。当然,这是一个非常不好的习惯。

所以有时候一 push 代码,发现有人提交了新代码,这时候就会自动 merge 一下。

今天主要讨论的是分支合并时的 merge。

下图展示了 merge 合并分支前后的版本变化情况。

merge 会搞出一个新的合并提交,把两个分支的历史记录全都塞一块儿。

它的特点就是日志全都保留,不管你之前合进来的那个分支有多少个提交历史,都会完整地塞进目标分支。

假设有两个分支,一个是 main,一个是 dev,在 dev 分支上开发,然后要合并到 main 分支,合并的大致流程如下。

  1. 先切到 main 分支:

git checkout main
  1. 拉取最新的 main 分支代码:

git pull origin main
  1. 切回 dev 分支:

git checkout dev
  1. 把 main 分支的最新代码合并到 dev 分支:

git merge main
  1. 解决合并冲突(如果有的话),然后提交合并结果:

git add .
git commit -m "resolve merge conflicts"
  1. 切回 main 分支:

git checkout main
  1. 把 dev 分支合并到 main 分支:

git merge dev
  1. 推送 main 分支的最新代码到远程仓库:

git push origin main

下面是 merge 合并后的主分支 Graph 看上去是不是有点乱糟糟的。

那么怎么才能让自己的提交记录一目了然,井然有序呢?

Rebase 合并分支

rebase 会将分支上的更改重新应用在目标分支上,重写提交历史。

用 rebase 合并的话,版本历史是线性的,不会搞出新的合并提交,历史记录特别干净。

假设现在有两个分支,一个是 main,一个是 dev,用 rebase 合并的流程大致是这样的:

  1. 先切到 dev 分支:

git checkout dev
  1. 把 main 分支的最新代码变基到 dev 分支:

git pull --rebase origin main
  1. 解决变基冲突(如果有的话),然后继续变基:

git add .
git rebase --continue
  1. 切回 main 分支:

git checkout main
  1. 把 dev 分支的改动合并到 main 分支:

git merge dev
  1. 推送 main 分支的最新代码到远程仓库:

git push origin main

合并压缩

在rebase 的时候还可以使用 squash 参数来压缩提交记录,例如下图,Feature 1 分支的 A、B、C 三个提交记录,使用 rebase squash 后会在主分支变为一个提交记录 F。

使用方式如下,git rebase -i HEAD~3 命令准备压缩最近的3次提交,然后在编辑模式下将pick 改为 squash,最后推送到远端仓库。

git checkout dev
git rebase -i HEAD~3
# 进入编辑模式后,修改 `pick` 为 `squash`
# 保存并关闭编辑器后,编辑新的提交信息并保存
git push origin dev --force

看到这里的小伙伴不禁要问了,到底应该用merge还是rebase呢?

最后

具体用哪种方式合并,看情况和个人习惯,没什么绝对的好坏。

用 merge 的话,如果你想保留分支的所有历史记录,并且不介意有合并提交,这种方式适合团队合作,能记录每个人的工作。

用 rebase 的话,如果你想保持提交历史的简洁和线性,这种方式适合那些希望有干净历史的项目。

有些公司只用 rebase,这适合那种只有一个主分支一直往前推进的项目。如果一个产品要同时维护 2.x 和 3.x 版本,那用 rebase 就不太合适了。

之前 Vue 项目就是用 rebase 来合并分支的。