前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Pro Git研读精选:分支介绍和分支合并

Pro Git研读精选:分支介绍和分支合并

作者头像
Java架构师必看
发布于 2021-05-17 01:45:06
发布于 2021-05-17 01:45:06
54600
代码可运行
举报
文章被收录于专栏:Java架构师必看Java架构师必看
运行总次数:0
代码可运行

本文摘取自《Pro Git》第三章的第一节和第二节,由本人进行适当修改和删减。

何谓分支

为了理解 Git 分支的实现方式,我们需要回顾一下 Git 是如何储存数据的。Git 保存的不是文件差异或者变化量,而只是一系列文件快照。

在 Git 中提交时,会保存一个提交(commit)对象,该对象包含一个指向暂存内容快照的指针,包含本次提交的作者等相关附属信息,包含零个或多个指向该提交对象的父对象指针:首次提交是没有直接祖先的,普通提交有一个祖先,由两个或多个分支合并产生的提交则有多个祖先。

为直观起见,我们假设在工作目录中有三个文件,准备将它们暂存后提交(代码如下)。暂存操作会对每一个文件计算校验和(即第一章中提到的 SHA-1 哈希字串),然后把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 类型的对象存储这些快照),并将校验和加入暂存区域:

$ git add README test.rb LICENSE

$ git commit -m 'initial commit of my project'

当使用 git commit 新建一个提交对象前,Git 会先计算每一个子目录(本例中就是项目根目录)的校验和,然后在 Git 仓库中将这些目录保存为树(tree)对象。之后 Git 创建的提交对象,除了包含相关提交信息以外,还包含着指向这个树对象(项目根目录)的指针,如此它就可以在将来需要的时候,重现此次快照的内容了。

现在,Git 仓库中有五个对象:三个表示文件快照内容的 blob 对象(图3-1的3个红色方块);一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象(图3-1的蓝色方块);以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象(图3-1的绿色方块)。概念上来说,仓库中的各个对象保存的数据和相互关系看起来如图 3-1 所示:

图 3-1. 单个提交对象在仓库中的数据结构
图 3-1. 单个提交对象在仓库中的数据结构

进行一些修改后再次提交,那么这次的提交对象会包含一个指向上次提交对象的指针(即下图中的 parent 对象)。两次提交后,仓库历史会变成图 3-2 的样子:最右边的为最新的提交,包含一个指向上次提交对象的指针。

图 3-2. 多个提交对象之间的链接关系
图 3-2. 多个提交对象之间的链接关系

现在来谈分支。Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作为分支的默认名字。在若干次提交后,你其实已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动。

 图 3-3. 分支其实就是从某个提交对象往回看的历史
图 3-3. 分支其实就是从某个提交对象往回看的历史

那么,Git 又是如何创建一个新的分支的呢?答案很简单,创建一个新的分支指针。比如新建一个 testing 分支,可以使用 git branch 命令:

$ git branch testing

这会在当前 commit 对象上新建一个分支指针(见图 3-4)。

 图 3-4. 多个分支指向提交数据的历史
图 3-4. 多个分支指向提交数据的历史

那么,Git 是如何知道你当前在哪个分支上工作的呢?其实答案也很简单,它保存着一个名为 HEAD 的特别指针。请注意它和你熟知的许多其他版本控制系统(比如 Subversion 或 CVS)里的 HEAD 概念大不相同。在 Git 中,它是一个指向你正在工作中的本地分支的指针(将 HEAD 想象为当前分支的别名。)。运行 git branch 命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去,所以在这个例子中,我们依然还在 master 分支里工作(参考图 3-5)。

 图 3-5. HEAD 指向当前所在的分支
图 3-5. HEAD 指向当前所在的分支

要切换到其他分支,可以执行 git checkout 命令。我们现在转换到新建的 testing 分支:

$ git checkout testing

这样 HEAD 就指向了 testing 分支(见图3-6)。

 图 3-6. HEAD 在你转换分支时指向新的分支
图 3-6. HEAD 在你转换分支时指向新的分支

这样的实现方式会给我们带来什么好处呢?好吧,现在不妨再提交一次:

$ git commit -a -m 'made a change'

图 3-7 展示了提交后的结果。

 图 3-7. 每次提交后 HEAD 随着分支一起向前移动
图 3-7. 每次提交后 HEAD 随着分支一起向前移动

非常有趣,现在 testing 分支向前移动了一格,而 master 分支仍然指向原先git checkout 时所在的commit 对象。现在我们回到 master 分支看看:

$ git checkout master

图 3-8 显示了结果。

