合并分支是团队合作开发中常见的操作,这里涉及到两个命令:git merge 和 git rebase

下面来好好说一下git merge和git rebase都是怎样工作的

一、

1、新建一个空目录并初始化为一个git项目

git init # 初始化git项目

在master分支上添加一个文件(readme.txt),并在其中添加内容

git add .  //提交刚添加的内容

git commit -m "c1"

2、创建并切换到dev分支

git checkout -b dev

此时在readme.txt文件添加内容

git add .

git commit -m "c2"

3、切换回master分支,并修改readme.txt文件内容

git checkout master

修改readme.txt文件在第二行增加一些内容:i am in master

git add .

git commit -m "c3"

4、在master分支新增一个文件addfile.js

git add .

git commit -m "c4"

5、切回到dev分支,同样新增addfile.js文件

git add .

git commit -m "c5"

至此,我们来理清一下刚才我们所做的提交:

master: c1--->c3--->c4

dev: c1--->c2--->c5

这时我们分别用两种方式来进行分支合并的操作,观察两种操作导致的结果有什么不同

1、git  merge:

git checkout master

git merge dev

quyangdeMacBook-Pro:testgit quyang$ git merge dev
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Auto-merging addfile.js
CONFLICT (add/add): Merge conflict in addfile.js
Automatic merge failed; fix conflicts and then commit the result.

手动解决两个文件的冲突后

git add .

git commit -m "c6"

git log命令查看带有分支图的历史提交信息:

quyangdeMacBook-Pro:testgit qy$ git log
commit 2d56c742fabc3db93969f9e5e357fc8e8e9e7f54 (HEAD -> master)
Merge: b845628 6dbfc35
Author: qy <qy@123.com.cn>
Date: Thu Apr 25 17:55:06 2019 +0800 c6 commit 6dbfc350483c6115ea22ca162b41eae4e75f6cab (dev)
Author: qy <qy@123.com.cn>
Date: Thu Apr 25 17:52:20 2019 +0800 c5 commit b8456285e2282a7edb9b89c21045484486b25403
Author: qy <qy@123.com.cn>
Date: Thu Apr 25 17:51:21 2019 +0800 c4 commit b58867fd0e2f35f667c2f905125879733be933a9
Author: qy <quyang@123.com.cn>
Date: Thu Apr 25 17:48:54 2019 +0800 c3 commit 38045deffc089318328079257c34ef334db253eb
Author: qy <quyang@123.com.cn>
Date: Thu Apr 25 17:48:24 2019 +0800 c2 commit 0c0b12c0c9bba105e617675db58eddbb115714c1
Author: qy <qy@123.com.cn>
Date: Thu Apr 25 17:47:43 2019 +0800 c1

  

quyangdeMacBook-Pro:testgit quyang$ git log --graph --oneline --decorate

*   2d56c74 (HEAD -> master) c6

|\

| * 6dbfc35 (dev) c5

| * 38045de c2

* | b845628 c4

* | b58867f c3

|/

* 0c0b12c c1

另外补充:

git log --graph --oneline --decorate

--oneline 显示在一行

–graph 选项会绘制一个 ASCII 图像来展示提交历史的分支结构。
–decorate 是用来可以显示出指向提交的指针的名字,也就是 HEAD 指针, feature/test等分支名称,还有远程分支,标签等。

可以看到通过merge合并分支后的历史提交信息和分支图

历史提交:c1--->c2--->c3--->c4--->c5--->c6 采用git merge dev处理提交log是按照时间戳先后顺序的

分支图:比较乱,因为现在我们做的提交比较少,真正开发的时候,分支图会很杂乱繁琐

下面我们来用git rebase 来处理

qy-Pro:testgit qy$ git rebase master

