合并分类
在合并分支的时候,默认是有三种选项
的,分别是
普通的 merge
squash merge
rebase merge
区分 merge 和 rebase
更多参考GIT使用rebase和merge的正确姿势
准备
假设我们有如下图一所示仓库,该仓库有master和develop两个分支,且develop是在(3.added merge.txt file)commit处从master拉出来的分支。
![在这里插入图片描述](https://img-blog.csdnimg.cn/377d1608ed95432bb347f7080dc2f575.png)
merge
假设现在HEAD在(6.added hello.txt file)
处,也就是在master分支最近的一次提交处
,此时执行git merge develop 处于 master 分支,将 develop 分支 merge 到 master 分支
, 结果如下图所示
![在这里插入图片描述](https://img-blog.csdnimg.cn/acaf6db1a8a44b93ada45a5d8f6cdbe0.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Y-u5b2T55qE54yr54yr,size_13,color_FFFFFF,t_70,g_se,x_16)
工作原理
就是:git 会自动根据两个分支的共同祖先
即 (3.added merge.txt file
)这个 commit 和两个分支的最新提交即 (6.added hello.txt file) 和 (5.added test.txt file)
进行一个三方合并
,然后将合并中修改的内容生成一个新的 commit
,即图二的(7.Merge branch ‘develop’
)。
这是merge的效果,简单来说就合并两个分支并生成一个新的提交
rebase
假设初始状态也是图一所显示的。两个分支一个master,一个develop,此时HEAD在(6.added hello.txt file)
处,git rebase develop 处于 master 分支,将 develop 分支 merge 到 master 分支
,结果如下图三所示。
![在这里插入图片描述](https://img-blog.csdnimg.cn/9d9c85d90c8b411288955790c3f8e573.png)
可以看见develop分支分出来分叉不见了
,下面来解释一下它的工作原理:
在执行git rebase develop
之前,HEAD在(6.added hello.txt file)
处,当执行rebase操作时,git 会从两个分支的共同祖先
(3.added merge.txt file)开始提取当前分支(此时是master分支)上的修改
,即 (6.added hello.txt file
)这个commit,再将 master 分支指向 目标分支的最新提交(此时是develop分支)即(5.added test.txt file)
处,然后将刚刚提取的修改应用到这个最新提交后面
。
简单来说,就是 先找到公共祖先
,合并祖先之后的 commit
, 将 delvelop 分支
在祖先之后的 commit
放到祖先 commit 之后
,然后把 master 在祖先之后的 commit
放到 develop 最新 commit 之后
如果提取的修改有多个,那git将依次应用到最新的提交后面,如下两图所示,图四为初始状态,图五为执行rebase后的状态。
![在这里插入图片描述](https://img-blog.csdnimg.cn/21a3b6ac575340a0b4eeff90dc88368a.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Y-u5b2T55qE54yr54yr,size_15,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/463f1a4f78a444a2ab5403d745972a06.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Y-u5b2T55qE54yr54yr,size_15,color_FFFFFF,t_70,g_se,x_16)
简单来说,git rebase
提取操作有点像git cherry-pick
一样,执行rebase后
依次将当前(master)的提交cherry-pick到目标分支(develop)上
,然后将在原始分支(master)上的已提交的commit删除。
比如,develop 上的 5 commit 和 master 上的 6 commit 实际上是相同的修改,所以会把 当前(master)分支的 6 commit 删除
举个例子,那上图四、五来说,在master分支上执行rebase操作后,git 对比 develop 和 master 分支的5 commit 是相同的,所以删除 master 的 5 commit ,将 7、8commit依次挪到 develop 的 5 commit后面。
merge OR rebase
那什么时候用merge,什么时候用rebase呢?
再举个例子:
初始状态如下图六所示:
和之前一样的是,develop分支
也是在 (3.added merge.txt file)处
从master分支拉取develop分支。不一样的是两个分支各个commit的时间不同
,develop分支的4提交早于master分支的5提交
,develop分支的6提交晚于master的5提交早于master的7提交。
![在这里插入图片描述](https://img-blog.csdnimg.cn/453a9bb32d384e799d3e1790db84e512.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Y-u5b2T55qE54yr54yr,size_15,color_FFFFFF,t_70,g_se,x_16)
在上图情况下,在master分支的7commit
处,执行git merge develop
,结果如下图所示:
![在这里插入图片描述](https://img-blog.csdnimg.cn/b83d63109041422ab1dd7044dffa871a.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Y-u5b2T55qE54yr54yr,size_13,color_FFFFFF,t_70,g_se,x_16)
执行git rebase develop
,结果如下图所示:
![在这里插入图片描述](https://img-blog.csdnimg.cn/0f58bde871994719a1053eaffca93356.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Y-u5b2T55qE54yr54yr,size_13,color_FFFFFF,t_70,g_se,x_16)
总结
可以看出merge结果能够体现出时间线
,但是rebase会打乱时间线
。
而rebase看起来简洁
,但是merge看起来不太简洁
。
最终结果是都把代码合起来了,所以具体怎么使用这两个命令看项目需要。
普通 Merge
说到合并分支,可能我们最熟悉的操作是这样的:
先切换到目标分支:git checkout master
执行命令:git merge devel
删除旧分支(可以在上面一同做):git branch -D devel
提交到远程分支:git push origin master
假设合并之前的这样的:
![在这里插入图片描述](https://img-blog.csdnimg.cn/68568a870ec14c3b9c1c110ae86e6d81.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Y-u5b2T55qE54yr54yr,size_20,color_FFFFFF,t_70,g_se,x_16)
我们这么上述操作之后,分支的 commit 历史将会是这样的:
![在这里插入图片描述](https://img-blog.csdnimg.cn/9dbf4c7b3cbe49ef84e37e5537efea6d.png)
看上去是一条直直的 commit line
,我们在devel 分支中的 commit
也是一个不差得保留在了 master 中
。但是很多时候,我们并不需要那么多的 commit
,假设你给一个开源项目提交一个 Bug Fixes,然后一个简单的修改因为你的粗心大意 pull request 了十几个 commit 过去
,如果作者给你 merge 了,这就在这个项目的历史长河中增加了十几个 commit
啊,以后的人看 commit history 估计都崩溃了吧;同时,当你 merge 之后
发现有问题,想回滚都麻烦
。
squash merge
在使用 git 的过程中,可能你遇到过想要合并多个 commit 为一个
,然后很多人会告诉你用 git commit --amend
,然后你发现里面有你的多个 commit 历史
,你可以通过 pick 选择
,squash 合并
等等。同样得,merge 的时候也可以这么干,你只需要这么简单的两步:
切换到目标分支:git checkout master
以 squash 的形式 merge:git merge --squash devel
你会发现,在 master 分支上居然有未提交的修改
,然后你就需要在 master 上主动提交了修改
,注意,这里是你 commit 的,也就是改变了 commit 的 author
。结果是这样的:
![在这里插入图片描述](https://img-blog.csdnimg.cn/c256dbddbfe845cf8be9d2aea2a77bc3.png)
比前面普通的 merge 来说,我们只有一个 commit
了,不管在分支中 commit 了多少,这里都只有一个
。
rebase merge
下面的 master 分支,最新的 P 记录没有 commit
![在这里插入图片描述](https://img-blog.csdnimg.cn/c15156b9cba4415b9cb926dc35ab3674.png)
我们既想合并 commits
,又想保留作者的信息
,那么有没有什么好办法呢?肯定是有的啦,这个时候我们可以尝试一下 rebase,操作步骤是这样的:
先切换到 devel 分支:git checkout devel
变基:git rebase -i master
切换回目标分支:git checkout master
合并: git merge devel
这里完成了第二步之后我想你应该大概知道发生了什么事了,我们在devel
里面对照 master 进行了变基
,所谓的变基其实就是找到两个分支共同的祖先
,然后在当前分支上
合并从共同祖先到现在的所有 commit
,所以我们在第二步的时候会选择怎么处理这些 commit,然后我们就得到了一个从公共commit 到现在的单个 commit
,这个时候别人讲我们这个 commit 合并到 master 也只会在 master 上留下一个 commit 记录,就像这样:
![在这里插入图片描述](https://img-blog.csdnimg.cn/67f9f642b3904bfe8c9af133ebb02227.png)
总结
rebase
可以保持 master 分支整洁
,易于识别 author
,容易回滚
squash
可以保持 master 分支整洁
,但是 master 中 author 都是 maintainer
,而不是原 owner
,容易回滚
普通merge
不能保持 master 分支整洁
,但是保持了所有的 commit history,
回滚时麻烦
操作复杂程度 rebase > squash > 普通merge
回滚复杂度 rebase = squash > 普通merge
fetch
git pull origin dev 相当于以下两句
git fetch origin dev 代码拉到本地版本库
git merge origin/dev 把本地版本库代码合并到工作区
git pull origin dev -r 相当于
git fetch origin dev
git rebase origin/dev
在项目中经常使用git pull来拉取代码,git pull
相当于是git fetch + git merge
,如果此时运行git pull -r
,也就是git pull --rebase
,相当于git fetch + git rebase
平时在拉代码时,还是使用 git pull
,在合并时再考虑使用 git fetch + git merge