图 3-8. HEAD 在一次checkout 之后移动到了另一个分支
图 3-8. HEAD 在一次checkout 之后移动到了另一个分支

这条命令做了两件事。它把 HEAD 指针移回到 master 分支,并把工作目录中的文件换成了 master 分支所指向的快照内容。也就是说,现在开始所做的改动,将始于本项目中一个较老的版本。它的主要作用是将 testing 分支里作出的修改暂时取消,这样你就可以向另一个方向进行开发。

我们作些修改后再次提交:

$ vim test.rb

$ git commit -a -m 'made other changes'

现在我们的项目提交历史产生了分叉(如图 3-9 所示),因为刚才我们创建了一个分支,转换到其中进行了一些工作,然后又回到原来的主分支进行了另外一些工作。这些改变分别孤立在不同的分支里:我们可以在不同分支里反复切换,并在时机成熟时把它们合并到一起。而所有这些工作,仅仅需要branch 和 checkout 这两条命令就可以完成。

 图 3-9. 不同流向的分支历史
图 3-9. 不同流向的分支历史

由于 Git 中的分支实际上仅是一个包含所指对象校验和(40 个字符长度 SHA-1 字串)的文件,所以创建和销毁一个分支就变得非常廉价。说白了,新建一个分支就是向一个文件写入 41 个字节(外加一个换行符)那么简单,当然也就很快了。

这和大多数版本控制系统形成了鲜明对比,它们管理分支大多采取备份所有项目文件到特定目录的方式,所以根据项目文件数量和大小不同,可能花费的时间也会有相当大的差别,快则几秒,慢则数分钟。而 Git 的实现与项目复杂度无关,它永远可以在几毫秒的时间内完成分支的创建和切换。同时,因为每次提交时都记录了祖先信息(译注:即 parent 对象),将来要合并分支时,寻找恰当的合并基础(译注:即共同祖先)的工作其实已经自然而然地摆在那里了,所以实现起来非常容易。Git 鼓励开发者频繁使用分支,正是因为有着这些特性作保障。

关于分支的小例子:

在任意一个仓库新建一个test分支,切换到test分支后,在仓库里新建一个test3.txt并进行提交,当你切换回master分支时,test3.txt便消失了,如下图,说明此时master分支正处于一个比较老的版本。

分支的合并

现在让我们来看一个简单的分支与合并的例子,实际工作中大体也会用到这样的工作流程:

  1. 开发某个网站。
  2. 为实现某个新的需求,创建一个分支。
  3. 在这个分支上开展工作。

假设此时,你突然接到一个电话说有个很严重的问题需要紧急修补,那么可以按照下面的方式处理:

  1. 返回到原先已经发布到生产服务器上的分支。
  2. 为这次紧急修补建立一个新分支,并在其中修复问题。
  3. 通过测试后,回到生产服务器所在的分支,将修补分支合并进来,然后再推送到生产服务器上。
  4. 切换到之前实现新需求的分支,继续工作。

首先,我们假设你正在项目中愉快地工作,并且已经提交了几次更新(见图3-10)。

图 3-10. 一个简短的提交历史
图 3-10. 一个简短的提交历史

现在,你决定要修补问题追踪系统上的 #53 问题。顺带说明下,Git 并不同任何特定的问题追踪系统打交道。这里为了说明要解决的问题,才把新建的分支取名为 iss53。要新建并切换到该分支,

这条命令相当于执行下面这两条命令

$ git branch iss53

$ git checkout iss53

图 3-11 示意该命令的执行结果。

 图 3-11. 创建了一个新分支的指针
图 3-11. 创建了一个新分支的指针

接着你开始尝试修复问题,在提交了若干次更新后,iss53 分支的指针也会随着向前推进,因为它就是当前分支(换句话说,当前的 HEAD 指针正指向 iss53,见图 3-12):

$ vim index.html

$ git commit -a -m 'added a new footer [issue 53]'

 图 3-12. iss53 分支随工作进展向前推进
图 3-12. iss53 分支随工作进展向前推进

突然,此时你接到了一个电话,需要紧急解决一个问题。于是我们创建一个紧急修补分支 hotfix 来开展工作,直到搞定(见图 3-13):

下面两行代码是在hotfix分支下进行的操作(此处只用这两行举例)

 $ vim index.html

 $ git commit -a -m 'fixed the broken email address'

 图 3-13. hotfix 分支是从 master 分支所在点分化出来的
图 3-13. hotfix 分支是从 master 分支所在点分化出来的

