Git之旅(7):分支是啥?

  • A+
所属分类:Git  运维技术

  

博主会将与Git有关的知识点总结到"通俗易懂Git入门系列"文章中,如果你对Git不是特别了解,请按照顺序阅读"Git系列",以便站在前文的基础上理解新的知识点。

 

题外话:由于孩子出生,生活节奏与之前截然不同,所以停更了一段时间,最近刚刚适应一些,所以抽空写完了这篇文章,这篇文章其实写了好久,因为没空,所以总是断断续续的去写,每次写又要从头整理思绪,所以也修改了好几次,不过今天总算出了结果,也算完成了一个阶段性的任务吧,希望这篇文章能够帮助到你,加油~

 

直到现在,我们一直都偏重概念,通常是在介绍清楚相关概念以后,才会结合相关的命令进行练习,所以,如果你同时也在看别的教程,你会发现很多不同之处,你甚至会觉得我们的进度太慢了,如果你看别的教程,在看到第7篇的时候,应该已经介绍了很多命令,但是你可能并不理解为什么要那样做,我们力求在使用命令的同时能够理解相关的原理,所以,不要着急,厚积薄发,搞定命令简直不要太轻松。

 

前文中,我们一直在创建提交,但是一直没有回退到过任何一个版本,主要执行的命令就是,查看状态、暂存修改、创建提交,一直在重复这三件事,这样做是为了更好的理解工作区、暂存区、提交之间的关系,不如现在,我们来尝试一下,回退到某个版本,看一看利用git进行版本回退的效果,同时,通过版本回退的操作引出"分支"的概念。

 

为了我们的思路能够同步,我决定重新创建一个测试仓库,一步一步的创建提交,并且回退到特定的版本,过程如下:

 

创建测试仓库

 
 

进入仓库,创建两个测试文件,f1和f2

如上述命令所示,f1的内容为1,f2的内容为A。

 

测试文件已经初始化完成了,我们来创建第一个提交吧。

使用"git add ."命令将当期目录中的所有变更状态加入到暂存区,然后使用如下命令创建第一个提交,注释信息为1A

 

多次修改f1和f2的文件内容,并且创建对应的提交,为一会儿的回退测试做准备,过程如下,不再详细描述

我又一口气创建了4个提交,每次修改都对两个文件加入一行新文本,到目前为止,我们一共已经有了5个提交,最新的提交是注释信息为"5E"的提交,当前状态下,两个文件的内容如下:

现在,我后悔了,我想要回到"3C"时的状态,该怎么办呢?

如果想要回到"3C"时的状态,首先要找到"3C"提交的哈希码,我们先通过git log命令看看3C状态对应的哈希码吧。

从上述信息可以看出,"3C"提交对应的ID是aa81e63 ,好了,状态对应的哈希码已经找到了,我们可以使用如下命令,回到"3C"提交对应的状态

 

执行完上述命令后,再次查看工作区内两个文件的内容,如下:

可以看出来,我们已经回到过去了,轻松的实现了版本回退,我们只是执行了一条"git reset --hard"命令而已,我们先不纠结这条命令的参数都是什么含义,之后我们再去详细的了解它。

 

如果,我回到"3C"状态以后又反悔了,想要再次回到"5E"时的状态,该怎么办呢?聪明如你一定想到了,仍然使用刚才的命令啊,我们只要找到"5E"状态的哈希码,就能够通过"git reset --hard"命令再次回到"5E"的状态了,没错,思路完全正确,于是,我们可能会想要尝试使用"git log --oneline"命令查找"5E"状态的哈希码,但是当我们执行"git log"命令以后,发现"5E"状态的哈希码不见了,如下:

由于我们之前已经使用"git reset --hard"命令回退到了"3C"时的状态,所以,"git log"命令执行后显示的最近的"commit id"只会显示到"3C",别慌,肯定还有办法能够找到我们想要的哈希码,没错,还有一条命令,那就是"git reflog"命令,执行"git reflog"命令,可以看到如下内容:

注:我们暂且不用纠结git log命令和git reflog命令的区别,因为如果想要彻底理解git reflog命令,可能还需要理解一些其他的概念,所以先往下看。

从上述信息可以看出,"5E"对应的哈希码为"526ff66",所以,我们仍然可以通过这个哈希码再次回到"5E"状态对应的提交,执行如下命令即可

 

再次查看文件的内容,发现真的回到了"5E"的状态

此时,使用"git log"命令,可以看到,最近的一次提交又回到了"5E"了,如下:

 

你看,使用git进行版本回退是不是非常的方便呢?

细心如你一定已经发现了,无论是回退操作,还是使用git log命令查看日志,都会在命令执行后的返回信息中看到一个词,这个词就是"HEAD ",那么"HEAD "是什么意思呢?你可以把"HEAD "理解成一个指针,这个指针通常指向了当前"分支"的最新的提交,你肯定会有疑问,"分支"又是个什么东西呢?为什么"HEAD "这个概念还没说明白,就又多出了一个"分支"的概念呢?不如我们先来搞明白分支是个什么东西吧。

 

如果想要搞明白分支的概念,不如先来看一个实际的问题,从解决问题的思路去理解一个概念,似乎更加容易一些,为了能够更加轻松的、清晰的描述问题,我又创建了一个新的测试仓库,并且创建了两个用于测试的文件,m1和m2

如上例所示,我创建了两个测试文件,m1和m2,假设这两个文件分别代表了两个模块,这两个模块共同组成了我想要的项目,并且假设这两个模块之间的代码在业务逻辑上是没有关系的,是相互独立的,现在,我来对这两个文件进行一些修改,模拟在实际工作中分别在两个模块上进行开发的工作,操作如下:

我再把上述模拟工作的过程用文字大概的描述一遍,我先是只修改了m1文件,然后针对这个修改创建了一个提交(模拟开发模块一),然后我又修改了m2文件,针对这个修改又创建了一个提交(模拟开发模块二),然后我又重复了上述过程,分别对m1和m2进行了修改,并且为各自的修改创建了提交。

 

假设,我现在后悔了,我想让m2文件回到"add B in m2"的状态,我该怎么办呢?没错,我们只要回退就行了,就用刚才总结的命令回退,我们来试试,执行如下命令:

可以发现,我们已经成功的将m2文件变成了"add B in m2"的状态,但是,你会发现一个问题,问题就是,如果你此时查看m1文件的内容,你会发现,m1文件的状态也跟着回退了,如下

我们的初衷是为了让m2回到"add B in m2"的状态,但是并没有想要修改m1的状态,因为之前说过,两个模块在业务逻辑上是独立的,我们并不想为了回退某个模块,而影响另一个模块的代码。

 

那么我们该怎样解决这样的问题呢?如果仍然按照上面的操作方式,我们没有任何办法能够解决这个问题,因为问题的根本原因在于,针对模块一的提交和针对模块二的提交是交错的,这些提交交错在同一条'逻辑线'上,很有可能,针对模块一的某个修改就是针对模块二的某个修改的父提交,而针对模块二的某个修改又是针对模块一的某个修改的父提交,所以在这种情况下,如果你想要针对某个模块的代码进行回退,势必会影响到另外一个模块。换句话说就是,你想要回退的提交之后的提交很有可能包含其他模块的代码改动。

其实,造成上述问题的根本原因就在于,针对两个模块的提交混用了一条"逻辑线",你可以把这条逻辑线理解成一条所谓的"分支",默认情况下,git仓库会为我们创建一条名为master的分支,我们创建的所有提交默认都会在master分支上,这样说不够明了,不如来直观的看一下,在上例中的测试仓库中执行gitk命令,打开图形化工具,因为图形化工具能让我们更加直观的理解"分支"的概念,执行gitk命令后,如下图所示:

注:为了能显示更多的提交,我已经回退到了之前最新提交,此处省略命令

Git之旅(7):分支是啥?