First, rewinding head to replay your work on top of it...
Applying: c2
Using index info to reconstruct a base tree...
M readme.txt
Falling back to patching base and 3-way merge...
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
error: Failed to merge in the changes.
Patch failed at 0001 c2
Use 'git am --show-current-patch' to see the failed patch Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
手动解决完冲突后查看历史提交信息以及分支图
quyangdeMacBook-Pro:testgit qy$ git log
commit ec0fcbe53db0021eec6fb9f76f139197086e5f01 (HEAD -> dev)
Author: qy <qy@123.com.cn>
Date: Thu Apr 25 18:14:59 2019 +0800
c5 commit 4dd6e0918eff806e158714327fd0b5b638d5e5d0
Author: qy <qy@123.com.cn>
Date: Thu Apr 25 18:13:05 2019 +0800 c2 commit 91a30e861cab43ccc86b06e9ac5fba5d81cdbf58 (master)
Author: qy <qy@123.com.cn>
Date: Thu Apr 25 18:14:15 2019 +0800 c4 commit 0ea798038e237ae76364c613a780e27e50311a63
Author: qy <qy@123.com.cn>
Date: Thu Apr 25 18:13:38 2019 +0800 c3 commit 5b3f829baf568ce71e926b9fcc55ae1aa0d88fcf
Author: qy <qy@123.com.cn>
Date: Thu Apr 25 18:12:34 2019 +0800 c1
quyangdeMacBook-Pro:testgit quyang$ git log --oneline --graph --decorate
* ec0fcbe (HEAD -> dev) c5
* 4dd6e09 c2
* 91a30e8 (master) c4
* 0ea7980 c3
* 5b3f829 c1
quyangdeMacBook-Pro:testgit quyang$   

最后切换回master分支:

git checkout master

git merge dev

通过历史提交信息和分支图可以看出:

采用rebase 处理的历史提交信息和分支图:

提交历史:c1--->c3--->c4--->c2--->c5    采用git rebase处理不是按照时间戳顺序的

分支图: 采用rebase处理得到了一个清晰的分支图

git rebase过程相比较git merge合并整合得到的结果没有任何区别,但是通过git rebase衍合能产生一个更为整洁的提交历史。
如果观察一个衍合(git rebase)过的分支的历史提交记录,看起来会更清楚:仿佛所有修改都是在一根线上先后完成的,尽管实际上它们原来是同时并行发生的。

二、

为什么会产生这样不同的效果呢,就要弄懂,git merge和git rebase这两个指令到底分别做了什么?

基点的概念:如上例子中,在master的c1节点上创建了dev分支,这个c1就是dev分支的基点。

首先执行git merge将a分支合并到b分支其实就是将a分支上所有从未被合并到b分支的提交节点(commit)和b分支上的基点(a和b最新同步的提交节点)以后的所有提交节点按照时间戳的顺序整合,会有两种情况:

(1)在上面的例子中(如下图),c1是master和dev分支的共同祖先,当要合并的时候,dev的祖先节点c1已经不是master分支的最新提交节点了,即dev分支从master创建后master又往前走了一些commits(这可能是由于其他的branch已经merge到了master,或者在master上直接做了commit,或者有人在master上cherry-picked了一些commits),这时候git merge dev将dev分支合并到master分支,就是将dev分支上所有commit节点(如下面的c2、c5)和master分支上c1以后的所有commit节点(c3,c4),按照时间戳的顺序进行整合(如下右图),若产生冲突,则需要手动解决冲突后再进行一次提交,这种情况会因为解决冲突多增加一次提交(如下右图中的的c6)。即便不需要解决冲突,这时也会默认产生一个merge commit的历史信息。

  

(2)另一种情况,master分支和dev分支的共同祖先c4之后,master分支没有继续往前走了,而dev分支进行了一些提交,这之后将dev分支合并到master分支的时候,GIT默认地在merge时是执行一个fast-forward的merge策略,git并不会创建一个merge commit而是简单地把master分支标签移动到dev分支tip(最新提交)所指向的commit,合并后如右图。

  

这种情况在历史图谱中无法得知dev分支的起始位置在哪里,并且一旦这个branch被删除,那么从历史上我们再也无法看到任何关于这个开发分支曾经存在的历史渊源。这就用到了--no-ff参数,它可以强制git产生一个真正的merge。

git merge --no-ff dev    //强制git产生一个merge历史信息

接下来看git rebase: 它其实是一个变基操作,改变当前分支的基点(不要拘泥于合并分支的使用,合并分支只是它的众多用法之一)

在b分支上执行git rebase a 其实做的是将指定分支a上的与当前分支b最新一次同步的节点后的commit节点放到当前分支b所有没同步过的commit节点之前。

