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 快进。
创建文件夹,新建 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 dev test
就是将 test 分支 rebase 到 dev 里,和上面指令的区别是:执行这条指令不需要当前处于 test 分支。
先通过 reset 将 dev 分支指针移到 B 上面,将 test 分支指针移到 A 上面,并且保留代码:
$ git reset --hard HEAD~2
$ git checkout test
$ git reset --soft HEAD~3
然后提交,这样 dev 和 test 有个分叉了:
$ git commit -m 'test commit'
文本内容是:
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
如果不是在 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'
现在的状态是:
$ 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 到谁,和当前在什么分支完全无关。
git rebase --onto [目标分支] [排除分支] [被 rebase 分支]切换到 dev 分支,此时 B 的内容为:
branch dev update
修改内容并提交:
branch dev update second
hahahaha
$ git commit -a -m 'dev commit 2'
现在想将 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
现在的状态是:
一个应用公司的电脑编译 Android 项目,七八分钟甚而要十分钟才能成功,而且一编译内存就占满,电脑卡的什么都做不了,许多时间都耗在编译上了,所以接入了阿里的 Freeline 框架,但是并不想把这种引入放到协作的分支上,别人用 Mac 没那么慢,而且不允许随便引入框架,所以自己本地单独建立分支引入 Freeline,比如 feature-freeline 分支。
这样在协作分支 develop 有更新修改,我每次都 merge 到 feature-freeline 分支,这样将来比较两个分支,feature-freeline 有大量的合并指向两个父提交的提交
而假设使用 rebase,就可以保证每次 feature-freeline 指向的提交紧跟在 develop 提交的后面,rebase 后 develop 分支不要 merge,就在原来的地方开发,这样分叉开来,需要编译时再次 rebase。
PS:这只能做一个例子,实际上后来发现也不能每次要编译了,都得先提交再 rebase 啊。
共同学习,写下你的评论
评论加载中...
作者其他优质文章