Git之旅(6):从概念到实践

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

  

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

 

在git中,有一些"区域"的概念,我们在使用git时,其实一直都在使用这些"区域",所以,了解git"区域"的概念是必须的,这有助于我们使用git和理解git的工作原理。

 

从物理上来说,我们能直观的看到两个"区域",一个是"工作区",一个是"版本库"。

就拿我们之前的测试仓库举例,进入git_test仓库,你会发现一个名为".git"的隐藏目录,以及除了".git"目录的其他文件和目录,如下图所示

Git之旅(6):从概念到实践

其实,上图中的".git"目录就是所谓的"版本库",而上图中除了".git"目录以外的其他文件和目录组成了"工作区",换句话说就是,进入git_test目录,排除".git"目录以后的区域就是工作区,工作区的概念非常容易理解,因为我们的实际工作就发生在这个区域,我们在这个区域创建文件和目录、编辑文件、删除文件和目录,这些操作都是在工作区完成的,而当我们需要把工作区的状态保存到版本库时,则需要借助到"版本库"区域了,也就是说,两个区域的关系如下图

Git之旅(6):从概念到实践

当工作区的状态保存到版本库以后,工作区的文件就会转换成blob对象,工作区的目录结构会转换成对应数量的tree对象(前文中已经总结了git对象的概念,此处不再赘述),这些git对象存储在"版本库区域"的对象库中。

 

不知道你还记不记得,在前文中,当我们想要将一个状态保存下来时,通常会通过类似如下两条命令创建一个提交

如你所见,上述两条命令协力将工作区的状态保存到了版本库中,也就是说,仅仅靠上述一条命令是无法完成整个操作的,前文说过,我们可以利用"git add"命令选择将哪些变更加入到下一次的提交中,其实,当我们执行"git add"命令以后,工作目录中文件的状态就已经转换成blob对象了,当我们使用"git commit"命令以后,才会创建出commit对象。

 

其实,在工作区和最终存储的对象之间还有一个"重要区域",这个区域被称之为"索引"或者"暂存区",而刚刚提到的"git add"命令其实就是用来操作索引区域的,如果跟前文中的对象的概念结合在一起,那么索引区域应该在下图中的如下位置

Git之旅(6):从概念到实践

上图描述了第一次提交产生以后,各个区域的状态,从上图可以看出,工作区有两个文件,file1和file2,文件内容分别为f1和f2,在对象库区域中,第一次提交对应的commit对象(圆形)已经指向了对应的tree对象(三角形),tree对象又指向了直接子目录的blob对象,而此时,索引的结构与对象库其实是一样的,索引也指向了file1和file2对应的blob,或者说,索引中记录的file1和file2的哈希就是上图中那两个blob的哈希,目前来说,我们不用纠结索引到底是什么样的,在后文中我们会通过更加直观的方式去了解它,但是在这之前,我们先借助上图去搞明白它的概念和作用就好,只依靠上面一幅图就想完全搞明白索引的作用有些不太现实,最好坚持看完后文,再回过头来理解,就容易多了, 就目前的状态而言,索引和对象库中的状态是一致的,都指向了同样的blob。

 

如果此时,我们修改了工作目录中的file2文件,我们将其内容从f2改为了f22,那么此时,各个区域的状态如下图所示。

Git之旅(6):从概念到实践

当file2的内容变化以后,工作区file2文件的状态已经发生了改变,工作区中file2的新状态已经与索引区和对象库中的状态不一致了,索引和对象库中指向的仍然是file2的内容为f2时的状态(即上图中哈希为9de77c1的blob),这时,file2的新状态对应的blob对象还没有生成,file2的新状态只存在于工作目录中,那么file2的新状态对应的blob对象是在什么时候生成的呢?刚才其实我们已经剧透了,当我们执行"git add"命令以后,对应的blob就会产生,"git add file2"命令执行后,对象库和索引区的状态如下图:

Git之旅(6):从概念到实践

