晓夏

YoungCheung

Zhang Sir's technical way

Git原理深入浅出

浏览量:332

Git是什么?

Git是一个开源分布式版本控制系统,可以高速并且有效的处理很小到非常大的项目版本管理。Git是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开源版本控制软件

什么是版本控制?

版本控制一般是指程序开发过程中的代码版本,配置文件及说明文档等文件修改变更的管理,版本控制最主要的功能就是追踪文件的修改变更等操作,称之为版本控制!

例子-代码

公司研发部门接到老板要求开发一套为公司内部运营人员所使用的内部管理系统,公司研发有主管、小张、小王、小徐等四人,项目需求初步定为此套系统需要20个功能,他们四人各负责开发5个功能,一个月后他们各自写好了自己所开发的功能;主管说:“把你们的代码都给我吧,我把咱们的所有代码整合到一起”,随后,20个功能被主管整合到了一起,因此,此项目的“初版”已成型,然后把代码放到测试环境进行测试,发现小张的某个功能有问题,让小张找出BUG并修复,小张修复后,整个项目的代码等于在“初版”之上调整一次,成为了“二版”,再次上测试环境进行测试之后,完整通过,老板开始验收他自己想要的这20个功能,老板看到功能后发现其中有8个功能标准不符合他预期,要求重做,并且再次增加5个功能,研发部门开始分工重做及开发新功能,研发累死累活三天,每人都熬了两个迷人黑眼圈后,修改及新开功能完成,测试环境通过,这次,是第三次修改代码,我们这里称之为“三版”,然后再次向老板提功能验收,老板验收成功,研发挑了个黄辰吉日,然后提出新系统上线申请,申请通过后,通知运维大哥们,要求黄辰吉日那天一起协助新系统上线,新系统毕竟首次上线,线上版本定为0.1V版本!
例子讲解:主管、小张、小王、小徐他们四人初期各开发5个功能,姑且不说他们各自对自己所开发的功能是怎样管理的,他们20个功能整合到一起后,第一个“初版完成”,测试到小张的某个功能有问题,小张修复后,“二版完成”,这个时候就可以使用版本管理系统,每次代码的变更都属于版本控制的管理范围之内;例如变为了二版,形成了新的目标版本,而对于不受变更影响的配置项则不应发产生变动。同时,应能够将变更所产生的对版本的影响进行记录和跟踪。必要时还可以回退到以前的版本。例如当开发需求或需求变更被取消时,就需要有能力将版本回退到开发基线版本。在曾经出现过的季度升级包拆包和重新组包的过程中,其实就是将部分配置项的版本回退到开发基线,将对应不同需求的不同分支重新组合归并,形成新的升级包版本。

例子-简历

假如我们想修改简历的项目经验这一块内容,但是又怕修改后还不如不改之前更令人信服,我们常规的办法就是,复制一份“副本”,在副本之上做修改,反反复复几次,就变成了下面这样,例如过了两个月,你又想跳槽了,你想找到简历内你想要的内容,只能依次打开去找,真头大!
fa834512933f.png
这个时候我们就可以使用到版本管理系统,版本管理系统功能同上讲的代码例子一致

例子-设备清单

116af6d12e11.png
同上也可以使用版本管理工具

版本控制系统

本地版本控制

其中一种叫做 RCS,很少见的系统,已经被淘汰掉了,说实话我没听过, 它的工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。你们忘了吧RCS吧,记住他的工作原理就行。保存补丁集
453cd96ae8c5.png
我们经常用的就是复制整个项目目录的方式来保存不同的版本,或者进行版本迭代及时间迭代,这么做的唯一好处就是简单,但是特别容易犯错,有时候会混淆所在的工作目录,一不小心会写错文件或者覆盖掉重要文件。
为了解决这个问题,很久以前就开发了许多种本地版本控制系统,在本地搭建版本控制系统,大多都是采用某种简单的数据库来记录文件的历次更新差异。便于版本回退等操作。
问题:我们项目组内有8个人,8个人如果在不同的系统上协同工作呢?
这个就是下面我们要讲的集中化版本控制系统了

集中化版本控制

集中化的版本控制系统(Centralized Version Control Systems,简称 CVCS)应运而生。
这类系统,诸如 CVS、Subversion 以及 Perforce 等,都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。 个人理解,像一个FTP服务器,文件上传下载以及修改都要在本地使用可ftp客户端来下载或者上传文件,多年以来,这已成为版本控制系统的标准做法。
c558fe753311.png
这种做法带来了许多好处,特别是相较于老式的本地 VCS 来说。 现在,每个人都可以在一定程度上看到项目中的其他人正在做些什么。 而管理员也可以轻松掌控每个开发者的权限,并且管理一个 CVCS 要远比在各个客户端上维护本地数据库来得轻松容易。
接下来我们讲讲坏处:
1.这种集中化版本控制的情况必须要联网吧?无论是公网还是公司内网都需要接入internet,假如我在无网环境下需要这些文件呢?例如在飞机上需要紧急工作,更改代码,却发现连接不了版本控制服务器!!!
2.由于是集中化的版本控制,需要一台单独的管理服务器来做版本控制系统吧,如果集中管理服务器的单点故障。 如果宕机一小时,那么在这一小时内,谁都无法提交更新,也就无法协同工作。 如果版本管理数据库所在的磁盘发生损坏,又没有做恰当备份,毫无疑问你将丢失所有数据,包括项目的整个变更历史,只剩下人们在各自机器上保留的单独快照。 本地版本控制系统也存在类似问题,只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险。Subversion采用的就是集中化版本控制系统。
问题:那么怎么让大家同时协同工作的时候不需要联网又不怕版本控制服务器单点故障及磁盘损坏呢?
首先,如果公司money充足,可以给版本管理服务器做个热备服务器,磁盘可以做RAID5或者RAID10
但是,我们无法解决大家在无网络的情况下正常工作,我们下面要讲的分布式版本控制系统可以很好的解决这个问题

