初学Git的时候,一个很大的疑问是
Git中的都有哪些数据结构?如何设计实现的?用途是什么?
本文是个人在学习中的一些记录,仅以初学者水平回答上述几个问题
先在本地上建一个Git仓库
$ mkdir ~/workspace/git/ -p #建立用于存放git的目录
$ cd ~/workspace/git/
$ git init # 初始化一个git仓库
Initialized empty Git repository in /home/***/workspace/git/.git/
$ find . # 可以查看一下初始化生成的文件
.
./.git
./.git/HEAD
./.git/refs
...
./.git/info
./.git/info/exclude
Concept1: 仓库(repository)
- 仓库实际上是一个数据库(上面git init命令初始化后就是一个Git仓库);
- 仓库中包含并管理着项目所有文件、历史版本、日志、配置等,大致结构如图1所示:
- 工作区(workspace):上面的实例中~/workspace/git/就是一个项目的工作区,包含项目的所有文件(.git/是由Git管理目录);
- 版本库(.git目录):由Git管理的一个隐藏目录.git/,包含项目的历史版本、提交信息、日志、配置等,则都放在该目录中,其中两个比较重要的功能:
- 暂存区:也叫缓存区(stage),实际是.git/index文件,当添加/变更文件并用git add ***提交后,Git先在.git/objects/目录下创建一个块(blob)存储文件的内容,并在.git/index记录文件名、块(blob)号等信息;
- 历史版本:通过HEAD文件中找到当前所在分支,通过分支确定的最新的commit对象,每个commit对象包含一个指向更前一次的commit对象。
Concept2: Git对象
对象库是Git实现版本管理功能的重要部分(对应图1的objects,对应目录.git/objects/),包含文件的历史内容文件、提交等。
根据存储数据的类型不同,Git对象库中的对象分成以下4种:
- 块(blob):“二进制大对象(binary large object)”,用来保存数据的对象,且只保存文件的数据,不包含文件的元数据(如,文件名);
- 目录树(tree):一个tree对象可用来存储一层目录信息和该目录下各文件的元数据;
- 提交(commit):保存版本提交的元数据,包括作者、提交者、提起、日志信息等。每个提交对象又包含一个tree对象,该tree索引项版本提交时的各个文件的状态;
- 标签(tag):分配一个任意且人类可读的名字给一个特定对象,一般是一个提交对象;
Git对象间的关系
- 一个tag对象最多指向一个commit对象;
- 一个commit对象包含一个tree对象,并且该对象由提交引入版本的;
- 除非第一次提交的commit对象,否则commit对象中还包含指向上一次提交时产生的commit对象;
- tree对象指向若干blob对象,也可以指向其他tree对象;
- blob对象是最“底端”对象,不能指向其他对象,且只能被tree对象应用;
Concept3: SHA1散列
前面说到了Git对象,可以在Git目录创建文件,并通过git add *** 和 git commit命令将文件加到Git版本库中,然后就能在./git/objects/目录下看到由Git创建的对象,如:
$ echo "hello, Git." >> hello.txt
$ git add hello.txt
$ git commit -m 'add hello.txt'
$ find .
...
./.git/objects
./.git/objects/94
./.git/objects/94/d2603b996fcc8a68caa60217853c4368b9ac22
./.git/objects/bf
./.git/objects/bf/5dc1fe68d092ea5ff89163e191c8e2b957a684
...
以 ./.git/objects/94/d2603b996fcc8a68caa60217853c4368b9ac22 为例,这是一个对象名为 94d2603b996fcc8a68caa60217853c4368b9ac22 的对象。(注意,对象名中包含目录名94,把对象名的前两位单独拿出来又做一层目录是为了提高文件查找的效率)
关于对象名,只需要知道:
- 对象名实际是一个160bit的SHA1的散列码,一般表示成40为十六进制数;
- 对象名是根据对象内容做SHA1散列计算得到的,不包含文件的元数据(如,文件名);
关于SHA1,还有两个问题觉得有必要说明一下:
- 为什么用内容做SHA1散列计算,不把文件名之类的元数据(文件名等)也包含进去?
首先,上面介绍blob和tree对象的时候有说过,文件的元数据blob对象中不保存的;如果为了计算SHA1散列去取不仅麻烦还降低效率;
其次,如果项目有两个文件内容是一样的,但文件的元数据不一样,则只需要存一个blob对象即可;
最后,虽然这种方式有点反常规,但不得不说在Git中不会有任何问题,而且还有上面两个好处(相信有这个疑问的也是受到之前的习惯影响) - SHA1会不会出现冲突?冲突怎么解决?
前面说过,SHA1散列码有160bit位,即可以表示2^160种可能,这个数值大到没有具体的感觉。。(听说一张0.1mm的纸,对折20次厚度能到100米;对折42次,能超过地月距离;对折80次能超过银河系的厚度;对折103次能超过宇宙的可观测直径(930亿光年),对折160次能到有多大🙄???)
不过就算概率真的很小还是会有可能发生冲突。如果真的冲突,Git中似乎没有设计解决冲突的方案,而是让新文件覆盖旧文件(查到的是这样的,如果不对请指正)
看下一个blob对象和一个tree对象中的内容:
$ #查看blob对象中的内容
$ git cat-file -p 94d2603b996fcc8a68caa60217853c4368b9ac22
hello, Git.
$ #查看tree对象的内容
$ git ls-tree 5d810f247c9059618c410b63661691aa2eddca2c
100644 blob 94d2603b996fcc8a68caa60217853c4368b9ac22 hello.txt
PS:Git中还有很多自己不懂的,本篇博文只是个人初学时的一些记录,有错误的地方还请不吝指教~
[没有记录 就没有发生]