有必要作些测试,确保修补是成功的,然后回到 master 分支并把它合并进来,然后发布到生产服务器。用 git merge 命令来进行合并:

请注意,合并时出现了“Fast forward”的提示。由于当前 master 分支所在的提交对象是要并入的 hotfix 分支的直接上游,Git 只需把 master 分支指针直接右移。换句话说,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。

现在最新的修改已经在当前 master 分支所指向的提交对象中了,可以部署到生产服务器上去了(见图 3-14)。

  图 3-14. 合并之后,master 分支和 hotfix 分支指向同一位置。
图 3-14. 合并之后,master 分支和 hotfix 分支指向同一位置。

由于当前 hotfix 分支和 master 都指向相同的提交对象,所以 hotfix 已经完成了历史使命,可以删掉了。使用 git branch 的 -d 选项执行删除操作:

解决完紧急问题后,我们继续回到iss53分支上继续之前的工作。

 图 3-15. iss53 分支可以不受影响继续推进。
图 3-15. iss53 分支可以不受影响继续推进。

不用担心之前hotfix分支的修改内容尚未包含到iss53 中来。如果确实需要纳入此次修补,可以用 git merge master 把 master 分支合并到iss53;或者等iss53完成之后,再将iss53分支中的更新并入master。

在问题 #53 相关的工作完成之后,可以合并回 master分支。实际操作同前面合并hotfix分支差不多,只需回到master分支,运行git merge命令指定要合并进来的分支:

请注意,这次合并操作的底层实现,并不同于之前 hotfix的并入方式。因为这次你的开发历史是从更早的地方开始分叉的。由于当前master分支所指向的提交对象(C4)并不是iss53 分支的直接祖先,Git 不得不进行一些额外处理。就此例而言,Git 会用两个分支的末端(C4 和 C5)以及它们的共同祖先(C2)进行一次简单的三方合并计算。图 3-16 用红框标出了 Git 用于合并的三个提交对象:

 图 3-16. Git 为分支合并自动识别出最佳的同源合并点。
图 3-16. Git 为分支合并自动识别出最佳的同源合并点。

这次,Git 没有简单地把分支指针右移,而是对三方合并后的结果重新做一个新的快照,并自动创建一个指向它的提交对象(C6)(见图 3-17)。这个提交对象比较特殊,它有两个祖先(C4 和 C5)。

值得一提的是 Git 可以自己裁决哪个共同祖先才是最佳合并基础;这和 CVS 或 Subversion(1.5 以后的版本)不同,它们需要开发者手工指定合并基础。所以此特性让 Git 的合并操作比其他系统都要简单不少。

 图 3-17. Git 自动创建了一个包含了合并结果的提交对象。
图 3-17. Git 自动创建了一个包含了合并结果的提交对象。

既然之前的工作成果已经合并到master了,那么iss53也就没用了。你可以就此删除它,并在问题追踪系统里关闭该问题。

遇到冲突时的分支合并

有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起(译注:逻辑上说,这种问题只能由人来裁决。)。如果你在解决问题 #53 的过程中修改了 hotfix 中修改的部分,将得到类似下面的结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git merge iss53 Auto-merging index.html CONFLICT (content): Merge conflict in index.html Automatic merge failed; fix conflicts and then commit the result.

Git 作了合并,但没有提交,它会停下来等你解决冲突。要看看哪些文件在合并时发生冲突,可以用 git status 查阅:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[master*]$ git status index.html: needs merge # On branch master # Changes not staged for commit: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # unmerged: index.html #

任何包含未解决冲突的文件都会以未合并(unmerged)的状态列出。Git 会在有冲突的文件里加入标准的冲突解决标记,可以通过它们来手工定位并解决这些冲突。可以看到此文件包含类似下面这样的部分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<<<<<<< HEAD:index.html <div id="footer">contact : email.support@github.com</div> ======= <div id="footer"> please contact us at support@github.com </div> >>>>>>> iss53:index.html

可以看到 ======= 隔开的上半部分,是 HEAD(即 master 分支,在运行 merge 命令时所切换到的分支)中的内容,下半部分是在 iss53 分支中的内容。解决冲突的办法无非是二者选其一或者由你亲自整合到一起。比如你可以通过把这段内容替换为下面这样来解决:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<div id="footer"> please contact us at email.support@github.com </div>