如你所见,上图中有5个提交,对于这5个提交来说 ,每个提交都是下一个提交的父提交,它们组成了一条所谓的"逻辑线",这些提交所连成的线就是所谓的分支,只不过默认情况下,所有提交会连成一条名为master的分支,从字面上理解,master分支的意思是主分支,从上图可以看出,绿色的master方形总是指向master分支上最新的提交,我们可以把上图中绿色的master方形称之为分支指针,分支指针总是指向当前分支的最新提交。

 

那么回到刚才的问题,上例中,当想要回退某个模块时,会影响另一个模块,这是因为默认情况下,所有提交交错在一条分支上,那么你肯定想到了解决方法,我们只要将两个模块的代码分别提交到两个分支不就能解决问题了么,没错,我们可以再创建一条分支,然后就可以将两个模块的代码分别提交到不同的分支上,这样,处于两个分支的提交在进行版本回退的操作时,就不会相互影响了,大致思路如下图所示

Git之旅(7):分支是啥?

如上图所示,橘色代表m1相关的代码,蓝色代表m2相关的代码,我们根据当前的状态创建出一个新的分支,然后将之后的代码修改按照模块逻辑进行区分,分别提交到不同的分支上,这样就能达到我们的目的,在之后的工作中,让两个模块互不影响了。

当我们需要一个完整的项目时,则需要所有模块的代码,这时,我们只需要将两个分支合并在一起就好了,我们只要知道分支从本质上就是一些提交所连成的线,当我们需要某些提交与另一些提交之间不会相互影响时,就可以利用分支将它们分开。

当然,在实际工作中,即使使用分支,也不一定能将代码完全从业务逻辑上分开,比如,一个团队中有三个开发,A、B、C,他们为了自己的工作不影响别人,分别创建了A、B、C三条分支,但是并不代表他们不会同时修改同一个业务模块的代码,很有可能他们三个人的开发工作所对应的代码是处于同一个模块的,所以,在这种情况下,分支的作用只是为了暂时的将每个人的工作隔离开,以便不影响别人,并不是为了从业务逻辑上分开代码,但是本质上,仍然利用了分支之间的提交互不影响的特性,当然,如果A、B、C三个人同时修改了同一模块的某一段代码,当我们需要将三个人的代码整合到一起时,很有可能出现所谓的"冲突",这时候,就需要人为介入,解决冲突。

 

此时我们还没有完全使用过分支,所以不理解"合并分支"以及"解决冲突"这些操作是完全正常的,我们先别在意它们,不如先通过实际操作来更加具象化的认识一下分支吧。

 

现在,我们通过实际操作来创建一个分支,为了方便,我们继续使用上文中的示例仓库,在仓库目录中打开git bash,可以从git bash的命令提示符中看到当前工作目录处于哪个分支,如下图所示

Git之旅(7):分支是啥?

可以看到,我们当前处于默认的分支master分支,除了在命令提示符中能够看到当前处于哪个分支以外,使用" git status"命令也可以,如下:

从上述返回信息可以看出,我们处于master分支。

使用"git branch"命令能够查看现在都有哪些分支

如上述命令所示,我们当前只有一个分支,这个分支的名字是master,当有多个分支时,会显示所有分支的名字,我们当前所处的分支前面会有一个"*"(星号)

使用"git branch"命令时还可以加上"-v"参数或"-vv"参数,使用"-v"参数或"-vv"参数可以查看更加详细的分支信息。

可以看到,目前我们处于master分支,master分支的最新的提交的哈希值为89b7282,最新的提交的注释信息为"add C in m2"

 

现在,我们就来创建一个新的分支,创建新分支时,默认是以当前所在的分支作为基础创建的,你可以这样理解,当我们创建新分支时,是将当前所在的分支'复制'了一份(并不是真正的复制,只是将新的分支指针指向所基于的分支对应的提交,后面会有解释,此处不用纠结),我们当前处于默认的master分支,假如我们想要创建一个用于测试的名为test的分支,那么可以使用如下命令创建

上述命令的意思是,根据当前所在分支(master分支)创建一个名为test的分支,但是并不切换到新的分支(test分支),仍然停留在当前分支(master分支)。