分布式版本控制

为了解决大家同时协同工作的时候不需要联网又不怕版本控制服务器单点故障及磁盘损坏,于是分布式版本控制系统(Distributed Version Control System,简称 DVCS)面世了。 在这类系统中,像 Git、Mercurial、Bazaar 以及 Darcs 等,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。整个仓库的镜像复制到本地以后还需要联网吗?不需要了吧,除非大家需要同步数据,我们每人的客户端都可以成为版本控制系统平台, 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。
6c44d6c80ec6.png
举个例子,要浏览项目的历史,Git 不需外连到服务器去获取历史,然后再显示出来——它只需直接从本地数据库中读取。 你能立即看到项目历史。 如果你想查看当前版本与一个月前的版本之间引入的修改,Git 会查找到一个月前的文件做一次本地的差异计算,而不是由远程服务器处理或从远程服务器拉回旧版本文件再来本地处理。
许多这类系统都可以指定和若干不同的远端代码仓库进行交互。籍此,你就可以在同一个项目中,分别和不同工作小组的人相互协作。 你可以根据需要设定不同的协作流程,比如层次模型式的工作流,而这在以前的集中式系统中是无法实现的。

Git特性

• 速度
• 简单的设计
• 对非线性开发模式的强力支持(允许成千上万个并行开发的分支)
• 完全分布式
• 有能力高效管理类似 Linux 内核一样的超大规模项目(速度和数据量)

文件存储方式

Delta Storage

cvs和svn的文件存储方式采用Delta Storage的方式,我们画了一张图来演示Delta Storage的存储原理:我们有四个文件;
8a625faba0ae.png
在第一阶段都为同一状态
第二个阶段后fileB和fileD发生了修改,这个时候Delta Storage将会对修改过的fileB和fileD进行一次快照,快照备份内容为fileB和fileD修改后和修改前进行差异比较,备份差异内容,并记录快照,而fileA和fileC没有发生修改则不记录
第三阶段后file1和fileC发生了修改Delta Storage也将会对fileA和fileC进行一次快照,本阶段fileB和fileD未发生修改则不记录
第四阶段和第五阶段也是一样
总结:Delta Storage只对发生修改过的文件进行快照备份,快照备份保存的是本次修改和上次文件的差异,未发生修改的文件不记录。

DAG Storage

Git所采用的文件存储方式为DAG Storage,也是画了一张图来演示DAG Storage的存储原理:同样有四个文件
3dcb3d94a50e.png
在第一阶段都为初始状态
第二阶段,fileA发生了第一次变化,DAG Storage就会对fileA进行一个快照并保存这个快照的索引,其它文件未发生变化,DAG Storage为了高效,会在此阶段会保留一个链接指向之前存储并未修改的文件
第三阶段,fileC发生了第一次变化,DAG Storage它进行快照备份为C1状态,DAG Storage也会对fileC进行一个快照并保存这个快照的索引,其它未发生改变的文件,继续保留一个链接指向未修改的文件
第四和第五阶段也是如此
总结:DAG Storage并不像Delta Storage一样的存储数据,DAG Storage是把数据看做小型文件系统的一组快照,假如在Git中,你每次提交更新,DAG Storage对你的的全部文件制作一个快照并保存这个快照的索引。 为了高效,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 快照流。

存储形式

每个存储方式都有三种形式来方式来存储数据,就是我们上面讲到的三种版本控制系统
Local == 本地版本控制存储方式
Centralized == 集中化版本控制存储方式
Distributed == 分布式版本控制存储方式

Delta Storage

b85b4167fb06.png
Local本地版本控制存储方式的版本控制系统有我们上面讲的我都没听过的rcs系统
Centralized集中化版本控制存储方式的版本控制系统有cvs、Subversion、perforce,这几个系统我们应该略有耳闻,特别是Subversion SVN,SVN是Delta Storage存储方式的代表作,可以说SVN是基于在CVS之上所构建的一套系统,有继承CVS的宗旨,也有创新
Distributed分布式版本控制存储方式的版本控制系统有darcs、mercurial,darcs是基于rcs之上的版本控制系统,mercurial是走商业化的版本控制系统

DAG Storage