这个解决方案各采纳了两个分支中的一部分内容,而且我还删除了 <<<<<<<======= 和 >>>>>>> 这些行。在解决了所有文件里的所有冲突后,运行 git add 将把它们标记为已解决状态(译注:实际上就是来一次快照保存到暂存区域。)。因为一旦暂存,就表示冲突已经解决。如果你想用一个有图形界面的工具来解决这些问题,不妨运行 git mergetool,它会调用一个可视化的合并工具并引导你解决所有冲突:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git mergetool merge tool candidates: kdiff3 tkdiff xxdiff meld gvimdiff opendiff emerge vimdiff Merging the files: index.html Normal merge conflict for 'index.html': {
   local}: modified {
   remote}: modified Hit return to start merge resolution tool (opendiff):

如果不想用默认的合并工具(Git 为我默认选择了 opendiff,因为我在 Mac 上运行了该命令),你可以在上方"merge tool candidates"里找到可用的合并工具列表,输入你想用的工具名。我们将在第七章讨论怎样改变环境中的默认值。

退出合并工具以后,Git 会询问你合并是否成功。如果回答是,它会为你把相关文件暂存起来,以表明状态为已解决。

再运行一次 git status 来确认所有冲突都已解决:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: index.html #

如果觉得满意了,并且确认所有冲突都已解决,也就是进入了暂存区,就可以用 git commit 来完成这次合并提交。提交的记录差不多是这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Merge branch 'iss53' Conflicts: index.html # # It looks like you may be committing a MERGE. # If this is not correct, please remove the file # .git/MERGE_HEAD # and try again. #

如果想给将来看这次合并的人一些方便,可以修改该信息,提供更多合并细节。比如你都作了哪些改动,以及这么做的原因。有时候裁决冲突的理由并不直接或明显,有必要略加注解。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
CSS样式更改——2D转换
上篇文章主要讲述了CSS样式更改中的裁剪、Z-Index、清除、改变元素的特性基础知识,这篇文章我们来介绍下CSS样式更改中2D转换的基础用法。
前端皮皮
2020/11/25
1K0
CSS3详解:transform
近来,HTML5和CSS3的发展速度还是很可观的,国内外不乏一些大站,酷站都在使用最新的技术。面对新的技术,我们该如何正确的把握和使用到今后的项目实战中呢?针对这一问题,浩子决定剖析CSS3,一一像各位同学解答CSS3到底是个神马东西。就已transform开始吧,写的不好的话,请严重拍砖!!
py3study
2020/01/15
5760
【一起来烧脑】一步学会CSS3体系
background-image属性添加背景图片 background-size指定背景图像的大小 background-origin属性指定了背景图像的位置区域
达达前端
2019/07/19
7180
【一起来烧脑】一步学会CSS3体系
收集整理一些css3的图片阴影效果
主要利用gradient,transform,配合after以及before伪元素来实现各种图片阴影,卷角效果。 <!DOCTYPE HTML> <html lang="en-US"> <head>
练小习
2017/12/29
7380
CSS3 transform 属性(2D,3D旋转)
Internet Explorer 10、Firefox、Opera 支持 transform 属性。
小小咸鱼YwY
2020/06/19
6810
css笔记 - transform学习笔记(二)
属于2D/3D上的转换、变形效果。他不是一个动画,他就是变形。比如正方形变平行四边形,再变圆形。都是形状变成另一个形状。
xing.org1^
2018/09/20
1.7K0
CSS3实现图形曲线阴形和翘边阴影
本文首发于前端Q公众号,时间为2016年03月12日,由于当时没标原创,现重发一下标注原创...
winty
2019/12/21
8660
CSS3实现图形曲线阴形和翘边阴影
css 总结2 原
 div { transform: translate(50px,100px); -ms-transform: translate(50px,100px);        /* IE 9 */ -webkit-transform: translate(50px,100px);    /* Safari and Chrome */ -o-transform: translate(50px,100px);        /* Opera */ -moz-transform: translate(50px,100px);        /* Firefox */ } div { transform: rotate(30deg); -ms-transform: rotate(30deg);        /* IE 9 */ -webkit-transform: rotate(30deg);    /* Safari and Chrome */ -o-transform: rotate(30deg);        /* Opera */ -moz-transform: rotate(30deg);        /* Firefox */ } div { transform: scale(2,4); -ms-transform: scale(2,4);    /* IE 9 */ -webkit-transform: scale(2,4);    /* Safari 和 Chrome */ -o-transform: scale(2,4);    /* Opera */ -moz-transform: scale(2,4);    /* Firefox */ } div { transform: skew(30deg,20deg); -ms-transform: skew(30deg,20deg);    /* IE 9 */ -webkit-transform: skew(30deg,20deg);    /* Safari and Chrome */ -o-transform: skew(30deg,20deg);    /* Opera */ -moz-transform: skew(30deg,20deg);    /* Firefox */ } translate(x,y)    定义 2D 转换,沿着 X 和 Y 轴移动元素。 translateX(n)    定义 2D 转换,沿着 X 轴移动元素。 translateY(n)    定义 2D 转换,沿着 Y 轴移动元素。 scale(x,y)    定义 2D 缩放转换,改变元素的宽度和高度。 scaleX(n)    定义 2D 缩放转换,改变元素的宽度。 scaleY(n)    定义 2D 缩放转换,改变元素的高度。 transform:translate(0 ,-50%) rotate(45deg);