如上图所示,当"git add file2"命令执行以后,"git add"命令会做两件事,第一件要做的事就是为file2的新状态创建blob对象,也就是上图中浅灰色曲线所表示的步骤,当新的blob对象(即上图中哈希为a8319的blob对象)创建完成后,"git add"命令就会做第二件事,即更新索引,将索引中的file2指向新创建的blob对象,即上图中橘黄色曲线所表示的步骤,橘黄色的区线表示索引中的file2原来指向9de77c1,当"git add"命令执行后,索引中的file2指向了a8319f8。

此时,虽然在对象库中已经生成了file2文件的新状态对应的blob,但是仍然没有任何一个提交通过tree指向这个blob,前文一直在强调,提交(commit)代表了一个状态,我们如果想要恢复到某个状态,必须依靠提交,而上图中的a8319f8并没有任何一个commit通过tree指向它,所以,我们还差一步,就是创建提交,没错,执行"git commit"命令即可创建提交,提交命令执行后,会进行如下图中的操作

Git之旅(6):从概念到实践

当执行提交命令以后,git会根据索引中的结构,在对象库中创建出对应的tree对象,也就是上图中灰色曲线所表示的步骤,之后,git会创建一个commit对象,并且将新创建的commit对象指向刚才新创建的tree,于是,一个新的提交产生了,它记录了一个状态,我们可以随时通过这个提交回到对应的状态,而且这个时候,索引的结构和最新的提交所对应的结构是一致的。

 

如上图所示,这个新创建的提交也会指向前一个提交,每个提交都会指向自己的父提交。

 

到目前为止,我们已经结合"git add"命令和"git commit"命令,了解了对象创建的整个过程,并且明白了,整个过程会经历多个区域,我们先是在工作区对文件进行编辑修改,然后使用"git add"命令操作暂存区(索引)和对象库,将修改后的文件状态转换成git对象,并且将暂存区的指向结构更新,以便在下载提交创建的时候,可以根据当前暂存区的结构创建出对应的tree对象,直到一个新的提交生成,也就是说,它们之间的关系如下图所示

Git之旅(6):从概念到实践

到目前为止,我们已经了解了很多的基础概念,但是总感觉没有操作过几个git命令,不如我们来痛快一次,以命令操作为主,以解释为辅,更加清晰将概念和命令相结合吧,我们从创建仓库开始(下列操作中可能涉及一些新命令,不过在了解了上述概念以后,再看这些命令就非常容易理解了)。

 

初始化名为test的git仓库

 

进入test仓库

 

创建两个测试文件

 

使用"git status"命令可以查看哪些文件的状态有变更,按照上图中的理解就是,哪些文件在工作区的状态发生了变化,可以被加入到暂存区,以备之后创建提交,但是由于我们刚刚创建仓库,所有创建的文件都是新加入工作目录的文件,所以,当我们使用"git status"命令时(如下所示),会查看到两个"Untracked files",即"未被跟踪"的文件,从你的客户端看到的file1和file2应该是红色的字体,红色表示当前状态只存在于工作区。

而且从上述提示可以看出,使用"git add"命令可以追踪这两个文件,其实,我们也可以把新增文件理解成一种状态的变更,所以,如果想要在下次提交时保存这个状态,需要先使用"git add"命令,将它们的转化成git对象,你可以执行两条git命令分别操作两个文件,如下:

也可以使用"git add ."命令,将当前目录中所有处于变更状态的文件一次性的加入到暂存区中,执行上述命令,再次使用"git status"命令查看状态,命令如下:

在你的git bash终端中,你看到的file1和file2应该是绿色的,这表示这两个文件的状态已经被加入到了暂存区,而且从上述提示信息可以看出,"Changes to be committed"的文件有两个,这两个文件都是" new file",也就是说,新创建的commit中会提交这两个变更(chages)。

 

好了,现在来提交

提交完成后,使用如下命令查看提交历史

从上述信息可以看出,我们创建的第一个提交的哈希值为0e3363697eacbd43d7bc111b0a63c1cf1b6e4604

作者是zsythink,作者邮箱是zsy@zsythink.net

以及这个提交的创建日期。

 