执行完上述命令后,再次使用"git branch"命令查看分支信息,你会看到test分支已经被创建了,如下所示

从命令的返回信息可以看出来,test分支已经被创建了,由于test分支是基于master分支创建的,所以目前来说,这两条分支是完全相同的,之前说过,分支指针总是指向当前分支的最新提交,所以test分支的分支指针也会指向test分支上最新的提交,但是由于test分支是基于master分支刚刚创建完成的,所以这两条分支完全相同,test分支上最新的提交与master分支上最新的提交自然是同一个提交。

而且,从上述信息可以看出,目前我们仍然处于master分支,并没有切换到test分支,因为星号仍然处于master分支,也就是说,现在,如果在工作空间中进行改动并且创建提交,新创建的提交仍然属于master分支,因为我们并没有切换到test分支,如果想要在test分支上进行工作,则必须先切换到test分支,但是在切换分支之前,我们先使用gitk命令看一下图形化界面,看看当前的状态到底是个什么样子,执行gitk命令,可以看到当前的提交状态如下:

Git之旅(7):分支是啥?

可以看到, test分支和master分支的分支指针同时指向了同一个提交,刚才提到过," git branch test"命令的意思是基于当前分支创建test分支,但是并不是真正的将当前分支复制一份,什么意思呢?你可以这样理解,在没有创建test分支之前,master分支如下图所示:

Git之旅(7):分支是啥?

如上图所示,上例的测试仓库中一共有5个提交,我们用C1代表第一个提交"init file m1 and m2",用C2代表第二个提交"add 2 in m1",以此类推,每个提交都指向了自己的父提交(前文中有说过,每个提交都会指向自己的父提交,如果忘了请回顾前文),上图中绿色的master方框表示master分支的分支指针,它指向了master分支的最新提交。

当我们执行" git branch test"命令后,会基于master分支创建test分支,你可以把创建test分支的过程理解成"复制"当前的master分支,但不是真的复制,前文中一直在说,git不会像我们一样手动的创建真实的"副本",因为通过"复制操作"创建的副本总是会占用相对较多的磁盘空间,前文中git所体现出的'智慧'在这里同样适用,当我们基于master分支创建test分支时,git也并不会真正的将master分支"复制",git只会创建一个test分支指针,并且让test分支指针指向master分支对应的最新的提交,如下图所示:

Git之旅(7):分支是啥?

聪明如你一定想到了,从"概念"上来说,目前存在两条分支,master分支和test分支,但是从"物理"上来说,其实就是5个提交连成的线,我们根据需要赋予了这条线两个含义,这两个含义就是master分支(默认创建的含义)和test分支(我们所赋予的含义),你如果阅读过前文,肯定能明白,为什么git没有将这个5个提交复制一遍,因为这时,test分支和master分支是完全一样的,所以我们只要引用这5个提交即可,没有必要的复制出5个相同的提交,也就是说,从概念上来说,现在master分支上有5个提交,test分支上同样有5个提交,并且从当前的情况来说,test分支和master分支是完全相同的,但是它们之间却又互不影响,你现在可以在test分支上随意的回退到任何一个提交,都不会影响master分支上的提交,这样说可能不太容易理解,不如先向下看。

 

刚才说过,如果想要使用新创建的test分支,则必须切换到新分支,那么怎样切换分支呢?使用"git checkout test"命令即可从当前分支切换到test分支,如下:

Git之旅(7):分支是啥?

'checkout'的字面意思是'检出',也就是说,我们可以使用上述命令,从当前分支检出(切换)到其他分支,当你切换到test分支时,就表示当前工作空间中的内容已经变为test分支指针所指向的提交所对应的状态了,但是由于当前test指针和master指针指向的是同一个提交,所以即使你切换到test分支,当前工作空间中的内容也不会发生改变。

从返回信息可以看出,执行上述命令后,已经切换到test分支,在新的命令提示符中,也显示为test分支,此时,如果在工作空间进行修改并创建提交,新的提交就会属于test分支。

注:在没有创建test分支时,我们可以使用"git checkout –b test"命令同时完成创建test分支并检出test分支的操作。

 

