Skip to content
On this page

git rebase与git merge

前言

世界上对待任何事物都会有至少2种看法,好的或坏的或好坏折中等。

规范或许就是你眼中的最折中的方案。

而git rebase与git merge,对应的2种看法:

有一种观点认为,仓库的提交历史即是 记录实际发生过什么。 它是针对历史的文档,本身就有价值,不能乱改。 从这个角度看来,改变提交历史是一种亵渎,你使用_谎言_掩盖了实际发生过的事情。 如果由合并产生的提交历史是一团糟怎么办? 既然事实就是如此,那么这些痕迹就应该被保留下来,让后人能够查阅。

另一种观点则正好相反,他们认为提交历史是 项目过程中发生的事。 没人会出版一本书的第一版草稿,软件维护手册也是需要反复修订才能方便使用。 持这一观点的人会使用 rebase 及 filter-branch 等工具来编写故事,怎么方便后来的读者就怎么写。

就像使用resetrevert一样,并不是极端的认为谁谁就一定不能用。此时,脑子是个好东西。

git rebase

我也称之为“变基”,通俗点说就是将修改的提交从一个“同源基础”移植变换到另一个新的“同源基础”上。

变基的2种使用场景

假设我们的git分支为

* master
  name_a
  • 1.简单的分支变基操作

假设我们对分支做了如下操作:

5b6bb3b69f54540035ed8eef

接着做变基操作

# 当前分支
* name_a
  master
git rebase master name_a
git checkout master
git merge name_a

结果如图所示

5b6bbcaaa22b9d0031646613

可以发现,在做变基之后,name_a分支和master分支在同源祖先c0后的提交一模一样了

变基的流程是:

  • 找到同源祖先c0
  • 对比name_a分支与祖先的修改c2,提取暂存到缓存区
  • 将c2分支变基指向c1

变基有冲突时,git会提示你先resolve掉,接着git add/rm file,然后git rebase --continue

First, rewinding head to replay your work on top of it...
Applying: 修改name_a
Using index info to reconstruct a base tree...
M	02.js
.git/rebase-apply/patch:9: trailing whitespace.
222	
warning: 1 line adds whitespace errors.
Falling back to patching base and 3-way merge...
Auto-merging 02.js
CONFLICT (content): Merge conflict in 02.js
error: Failed to merge in the changes.
Patch failed at 0001 修改name_a
The copy of the patch that failed is found in: .git/rebase-apply/patch

master上查看log 5b6bbb15ee920a003b0de529

name_a上查看log 5b6bbb4a67f3560035a12c1e

可以看到分支name_a上和master上的commit记录是一样的。

疑问?

网上的资料说

一个提交仅仅包括很少的属性,比如作者,日期,变动和谁是它的父提交。如果改变其中任何一个信息,就必须创建一个全新的提交。当然,新的提交也会拥有一个新的 hash ID 。

此时c2与c2*虽然为相同的修改,但是由于目标基底不一致了,所以应该为不同的提交。但是我查看commit_id后发现是一样的。作何理解?

  • 2.多分支的变基操作

此时我们需要新增一个分支name_b,分支即为

* master
  name_a
  name_b

5b6bc0e6ee920a003b0e1ee2

修改过程:

  • 我们从master上做了c1修改并提交
  • 同时拉了一个新分支name_a,并做了c2修改
  • 在c2的基础上新拉一个分支name_b
  • name_a继续做修改c3
  • name_b做了c4,c5修改

突然有一天,需要先发布c4, c5修改。像极了你在功能分支develop上新拉分支写新需求的场景有没有?

此时,我们可以使用

git rebase --onto master name_a name_b
git checkout master
git merge name_b

--onto的过程解释为:找到name_b分支,再找到它与name_a分支同源祖先c2之后的修改,即c4,c5,将他们变基到master上,得到的结果如下图:

5b6bc49ea22b9d003164b1d2

分支name_b上查看log 5b6bc3e2a22b9d003164ab54

接着可能又有一天分支name_a的修改可以发布了,需要合并到master,接着我们再使用简单的变基即可

5b6bc593ee920a003b0e4986

master上查看log 5b6bc6849f54540035ee3f35

查看--graph会发现是一条线,很清晰 5b6bc662ee920a003b0e4feb

rebase的适用场景

1 .只适合本地提交记录的清理,不能将已push的记录再做rebase。

例如,你提交了c1,c2并push到了仓库,别人也pull你的c1, c2到本地进行了开发并push了c3,c4,此时如果你回滚掉c2,再做rebase,那将使得你自己得整合冲突,并且提交记录里会出现一模一样的2条。如下图中的c7,c8,就是一模一样的2条提交记录

5b6be84a9f54540035ef7035

2 .当自己本地分支rebase后,在master上准备push前,一定要先git pull --rebase,因为很有可能别人已经push过了,git pull --rebase可以认为是2步:

  • git fetch到缓存区一个分支
  • 在master上git rebase 这个分支

那么你的提交永远变基在最前面。

以上部分参考