内容简介:前情提要:这一节主要介绍
前言
前情提要: Git应用详解第八讲:Git标签、别名与Git gc
这一节主要介绍 git cherry-pick
与 git rebase
的原理及使用。
一、 Git cherry-pick
Git cherry-pick
的作用为移植提交。比如在 dev
分支错误地进行了两次提交 2nd
和 3rd
,如果想要将这两次提交移植到 master
分支上。采用先删除再添加的方法将会很繁琐,而使用 cherry-pick
就能轻松实现这一需求。
首先在版本库中创建了两个分支 master
和 dev
,并模拟上述场景:
可以看到,在 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
值:
移植过程为:
-
如上图所示,执行了两次
cherry-pick
指令,创建了两个内容与2nd、3rd
一致的提交对象50477
和f05a0
。所以,cherry-pick
指令移植提交的实质是:先将需要移植的提交复制一份,再拼接到master
分支上,简称 先复制,再拼接 ; -
上面按照顺序先移植了提交
2nd
再移植提交3rd
,不会发生冲突; -
不按顺序移植,如先移植提交
3rd
会发生 合并冲突 ,需要手动解决:
通过 vi test.txt
查看发生合并冲突的 test.txt
文件:
可以发现 master
分支上 initial commit
提交中的文件 test.txt
直观上 并不与提交 3rd
中的 test.txt
冲突,如下图所示:
但是为什么会发生合并冲突呢?原因在于 三方合并原则 :
如上图所示,当想要将 dev
中的提交 E
与 master
分支的提交 B
合并时,首先要找到 B
和 E
的公共父节点 A
,在 A
的基础上根据 B
和 E
进行三方合并;
了解了三方合并原则后就能解释上面发生合并冲突的原因了:
-
由于提交
3rd
是基于提交2nd
创建的,因此3rd
中保留了2rd
中对文件的操作记录; -
如果直接将
3rd
拼接到initial commit
后面,就会失去提交2nd
的记录; -
由此提交
3rd
就不能通过提交2nd
找到公共提交节点init
,这就会导致合并失败;
所以,无论内容是否冲突,合并过程都会出现冲突:
解决方法:手动合并三步曲:
- 首先,选择要保留的内容,解决冲突:
- 然后,通过
git add
将修改信息纳入暂存区:
- 最后,通过
git commit
提交修改信息:
完成后查看 master
分支的提交历史:
可以看到解决冲突,手动合并后,成功完成了整个 cherry-pick
过程。并且新增的提交是手动合并时进行的提交,而不是直接复制的提交 3rd
:
2.第二步
此时两分支的状态为:
接下来就要删除 dev
分支上错误的两次提交 2nd
和 3rd
,相当于版本回退;可以使用三种方法: revert
、 reset
和 checkout
,这里演示 checkout
和 reset
两种方法。
使用 checkout
首先切换到 dev
分支,然后通过以下指令切换到提交 initial commit
:
//dd703是提交initial_commit的SHA1值 git checkout dd703
此时该节点处于游离状态:
然后再删除 dev
分支:
由于之前修改的 dev
分支没有与 master
进行合并,所以删除时需要使用参数 -D
强制删除。
删除后,剩下 master
分支与游离提交。此时再通过以下指令将游离的节点设置为 dev
分支即可:
git checkout -b dev
由此通过" 偷天换日 "的方式使 dev
分支回到了错误提交前的状态;
使用 reset
由于使用 checkout
只是移动了 HEAD
指针,没移动 dev
分支指针,所以会出现游离提交节点;而 reset
会同步移动 HEAD
和 dev
分支指针,不会造成这样的问题。所以这里使用 reset
进行版本回退会简单很多:
git reset --hard dd703
二、 git rebase
简介
首先, rebase
有两个意思: 变基 、 衍合 ,即变换分支的参考基点。默认情况下,分支会以分支上的第一次提交作为基点,如下图所示 master
分支默认以提交 1st
作为基点:
如果以提交 4th
作为 master
分支的基点, master
分支就会变为:
这个变化基点的过程就称之为变基( rebase
);
rebase
与 merge
十分相似,不过二者的工作方式有着显著的差异。比如:将 A
和 B
两分支进行合并:
- 在
A
分支上执行git merge B
,表示的是将B
分支 合并到A
分支上; - 而在
A
分支上执行git rebase B
,则表示将A
分支通过变基 合并到B
分支上;
三、 merge
与 rebase
1.采用 merge
合并分支
现在有两个分支 origin
和 mywork
,如果想要将 origin
分支 合并到 mywork
分支上。根据三方合并原则,需要在 c4
、 c6
和它们的公共父提交节点 c2
的基础上进行合并:
合并后产生一次新的提交 c7
,该提交有两个父节点 c4
和 c6
。具体的合并方式为:如果没有冲突 git
就会自动采用 Fast-forward
方式进行合并,有冲突就解决冲突再进行手动合并。
2.采用 rebase
合并分支
由于是 mywork
分支需要变基合并到 origin
分支上,所以首先切换到 mywork
分支(注意这里与采用 merge
方法时所在的分支相反):
git checkout mywork
再进行合并:
git rebase origin
合并后的结果为:
注意:被合并的分支 origin
保持不动 ,而合并它的分支 mywork
将自己的提交作为补丁( patch
)一个个应用( applying
)到分支 origin
指向的提交后面;
在这个过程中 git
会自动创建 c5'
和 c6'
。原来的 c5
和 c6
就没用了,会被 git gc
回收。合并后分支 mywork
的提交记录变成了一条直线:
也就是说: rebase
会将被合并分支( mywork
)上的提交应用到合并分支( origin
)上,并且修改被合并分支( mywork
)的提交记录。
四、 rebase
原理分析
如图所示, master
和 dev
分支都以提交节点 A
为基准点:
如果 dev
分支想要变换 A
这个基准点,那么:
第一步:切换到 dev
分支上;
第二步:执行 git rebase master
,过程如下;
上述命令中 rebase
参数后面指定的就是变更后的基准点:
- 如果是分支,如
master
,基准点为该分支的最新提交节点,也就是C
; - 如果是一个
commit_id
,基准点为该commit_id
对应的提交节点;
1.基准点为分支
沿用以上模型:
- 首先,将
dev
分支上除了基准点A
外的所有节点复制一份,即D'
和E'
,作为补丁备用,并将分支dev
指向新基准点C
:
- 然后,按原来
dev
上的节点顺序(D->E
)将补丁应用(Patch Applying
)到新基准点C
后面,并同时改变分支dev
指向:
追加补丁 D'
:
每次向新基准点应用补丁时,都会出现 三个选项 :
git rebase --continue
该选项表示:解决了合并冲突后,继续应用剩余补丁 E'
:
git rebase --skip
该选项表示:跳过当前补丁,继续应用下一个补丁:
如果一直执行该选项,直到应用完分支 dev
上的补丁,结束 rebase
后,两分支的状态为:
git rebase --abort
该选项表示:终止 rebase
操作,回到执行 rebase
指令前的状态:
2.基准点为提交
过程详解
如图所示,若将提交节点 B
作为基准点,在当前 test
分支上执行:
git rebase 3ccc8
会直接将原来的节点 C
和 D
应用到新基准点 B
后,相当于没有发生变化,这个变基的过程为:
- 首先,将基准点和
test
分支指向改变为节点B
,并将test
分支上基准点往后的提交节点作为补丁:
- 然后,按顺序将补丁
C
和D
应用到新基准点B
后面:
- 最后,
test
分支的状态为:
所以,直接执行 git rebase 678e0
不会有任何变化:
但是,我们可以通过在 rebase
中添加参数 -i
,进入 rebase
交互模式,这样就能在 rebase
操作过程中对特定的补丁进行一系列操作;
实战演示
首先在 test
分支上进行了四次提交:
执行以下指令将 test
分支的基准点变为提交节点 B
( 678e0
),并进行变基:
git rebase -i 678e0
执行该指令后,会进入 vim
编辑器:
可以根据需要将 pick
参数,改变为下面代表不同作用的参数;这样就可以对节点 C
和 D
进行不同的操作了。比如:
-
pick
:默认参数,表示不对提交节点进行任何操作,直接应用原提交节点。不创建新提交; -
reword
:应用复制过后的原提交节点,但是可以编辑该节点的提交信息。通过这个参数,可以修改特定提交的提交信息。会创建新的提交; -
edit
:应用复制过后的原提交节点,会在设置了该参数的补丁上停止rebase
操作。待修改完该补丁后,调用git rebase --continue
继续进行rebase
。会创建新的提交; -
squash
:将新基点后面的全部提交节点进行合并,也就是将这里的C
和D
两个节点进行合并。会创建新的提交; - 还有其他参数这里就不一一介绍了。
这次直接使用默认的 pick
参数,通过 :wq
保存并退出 vim
编辑器,完成 rebase
操作:
执行 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
进行合并。如下图所示:
本地 master
与远程 master
分支没有公共提交节点,无法采用 git merge
合并。可采用 rebase
进行合并:
//origin/master代表着远程master分支 git rebase origin/master
合并后本地 master
分支的状态为:
2.修改特定提交
以下情况就适合使用 rebase
来解决,当回退版本并进行修改时:
比如在 master
分支上进行了 3
次提交:
回退到第二次提交 2nd
,并对提交信息进行修改:
当我们回到原来的第三次提交 3rd
时,会发现之前的修改并没有被保存:
此时可以使用 rebase
,将提交 1st
作为新的提交节点(正如第四大点讲解的)。首先执行:
git rebase -i 5ab3f
通过添加参数 -i
进入交互模式,将提交 2nd
默认的 pick
参数修改为 reword
参数:
保存并退出后,进入修改提交信息界面:
保存并退出,由此完成修改:
七、 rebase
实战
为了演示,额外创建两个分支 dev
和 test
,分别在两个分支上进行两次提交:
它们有一个共同的父节点提交节点 init
,此时本地仓库的状态如下:
-
由于要对
test
分支进行变基,从而 合并到dev
分支上,所以需要先切换到test
分支上,这与merge
操作是相反的; -
随后在
test
分支上执行如下命令对该分支进行变基:
git rebase dev
该指令翻译过来就是:我 test
分支,现在要重新定义我的基准点,即使用 dev
分支指向的提交作为我新的基准点。过程如下:
-
首先,将
test
分支上的提交(补丁)tes1
应用到 新基准点dev2
尾部,出现了合并冲突:查看状态,发现
test
分支变基过程中的新基准点正是dev
分支指向的提交361be
,即提交节点dev2
:
如图所示,此时有三个选项:
-
选项一:
git rebase --abort
:表示终止rebase
操作,恢复到操作前; -
选项二:
git rebase --skip
:表示丢弃当前test
分支的补丁,如果一直执行该选项,变基完成后,两分支的状态如下所示:即此时
test
分支与dev
分支上具有相同的文件:并且
test
分支上的提交记录被改变为了dev
分支上的提交记录:这就是一直执行选项
git rebase --skip
,丢弃全部test
分支补丁的结果: -
选项三:
git rebase --continue
:解决冲突,手动合并后,继续变基;在
dev
分支上新增两次提交dev3
和dev4
:切换回
test
分支同样新增两次提交tes3
和tes4
:此时两分支的状态为:
随后在
test
分支上执行git rebase dev
,在处理test
分支上的第一个补丁tes3
时出现冲突:打开冲突文件
test.txt
,手动解决冲突:删除
4、7、9
行:解决冲突后,执行
git add
将对文件``test.txt`的修改操作纳入暂存区,标识已解决冲突:注意:这里并不需要进行一次提交,继续执行
rebase
操作即可;随后再执行
git rebase --continue
,继续处理test
分支的下一个补丁(变基):rebase
结束后,查看test
分支的提交记录:可以发现修改了
test
分支的提交历史,达到了预期的合并效果。并且,此时
test
分支上的tes3
与tes4
两次提交的SHA1
值与执行rebase
前这两次提交的SHA1
值是不一样的:这也就验证了,
git
在rebase
过程中会自动创建提交节点的结论。此时dev
分支与test
分支的状态如下所示:如果在
dev
分支上执行git merge test
,采用的应当是Fast-forward
方式:使用
gitk
可以更加直观地表示这一状态:
细心的你可能已经发现了, 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
。期待与你再次相见!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Zookeeper详解-应用程序(七)
- Go语言sync包的应用详解
- Go 语言 sync 包的应用详解
- 详解Spring Boot的应用限流
- Storm详解二、写第一个Storm应用
- 一看就懂,Python 日志模块详解及应用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Linux程序设计
Neil Matthew、Richard Stones / 陈健、宋健建 / 人民邮电出版社 / 201005 / 99.00元
时至今日,Linux系统已经从一个个人作品发展为可以用于各种关键任务的成熟、高效和稳定的操作系统,因为具备跨平台、开源、支持众多应用软件和网络协议等优点,它得到了各大主流软硬件厂商的支持,也成为广大程序设计人员理想的开发平台。 本书是Linux程序设计领域的经典名著,以简单易懂、内容全面和示例丰富而受到广泛好评。中文版前两版出版后,在国内的Linux爱好者和程序员中也引起了强烈反响,这一热潮......一起来看看 《Linux程序设计》 这本书的介绍吧!
UNIX 时间戳转换
UNIX 时间戳转换
HEX HSV 转换工具
HEX HSV 互换工具