好了,我们就在现在的状态下(test分支中)做一些修改,首先,先看看当前目录都有哪些文件,文件都有什么内容

没错,正如刚才所说,由于test分支是基于master分支刚刚创建的,所以文件目录结构和文件内容与master分支的最新状态都是一样的。

 

从现在开始,我们规定,m1文件的修改以后都在master分支上进行,m2文件的修改以后都在test分支上进行,这样做为了模拟不同模块在不同分支上开发的那种场景,以便解决上文中提出的模块之间的提交互相影响的问题,那么,我们当前处于test分支,我现在修改一下m2文件(模拟一下在test分支上开发模块二的场景),并将修改创建成新的提交。

从上述信息可以看出,我们在test分支上创建的最新的提交的哈希码为30d80b0

此时,使用gitk命令打开图形化界面,如下

Git之旅(7):分支是啥?

可以看到,新的提交已经属于test分支了,但是由于我们并没有修改master分支,所以master分支的最新提交仍然是上一个commit,以目前的状态来看,两个分支的关系可能并不是特别明了,我们换一种方式来解释一下,当前状态如下图所示。

Git之旅(7):分支是啥?

现在,master分支和test分支已经不一样了,目前来说,master分支上只存在5个提交,而test分支上存在6个提交,因为从master分支的分支指针开始,沿着"箭头"的方向(不能与箭头的指向相逆),能找到5个提交,而从test分支的分支指针开始,沿着"箭头"的方向,能找到6个提交,所以,当前的状态来讲,master分支由5个提交组成,test分支由6个提交组成。

 

那么现在,我们切换回master分支,并且查看一下m2文件的内容,(注意,切换回master分支就表示当前的工作空间中的内容会变成master分支指针所指向的提交所对应的状态),操作如下:

你会发现,master分支中的m2文件中并没有字母D,因为刚才对m2添加字母D的操作对应的提交是属于test分支上的,所以对master分支上的文件内容并没有任何影响。

 

当前,我们处于master分支,那么我们来修改一下m1文件(模拟一下在master分支上开发模块一的场景),过程如下:

这个在master分支上创建的最新的提交的哈希码为7406a10

完成上述操作,再次打开gitk图形化,你会发现如下图所示

(注:后面会有解释为什么下图中看不到test分支)

Git之旅(7):分支是啥?

你可能会有疑问,为什么没有看到test分支的分支指针,只能看到master分支,这是因为默认的视图在上述情况下不会显示所有分支,我们可以新建一个适合自己使用的视图,步骤如下:

点击gitk的View菜单,选择New view新建视图

Git之旅(7):分支是啥?

 

你可以根据自己的喜好给视图取个名字,我写入的名字叫allbranch,勾选Rememer this view,以便这个视图可以被保存,下次还可以从View菜单选择这个视图,勾选下图中红框中的选项,以便所有我们需要的信息都会显示

Git之旅(7):分支是啥?

按照上图设置后,点击下方的OK按钮,点击后,gitk即会使用我们创建的视图显示分支,于是,你会看到gitk如下图所示:

Git之旅(7):分支是啥?

如上所示,你会看到一个'分叉',能同时看到master分支和test分支,'分叉点'是'add C in m2'对应的提交,分叉点以后,master分支和test分支分别有一个新的提交,这两个提交分别属于master分支和test分支,虽然这两个提交属于不同的分支,但是它们有同一个父提交,就是'add C in m2'对应的提交。

 

对于上例来说,分叉点之前的提交仍然可以理解成是两个模块的逻辑交错在一条逻辑线的提交,因为无论对于master分支来说,还是对于test分支来说,这两个模块的提交都是交错的存在于master分支上或者说交错的存在于test分支上,分叉点之后的提交才是我们人为的、按照所谓的'模块逻辑'区分隔离在不同分支上的提交。

 

我们换一种方式,来描述一下上例的状态,如下图所示:

Git之旅(7):分支是啥?

