Git:Git内幕
作者:互联网
一、深入Git内幕研究的引子介绍
从这一讲往后,我们会不少讲,是进入这个git课程的最后一块内容,就是git内幕原理
简单带一句,我现在的一个授课思想,刚开始学习一个技术,必须先短平快引入,入门要快,把这个技术最核心需要用到的一些东西,用一点简单的demo带一些,快速的就入门了
在入门的同时,用的是那个技术最核心的一些东西,直接把这个技术最最根本,最最基础的原理,给大家阐明
刚有个感觉,体验,功能背后的一套比较根本的原理是那样子的,三个区域
就是纯实战驱动,这个技术在实际企业中是怎么来运用的,如果是类似maven,git这种技术,是没法做项目实战的,融入在我们后面的大项目里的,贯穿了整个做项目的环节
但是我们可以把企业中常见的一个一个的实战场景,应用场景,反过来推出,我现在有这么一个企业里的而应用场景,需求,问题,然后呢,我的git技术有哪个命令或者说是功能,可以解决他??
每个技术讲解完之后,每个git的命令,如果有可能,再给大家带一带,深入剖析一下命令背后的原理。功能分支工作流实战切入,分支那一套的内幕原理,后面的一系列的命令
实战驱动,深入原理
收尾的那一段,精华,比如说git,其实它是有自己的一套内幕原理的,非常深入的去探索一下git内幕里的原理,把深入的最后一块东西给讲好,整个这个技术学习,完美
(1)学习一些开源技术背后的原理和思想
(2)加深你的技术内功,会用一大堆技术有什么用?但是如果你对很多技术背后的内幕原理和整体上的架构设计思想,都很精通,那么你的技术内功会很强悍 => 你在后面做项目的时候,设计,解决问题,得心应手 => 内功越深厚,你以后在学习类似技术,或者其他技术的时候,速度就会更快
(3)直接能够提升你的以后做自己项目时候的技术能力 => 吸收了很多开源技术的设计思想,架构,方案,机制 => 运用到了你自己的项目里,你会发现很多问题和场景,都可以吸取他们的思想去解决
(4)如果你的很多技术内功很深厚的,就业,职场发展 => 99个程序员的简历上写的都是熟悉spring boot,spring cloud等技术的使用,有过什么什么经验 => 深入阅读过spring boot和spring cloud的源码,对其架构体系和设计原理较为精通 => BAT,简历上的亮点 => 面试的时候,如果你能把这些深入的东西讲出来,架构师,技术专家,高级工程师,BAT,中小型公司,都会有很大的优势
一张思维导图带你梳理清楚学习到的技术体系,maven课程,反馈都很好
授人以鱼不如授人以渔:学习方法,如何去自己去未来更加深入的研究和学习相关的技术,自己如何能够走得越来越远
包括我之前讲的很多讲,Git高阶实战技巧,讲了一大通,“废话”,精华
光讲技术,是没什么稀奇的
稀奇的是,我在公司里什么场景下,是怎么用这个技术的,经验,会花很多时间去给大家铺垫各种git命令讲解之前的场景,那些东西我认为比git命令更重要,几十个git命令,你都不知道用来干嘛的,过了2个月,保证你全部忘记
记忆曲线,如果脑海中有个场景,有个图片帮助你记忆,你的记忆的遗忘会慢很多,记忆清晰很多,记忆的时间会长很多
我觉得,知其然而知其所以然,你不要光学,你为什么要学,你学了以后有什么用
BAT,晋级答辩,职级体系,升级打怪
每次去要网上晋升一级的时候,非常强调你的思考的能力,你不要告诉我你做了什么?你告诉我,你为什么要做,你为什么要这样做,你这样做了以后效果是什么,作用是什么?
技术评审委员会的成员之一,参加过很多初中高级工程师的晋级答辩,工程师,what,我做了什么
技术工程师,必须要懂得思考,梳理,总结,抽象,提取。为什么做,为什么要这样做,这样做以后我对公司的产出是什么,价值是什么,业务的促进是什么
软素质真正强悍的人,才可以去做技术经理,技术总监,CTO,CEO
1.1、Git内幕研究的一个思路
git add,git commit,git push,这些命令背后在干什么?git内幕原理
不是纯讲,要在动手实践中,去给大家讲请清楚这些命令背后的原理是什么?
porcelain命令,这些命令,指的就是类似git add,日常开发中,程序员会直接使用的git命令
这些porcelain命令底层都是调用的一些git的底层命令,plumbing命令,实际上是不是给我们用的,是给git的那些高阶命令去用的
我们在这里讲解git add这些高阶命令背后的原来,是带着大家动手实践去体验一下底层的命令是用来干嘛的,当你把那些对应的底层命令都学会之后,明白那些底层命令在干什么,接着你就会瞬间明白,git add,git commit这些高阶命令,你在执行的时候,他们背后在干什么 => 内幕原理,你就全部清楚了
1.2、.git目录研究
平时我们如果执行git init之后,就会生成一个.git目录,其中就是git存放所有数据的地方。如果我们拷贝这个.git目录,那么就相当于备份了一个完整的git项目的数据。
在.git目录中,有如下一些内容:
config
description
HEAD
hooks/
info/
objects/
refs/
description文件仅仅是由Git WebUI程序去使用的,我们一般不用去管这个文件
config文件包含了这个git项目的所有配置项
info目录中包含了我们在.gitignore文件中定义的不需要git追踪的那些被排除掉的文件
hooks目录中包含了我们的client-side或者是server-side的钩子脚本
其实上面那些都不是最重要的,最重要的是这几个东西:HEAD文件,indexes文件,objects目录,以及refs目录。
objects目录存储了git中所有的数据,refs目录中存储了所有的指针(包括了branch,tag,remote,等等,指向了objects目录中的数据),HEAD指向了当前我们所处的分支指针,indexes文件存储了暂存区中的内容。
把接下来的课程听懂之后,你就会了解常见的git命令背后的原理,还有git命令结合.git目录的存储相关的原理
二、在动手实践中掌握提交代码的内幕原理
2.1git数据存储机制
2.1.1git hash-object初体验
git的数据存储格式实际上是一种非常简单的key-value存储格式。就是说,git支持将任何东西存储到其数据库中,一般就是存储文件的内容,然后用一个key值来引用它。
(1)首先随便找一个目录,初始化为git项目:
git init
此时去.git/objects目录中检查一下,发现只有两个子目录,info和pack,没有任何文件,此时数据库是空的。
用下面命令,可以手动将一个字符串作为value存储到git的数据库(objects,git的数据库,用了比较简单的方式去存储)中去,会看到返回了一个hash值来引用那个字符串value:
echo 'hello world' | git hash-object -w --stdin
解释一下上面的命令:
git hash-object就是git的一个底层命令,一般我们是不会直接用的,一般都是git add,git commit之类的命令在调用这种底层命令。
git hash-object命令,默认的表现是这样的,接受一个value,然后针对这个value返回一个hash值,默认是不将value写入自己的数据库的
但是使用了-w之后,git就会将value写入数据库之中,同时用hash值作为key来引用那个value对应的文件。同时–stdin指的是从命令行接收value,也就是echo ‘test content’中的test content。如果不使用–stdin,那么要求是在hash-object之后跟一个文件名,它就会将一个文件作为value存储到数据库中去,然后返回一个hash值。
git返回的是SHA-1 hash值,是40位的字符串,是根据文件内容计算出来的一个checksum,校验和。
2)git的文件存储机制(轻量级索引机制)
你往git里存储一个文件,git一定会根据文件的内容计算一个40位的hash值出来,作为那个文件的名称,也是指针,40位hash值就可以来引用这个文件。存储的时候,40位hash的头2位,会作为目录名称放在objects里面,然后那个存储的文件会放在那个目录中,文件名是40位hash值的后38位。
大家来思考一下,git作为一个高性能的版本控制系统,一大特点就是性能超高,在切换分支的时候,都需要进行大量的磁盘读写,磁盘存储机制是比较值得我们注意的
将40位hash值的头2位作为目录,其实你可以认为是一种轻量级的索引机制
这样的话,有一个好处,就是每次你要根据一个hash值定位一个数据的时候,直接根据头2位先定位到一个目录,然后再在这个目录下去查找,linus写git的时候,就是用了一种非常轻量级的文件索引机制
直接查看git存储的文件的内容是不ok的,我们后面会讲解要用git专用的底层命令来查看
3)git cat-file体验
使用下面的命令可以查看刚存储进去的文件值,git cat-file是另外一个底层命令,就是专门用来查看存储到git中的文件的内容的:
git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
4)一个文件的多个版本存储机制的体验
下面的命令可以将一个文件存储到git数据库中:
echo 'version 1' > test.txt
git hash-object -w test.txt
echo 'version 2' > test.txt
git hash-object -w test.txt
git会将一个文件的多个不同版本,以完整快照的方式存储在objects数据库中,每个版本都用一个单独的文件来存储,每个版本的文件都有一个不同的hash值。查看每个版本存储的情况:
使用下面的命令可以查看.git/objects中所有的文件:find .git/objects -type f
接着可以试着将这个文件给删除,然后从git数据库中再恢复回来这个文件:
rm -rf test.txt
git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
cat test.txt
也就是说,我们只要将数据存储到了git中,那么就不用担心丢失了,随时都可以找回来这个文件的任意一个版本
我们用下面的命令可以查看一下git中存储的文件的类型,默认都是blob:
git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
这里,我们就发现跟之前给大家讲解的那个原理就可以串起来了,因为实际的文件的内容,每个文件的版本快照,会作为一个blob object存储在git数据库中
git数据库,git仓库,database,repository
5)梳理一下
实际上,就是一个文件的不同的版本,都会用一个完整的快照的形式,作为一个单独的文件存储在git数据库中,所谓的完整快照,每个版本,都是用一个文件存储这个版本的完整的内容的
每个版本的文件内容,都是通过blob object的类型存储在git数据库中的
(2)tree object
git有另外一种object叫做tree object,每次将一堆文件的一个版本存储到git仓库中,都是用blob object来存储那些文件的版本,然后用一个tree object来引用多个blob object。tree object通常会包含指向其他多个blob的指针,或者是其他tree object的指针。用下面的命令,可以查看master分支指针指向的那个tree object:
git cat-file -p master^{tree}
git通常是基于暂存区中的内容来创建一个tree object的,我们首先需要在暂存区中加入一些内容,然后可以试着手动创建一个tree object。可以将仓库中存储的某个commit object的内容放入暂存区中:
git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt
git update-index:底层命令,一般是git add命令去调用的,将数据库中存储的某个blob数据的引用放到暂存区中
–add:因为暂存区中还没有这个文件,你可以认为这个文件是第一次进入暂存区,所以需要这个选项
–cacheinfo:不是从工作区中加入文件放到暂存区,是从git仓库(git数据库)中加入文件
100644 mode:这种模式的意思是,这个是一个普通的文件
接着使用下面的命令基于暂存区中的内容,创建一个tree object:
git write-tree:将暂存区中的内容创建为一个tree object,tree object也是放入了git仓库,主要是包含了对一个blob obejct的引用
git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
下面我们再加入一个new.txt文件放入暂存区中,同时将test.txt文件的另外一个版本放入暂存区中,再次创建一个tree object:
echo ‘new file’ > new.txt
git update-index --add new.txt,这个就是将工作区里的内容直接放入暂存区
git update-index --add --cacheinfo 100644 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
git write-tree
git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
此时会发现这个tree object对应着两个文件
下面的命令可以从仓库中读取一个tree object放入暂存区,然后再基于这个tree object创建一个新的tree object出来:
所谓的将仓库里的东西读出来放到暂存区,实际上只是将一些引用放到暂存区,但是blob,tree,实际还是存储在objects中的
git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
git write-tree
git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
此时tree object包含两个文件和一个tree object,如果将这个tree object的数据恢复到工作区中,就会有两个文件和一个叫做bak的文件夹,bak文件夹中也包含了一个文件。
(3)commit object
随着你不断的在工作区中编写代码,然后呢,每次如果执行了一些相关的操作,可以将当前这个时刻,有变化的文件的版本都存储到git仓库中,同时放入暂存区中。
此时,暂存区中,没有变化的文件,还是保持着之前的版本;有变化的文件,暂存区中会存放最新版本的blob对应的引用
然后,如果此时创建一个tree object,就是基于暂存区中当前所有的文件的版本的blob,创建一个tree
此时,这个tree object就代表了这个项目的所有代码的此时此刻的一个完整的快照
现在我们实际上是有3个tree object,分别代表了项目的不同时刻的快照。但是此时此刻,我们有个文件,就是不知道那些tree object是谁添加的,为什么添加,什么时候添加的,什么都不知道。
但是,光有tree object是不够的
此时就需要commit object了。
用下面的命令,基于已有的tree object创建一个commit object,同时给这个commit object一个提交备注:
echo ‘first commit’ | git commit-tree d8329f
然后查看一下这个commit object,会发现包含了一个tree object,同时又作者,提交日期,以及提交备注:
git cat-file -p fdf4fc3
commit object的含义很简单,就是指向了一个tree object,而那个tree object指向了多个blob,其实这个tree object就代表了某一时刻项目中所有代码文件的快照版本,同时那个commit object包含了作者、提交时间、提交备注,此外还包含了指向上一次commit object的指针。
同样的,我们可以为另外两颗tree object分别创建一个commit object,代表了另外两次提交:
echo ‘second commit’ | git commit-tree 0155eb -p fdf4fc3
echo ‘third commit’ | git commit-tree 3c4e9c -p cac0cab
此时相当于我们就有了三次跟真实一样的提交了,可以用git log来查看一下提交历史:
git log --stat
就会看到最近的3次提交
其实上面的实验步骤就彻底揭示了git的底层原理了git add和git commit命令执行时使用的就是上面那些低级别的命令:
git add:相当于是将你的有修改的文件作为一个新的版本,采用单独的blob文件存储到仓库中去,同时将仓库中的文件更新到暂存区中
git commit:相当于就是将暂存区中的文件快照,也就是对应的blob指针创建一个tree object,写入仓库中,同时再创建一个commit object包含了提交人/提交时间/提交备注,来指向那个tree object,代表了一次提交对应的仓库快照
blob、tree和commit,三种object都是作为一个独立的文件在objects目录中存储的,存储时的子目录命名和文件名,都是基于hash值来的
最后用一张图来说明,到目前为止,整个commit object、tree object和blob的关系。
(4)SHA-1 hash值如何计算
首先blob的内容分为两块,一个是header,一个是content。header就是类型+空格+长度+\u0000。然后会用header+content来计算出一个SHA-1校验和,作为hash值。
(5)梳理一下
git update-index --add 文件名:其实就是,我们在工作区里修改了文件以后,会用这个底层命令,将工作区中有修改的文件的一个版本,放入git仓库中作为一个blob去存储这个版本,同时将这个blob的引用放入暂存区中
多次有git update-index之后,git仓库中会存储项目中每个文件的所有版本,同时在暂存区中保留了每个文件的最新版本,没有变化的就保留值钱的版本
执行git write-tree,就可以针对暂存区中当前项目的最新版本,创建一个tree object,引用项目中所有的文件当前此时此刻的一个版本对应的blob,tree object,就代表了项目当前的一个完整快照版本
接着执行git commit-tree,就可以基于这个tree object创建一个commit object,包含了tree object的创建人,创建时间,备注信息,等等,作为一次提交
(6)将上层命令和底层命令结合起来
1)我们首先在工作里各种新增、修改文件
2)我们执行git add --all .这个命令:此时会将工作区中有变化的文件,作为一个新的版本,直接存储到git仓库中去,作为一个blob来存储;同时在暂存区中加入这些最新版本的blob的引用,替换同一个文件之前在暂存区中的版本
git update-index
3)我们执行git commit,同时给出一个提交备注:此时会针对暂存区中现在的所有的版本,创建一个tree object,作为项目此时此刻的一个快照版本,放入仓库;另外再创建一个commit object,包含了提交人,提交时间,提交备注,来引用这个tree object
4)到此为止,完成一次git add和git commit
5)整个git内幕原理,剖析的非常清楚了
(7)最后再来一张图,结合命令,演示一下
1)编写两个文件,test1.txt和test2.txt
2)git add --all .
将两个文件的第一个版本,作为两个blob保存到了Git仓库中,同时也放入了暂存区中
3)git commit -m “第一次提交”
基于暂存区里的两个文件的第一个版本,创建了一个tree
然后基于提交信息创建了一个commit,引用了那个tree,此时这个commit就代表当前这个项目此时此刻的一个完整的版本
4)修改test1.txt位第二个版本
5)git add --all .
将test1.txt的第二个版本作为blob存储到git仓库中
同时将test1.txt的第二个版本覆盖了暂存区中的test1.txt的第一个版本
6)git commit -m “第二次提交”
创建了一个tree,引用了test1.txt的第二个版本,test2.txt的第一个版本
另外创建了一个commit,引用了那个tree,代表了此时此刻项目的一个完整的版本
7)每个commit,就是当前项目的一个完整的快照版本,包含了项目的所有文件的一个最新版本
8)为什么大公司里,规范项目里,对git commit都有要求的,不要随便瞎提交,都是完成一个完整的功能或者模块之后再提交一次,尤其是每天提交一次,或者每天提交多次,每次代表了一个完整的功能和模块
9)整个你的项目的提交历史,就是项目版本的变迁的一个完整的历史,每个commit都代表引入了一个新的功能或者是模块
标签:文件,存储,git,object,tree,Git,内幕,commit 来源: https://blog.csdn.net/weixin_42488772/article/details/117191325