tianyawhl
2019/04/04
5570
CSS3中的变形处理
变形分类 缩放 使用scale方法来实现文字或图像的缩放,在参数中指定缩放倍率。例如“scale(0.5)”,表示缩小50 倾斜 使用skew方法来实现文字或图像的缩放,在参数中指定水平方向的倾斜角度与垂直方向的倾斜角度,若只有一个数值,则为水平方向的倾斜角度,单位为deg。 注:rotate表示的是旋转,仅一个数值,表示水平方向的旋转角度。 移动 使用translate方法来实现文字或图像的移动,在参数中指定水平方向的移动与垂直方向的移动,若只有一个数值,则为水平方向的移动。 对
就只是小茗
2018/03/07
6870
javascript图片展示墙特效
查看效果:http://hovertree.com/code/javascript/pwl4bhoi.htm
全栈程序员站长
2022/07/15
4.5K0
CSS3之transition&tran
http://www.renniaofei.com/code/css3-transform-shuxing-shiyong-xiangjie/
py3study
2020/01/08
4520
CSS3透明属性opacity建议收藏
查看效果:http://hovertree.com/hvtart/bjae/q3etb2qv.htm
全栈程序员站长
2022/07/15
4570
CSS3-transform变形功能
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>css变形功能</title> <style> body{ background-color: #b9eef0; } h3,p{ margin:100px 0px; padding-bottom: 10px;
xing.org1^
2018/05/17
6810
CSS3 translate、transform、transition区别
translate:移动,     transform的一个方法               通过 translate() 方法,元素从其当前位置移动,根据给定的 left(x 坐标) 和 top(y 坐标) 位置参数:           用法transform: translate(50px, 100px);               -ms-transform: translate(50px,100px);               -webkit-transform: translate(50p
SpiritLing
2018/04/16
1.6K0
emlog图片鼠标经过抖动效果,全网适用
那么我给出一个方案,给需要抖动的img前面加个div  给div加class属性=dimg
用户8099761
2023/05/10
9850
CSS3 转换(Transform)
在CSS中,允许元素实现 2D 和 3D的转换效果,主要包含 :旋转,缩放,移动,倾斜        
py3study
2020/01/14
7970
html5手机端的点击弹出侧边滑动菜单代码
效果预览:http://hovertree.com/texiao/html5/19/
全栈程序员站长
2022/07/15
2.5K0
css3 动画应用 animations 和transtions transform在加上JavaScript 可以实现硬件加速动画。
transitions(过渡) 被应用于元素指定的属性变化时,该属性经过一段时间逐渐的过渡到最终想要的值。   主要包括四个属性:     执行变换的属性:transition-property 变换延续的时间:transition-duration     变换的速率变化:transition-timing-function     变换延迟时间:transition-delay。     1.transition-propery       语法:transition-property:
用户1197315
2018/01/19
1.7K0
一篇文章带你了解CSS基础知识和基本用法
相信做过网页的对Css都不是很陌生,它可以帮助我们重铸网页中很多绚丽的特效,尤其是现在Css已经发展3.0版本,很多功能更是丰富多彩,让我们的开发时间不仅大大缩短,而且还可以轻松做出许多华丽的特效,需要注意的是,Css相当于Html的一个美化装置,所以它必须依赖于Html才能发挥作用,那么今天我们就来深入了解下它吧。
前端皮皮
2020/11/26
11.2K0
一篇文章带你了解CSS基础知识和基本用法
如何实现复选框的全选和取消全选效果
如何实现复选框的全选和取消全选效果: 在很多网站都有这样的功能,当点击一个全选按钮之后,所有的复选框都会被选中,再点击之后会取消全选,功能非常的人性化,可以省却很多人力,下面就简单介绍一下JS如何实现此功能,代码实例如下:
全栈程序员站长
2022/07/15
2.3K0
推荐阅读
相关推荐
CSS样式更改——2D转换
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文