Git应用详解第九讲:Git cherry-pick与Git rebase

栏目: IT技术 · 发布时间: 6年前

内容简介:前情提要:这一节主要介绍

前言

前情提要: Git应用详解第八讲:Git标签、别名与Git gc

这一节主要介绍 git cherry-pick 与 git rebase 的原理及使用。

一、 Git cherry-pick

Git cherry-pick 的作用为移植提交。比如在 dev 分支错误地进行了两次提交 2nd 和 3rd ,如果想要将这两次提交移植到 master 分支上。采用先删除再添加的方法将会很繁琐,而使用 cherry-pick 就能轻松实现这一需求。

首先在版本库中创建了两个分支 master 和 dev ,并模拟上述场景:

Git应用详解第九讲:Git cherry-pick与Git rebase

可以看到,在 dev 分支上进行了两次提交,在 master 分支上只进行了一次提交。现在想要将这两次提交 移植 到 master 分支上。整体分为两步:

  • 第一步: 将 dev 分支上多余的两次提交移植到 master 分支上;
  • 第二步: 删除 dev 分支上多余的两次提交;

1.第一步

git cherry-pick commit_id

首先切换到 master 分支,然后使用如下命令将 dev 分支上的两次提交移植到 master 分支上:

//移植2nd提交
git cherry-pick 009dd
//移植3rd提交
git cherry-pick aec8c

009dd 和 aec8c 分别表示需要移植的提交 2nd 和 3rd 的 SHA1 值:

Git应用详解第九讲:Git cherry-pick与Git rebase

移植过程为:

Git应用详解第九讲:Git cherry-pick与Git rebase

  • 如上图所示,执行了两次 cherry-pick 指令,创建了两个内容与 2nd、3rd 一致的提交对象 50477 和 f05a0 。所以, cherry-pick 指令移植提交的实质是:先将需要移植的提交复制一份,再拼接到 master 分支上,简称 先复制,再拼接 ;

  • 上面按照顺序先移植了提交 2nd 再移植提交 3rd ,不会发生冲突;

  • 不按顺序移植,如先移植提交 3rd 会发生 合并冲突 ,需要手动解决:

Git应用详解第九讲:Git cherry-pick与Git rebase

通过 vi test.txt 查看发生合并冲突的 test.txt 文件:

Git应用详解第九讲:Git cherry-pick与Git rebase

可以发现 master 分支上 initial commit 提交中的文件 test.txt 直观上 并不与提交 3rd 中的 test.txt 冲突,如下图所示:

Git应用详解第九讲:Git cherry-pick与Git rebase

但是为什么会发生合并冲突呢?原因在于 三方合并原则 :

Git应用详解第九讲:Git cherry-pick与Git rebase

如上图所示,当想要将 dev 中的提交 E 与 master 分支的提交 B 合并时,首先要找到 B 和 E 的公共父节点 A ,在 A 的基础上根据 B 和 E 进行三方合并;

了解了三方合并原则后就能解释上面发生合并冲突的原因了:

  • 由于提交 3rd 是基于提交 2nd 创建的,因此 3rd 中保留了 2rd 中对文件的操作记录;

  • 如果直接将 3rd 拼接到 initial commit 后面,就会失去提交 2nd 的记录;

  • 由此提交 3rd 就不能通过提交 2nd 找到公共提交节点 init ,这就会导致合并失败;

所以,无论内容是否冲突,合并过程都会出现冲突:

Git应用详解第九讲:Git cherry-pick与Git rebase

解决方法:手动合并三步曲:

  • 首先,选择要保留的内容,解决冲突:

Git应用详解第九讲:Git cherry-pick与Git rebase

  • 然后,通过 git add 将修改信息纳入暂存区:

Git应用详解第九讲:Git cherry-pick与Git rebase

  • 最后,通过 git commit 提交修改信息:

Git应用详解第九讲:Git cherry-pick与Git rebase

完成后查看 master 分支的提交历史:

Git应用详解第九讲:Git cherry-pick与Git rebase

可以看到解决冲突,手动合并后,成功完成了整个 cherry-pick 过程。并且新增的提交是手动合并时进行的提交,而不是直接复制的提交 3rd :

Git应用详解第九讲:Git cherry-pick与Git rebase

2.第二步

此时两分支的状态为:

Git应用详解第九讲:Git cherry-pick与Git rebase

接下来就要删除 dev 分支上错误的两次提交 2nd 和 3rd ,相当于版本回退;可以使用三种方法: revert 、 reset 和 checkout ,这里演示 checkout 和 reset 两种方法。

使用 checkout