(1)合并分支时,当情况如上例(如下图):当想要合并的分支dev在创建后,master分支又有了新的提交,导致,dev分支的基点c1已经不是master分支的最新提交。这时在dev分支上运用git rebase master,可以将master分支上与dev分支的最新一次同步节点c1以后的commit节点c3和c4放到dev分支上c1之后的commit节点后的c2、c5之前,这样在dev分支上的历史就变成了c1->c3->c4->c2->c5,从而使dev合并到master变成了可以使用fast-forward模式的合并。当然如果你想要保留分支信息,则可以使用--no-ff参数,来强制git产生一个merge commit

(2)同步新的提交到一个古老的分支:有时候你创建一个feature分支开始工作后可能很长时间没有时间再做这个feature开发,当你回来时,你的feature分支就会缺失很多master上的bugfix或者一些其他的feature。在这种个情况下,我们先假设除了你没有其他人在这个分支上工作,那么你可以rebase你的feature分支:

git rebase [basebranch] [topicbranch] 注意这时git rebase的参数顺序,第一个为基分支,第二个为要变基的分支

git rebase origin/master feature    topicbranch不写默认为当前分支

git rebase origin/test_qy   当前分支的远程分支比本地分支超前了提交的时候,可以这样用

如果那个feature分支已经被push到remote了的话,你必须使用-f参数来push它,以便你覆盖这个分支的commits历史,这时覆盖这个branch历史也无所谓,因为历史的所有commits都已经相应重新生成了!!。(一个分支的历史由分支的起始commit和头tip commit来描述.有一点需要注意:一旦我们做一次rebase后,那么这个分支上的所有commit由于这次变基,其commit HASH都会改变!!)另外需要注意我们只能对private分支做这个rebase并且git push --force操作!!

三、常用的git pull命令的隐含操作

1. 将本地库和远程仓库做一次网络同步。这实际上就是一次git fetch,也只有这次我们需要有和远程仓库的网络连接;

2.默认的,一个git merge操作(将remote tracked branch(远程分支) merge到我们的local trakcing branch(本地分支),比如说orgin/featurex->featureX)

为了便于演示,我们假设如果我当前在feature分支上,而它的remote track branch是origin/feature,那么一个git pull操作就等效于:

1. git fetch;2.git merge origin/feature

我们就非常普遍地碰到下面的障碍:在我们的最近一次同步(使用git pull)和我们需要发布local history(要使用git push)的这个时刻,另外一个同事已经分享了他们的工作到中央库上面,所以remote branch(比如说origin/feature分支上) 是比我们的本地拷贝要更新一些。

这样,git push的时候,git就会拒绝接受(因为如果接受就会丢失历史)

(feature u+3) $ git push
To /tmp/remote
! [rejected] feature -> feature (fetch first)
error: failed to push some refs to '/tmp/remote'
hint: Updates were rejected because the remote contains work
hint: that you do not have locally. This is usually caused by
hint: another repository pushing to the same ref. You may want
hint: to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help'
hint: for details.
(feature u+3) $

 以上我们可以按照git的提示直接使用git pull,但其实这样也不是完全合适,可以使用git pull --rebase来明确要求git,但是这也不是一个很可靠的解决方案,因为这需要我们在git pull操作时时时保持警惕,但这往往并不太可能,因为只要是人就容易犯错误。 

