首页你好哇,世界马蹄疾
Git专题:rebase
马蹄疾
2019-01-07 20:15:19

git merge命令会生成一个新的合并commit。如果你有强迫症,不喜欢这个新的合并commit,git也有更加清爽的方案可以满足你,它就是git rebase命令。

git就是哆啦A梦的口袋。

rebase翻译过来是变基。意思就是将所有要合并进来的commit在新的基础上重新提交一次。

基础用法

git rebase <branch>会计算当前分支和目标分支的最近共同祖先,然后将最近共同祖先与当前分支之间的所有commit都变基到目标分支上,使得提交历史变成一条直线。

C0 -- C1 -- C2 -- C3(master) \ C4 -- C5 -- C6(HEAD -> dev)

mergerebase后跟的分支名是不一样的。合并是合并进来,变基是变基过去,你们感受一下。

$ git rebase master First, rewinding head to replay your work on top of it... Applying: C4.md Applying: C5.md Applying: C6.md
C0 -- C1 -- C2 -- C3(master) -- C4' -- C5' -- C6'(HEAD -> dev) \ C4 -- C5 -- C6

现在最近共同祖先与当前分支之间的所有commit都被复制到master分支之后,并且将HEAD指针与当前分支指针切换过去。这招移花接木玩的很溜啊,如果你置身其中根本分不出区别。

原来的commit还在吗?还在,如果你记得它的commit校验和,仍然可以切换过去,git会提示你当前处于detached HEAD状态下。只不过没有任何分支指针指向它们,它们已经被抛弃了,剩余的时光就是等待git垃圾回收命令清理它们。

好在,还有人记得它们,不是么?

git rebase完并没有结束,因为我变基的目标分支是master,而当前分支是dev。我需要切换到master分支上,然后再合并一次。

$ git checkout master
$ git merge dev

诶,说来说去,还是要合并啊?

别急,这种合并是Fast forward的,并不会生成一个新的合并commit。

如果我要变基的本体分支不是当前分支行不行?也是可以的。

$ git rebase master dev

你在任何一个分支上,这种写法都可以将dev分支变基到master分支上,变基完成当前分支会变成dev分支。

裁剪commit变基

变基有点像基因编辑,git有更精确的工具达到你想要的效果。

有了精确的基因编辑技术,妈妈再也不用担心你长的啦。

C0 -- C1 -- C2 -- C3(master) \ C4 -- C5 -- C6(dev) \ C7 -- C8(HEAD -> hotfix)
$ git rebase --onto master dev hotfix First, rewinding head to replay your work on top of it... Applying: C7.md Applying: C8.md
C0 -- C1 -- C2 -- C3(master) -- C7' -- C8'(HEAD -> hotfix) \ C4 -- C5 -- C6(dev) \ C7 -- C8

--onto参数就是那把基因编辑的剪刀。

它会把hotfix分支hotfix分支与dev分支的最近共同祖先之间的commit裁剪下来,复制到目标基础点上。注意,所谓的之间指的都是不包括最近共同祖先commit的范围,比如这里就不会复制C4commit。

$ git rebase --onto master dev First, rewinding head to replay your work on top of it... Applying: C7.md Applying: C8.md

如果--onto后面只写两个分支(或者commit)名,第三个分支(或者commit)默认就是HEAD指针指向的分支(或者commit)。

变基冲突解决

变基也会存在冲突的情况,我们看看冲突怎么解决。

C0 -- C1 -- C2(HEAD -> master) \ C3 -- C4(dev)
$ git rebase master dev First, rewinding head to replay your work on top of it... Applying: c.md Applying: a.md add banana Using index info to reconstruct a base tree... M a.md Falling back to patching base and 3-way merge... Auto-merging a.md CONFLICT (content): Merge conflict in a.md error: Failed to merge in the changes. Patch failed at 0002 a.md dev 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".

C2和C4同时修改了a.md的某一行,引发冲突。git已经给我们提示了,大体上和merge的操作一致。