当前我们处于master分支,你在master分支查看m2文件的内容,并不能看到字母D,同理,如果你切换回test分支,在test分支中查看m1文件的内容,也并不能看到数字4,这时,就可以让我们更加明显的体会到分支的作用了,以后,所有m1的修改都切换到master分支上进行并创建提交,所有m2的修改都切换到test分支上进行并提交,这样,就能将这些提交隔开在两个分支上,直到你想将两个模块的代码合并到一起时,利用分支的合并就可以了,但是分支的合并操作我们留在后文中总结,现在不急。

 

现在,你可以自己动手进行一些实验,按照我们刚才的规定,修改m1文件并在master分支上创建一些提交,然后再切换到test分支,修改m2文件并创建一些提交,(具体的操作步骤此处就省略了,你可以自己动手尝试一下),这样听上去似乎比较麻烦,但是在实际的工作中,你通常会检出某一个特定的分支,然后在这条分支上工作一段时间,在特定的分支上完成某项工作,并不会过于频繁的切换分支,上例之所以要频换的切换分支,只是为了展示出分支的使用方式和特性。

 

现在,我们来扩展一下,如果此时,我们想要在test分支上,回退到" add 2 in m1"时的状态,该怎么做呢?具体操作我就不在赘述了,聪明如你肯定已经胸有成竹了,等你操作完成后,记得用gitk看看两个分支的样子,然后再切换到不同的分支上,看看所有文件的内容,你就会更加强烈的体会到git的强大了。

 

再次声明一下,上例一直拿'分开代码之间的逻辑'来举例并不代表分支的作用仅仅是为了'分开代码之间的逻辑',之所以这样举例,是为了利用分支之间的提交互不影响的特性,所以,只要可以利用这个特性解决的问题,都可以使用分支,而且分支的命名也是根据你的需要命名的,只不过,经过不断的实践,人们往往会按照最佳实践中的方式去使用分支,所以说,上例只是为了方便快速的让我们理解分支的作用而已,我们现在不用考虑那么多,先理解分支的概念就好。

 

说到这里,我觉得我们肯定已经对分支的概念有了初步的认识,这是重要的一大步,因为在git中,是鼓励我们使用分支的,由于git的'智慧',我们在git中创建分支的成本其实相对较低(与其他版本管理软件相比),所以,理解分支,学会使用分支,能让我们更好的使用git进行版本管理的工作。

 

说到现在,也没有解释之前提到的HEAD是个什么东西,但是由于篇幅原因,我们暂且聊到这里吧,剩下的话题就留到后面的文章吧。

   

 

weinxin
我的微信公众号
关注"实用运维笔记"微信公众号,当博客中有新文章时,可第一时间得知哦~
朱双印

发表评论取消回复

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

目前评论:15   其中:访客  9   博主  6

    • avatar gavin 3

      想确认一下,如果在master分支下创建了三个commit对象,commit1、commit2、commit3,其中commit3是最新的对象,如果我将matser指针reset到了commit2,此时创建一个分支test,然后使用git branch -v 查看,其实test的分支指针指向的是commit2对象,而非matser分支最新的commit3对象

      • avatar fourq 0

        通俗易懂,对git原理的理解非常透彻,棒棒的 :wink:
        期待后面的多分支合并和github的pull/push,再添加使用git进行网页部署感觉能出一本书了。

        • avatar colorgg 0

          写的不错

          • avatar 赛腾的小猪 0

            写的很棒,这是我看到剖析最透彻的博主

            • avatar 大哥 1

              哥 我想学 容器,求求你写点吧,主流的docer k8s什么的

              • avatar kakc 1

                在这里学到了蛮多,就是没有使用的场景,学的快忘的也快。想请教下您如何才能成为Linux方面的大师呢

                  • avatar 朱双印 Admin

                    @kakc 其实,上文就是根据实际的使用场景进行描述的,其实只要你理解了用法,在实际工作中很快就可以对应上各种场景的,加油~

                  • avatar 陈程宸 1

                    写的很好,学到了很多,运维之路的领导者

                    • avatar 等待 3

                      恭喜博主。

                      • avatar skywind 0

                        写得太好了,应该去当老师或者培训师!期待后续更新!