好了,我们再来一遍,现在修改一下file1和file2文件,将file1的内容从f1修改为f11,将file2的内容从f2修改为f22。

 

再次使用"git status"命令查看工作区的变更,信息如下:

由于我们修改了file1的内容和file2的内容,所以,上述信息中显示这两个文件处于modified的状态,我们可以根据需要,将这些变更加入到暂存区,如果你觉得目前两个文件的状态都需要跟随下次提交进行保存,那么你可以将这两个变更一次性加入到暂存区,如果你觉得只有file1的状态适合下次提交时保存,file2的改动还没有完全完成,还需要继续修改,那么你只将file1加入到暂存区即可,这些都是可以根据需求灵活操作的。在你的git终端中,上述变更列表应该显示为红色,红色表示这些变更仍然只存在于工作区,还没有加入到暂存区,也就是说,这些新状态还没有被转换成git对象,它们只存在于你的工作目录中。此处为了示例方便,仍然一次性将所有变更加入到暂存区,然后使用"git status"命令查看,如下。

文件列表已经显示为绿色,证明它们已经被加入到暂存区,已经做好了被提交的准备。

 

我们现在已经有两个提交了,使用"git log"命令查看2个提交的详细信息,或者使用"git log"命令的--oneline选项,以简易模式查看提交的信息,示例如下:

简易模式中,会显示提交的哈希码,但是只显示哈希码的前几位,以及提交对应的注释信息, 最新的提交信息中会显示(HEAD -> master),这代表HEAD头指针指向了master分支,master分支指向了最新的提交,HEAD头指针和分支的概念我们后面再聊,此处不用理会它们。

 

当提交创建完成后,再次使用"git status"命令查看暂存区的状态,信息如下:

如上述信息所示,git显示,没有什么可以提交的,工作区是干净的,也就是说,工作区的状态,暂存区的状态,提交的状态是一致的。

 

通过上述重复的操作,我们已经初步掌握了如下命令

"git status":查看有没有变更的状态,并且查看哪些变更已经加入了暂存区,红色的变更表示只存在于工作区,还未加入暂存区,绿色的变更表示已经加入到暂存区,这些变更将会被提交。

"git add":将需要进行提交的变更加入到暂存区。

"git commit":将所有加入暂存区的变更作为一个变更集合,创建提交。

"git log":查看提交历史,此命令有很多实用参数可以使用,利用这些参数可以通过不同的方式查看历史,后面慢慢聊,不用着急。

 

有没有觉得自己对上述git命令、区域的概念以及git的工作原理有了进一步的了解呢?不如我们再进一步,通过git对象的思维方式来了解一下git,静下心来,把下文看完,之前说过,每个git对象都有一个身份证号,也就是其对应的哈希值,只要我们能够获取到git对象的哈希值,就能通过哈希值获取到git对象的一些信息,比如,通过哈希值判断git对象的类型,或者通过哈希值来查看git对象的内容。那么具体怎么做呢?其实很简单,借助一些git命令就行,就拿我们最近创建的提交来举例吧。

 

首先,通过git log找到我们最近创建的提交,如下:

从上述信息可以看出,最近的提交的哈希值为136146b,我们可以借助"git cat-file"命令,通过哈希值判断哈希对应的git对象类型,并且查看其内容,虽然我们已经知道136146b 这个哈希值对应的是一个commit对象,但是当你只知道哈希值的时候,可以通过如下命令获取到哈希对应的对象类型:

如上例所示,使用"git cat-file -t 哈希值"命令即可。

 

"git cat-file"命令的"-t"选项可以查看哈希对应的对象类型,而"git cat-file"命令的"-p"选项可以帮助我们查看git对象的相关内容。

 

比如,使用"git cat-file -p 哈希值"命令查看最近的提交对象的内容信息,示例如下:

从上述返回信息可以发现,最新的commit对象指向了一个tree对象,这个tree对象的哈希是22bb0fd......,同时,这个commit对象还指向了一个叫做parent的东西,这个叫做parent的东西也有一串哈希,那么我们来看看,这个哈希到底对应的是个什么东西,如下:

通过上述命令,我们看出这个哈希对应的其实是一个commit对象,那么我们再来看看这个对象的内容,如下:

从上述信息可以发现,这个提交的注释信息是"add file1 and file2",原来这个commit对象就是我们之前创建的第一个提交,这时我突然想起来,之前说过,一般情况下,每个提交对象都会指向自己的父提交,当然,第一个提交没有父提交。

 

让我们把目光重新放回到最新的这次提交中,最新的提交中除了自己的父提交,还指向了一个tree对象,那么我们来看看这个tree对象中都有什么,如下:

如上述信息所示,这个tree对象指向了两个blob对象,这两个blob对象都有自己的哈希值,这两个blob对象就是由file1文件和file2文件的状态转换而来的,你快通过"git cat-file"命令查看一下这两个git对象的内容吧,不正是f11和f22吗?

 

聪明如你应该已经发现了,我们通过提交的哈希,层层剥离,一直找到file1和file2对应的blob的过程,其实与之前图示中的对象库部分的对象指向关系不谋而合。

 

我们又掌握了两个小技巧:

"git cat-file -t 哈希值"查看对象的类型

"git cat-file -p 哈希值"查看对象的内容

 

再免费赠送你一个命令,通过简短的哈希值获取到整个哈希值,如下:

 

如果你坚持看到了此处,那么你可能会觉得越来越清晰了,如果你觉得越来越糊涂,不如从头看一遍,前后相互印证,也能加深理解,如果你的头脑仍然感觉很清晰,那么我们再换一个角度,来重新理解一遍上述过程,这次,我们进入".git"目录看看,看看会不会有什么新发现。

 

为了能够尽量减少干扰,我决定重新创建一个新的测试仓库,过程如下:

如上所示,我创建了一个新的测试repo,并且进入了仓库目录,不过这时候仓库目录空空如也,我们先来创建一些测试数据,并且将它们保存为第一个提交吧,过程如下:

现在,我们只将file1的修改加入暂存区,如下:

从上述信息可以看出,file1对应的颜色是绿色,它属于"Changes to be committed"列表,表示它准备被提交,而file2对应的颜色是红色,它属于"Untracked files"列表,表示它还没有被加入到暂存区,下次提交不会包含它的状态,也就是说,file1的状态其实已经被转化成git对象,存放在git的对象库中了,说到对象库,就能引出我们的".git"目录了,其实,git对象就存放在".git/objects/"目录中。先别急,我们先别跑的太快,先把第一个提交创建出来,如下:

提交变更后,使用"git status"命令,信息如下:

可以看到,由于file2的状态并没有添加到暂存区,所以上次提交并没有操作file2的状态,它是红色的,它仍然未被跟踪,我们先不理会它,先来看看刚才创建的提交

不错,第一个提交创建出来了,它的哈希是

4b3dfc8acb902ae15e32b167b009cc03330f54b1 。

 

现在,我们可以把焦点放在".git/objects/"目录中了,既然之前说过,git对象就放在这个目录中,那么我们在这个目录中肯定能找到刚才的提交,因为一个提交在对象库中就是一个commit对象啊,现在我们在这个目录中找找,看看能不能找到它,如下:

我们使用find命令查看了一下".git/objects/"目录中的内容,看看上述信息,是不是觉得有一个文件的文件路径跟刚才创建的提交的哈希码特别像,没错,.git/objects/4b/3dfc8acb902ae15e32b167b009cc03330f54b1这个文件其实就是刚才创建的提交所对应的git对象,那剩下的.git/objects/7e/4cccb4a643b0d9cb6ac9263779147c0937d0c4文件和.git/objects/8e/1e71d5ce34c01b6fe83bc5051545f2918c8c2b文件又是什么呢?使用"git cat-file"命令看看不就知道了(注意,git对象的哈希码的前两位以目录的形式存在,前两位以后的哈希码作为文件名)。

从上述信息可以看出,这两个git对象分别一个tree对象和一个blob对象,应该是刚才那个提交指向的tree以及tree指向的blob,如果不信,你就用之前层层剥离的方法,从commit对象一直找下去吧,你会发现他们的哈希值是对应的。

 

