git merge 与 git rebase 的对比分析

目录

相关概念

  以下概念和示例参考http://blog.csdn.net/wh_19910525/article/details/7554489

背景

假设现在基于远程分支”origin”,创建一个叫”mywork”的分支。
$ git checkout -b mywork origin
假设远程分支”origin”已经有了2个提交,那么新创建的mywork分支在现在未修改的状态下是和origin具有相同指向的。如图

现在我们在这个分支做一些修改,然后生成两个提交(commit):

1
2
3
4
5
$ vi file.txt
$ git commit
$ vi otherfile.txt
$ git commit
...

但是与此同时,有些人也在”origin”分支上做了一些修改并且做了提交了. 这就意味着”origin”和”mywork”这两个分支各自”前进”了,它们之间”分叉”了。

在这里,你可以用”pull”命令把”origin”分支上的修改拉下来并且和你的修改合并; 结果看起来就像一个新的”合并的提交”(merge commit)。而具体合并的方式,则有以下要说的git merge 与 git rebase两种。

git merge

1
2
# 将b分支合并到当前分支
git merge b

  git merge有很多参数,可通过git merge --help查看其命令。关于其详细使用,可参考http://blog.csdn.net/hudashi/article/details/7664382
  经过merge合并的分支示意如下:
  

git rebase

如果想让”mywork”分支历史看起来像没有经过任何合并一样(没有上边的“diamond shape”),也许可以用 git rebase:

1
2
# 将b分支合并到当前分支
git rebase b

1
2
$ git checkout mywork
$ git rebase origin

这些命令会把你的”mywork”分支里的每个提交(commit)取消掉,并且把它们临时 保存为补丁(patch)(这些补丁放到”.git/rebase”目录中),然后把”mywork”分支更新 为最新的”origin”分支,最后把保存的这些补丁应用到”mywork”分支上。

当’mywork’分支更新之后,它会指向这些新创建的提交(commit),而那些老的提交会被丢弃。 如果运行垃圾收集命令(pruning garbage collection), 这些被丢弃的提交就会删除.

在rebase的过程中,也许会出现冲突(conflict)。在这种情况,Git会停止rebase并会让你去解决冲突;在解决完冲突后,用”git-add”命令去更新这些内容的索引(index), 然后,你无需执行 git-commit,只要执行:
$ git rebase --continue
git会继续应用(apply)余下的补丁。
在任何时候,你可以用–abort参数来终止rebase的行动,并且”mywork” 分支会回到rebase开始前的状态。
$ git rebase --abort

git merge 与 git rebase的区别和适用场景

  • 区别

当我们使用Git log来参看commit时,其commit的顺序也有所不同。
假设C3提交于9:00AM,C5提交于10:00AM,C4提交于11:00AM,C6提交于12:00AM,
对于使用git merge来合并所看到的commit的顺序(从新到旧)是:C7 ,C6,C4,C5,C3,C2,C1
对于使用git rebase来合并所看到的commit的顺序(从新到旧)是:C7 ,C6‘,C5’,C4,C3,C2,C1
因为C6’提交只是C6提交的克隆,C5’提交只是C5提交的克隆,从用户的角度看使用git rebase来合并后所看到的commit的顺序(从新到旧)是:C7 ,C6,C5,C4,C3,C2,C1

  对于两个分支而言,rebase和merge没有区别,但是rebase更干净,因为log hisitory是线性的,但commit不一定按日期先后排,而是local commit总在后面,merge之后history变得比较复杂,但是commit按日期排序
  更多地,stackoverflow上对两者的区别有很好的回答:
What’s the difference between ‘git merge’ and ‘git rebase’?


实际测试

git merge的过程

  根据以上概念和示例,做如下测试:
1.新建文件夹并初始化为git仓库

2.默认是master分支,先在这个分支上做一次修改并commit,否则git不会真正创建master分支(git branch -a命令结果为空)


使用git log查看历史,只有一次提交,符合预期。

3.切换分支dev,在dev分支上对同一个文件进行修改(制造冲突的地方,观察合并效果)后commit


使用git log查看历史,有2次提交,一次是master分支的,一次是dev分支自己的,符合预期。

4.切换回master分支,在master分支上对同一个文件进行第二次修改后commit。


使用git log查看历史,有2次master上的提交,符合预期。

5.在master分支上合并(git merge)dev的内容,会出现冲突,如下所示

6.解决冲突后,在master上再次commit

7.使用git log观察历史版本情况

可以看出,将dev分支合并过来以后,dev分支上的历史记录也是被合并过来了的。并且,所有commit记录的顺序严格遵守时间先后。

git rebase的过程

1-4地步骤与git merge中的相同,都是为了构造两个分支并模拟冲突情况,不再重复(但是是创建的全新文件夹,内容上与上面没有重复),下面从第五步合并使用git rebase开始。

5..在master分支上合并(git rebase)dev的内容,会出现冲突,如下所示

可以看到提示我们由于冲突rebase过程暂时停止,并且patch文件保存在.git\rebase-apply,查看其内容如下:

1
2
3
4
5
6
7
diff --git a/test.txt b/test.txt
index 3dd79bb0b2a0e259cfac50c354f16a537c8217b9..d9180eaac8dd0a81ba9cad1e08a69a0ebd0d6646 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1 @@
-master:edit-1
+master:edit-1///master:edit-2

根据提示在冲突解决完成后使用git rebase –continue继续合并过程

6.手动解决冲突后使用git rebase –continue继续合并

发现提示我们在解决完成后要使用git add命令将其标记为解决。

7.git add后继续git rebase –continue

8.使用git log查看历史

可以看到,在文件操作完全相同的情况下,与git merge的历史相比,所合并的dev分支只保留了一次在dev分支上最新的提交记录(将其合并进master分支,由于本例在dev上只commit了一次,没有明显看出)。这样,提交记录就是线性的了。并且,我们可以明显看到,使用git rebase进行合并的时候不会产生新的commit(对应git merge中的那次“resolve conflict”)