git代码合并

文章主要介绍Git的三种代码提交合并方式:mergerebasecherry-pick

git代码合并

git代码合并(分支合并)操作主要有三种:mergerebasecherry-pick,其中:

  • merge:合并操作,会将其他分支的内容合并到本分支,并且视情况可能需要填写提交信息
  • rebase:变基操作,将其他分支的内容搬到当前分支,变基操作能够让当前的分支保持成一条线的形式,使分支看起来很干净
  • cherry-pick:樱桃摘,真正的神!我们有些时候并不需要完全合并另外一个分支,可能我们仅仅需要合并其他分支的某个或者某几个提交记录,这个时候mergerebase就不适用了,因为无论是merge还是rebase都会合并到待合并分支的最新的位置,而cherry-pick就能实现这种单个提交记录的合并的需求

简单来讲,mergerebase的功能都是将某个分支的提交记录(直到最新提交记录)合并到另外一个分支,而cherry-pick是将某个分支的某个或者某几个提交记录拿到当前分支。

但无论是mergerebase还是cherry-pick,在合并记录的过程中,都有可能会遇到一个问题:合并冲突。这些我们都会在文章后续进行介绍。

rebase操作

我们假定有如下的分支情况:dev分支在我们main分支的基础上更新了两个版本

image-20241028211622283

此时dev分支是最新的,我们希望将dev的代码合并到main,由于此时我们在dev分支(dev分支的星号代表我们当前在dev分支)

我们需要做的事情是:

  1. 先切换到main分支:git checkout main
  2. 进行变基操作:git rebase dev

此时我们就能合并完成(这种合并我们一般称为forward):

image-20241028212128641

这里要注意,我们希望将提交记录较为落后的A分支(比如上图的main分支)合并到提交记录较新的B分支(如上图的dev分支),我们需要先切换到提交记录相对落后的A分支上操作!

如果你的分支记录已经差分而非连续,即是下面的这种情况:

image-20241028212533651

则此时rebase的意思就不是简单的forward了,这时候我们在main分支上,我们对dev分支进行rebase,执行git rebase dev,将会得到下图的记录

image-20241028213152527

我们之前提过rebase会将提交记录整理成一条线,其实说的就是这种情况,并且可以看到rebase的过程实际上就是把C1记录之后的main分支上的所有提交搬运到dev分支的最新提交的后面,因此我们举一反三一下,目前我们的提交如下:image-20241028213308676

则我们进行rebase将会得到:image-20241028213416498

能理解rebase是怎么操作的了吗?好,这个时候我们回到这张图image-20241028212533651

我们checkout一下(git checkout dev),切换分支到dev,我们反过来,要将dev分支rebasemain又会是什么情况呢?没错,同样的方法,我们将C1之后的所有dev的提交记录搬运到main上(git rebase main

image-20241028213806564

image-20241028213841890

懂了吧,这就是rebase

merge操作

我们在介绍rebase的时候实际上分了两种情况,即:image-20241028211622283image-20241028212533651

我们可以看到rebase对这两种情况的处理是一样但又不完全一样,在操作上,第一种情况是直接前进而第二种情况是会将一个分支的C1之后的所有提交变基到另外一个分支上。但就结果而言他们是一样的,因为最终的提交记录都会变成一条线的形式!

然而,你可能看过一些其他的git的文章,很多博主都推荐使用merge而不是rebase,为什么呢?

第一,merge操作专门设置了两种模式来专门处理这两种情况。处理第一种情况的模式叫fast-forward(快速前进,--ff参数),我们切换到main分支,使用fast-forward模式来进行merge

1
2
git checkout main
git merge dev --ff

merge的结果如下,可以看到针对第一种情况,其实rebasemerge的结果并没有区别(因此实际上第一种情况使用rebasemerge都可以),这是因为fast-forward模式并不会发生真正的合并,只是通过移动指针造成合并的假象,即移动main分支的HEAD指针到dev的最新分支,来实现合并功能。

然而针对第二种情况,则merge就展现出其和rebase的不同了,第二种情况使用merge会产生新的提交记录,这种针对第二种情况产生新纪录的merge模式被称为non-fast-forward(需要加上-–no-ff参数),我们同样切换到main,然后使用这种模式来处理第二种情况:

1
2
git checkout main
git merge dev --no-ff

将得到下面的结果:image-20241028220111836

这也是为什么使用merge而不是rebase的情况,因为使用merge,则dev上的提交记录和main上的提交记录都被保留下来,并且你还能看到他们是在C1记录开始进行差分的,而使用rebase,则这些分支的差分记录都会被擦除,变成一条直线,这个时候你就看不出来到底devmain在哪个记录开始进行差分了!这一点非常不利于后续的bug跟踪和版本跟踪!!

使用non-fast-forward,在git log会看到很多类似:Merge branch 'feature001' into mastercommit。(即图上的C5

最后,merge除了上面两种模式之外,其实还有第三种模式-–ff-only,即只使用fast-forward模式进行合并,如果合并失败的话则直接退出,返回失败信息!

git merge在不加参数的时候会先使用fast-forward来合并,如果合并失败则会自动切为使用non-fast-forward模式。

个人使用建议,使用时先使用git merge --ff-only,如果合并失败才考虑是否要使用--no-ff

cherry-pick

// todo