首先切换到 dev 分支,然后通过以下指令切换到提交 initial commit :

//dd703是提交initial_commit的SHA1值
git checkout dd703

此时该节点处于游离状态:

Git应用详解第九讲:Git cherry-pick与Git rebase

然后再删除 dev 分支:

Git应用详解第九讲:Git cherry-pick与Git rebase

由于之前修改的 dev 分支没有与 master 进行合并,所以删除时需要使用参数 -D 强制删除。

删除后,剩下 master 分支与游离提交。此时再通过以下指令将游离的节点设置为 dev 分支即可:

git checkout -b dev

Git应用详解第九讲:Git cherry-pick与Git rebase

由此通过" 偷天换日 "的方式使 dev 分支回到了错误提交前的状态;

使用 reset

由于使用 checkout 只是移动了 HEAD 指针,没移动 dev 分支指针,所以会出现游离提交节点;而 reset 会同步移动 HEAD 和 dev 分支指针,不会造成这样的问题。所以这里使用 reset 进行版本回退会简单很多:

git reset --hard dd703

Git应用详解第九讲:Git cherry-pick与Git rebase

二、 git rebase 简介

首先, rebase 有两个意思: 变基 、 衍合 ,即变换分支的参考基点。默认情况下,分支会以分支上的第一次提交作为基点,如下图所示 master 分支默认以提交 1st 作为基点:

Git应用详解第九讲:Git cherry-pick与Git rebase

如果以提交 4th 作为 master 分支的基点, master 分支就会变为:

Git应用详解第九讲:Git cherry-pick与Git rebase

这个变化基点的过程就称之为变基( rebase );

rebase 与 merge 十分相似,不过二者的工作方式有着显著的差异。比如:将 A 和 B 两分支进行合并:

  • 在 A 分支上执行 git merge B ,表示的是将 B 分支 合并到 A 分支上;
  • 而在 A 分支上执行 git rebase B ,则表示将 A 分支通过变基 合并到 B 分支上;

三、 merge 与 rebase

1.采用 merge 合并分支

Git应用详解第九讲:Git cherry-pick与Git rebase

现在有两个分支 origin 和 mywork ,如果想要将 origin 分支 合并到 mywork 分支上。根据三方合并原则,需要在 c4 、 c6 和它们的公共父提交节点 c2 的基础上进行合并:

Git应用详解第九讲:Git cherry-pick与Git rebase

合并后产生一次新的提交 c7 ,该提交有两个父节点 c4 和 c6 。具体的合并方式为:如果没有冲突 git 就会自动采用 Fast-forward 方式进行合并,有冲突就解决冲突再进行手动合并。

2.采用 rebase 合并分支

由于是 mywork 分支需要变基合并到 origin 分支上,所以首先切换到 mywork 分支(注意这里与采用 merge 方法时所在的分支相反):

git checkout mywork

再进行合并:

git rebase origin

合并后的结果为:

Git应用详解第九讲:Git cherry-pick与Git rebase

注意:被合并的分支 origin 保持不动 ,而合并它的分支 mywork 将自己的提交作为补丁( patch )一个个应用( applying )到分支 origin 指向的提交后面;

在这个过程中 git 会自动创建 c5' 和 c6' 。原来的 c5 和 c6 就没用了,会被 git gc 回收。合并后分支 mywork 的提交记录变成了一条直线:

Git应用详解第九讲:Git cherry-pick与Git rebase

也就是说: rebase 会将被合并分支( mywork )上的提交应用到合并分支( origin )上,并且修改被合并分支( mywork )的提交记录。

四、 rebase 原理分析

如图所示, master 和 dev 分支都以提交节点 A 为基准点:

Git应用详解第九讲:Git cherry-pick与Git rebase

如果 dev 分支想要变换 A 这个基准点,那么:

第一步:切换到 dev 分支上;

第二步:执行 git rebase master ,过程如下;

上述命令中 rebase 参数后面指定的就是变更后的基准点:

  • 如果是分支,如 master ,基准点为该分支的最新提交节点,也就是 C ;
  • 如果是一个 commit_id ,基准点为该 commit_id 对应的提交节点;

1.基准点为分支

沿用以上模型:

Git应用详解第九讲:Git cherry-pick与Git rebase

  • 首先,将 dev 分支上除了基准点 A 外的所有节点复制一份,即 D' 和 E' ,作为补丁备用,并将分支 dev 指向新基准点 C :

Git应用详解第九讲:Git cherry-pick与Git rebase

  • 然后,按原来 dev 上的节点顺序( D->E )将补丁应用( Patch Applying )到新基准点 C 后面,并同时改变分支 dev 指向:

追加补丁 D' :

Git应用详解第九讲:Git cherry-pick与Git rebase

每次向新基准点应用补丁时,都会出现 三个选项 :

Git应用详解第九讲:Git cherry-pick与Git rebase

git rebase --continue

该选项表示:解决了合并冲突后,继续应用剩余补丁 E' :

Git应用详解第九讲:Git cherry-pick与Git rebase

git rebase --skip

该选项表示:跳过当前补丁,继续应用下一个补丁:

Git应用详解第九讲:Git cherry-pick与Git rebase

如果一直执行该选项,直到应用完分支 dev 上的补丁,结束 rebase 后,两分支的状态为:

Git应用详解第九讲:Git cherry-pick与Git rebase

git rebase --abort

该选项表示:终止 rebase 操作,回到执行 rebase 指令前的状态:

Git应用详解第九讲:Git cherry-pick与Git rebase

2.基准点为提交

过程详解

Git应用详解第九讲:Git cherry-pick与Git rebase

如图所示,若将提交节点 B 作为基准点,在当前 test 分支上执行:

git rebase 3ccc8

会直接将原来的节点 C 和 D 应用到新基准点 B 后,相当于没有发生变化,这个变基的过程为:

  • 首先,将基准点和 test 分支指向改变为节点 B ,并将 test 分支上基准点往后的提交节点作为补丁:

Git应用详解第九讲:Git cherry-pick与Git rebase

  • 然后,按顺序将补丁 C 和 D 应用到新基准点 B 后面:

Git应用详解第九讲:Git cherry-pick与Git rebase

  • 最后, test 分支的状态为:

Git应用详解第九讲:Git cherry-pick与Git rebase

所以,直接执行 git rebase 678e0 不会有任何变化:

Git应用详解第九讲:Git cherry-pick与Git rebase

但是,我们可以通过在 rebase 中添加参数 -i ,进入 rebase 交互模式,这样就能在 rebase 操作过程中对特定的补丁进行一系列操作;

实战演示

首先在 test 分支上进行了四次提交:

Git应用详解第九讲:Git cherry-pick与Git rebase

执行以下指令将 test 分支的基准点变为提交节点 B ( 678e0 ),并进行变基:

git rebase -i 678e0

执行该指令后,会进入 vim 编辑器:

Git应用详解第九讲:Git cherry-pick与Git rebase

可以根据需要将 pick 参数,改变为下面代表不同作用的参数;这样就可以对节点 C 和 D 进行不同的操作了。比如:

  • pick :默认参数,表示不对提交节点进行任何操作,直接应用原提交节点。不创建新提交;
  • reword :应用复制过后的原提交节点,但是可以编辑该节点的提交信息。通过这个参数,可以修改特定提交的提交信息。会创建新的提交;
  • edit :应用复制过后的原提交节点,会在设置了该参数的补丁上停止 rebase 操作。待修改完该补丁后,调用 git rebase --continue 继续进行 rebase 。会创建新的提交;
  • squash :将新基点后面的全部提交节点进行合并,也就是将这里的 C 和 D 两个节点进行合并。会创建新的提交;
  • 还有其他参数这里就不一一介绍了。

这次直接使用默认的 pick 参数,通过 :wq 保存并退出 vim 编辑器,完成 rebase 操作:

Git应用详解第九讲:Git cherry-pick与Git rebase

执行 rebase 操作前:

Git应用详解第九讲:Git cherry-pick与Git rebase

可以看到当新基准点为特定提交时:

  • 在 rebase 的过程中使用默认参数 pick ,并不会像当新基准点为分支时那样创建新的提交;
  • 而一旦使用其他参数(如 reword )对补丁进行了修改,就会创建新的提交;

五、 rebase 注意事项

  • 不要对 master 分支执行 rebase ,否则会引起很多的问题( master 一定是远程共享的分支);

  • 一般来说,执行 rebase 的分支都是自己的本地分支,千万不要在与其他人共享的远程分支上使用 rebase ;

    这不难理解,远程分支上的代码可能已经被其他人克隆到本地了,如果通过 rebase 修改了远程分支的提交历史,这样其他人每次拉取代码到本地时,就都需要进行复杂的合并。

  • 所以,本地的非 master 分支合并时推荐使用 git rebase ,其他分支的合并推荐使用 git merge ;

注意: git merge 和 git rebase 的显著区别是,前者不会修改 git 的提交记录,而后者会!

六、 rebase 应用场合

1.合并分支

由于 git merge 采用的是 三方合并 的原则,没有公共提交节点就无法进行合并,此时可以采用 rebase 进行合并。如下图所示:

Git应用详解第九讲:Git cherry-pick与Git rebase

本地 master 与远程 master 分支没有公共提交节点,无法采用 git merge 合并。可采用 rebase 进行合并:

//origin/master代表着远程master分支
git rebase origin/master

合并后本地 master 分支的状态为:

Git应用详解第九讲:Git cherry-pick与Git rebase

2.修改特定提交

以下情况就适合使用 rebase 来解决,当回退版本并进行修改时:

比如在 master 分支上进行了 3 次提交:

Git应用详解第九讲:Git cherry-pick与Git rebase

回退到第二次提交 2nd ,并对提交信息进行修改:

Git应用详解第九讲:Git cherry-pick与Git rebase

当我们回到原来的第三次提交 3rd 时,会发现之前的修改并没有被保存:

Git应用详解第九讲:Git cherry-pick与Git rebase

此时可以使用 rebase ,将提交 1st 作为新的提交节点(正如第四大点讲解的)。首先执行:

git rebase -i 5ab3f

通过添加参数 -i 进入交互模式,将提交 2nd 默认的 pick 参数修改为 reword 参数:

Git应用详解第九讲:Git cherry-pick与Git rebase

保存并退出后,进入修改提交信息界面:

Git应用详解第九讲:Git cherry-pick与Git rebase

保存并退出,由此完成修改:

Git应用详解第九讲:Git cherry-pick与Git rebase

七、 rebase 实战

为了演示,额外创建两个分支 dev 和 test ,分别在两个分支上进行两次提交:

Git应用详解第九讲:Git cherry-pick与Git rebase

它们有一个共同的父节点提交节点 init ,此时本地仓库的状态如下:

Git应用详解第九讲:Git cherry-pick与Git rebase

  • 由于要对 test 分支进行变基,从而 合并到 dev 分支上,所以需要先切换到 test 分支上,这与 merge 操作是相反的;

  • 随后在 test 分支上执行如下命令对该分支进行变基:

git rebase dev

该指令翻译过来就是:我 test 分支,现在要重新定义我的基准点,即使用 dev 分支指向的提交作为我新的基准点。过程如下:

  • 首先,将 test 分支上的提交(补丁) tes1 应用到 新基准点 dev2 尾部,出现了合并冲突:

    Git应用详解第九讲:Git cherry-pick与Git rebase

    查看状态,发现 test 分支变基过程中的新基准点正是 dev 分支指向的提交 361be ,即提交节点 dev2 :

    Git应用详解第九讲:Git cherry-pick与Git rebase

