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

合并分支是团队合作开发中常见的操作,这里涉及到两个命令: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操作时时时保持警惕,但这往往并不太可能,因为只要是人就容易犯错误。 

 

posted @ 2019-04-29 15:01  悠扬小曲儿  阅读(1009)  评论(0编辑  收藏  举报