我们可以手动解决冲突,然后执行git addgit rebase --continue来完成变基。

如果你不想覆盖目标commit的内容,也可以跳过这个commit,执行git rebase --skip。但是注意,这会跳过有冲突的整个commit,而不仅仅是有冲突的部分。

后悔药也是有的,执行git rebase --abort,干脆就放弃变基了。

cherry-pick

git rebase --onto命令可以裁剪分支以变基到另一个分支上。但它依然是挑选连续的一段commit,只是允许你指定头和尾罢了。

别急,git cherry-pick命令虽然是一个独立的git命令,它的效果却还是变基,而且是commit级别的变基。

git cherry-pick命令可以挑选任意commit变基到目标commit上。你负责挑,它负责基。

用法

只需要在git cherry-pick命令后跟commit校验和,就可以将它应用到目标commit上。

C0 -- C1 -- C2(HEAD -> master) \ C3 -- C4 -- C5(dev) \ C6 -- C7(hotfix)

将当前分支切换到master分支。

$ git cherry-pick C6 [master dc342e0] c6 Date: Mon Dec 24 09:13:57 2018 +0800 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 c6.md
C0 -- C1 -- C2 -- C6'(HEAD -> master) \ C3 -- C4 -- C5(dev) \ C6 -- C7(hotfix)

C6commit就按原样重新提交到master分支上了。cherry-pick并不会修改原有的commit。

同时挑选多个commit也很方便,往后面叠加就行。

$ git cherry-pick C4 C7 [master ab1e7c7] c4 Date: Mon Dec 24 09:12:58 2018 +0800 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 c4.md [master 161d993] c7 Date: Mon Dec 24 09:14:12 2018 +0800 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 c7.md
C0 -- C1 -- C2 -- C4' -- C7'(HEAD -> master) \ C3 -- C4 -- C5(dev) \ C6 -- C7(hotfix)

如果这多个commit正好是连续的呢?

$ git cherry-pick C3...C7 [master d16c42e] c4 Date: Mon Dec 24 09:12:58 2018 +0800 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 c4.md [master d16c42e] c6 Date: Mon Dec 24 09:13:57 2018 +0800 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 c6.md [master a4d5976] c7 Date: Mon Dec 24 09:14:12 2018 +0800 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 c7.md
C0 -- C1 -- C2 -- C4' -- C6' -- C7'(HEAD -> master) \ C3 -- C4 -- C5(dev) \ C6 -- C7(hotfix)

需要注意,git所谓的从某某开始,一般都是不包括某某的,这里也一样。

有没有发现操作连续commit的git cherry-pickgit rebase的功能已经非常接近了?所以呀,git cherry-pick也是变基,只不过一边变基一边喂樱桃给你吃。

冲突

git各种命令解决冲突的方法都大同小异。

C0 -- C1(HEAD -> master) \ C2(dev)
$ git cherry-pick C2 error: could not apply 051c24c... banana hint: after resolving the conflicts, mark the corrected paths hint: with 'git add <paths>' or 'git rm <paths>' hint: and commit the result with 'git commit'

手动解决冲突,执行git add命令然后执行git cherry-pick --continue命令。

如果被唬住了想还原,执行git cherry-pick --abort即可。

变基还是合并

这是一个哲学问题。

有一种观点认为,仓库的commit历史应该记录实际发生过什么。所以如果你将一个分支合并进另一个分支,commit历史中就应该有这一次合并的痕迹,因为它是实实在在发生过的。

另一种观点则认为,仓库的commit历史应该记录项目过程中发生过什么。合并不是项目开发本身带来的,它是一种额外的操作,会使commit历史变的冗长。

我是一个极简主义者,所以我支持首选变基。

#工具
@Git
0comments withand markdown
输入 @+用户名+空格 可以AT某人,AT某人时可以按TAB键补全
输入 登记过的邮箱 会自动补全剩余信息,可以登记原账号,也可以修改用户名再登记原账号
先登记后评论,乖
{粤ICP备18059296号}
性别: