本文介绍 Git 中合并分支常见的三种方法:

  • merge
  • rebase
  • cherry-pick

merge

git merge

Git会有很多合并策略,其中常见的是 Fast-forward、Recursive 、Ours、Theirs、Octopus。

默认Git会帮你自动挑选合适的合并策略,如果你需要强制指定,使用 git merge -s <策略名字>

fast-forward

# 语法格式
git merge <commit>|<branch-name>

Fast-forward merge:快进式合并。

当当前分支与目标分支之间存在线性路径时,就会发生快进式合并。Git不需要“实际”合并这些分支,只需将接收分支(main)的指针移动到合并分支(some-feature)的指针位置上,即可集成历史记录。这有效地组合了历史记录,因为从目标分支可达的所有提交现在都可以通过当前分支访问。例如,将 some-feature 快进式合并到 main 中如下图所示,Git只需要将main分支的指向移动到fome-feature分支指向的commit节点上:

fast-forward-merge

Fast-forward 是 Git 在合并两个没有分叉的分支时的默认行为,如果分支已经发散,则无法进行快进式合并。当没有通往目标分支的线性路径时,Git别无选择,只能通过三方合并将它们组合在一起。三方合并使用专用提交来连接两个历史记录。命名法源于Git使用三个提交生成合并提交:两个分支尖端和它们的共同祖先。

no-ff

# 语法格式
git merge --no-ff <commit>|<branch-name> [-m <message>]

no-ff:no-fast-forward,强制禁用 Fast forward 模式。使用 --no-ff 参数后,会执行正常合并,Git会为我们在分支上生成一个新的提交节点。

为了保证版本演进的清晰,推荐采用这种做法。

实际上,当使用 --no-ff 参数进行合并时,我们的合并方式就变成了 3-way merge 了。

img

squash

git merge --squash

Squash Merge其实很简单,它就是在 merge 分支的时候把分支上的所有 commit 合并为一个 commit 后再 merge 到目标分支

适合一个功能点被分成多次提交,如果合并到主分支的话,提交记录会显得繁琐,最终我们重点关注的应该是这个功能点的提交,而不是开发者中间做了多少开发,这时候就要用到 squash。

img

rebase

git rebase 变基,也是一种经常被用来做合并的方法,其与 git merge 的最大区别是,rebase 会变更历史commit节点。

merge 是一种非破坏性的操作,因此现有分支不会以任何方式被更改。这避免了所有可能出现的变基问题。但也意味着每次需要合并上游更改时,特性分支都将具有一个多余的合并提交记录。

变基的主要好处是可以获得更干净的项目历史记录。它可以消除 git 合并中不需要的合并提交节点;变基还会得到完全线性的项目历史记录。

但是,变基通过为原始分支中的每个提交创建全新的提交来重新编写项目历史记录,将会失去合并提交提供的上下文,无法看到上游的更改何时被纳入功能中。

如下图,第一张图是在 feature 分支通过 merge 合并 main 分支,会生成一个新的提交节点;第二张图在 feature 分支中通过 rebase 合并 main 分支,Git 会以 main 分支对应的 commit 节点为起点,新增三个全新的 commit 代替 feature 分支中的 commit 节点(图2中绿色所示的节点)。其原因是新的 commit 指向的 parent 变了,所以对应的SHA1值也会改变,所以没办法复用原 feature 分支中的 commit。

merging-vs-rebasing-1

merging-vs-rebasing-2


对于合并时候要使用 git merge 还是 git rebase 根据团队和项目习惯选择就可以。

git rebase 可以给我们带来清晰的历史记录,git merge可以保留真实的提交时间等信息,并且不容易出问题,处理冲突也比较方便。唯一有一点需要注意的是,不要对已经处于远端的多人共用分支做 rebase 操作。

个人习惯是:对于本地的分支或者确定只有一个人使用的远端分支用rebase,其余情况用merge。

交互式变基

rebase 还有一个非常好用的东西叫 interactive 模式,使用方法是 git rebase -i。他的作用是在提交执行变基之前,可以实现压缩几个 commit,修改 commit 信息,抛弃某个 commit 等功能。

交互式变基在想要修改分支上某些提交时会很有用,可以执行以下 6 个动作

  • reword:修改提交信息;
  • edit:修改此提交;
  • squash:将提交融合到前一个提交中;
  • fixup:将提交融合到前一个提交中,不保留该提交的日志消息;
  • exec:在每个提交上运行我们想要 rebase 的命令;
  • drop:移除该提交。

例如,使用 drop 移除一个提交:

img

cherry-pick

cherry-pick 适合用于只需要将部分代码变动(某几个提交)从一个分支转移到另一个分支的场景。

# 语法
git cherry-pick <commitHashA> <commitHashB> ...

# 转移连续一系列的提交(不包括A)
# 如下,转移从A到D的所有提交(A必须比D先提交)
git cherry-pick A..D

# 转移连续一系列的提交(包括A)
git cherry-pick A^..D

# 将指定分支的最新提交,转移到当前分支
git cherry-pick <branch-name>

git cherry-pick commitHash 会将指定的提交 commitHash 应用于当前分支。这会在当前分支产生一个新的提交,当然它们的哈希值会不一样。

img


YOLO