".git/objects/"目录中存放了git对象,那么之前所描述的"索引"信息,存放在哪里了呢?索引的信息其实存放在" .git/index"文件中,我们无法直接查看这个文件内容,如果想要查看这个文件中的索引信息,可以使用如下命令:

上述信息就是当前索引中的信息,可以看到,目前只有一个文件file1被索引记录了。

 

现在 ,我们继续一些其他操作,然后再来查看这些信息。

操作如下:

我们创建了一个目录,并且在其中创建了新文件file3,使用"git status"命令查看状态,如下:

由于上次我们并没有将file2的状态添加到暂存区,所以,它和dir1一样都显示为未跟踪,由于只会显示当前目录的结构,所以只显示了dir1/而没有显示dir1/file3,不过当你使用"git add"命令将dir1/添加到暂存区时,dir1目录中的所有文件都会被加入到暂存区,为了方便,此时一次性将当前目录的所有变更加入到暂存区

如上述信息所示,所有变更都已加入到暂存区,那么我们看看索引文件有没有更新

可以看到,索引文件已经更新了,file2以及dir1/file3都已经存在于索引列表中了,那么对象库中肯定也已经生成了对应的blob对象了,注意,此时tree对象还没有生成在对象库中,之前说过,tree对象是在提交命令执行后才创建的,我们看看对象库中的文件,如下。

可以看出,对象库中的blob文件也已经生成了,现在我们要做的就是提交了

再次查看对象库

你会发现多出了几个对象,多出的对象分别是新创建的commit对象,根目录的tree对象,以及dir1对应的tree对象。

 

其实,你应该手动单独再次修改一次file2的内容,并创建一个提交,然后再次使用上述方法,结合层层剥离的方法,自己再将上述过程重复一遍,在每操作一步时,都查看一下索引文件的内容,以及对象库中的内容有什么变化,这样,你就会更加理解上述整个过程了,我就不在文章里面啰嗦了,快动手试试吧。

 

最后,这里记录一个小问题,如果你跟我一样,喜欢在windows的git bash中使用vim编辑文本文件,那么,在你每次使用git add命令可能都会出现类似如下warning

这是由于换行符冲突引起的报警,因为git bash默认使用vim作为文件编辑器,vim默认使用LF作为换行符,与linux中的换行符一直,它们都是用LF换行符,但是windows默认使用CRLF作为换行符,大多数程序员都会使用IDE或者文本编辑器来编辑文本,这些编辑器通常能够自动识别换行符,除了windows自带的记事本等文本编辑器,记事本只会使用CRLF作为换行符,由于我习惯使用vim编辑文本,所以文件中的换行符都是LF,当git检测到时,它会贴心的帮我装换一下,但是其实我并不是特别需要,因为我在bash中不会使用记事本编辑文件,所以,我们可以禁用自动转换的功能,使用如下设置,禁用自动转换换行符:

当然,如果你的习惯就是使用CRLF换行符的编辑器,那么目前你是不会遇到上述问题的,所以你可以根据需要选择是否进行上述设置。

 

我们从理论到实践,从命令到文件,从头到尾的跟踪了一遍git工作的过程,这样有助于我们加深理解,这篇文章就先总结到这里,希望能够对你有所帮助~

 

 

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

发表评论

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

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

    • avatar 卡卡你真卡 0

      讲的太好了,一字不落的看完了。老哥能讲一哈git commit -a是怎样一个数据结构处理过程吗?

      • avatar 李阿斗 0

        老哥,加个友情链接吧
        www.lijinghua.club

        • avatar whenknown 0

          原理上面是理解了,谢谢博主.后面的git命令操作方面就靠我们自己学习了…哈哈哈

            • avatar 朱双印 Admin

              @whenknown 最近手忙脚乱,所以一直没有进展,等闲下来会继续总结的,可以先自学了解一下常用命令。

            • avatar andy 0

              那怎么回滚呢

              • avatar 夏123 0

                git就完了吗?

                • avatar 等待 3

                  感谢博主讲解。