git rebase

rebase 这个命令正式工作中基本上没有用过,只是学习时曾经写过 Demo,但具体指令的含义不是太理解,总觉得没有 merge 来得有掌控感,而且过去使用代码出过问题,所以一直知道但没去用它。

比如平时在 develop 分支上开发,因为想做什么试验临时开了个 feature-test 分支,如果没有切到 develop 再继续做事,那么 merge 也就是一个快进,没什么影响,如果 develop 又提交,再 merge 那么在提交历史上有 merge feature-test 之类的记录。虽然先 merge 后也可以通过交互式的重写历史,但那样更麻烦。而 rebase 本就是为了解决这种情况而存在的,所以还是再去看看。大概读的次数多了,这次再看感觉一下子豁然开朗明白了,如同当年初读《错误》这首诗觉得什么东西,连个韵都不搭,一点美感都无,但读的次数多了,某一次你顿悟了,只觉得如此婉约如此之美。

记得过去看《Pro Git》中文译名叫“衍合”的,等到了第二版中文译名变成“变基”了,其实“衍合”叫惯了,索性不说中文名了,直接叫 rebase 吧。

git rebase [目标分支]

假设有两个分支:dev 和 test,并且当前处于 test 分支,执行 git rebase dev 就是找到这两者共同的父提交 A,把 test 在这之后的操作(C、D)在 dev 上重做,这样两个 test 分支分叉开来的链路就没了,rebase 后产生两个新提交 C' 和 D',C' 的父提交是 B,test 指向 D'。dev 分支指针不变,仍然指向 B。

最后 HEAD 指针仍然指向 test,要更新 dev,需要切换然后 merge 快进。

绘图1.png

绘图2.png

创建文件夹,新建 test.txt 测试。

$ git init
$ git add test.txt
$ git commit -m 'master commit' # 相当于 A
$ git checkout -b dev

修改文本内容为:

branch dev update
$ git commit -a -m 'dev commit' # 相当于 B
$ git checkout master
$ git checkout -b test

连续修改两次文本,并提交两次,结果文本内容为:

branch test update 1
branch test update 2
$ git log --pretty=oneline
f5a18e72c93108a29a59993380d76d02f8819439 (HEAD -> test) test commit 2   # 相当于 D
422c92b2c4869927ed9bb11a6d35b903480d7f0b test commit 1  # 相当于 C
1835294189dc0724dc4fff8a9eb2963da1411810 (master) master commit   # 相当于 A

$ git branch
  dev
  master
* test  # 当前在 test 分支

$ git rebase dev
First, rewinding head to replay your work on top of it...
Applying: test commit 1
error: Failed to merge in the changes.
Using index info to reconstruct a base tree...
M       test.txt
Falling back to patching base and 3-way merge...
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Patch failed at 0001 test commit 1
The copy of the patch that failed is found in: .git/rebase-apply/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".

有冲突,按提示解决,打开文本:

<<<<<<< HEAD
branch dev update
=======
branch test update 1
>>>>>>> test commit 1

修改成:

branch dev update
branch test update 1
$ git add test.txt
$ git rebase --continue
Applying: test commit 1
Applying: test commit 2
error: Failed to merge in the changes.
Using index info to reconstruct a base tree...
M       test.txt
Falling back to patching base and 3-way merge...
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Patch failed at 0002 test commit 2
The copy of the patch that failed is found in: .git/rebase-apply/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".

又冲突,现在文本为:

<<<<<<< HEAD
branch dev update
branch test update 1
=======
branch test update 1
branch test update 2
>>>>>>> test commit 2

两个分支文本第一行,第一次冲突解决,由于 test 分支第二次提交时第一行还是原来的,所以又冲突,所以如果要被 rebase 的分支如果有多个提交历史,需要多次 rebase,可能在冲突时出现重复的内容,也不敢在第一次冲突就只保留 dev 的内容,这样代码很多时得小心。修改文本为:

branch dev update
branch test update 1
branch test update 2
$ git add test.txt
$ git rebase --continue
Applying: test commit 2

