• 欢迎访问 winrains 的个人网站!
  • 本网站主要从互联网整理和收集了与Java、网络安全、Linux等技术相关的文章,供学习和研究使用。如有侵权,请留言告知,谢谢!

git操作(6):rebase

Git winrains 来源:veedrin 1年前 (2019-08-30) 62次浏览

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历史变的冗长。
我是一个极简主义者,所以我支持首选变基。

作者:veedrin

来源:https://github.com/veedrin/horseshoe/blob/master/git/rebase.md


版权声明:文末如注明作者和来源,则表示本文系转载,版权为原作者所有 | 本文如有侵权,请及时联系,承诺在收到消息后第一时间删除 | 如转载本文,请注明原文链接。
喜欢 (1)