如图所示,此时有三个选项:

  • 选项一: git rebase --abort :表示终止 rebase 操作,恢复到操作前;

  • 选项二: git rebase --skip :表示丢弃当前 test 分支的补丁,如果一直执行该选项,变基完成后,两分支的状态如下所示:

    Git应用详解第九讲:Git cherry-pick与Git rebase

    即此时 test 分支与 dev 分支上具有相同的文件:

    Git应用详解第九讲:Git cherry-pick与Git rebase

    并且 test 分支上的提交记录被改变为了 dev 分支上的提交记录:

    Git应用详解第九讲:Git cherry-pick与Git rebase

    这就是一直执行选项 git rebase --skip ,丢弃全部 test 分支补丁的结果:

  • 选项三: git rebase --continue :解决冲突,手动合并后,继续变基;

    在 dev 分支上新增两次提交 dev3 和 dev4 :

    Git应用详解第九讲:Git cherry-pick与Git rebase

    切换回 test 分支同样新增两次提交 tes3 和 tes4 :

    Git应用详解第九讲:Git cherry-pick与Git rebase

    此时两分支的状态为:

    Git应用详解第九讲:Git cherry-pick与Git rebase

    随后在 test 分支上执行 git rebase dev ,在处理 test 分支上的第一个补丁 tes3 时出现冲突:

    Git应用详解第九讲:Git cherry-pick与Git rebase

    打开冲突文件 test.txt ,手动解决冲突:

    Git应用详解第九讲:Git cherry-pick与Git rebase

    删除 4、7、9 行:

    Git应用详解第九讲:Git cherry-pick与Git rebase

    解决冲突后,执行 git add 将对文件``test.txt`的修改操作纳入暂存区,标识已解决冲突:

    注意:这里并不需要进行一次提交,继续执行 rebase 操作即可;

    Git应用详解第九讲:Git cherry-pick与Git rebase

    随后再执行 git rebase --continue ,继续处理 test 分支的下一个补丁(变基):

    Git应用详解第九讲:Git cherry-pick与Git rebase

    rebase 结束后,查看 test 分支的提交记录:

    Git应用详解第九讲:Git cherry-pick与Git rebase

    可以发现修改了 test 分支的提交历史,达到了预期的合并效果。

    并且,此时 test 分支上的 tes3 与 tes4 两次提交的 SHA1 值与执行 rebase 前这两次提交的 SHA1 值是不一样的:

    Git应用详解第九讲:Git cherry-pick与Git rebase

    这也就验证了, git 在 rebase 过程中会自动创建提交节点的结论。此时 dev 分支与 test 分支的状态如下所示:

    Git应用详解第九讲:Git cherry-pick与Git rebase

    如果在 dev 分支上执行 git merge test ,采用的应当是 Fast-forward 方式:

    Git应用详解第九讲:Git cherry-pick与Git rebase

    使用 gitk 可以更加直观地表示这一状态:

    Git应用详解第九讲:Git cherry-pick与Git rebase

细心的你可能已经发现了, rebase 与 cherry-pick 十分类似。只不过 cherry-pick 不会修改分支提交记录,而 rebase 会。

八、 merge 与 rebase 的选择

使用 rebase 时要遵循 rebase 的黄金法则:永远不要在公共分支上使用 rebase 。公共分支可以理解为 master 分支。由于 rebase 会重写分支提交记录,因此会给项目的回溯带来危险。以下为它与 merge 的区别:

  • merge 是一个合并操作,使用 git merge 提交历史会出现分叉,显得不是那么简洁。但是,它的好处在于不会修改任何一次提交,会完整地将所有的提交都保存下来,方便回溯。 并且只能合并有公共提交节点的分支;

  • rebase 是没有合并操作的,它只是将当前分支所做的修改复制到了目标分支的最后一次提交上。所以可以不受 三方合并原则 约束,合并没有公共提交节点的分支;

    使用 rebase 会修改提交历史,得到的分支提交历史更加整洁。就好像写书,只会出版最终版本,之前的书稿并不会出版。但是,一定要注意 不能在共享的分支上使用 rebase 。

二者都是很强大的分支整合命令,使用哪个由具体情境决定。

九、 rebase 、 reset 、 revert

这三个指令的名字很像,容易混淆,下表对比了它们的用途以及区别:

指令 改变提 交历史 用途
Reset 是 把目前分支的状态设定成某个指定的 Commit 状态,通常适用于尚未推送的 Commit
Rebase 是 不管是新增、修改、删除 Commit 都相当方便。可用来整理、编辑还未推送的 Commit ,通常也只适用于尚未推送的 Commit
Revert 否 新增一个 Commit 来反转(取消)另一个 Commit 内容,原本的 Commit 依旧会保留在提交历史中。虽然会因此而增加 Commit 数,但通常比较适用于已经推送的 Commit ,或者不允许使用 Reset 或 Rebase 指令修改提交历史的场合

十、 git 最佳实践

学到这里就可以完全理解使用 git 将本地仓库文件推送到远程仓库的一般步骤了:

  • 第一步:创建本地仓库:

    git init
  • 第二步:添加用户信息:

    git config --global user.name '张三'
    git config --global user.email 'zhangsan@git.com'
  • 第三步:添加远程仓库地址:

    git remote add origin https://www.github.com/example
  • 第四步:修改文件;

  • 第五步:将工作区中的文件纳入暂存区:

    git add .
  • 第六步:将暂存区中的文件提交到版本库:

    git commit -m '注释'
  • 第七步:与远程仓库进行同步:

    git pull --rebase origin master
  • 第八步:建立本地分支与远程分支的联系,并进行推送:

    git push -u origin master

通过这一节的学习,相信你已经熟练掌握了 cherry-pick 和 rebase 的原理及使用方法了。下一节将会介绍 Git 子库: submodule 与 subtree 。期待与你再次相见!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

数据结构与算法分析

数据结构与算法分析

[美]Mark Allen Weiss / 张怀勇 / 人民邮电出版社 / 2007年 / 49.00元

《数据结构与算法分析:C++描述(第3版)》是数据结构和算法分析的经典教材,书中使用主流的程序设计语言C++作为具体的实现语言。书的内容包括表、栈、队列、树、散列表、优先队列、排序、不相交集算法、图论算法、算法分析、算法设计、摊还分析、查找树算法、k-d树和配对堆等。《数据结构与算法分析:C++描述(第3版)》适合作为计算机相关专业本科生的数据结构课程和研究生算法分析课程的教材。本科生的数据结构课......一起来看看 《数据结构与算法分析》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具