为了更近一步的学习和理解Git的理念,这一节介绍一下Git中的一些基本概念。git
Git的版本库就是一个简单的数据库,其中包含全部用来维护和管理项目的修订版本和历史的信息。数据库
Git不只提供版本库中全部文件的副本,还提供了版本库自己的副本。数据结构
Git在每一个版本库里维护一组配置值,这个咱们在上一篇文章中已经有所说起,好比版本库的用户名和email地址。当把一个版本库进行克隆的时候,会复制文件数据以及其余的一些版本库的元数据,可是不会复制配置设置。学习
Git维护两个主要的数据结构:对象库和索引。全部这些数据都存放在.git隐藏目录内。测试
对象库包含原始数据文件、全部的日志消息、做者信息、日期、以及其余用来重建项目任意版本或分支的信息。spa
Git放在对象库里的对象只有4种类型:块(blob)、目录树(tree)、提交(commit)、标签(tag)3d
A、块:日志
文件的每个版本被表示为一个blob。一个blob保存一个文件的数据,但不包含任何关于这个文件的元数据,甚至文件名。code
B、目录树(tree):对象
一个目录树对象表明一层目录信息。它记录了blob标识符、路径名和在一个目录里全部文件的一些元数据。
C、提交(commit):
一个提交对象保存版本库中每一次变化的元数据:做者、提交者、提交日期、日志消息。每个提交对象指向一个目录树对象,除了根提交,大多数提交都有一个父提交。
D、标签(tag):一个标签对象分配一个任意的可读的名字给一个特定对象(一般是一个提交对象)。
目前来讲,感受这个概念不太好解释。
索引是一个临时的、动态的二进制文件,它描述整个版本库的目录结构。
个人认识是,索引是介于工做区和对象库之间的一个缓冲区。工做区里的变化会最先在这里有所体现,最后由索引将全部的变更提交到对象库中。
Git对象库被组织及实现为一个内容寻址的存储系统。对象库中的每个对象都有一个惟一的名称,这个名称是向对象的内容应用SHA1获得的散列值。
首先,Git追踪的是基于对象内容计算出的散列值,而不关心文件名或目录名。若是两个文件的内容相同,那么无论是否在一样的目录,Git在对象库中只保存一份blob形式的内容副本。
其次,当文件从一个版本变到下一个版本的时候,Git的内部数据库会有效的存储每个文件的每一个版本,而不只仅是差别。
固然,你或许已经发现,上面说的这种存储方式效率很低,由于每个都要保存副本。这个会在后面打包文件的地方进行说明。
Git仅仅记录每一个文件名,而且确保能经过它的内容精确的重建文件和目录。
好吧。。。我仍是感受没法理解做者的意思。
回到上面提出的问题:直接存储每一个文件每一个版本的完整内容是否过低效了?
Git使用了一种叫作打包文件的存储机制。要建立一个打包文件,Git首先定位内容很是类似的所有文件,而后为它们之一存储整个内容。以后计算类似文件之间的差别而且只存储差别。
先来张图压压惊。。。经过这张图看看对象库中的各个对象是如何协做造成整个系统的。
下面咱们让对象库变得复杂一些。保留原来的两个文件不变,添加一个包含一个文件的新目录到版本库中。对象图以下:
这里由于顶级目录被添加的新子目录改变了,因此顶级树对象的内容也就跟着改变了。因此Git引入了一棵新的树cafed00d。
树cafed00d依旧引用了原来的两个文件的blob对象,同时新增了一个指向新子目录的树对象。
上面的图是全部Git操做诸如add,commit等所有结束后的对象库总体图,但是咱们可能更想知道如下问题:
后面的章节会依次说明上述问题。
咱们新建一个新的版本库,逐步查看一下每次操做后.git目录中会发生哪些变化:
首先,咱们建立一个空的版本库。
咱们能够看到,除了几个占位符外,.git/objects目录下是空的:
[root@flower1 test]# find .git/objects/ .git/objects/ .git/objects/pack .git/objects/info
如今,咱们建立一个简单的文件,并添加到对象库中。
[root@flower1 test]# echo "hello world" > hello.txt [root@flower1 test]# git add hello.txt
接下来,咱们看一下objects目录下发生的变化:
[root@flower1 test]# find .git/objects/ .git/objects/ .git/objects/3b .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad .git/objects/pack .git/objects/info
从上面的例子里,咱们看到,当为hello.txt建立一个对象的时候,Git并不关心hello.txt的文件名。Git只关心文件里的内容。Git对这个blob执行一些操做,计算它的SHA1散列码,并散列码的十六进制表示做为文件名放进对象库里。
其实上述文件的散列码是3b18e512dba79e4c8300dd08aeb37f8e728b8dad。
Git将散列码的前2个字符做为一级目录名,能够提升文件系统的效率,至关于将不一样对象散列到了不一样的文件夹下,避免某个文件目录下文件过多。
咱们可使用下面命令使用散列码将文件的内容提出出来:
[root@flower1 test]# git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad hello world
固然,手动收入长度为40的散列码很不切实际,因此Git提供了下面的方式:经过对象的惟一前缀来查找对象的散列值。
[root@flower1 test]# git rev-parse 3b18 3b18e512dba79e4c8300dd08aeb37f8e728b8dad [root@flower1 test]# git rev-parse 3b18e 3b18e512dba79e4c8300dd08aeb37f8e728b8dad [root@flower1 test]# git rev-parse 3b18e5 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
我测试的结果是,前缀最少要达到4位才能够。
经过上面的演示,咱们看到表明hello world的blob对象已经存放到对象库中了。那么它的文件名又发生了什么事呢?
Git经过另外一种叫作目录树的对象来跟踪文件的路径名。当使用git add命令时,Git会给添加的每一个文件的内容建立一个对象,但不会立刻为树建立一个对象。
此时,索引更新了。索引位于.git/index中,它跟踪文件的路径名和相应的blob。每次执行命令时,Git会用心的路径名和blob信息来更新索引。
任什么时候候,均可以从当前索引建立一个树对象,只要经过底层的git write-tree命令来捕获索引当前信息的快照就能够了。
当前,该索引只包含一个文件:
[root@flower1 test]# git ls-files -s 100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 hello.txt
从上面的打印信息,能够看到文件的关联,hello.txt和3b18e512…的blob。
接下来咱们捕获索引状态并把它保存到一个树对象里:
[root@flower1 test]# git write-tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60
[root@flower1 test]# find .git/objects/ .git/objects/ .git/objects/3b .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad .git/objects/68 .git/objects/68/aba62e560c0ebc3396e8ae9335232cd93a3f60 .git/objects/pack .git/objects/info
如今有2个对象:3b18e5的hello world对象和68aba6的树对象。
咱们也能够看看树对象里面的内容究竟是什么:
[root@flower1 test]# git cat-file -p 68ab 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad hello.txt
该节经过建立一个新的子目录来看看Git是如何处理多个层次的目录结构的:
[root@flower1 test]# pwd /root/test [root@flower1 test]# mkdir subdir [root@flower1 test]# cp hello.txt subdir/hello.txt [root@flower1 test]# git add subdir/hello.txt [root@flower1 test]# git write-tree 492413269336d21fac079d4a4672e55d5d2147ac [root@flower1 test]# git cat-file -p 492413 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad hello.txt 040000 tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60 subdir
能够看到subdir的树对象和以前的顶级目录树对象时彻底同样的。
以前已经经过git add命令添加了hello.txt文件,并经过git write-tree命令生成了树对象。
用下面的底层命令建立一个提交对象:git commit-tree SHA1
[root@flower1 test]# echo -n "Commit a file" | git commit-tree 492413269336d21fac079d4a4672e55d5d2147ac 204fa4469c1642cb9190048ebdc8fabc9a4a4af1
提交对象包含如下信息:
有2种基本的标签类型:轻量级的和带附注的。
轻量级标签只是一个提交对象的引用,一般被版本库视为是私用的。这些标签并不在版本库里建立永久对象。
带附注的标签会建立一个对象。
能够经过git tag命令来建立一个带有提交信息、带附注切未签名的标签:
[root@flower1 test]# git tag -m "Tag version 1.0" V1.0 204fa