bcfa8cc1a5db.png
Local本地版本控制存储方式版本控制系统有我们传统使用的cp -r和time machine系统
Centralized集中化版本控制存储方式的版本控制系统有bitkeeper,但bitkeeper又有Distributed分布式存储系统的特性
Distributed分布式版本控制存储方式的版本控制系统有git、bazaar,其中git是DAG Storage的代表作

Git工作原理

三种状态

Git有三种状态分别是:已提交(committed)、已修改(modified)、已暂存(staged)
已提交(committed):已提交标识数据已经安全保存在本地数据库中。
已修改(modified):已修改表示修改了文件,但还没存到数据库中。
已暂存(staged):已暂存表示对一个已修改的当前版本做了标记,让此包含在下次提交的快照中。

三个工作区域

Git三个工作区域的概念分别是:Git仓库、工作目录、暂存区域
39e6b77114ba.png
Git仓库:目录是Git用来保存项目的元数据和对象数据库的地址,这个Git中最重要的部分,从其它计算机克隆仓库时,换句话说,就是从Git客户端来获取数据时,拷贝的就是这里的数据,Git仓库!我们安装好Git之后,在我们初始化的工作目录下有一个隐藏的.git目录,.git目录内有个object database目录,此目录即为Git仓库。
工作目录:工作目录是对项目的某个版本独立提取出来的内容。这些从Git仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或者修改的一个目录。要想git能够工作,就必须要设置其工作目录,创建一个目录,然后使用git init 目录名称来把此目录定义为工作目录。
暂存区域:暂存区域是一个文件,保存了下次将提交的文件列表信息,一般在Git仓库目录中。有时候也被称作“索引”,不过一般说法还是叫暂存区域。在我们安装好Git以后,在我们初始化的目录下有一个隐藏的.git目录,.git目录内有个index目录,即成为“暂存区”又叫做“索引目录”。

Git工作流程

1.在工作目录中修改文件。
2.暂存文件,将文件的快照放入暂存区域。
3.提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。
总结:如果Git目录中保存着特定版本的文件,就属于已提交状态。如果做了修改并放入暂存区域,就属于已暂存状态。 如果自上次取出后,做了修改但还没有放到暂存区域,就是已修改状态.

Git是怎么存储数据的

Git配置文件简介    

Git安装完成后,会在初始化的工作目录下生成一个隐藏的.git文件,展开.git文件有以下文件
167dd2393415.png

object database是怎样存储对象的

351dfad10a4d.png
参数解释:
content:假如为一个文件的初始版本
new_content:为一个content修改后的content,被称为新的文件,在初始版本上进行了哪些操作才会生成一个新的new_content呢?
type:文件类型,在这里表示content的文件类型
content.size:content文件内容的大小
\0:content的元数据
hash:以计算校验和的机制叫做 SHA-1 散列(hash,哈希),由40 个十六进制字符(0-9 和 a-f)组成字符串,基于 Git 中文件的内容或目录结构计算出来
新文件生成解释:
一个新的new_content生成 = content的文件类型 + content文件内容大小 + \0文件的元数据 + 加content本身来做hash进行计算成hash码
最后文件名称被计算成了hash码保存在object database中,既然保存的是hash码,那么每个文件的hash是绝对不会重复的。
当我们读取某个文件时,git就会根据它的hash码来索引到文件位置并打开呈现给你,那么git只存储了文件计算的hash码?NO、NO、NO
文件内容默认通过zlib进行压缩归档存储了,那么归档到哪里了呢?
默认git是截取文件的hash码的首两位作为子目录来分层存放这个文件,如上图所示,前两位是hash码作为子目录,后面38位hash码为压缩后的文件名称

Git的四种类型对象

工作目录下有子目录,当我们工作当中把工作目录内的文件删除了,但是.git目录未被删除,现在需要使用object database来恢复,恢复回来是指定没有问题的,但是我们不但要恢复文件,还要恢复回来文件所属的文件目录层级结构,从这点可以看出,我们的object database不光是要存储你的文件内容本身,还要存储你的文件目录层级结构,那么这些目录层级结构靠什么来存储呢?
如请看下图,Git一共有四种类型的对象
4ca1350f3b8c.png
blob:存储文件对象,存储二进制内容,为存储文件内容
tree:表面意思为树,我们linux中也有tree命令,是用来展开所在路径下的所有目录,这里的tree作用就是存储工作目录当中目录的层级结构
commit:表面意思为提交,在我们git中,每次提交都会写入到object database对象存储系统中,也可以说为git仓库中,一旦存储就会生成快照
tag:表面意思为标签,我们每次提交都知道git存储的是一个hash码,但是hash码太长了呢,我不好记,怎么办呢?就可以用到tag标签来打标,可以理解为linux汇总的alias别名命令
总结:Git一共有四种对象,blob、tree为核心对象,commit你的每一次提交都有一个commit对象,一般tag并不是必须使用的对象
逻辑讲解:假如我们新建一个文件,并且在git中做了提交操作,就会生成一个commit对象,tree对象用来存储此文件的目录层级结构,blob来存储文件内容,而tag对象,我们并没有使用到,为可选对象


神回复

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。