git总结三、关于分支下——团队合作中最重要的合并分支的更多相关文章

  1. GIT 分支管理:创建与合并分支、解决合并冲突

    分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN. 如果两个平行宇宙互不干扰,那对现在的你也没啥影响.不过,在某个时间点,两个平行宇宙合并 ...

  2. Git 分支的一些特殊的使用方式:Bug分支/feature分支/储存现场/

    参考链接:https://www.liaoxuefeng.com/wiki/896043488029600/900388704535136 一般都与dev分支进行合并 Bug分支 Bug分支也是一个分 ...

  3. [GitHub]第四讲:合并分支

    本地两个分支合并 先从最简单的一种情况着手.现在项目只有一个 master 分支,我来新建一个 idea 分支,实现自己的想法,commit 一下.那现在仓库内的情况就是这样的 这个是前面已经见过的情 ...

  4. git 在某个分支下创建新分支

    首先要强调一个观念,那就是在某个分支A下创建新的分支B,是指使用A分支下的代码,并不是A/B这样的层级结构. 比如,我想要在非主分支dev 下面创建子分支dev_dev >>>1.创 ...

  5. git之合并分支(git merge)------(三)

    最近几天写小demo,总是自己拉取他人的代码,然后创建分支,在自己的分支上进行修改,然后提交到自己的分支,具体的这一步,我就不多讲了,因为在我的博客“工作中常用的Git操作”中有详细的介绍,今天主要讲 ...

  6. [Git01]Pro Git 第三章 分支 读书笔记

    [git]分支   Git 的分支模型称为“必杀技特性”,而正是因为它,将 Git 从版本控制系统家族里区分出来. Git 有何特别之处呢?Git 的分支可谓是难以置信的轻量级,它的新建操作几乎可以在 ...

  7. 从零开始使用git第三篇:git撤销操作、分支操作和常见冲突

    从零开始使用git 第三篇:git撤销操作.分支操作和常见冲突 第一篇:从零开始使用git第一篇:下载安装配置 第二篇:从零开始使用git第二篇:git实践操作 第三篇:从零开始使用git第三篇:gi ...

  8. Git速成学习第三课:创建与合并分支

    本来第三课想记录一下远程仓库的创建与克隆0.0但是想了想还是不写了. 这里写一下分支管理中的创建与合并. Git速成学习笔记整理于廖雪峰老师的官网网站:https://www.liaoxuefeng. ...

  9. git学习(三) git的分支操作

    git的分支操作 软件项目中启动一套单独的开发线的方法,可以很好的避免版本兼容开发的问题,避免不同版本之间的相互影响,封装一个开发阶段,解决bug的时候新建分支,用于对该bug的研究: git中跟分支 ...

随机推荐

  1. 一套代码小程序&Web&Native运行的探索01

    前言 前面我们对微信小程序进行了研究:[微信小程序项目实践总结]30分钟从陌生到熟悉 并且用小程序翻写了之前一个demo:[组件化开发]前端进阶篇之如何编写可维护可升级的代码 之前一直在跟业务方打交道 ...

  2. 自定义Visual Studio.net Extensions 开发符合ABP vnext框架代码生成插件[附源码]

    介绍 我很早之前一直在做mvc5 scaffolder的开发功能做的已经非常完善,使用代码对mvc5的项目开发效率确实能成倍的提高,就算是刚进团队的新成员也能很快上手,如果你感兴趣 可以参考 http ...

  3. electron开发客户端注意事项(兼开源个人知识管理工具“想学吗”)

    窗口间通信的问题 electron窗口通信比nwjs要麻烦的多 electron分主进程和渲染进程,渲染进程又分主窗口的渲染进程和子窗口的渲染进程 主窗口的渲染进程给子窗口的渲染进程发消息 subWi ...

  4. k8s滚动更新(六)--技术流ken

    实践 滚动更新是一次只更新一小部分副本,成功后,再更新更多的副本,最终完成所有副本的更新.滚动更新的最大的好处是零停机,整个更新过程始终有副本在运行,从而保证了业务的连续性. 下面我们部署三副本应用, ...

  5. 面试被问烂的 Spring IOC(求求你别再问了)

    广义的 IOC IoC(Inversion of Control) 控制反转,即"不用打电话过来,我们会打给你". 两种实现: 依赖查找(DL)和依赖注入(DI). IOC 和 D ...

  6. Eureka的工作原理以及它与ZooKeeper的区别

    1.Eureka 简介: Eureka 是 Netflix 出品的用于实现服务注册和发现的工具. Spring Cloud 集成了 Eureka,并提供了开箱即用的支持.其中, Eureka 又可细分 ...

  7. .net MVC +EF+VUE做回合制游戏(二)

    Emmm,游戏中的属性购买页面 话不多说先上代码 <form id="vue" action="/ltgdGame.Web/Main/Index" met ...

  8. 对于jQuery的了解

    1.了解jQuery与JavaScript的区别 css --外貌特征Html --躯干,骨架js --运动神经 jQuery就是对JavaScript的一个拓展,封装,就是让JavaScript更好 ...

  9. JAVA IO流编程 实现文件的写入、写出以及拷贝

    一.流的概念 流:数据在数据源(文件)和程序(内存)之间经历的路径. 输入流:数据从数据源(文件)到程序(内存)的路径. 输出流:数据从程序(内存)到数据源(文件)的路径. 以内存为参照,如果数据向内 ...

  10. java基础-String不可变的好处

    一.java内部String类的实现: java 8: public final class String implements java.io.Serializable, Comparable< ...