$ git log --graph
* commit f7971b5e7fa18039bcef610496fe202ce16f43f0 (HEAD -> test)
| Author: mazhijun <zhijun.ma@chelaile.net.cn>
| Date:   Thu Mar 8 13:09:32 2018 +0800
|
|     test commit 2
|
* commit 0df6161052eb881e5d89b2ea1afce6194b47884e
| Author: mazhijun <zhijun.ma@chelaile.net.cn>
| Date:   Thu Mar 8 13:09:10 2018 +0800
|
|     test commit 1
|
* commit df715abd4342208f238ff4e58c559c0e7ebb352c (dev)
| Author: mazhijun <zhijun.ma@chelaile.net.cn>
| Date:   Thu Mar 8 12:16:56 2018 +0800
|
|     dev commit
|
* commit 1835294189dc0724dc4fff8a9eb2963da1411810 (master)
  Author: mazhijun <zhijun.ma@chelaile.net.cn>
  Date:   Thu Mar 8 12:13:58 2018 +0800

      master commit

对比原来的 log,可见 test 分支过去的两次提交历史没了,出现了两次新的提交,并且 HEAD -> test

$ git checkout dev

$ git merge test
Updating df715ab..f7971b5
Fast-forward        # 直接快进
 test.txt | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

$ git log --pretty=oneline
f7971b5e7fa18039bcef610496fe202ce16f43f0 (HEAD -> dev, test) test commit 2  
0df6161052eb881e5d89b2ea1afce6194b47884e test commit 1
df715abd4342208f238ff4e58c559c0e7ebb352c dev commit
1835294189dc0724dc4fff8a9eb2963da1411810 (master) master commit

HEAD -> dev, test,因为它们现在在一个地方,同时指着 D'。

git rebase [目标分支] [被 rebase 分支]

比如 git rebase dev test 就是将 test 分支 rebase 到 dev 里,和上面指令的区别是:执行这条指令不需要当前处于 test 分支。

绘图3.png

先通过 reset 将 dev 分支指针移到 B 上面,将 test 分支指针移到 A 上面,并且保留代码:

$ git reset --hard HEAD~2
$ git checkout test
$ git reset --soft HEAD~3

绘图4.png

然后提交,这样 dev 和 test 有个分叉了:

$ git commit -m 'test commit'

绘图5.png

文本内容是:

branch dev update
branch test update 1
branch test update 2
$ git checkout dev    # 当前不在 test 分支
$ git rebase dev test
First, rewinding head to replay your work on top of it...
Applying: test commit
error: Failed to merge in the changes.
Using index info to reconstruct a base tree...
M       test.txt
Falling back to patching base and 3-way merge...
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Patch failed at 0001 test commit
The copy of the patch that failed is found in: .git/rebase-apply/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".

文本为:

<<<<<<< HEAD
branch dev update
=======
branch dev update
branch test update 1
branch test update 2
>>>>>>> test commit

修改为

branch dev update
branch test update 1
branch test update 2
$ git add test.txt
$ git rebase --continue
Applying: test commit

在 dev 分支执行这个指令,rebase 之后自动切换到了 test 分支,并且有了一个新提交 E'。

$ git branch
  dev
  master
* test

$ git log --pretty=oneline
c57c2d1f49f0f97b5ed4a3145fba786d7bdafec4 (HEAD -> test) test commit
df715abd4342208f238ff4e58c559c0e7ebb352c (dev) dev commit
1835294189dc0724dc4fff8a9eb2963da1411810 (master) master commit

绘图6.png

如果不是在 dev 分支执行指令,而是在一个完全不相干的分支执行会怎么样?

$ git reset --soft HEAD~2 # test 指针到了 A
$ git commit -m 'test commit'
$ git checkout master
$ git checkout -b feature

修改文本内容:

branch feature update
$ git commit -a -m 'feature commit'

现在的状态是:

绘图7.png

$ git rebase dev test
First, rewinding head to replay your work on top of it...
Applying: test commit
error: Failed to merge in the changes.
Using index info to reconstruct a base tree...
M       test.txt
Falling back to patching base and 3-way merge...
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Patch failed at 0001 test commit
The copy of the patch that failed is found in: .git/rebase-apply/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".

文本内容为:

<<<<<<< HEAD
branch dev update
=======
branch dev update
branch test update 1
branch test update 2
>>>>>>> test commit

修改为:

branch dev update
branch test update 1
branch test update 2
$ git add test.txt
$ git rebase --continue
Applying: test commit
$ git branch
  dev
  feature
  master
* test

可见当前分支也自动切换到了 test,说明这条指令由于已经指明了谁要 rebase 到谁,和当前在什么分支完全无关。

绘图8.png

git rebase --onto [目标分支] [排除分支] [被 rebase 分支]

切换到 dev 分支,此时 B 的内容为:

branch dev update

修改内容并提交:

branch dev update second
hahahaha
$ git commit -a -m 'dev commit 2'

绘图9.png

现在想将 dev 分支内容 rebase 进 feature,但又想排除也在 test 分支的内容,即希望将在 dev 而不在 test 的提交,即 H 的内容在 feature 重做,希望 feature 分支文本内容变成:

branch feature update
branch dev update second
hahahaha

而不要有 B 的内容 branch dev update,这应该也和当前所处分支没关系,直接在 dev 上执行:

$ git rebase --onto feature test dev
First, rewinding head to replay your work on top of it...
Applying: dev commit 2
error: Failed to merge in the changes.
Using index info to reconstruct a base tree...
M       test.txt
Falling back to patching base and 3-way merge...
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Patch failed at 0001 dev commit 2
The copy of the patch that failed is found in: .git/rebase-apply/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".

解决冲突:

<<<<<<< HEAD
branch feature update
=======
branch dev update second
hahahaha
>>>>>>> dev commit 2

变成:

branch feature update
branch dev update second
hahahaha

现在的状态是:

绘图10.png

一个应用

公司的电脑编译 Android 项目,七八分钟甚而要十分钟才能成功,而且一编译内存就占满,电脑卡的什么都做不了,许多时间都耗在编译上了,所以接入了阿里的 Freeline 框架,但是并不想把这种引入放到协作的分支上,别人用 Mac 没那么慢,而且不允许随便引入框架,所以自己本地单独建立分支引入 Freeline,比如 feature-freeline 分支。

这样在协作分支 develop 有更新修改,我每次都 merge 到 feature-freeline 分支,这样将来比较两个分支,feature-freeline 有大量的合并指向两个父提交的提交

绘图11.png

而假设使用 rebase,就可以保证每次 feature-freeline 指向的提交紧跟在 develop 提交的后面,rebase 后 develop 分支不要 merge,就在原来的地方开发,这样分叉开来,需要编译时再次 rebase。

绘图12.png

PS: 这只能做一个例子,实际上后来发现也不能每次要编译了,都得先提交再 rebase 啊。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏TechBox

3分钟实现iOS语言本地化/国际化(图文详解)前言配置需要国际化的语言(一)应用名称本地化/国际化(二)代码中字符串的本地化(三)多人开发情况下的字符串本地化(四)图片本地化(五)查看/切换本地语言

2692
来自专栏Fish

利用IDEA查看和修改spark源码

经过了两天的摸索,算是初步学会了如何查看和修改spark源码。 大坑 对,这个要写在最前面,那就是注意你的scalaSDK版本!!!!不同的Spark版本支持的...

4219
来自专栏Felix的技术分享

《一个操作系统的实现》笔记(5)--内核雏形

2024
来自专栏码农阿宇

国内开源社区巨作AspectCore-Framework入门

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技...

2811
来自专栏岑玉海

hbase源码系列(十)HLog与日志恢复

HLog概述 hbase在写入数据之前会先写入MemStore,成功了再写入HLog,当MemStore的数据丢失的时候,还可以用HLog的数据来进行恢复,下面...

4038
来自专栏世界第一语言是java

网站调用支付宝进行支付-Java后台调用支付宝支付

6993
来自专栏iOS技术

YYCache 源码剖析:一览亮点

YYCache 作为当下 iOS 圈最流行的缓存框架,有着优越的性能和绝佳的设计。笔者花了些时间对其“解剖”了一番,发现了很多有意思的东西,所以写下本文分享一下...

4765
来自专栏葡萄城控件技术团队

七天学会ASP.NET MVC (三)——ASP.Net MVC 数据处理

? 第三天我们将学习Asp.Net中数据处理功能,了解数据访问层,EF,以及EF中常用的代码实现方式,创建数据访问层和数据入口,处理Post数据,以及数据验...

34510
来自专栏我和PYTHON有个约会

Django来敲门~第一部分【5.1.项目配置settings.py详解】

我们创建好了一个Python项目(mysite/)之后,需要在项目中添加模块应用(polls/),在模块应用中添加处理功能逻辑,如添加模块中的视图处理函数(po...

1063
来自专栏IT笔记

JAVAWEB开发的微信公众号H5支付

一切需求都是来源于业务需要,前一阵子做了微信扫码支付,的确相对PC用户来说方便了很多。但是如果手机下单,你总不能让用户自己扫自己吧?查看了一下文档,微信还是支持...

1.7K5

扫码关注云+社区

领取腾讯云代金券