Git 对象
Git的核心部分如同一个map,是键值对数据库。它会将任何内容作为对象保存在它的数据库中,然后返回给用户一个键値。通过这个键値就可以再次取得这个对象。这个键値,其实就是一个SHA-1。另外,这个所谓的数据库就存在本地的.git文件夹下。
下面使用Git的底层命令hash-object来将一个文本文件保存起来。普通的对象保存在.git/object文件夹中。
首先生成一个空的本地仓库
$ git init
Initialized empty Git repository in F:/97_example/git/ByHand/GitObjectTestProject/.git/
执行find命令,确认.git/object文件夹的内容是空的
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f
下面执行hash-object命令添加对象
$ echo 'first object' | git hash-object -w --stdin
3b27636fd85f44b8ebd64e7ff051a3bd47ed5edc
这是如果打开object文件夹,可以看到生成了一个文件夹,以3b为文件夹名字。这是Git生成新对象的SHA-1的前两位,后面的38位位文件名。使用下面的命令来确认文件内容
$ git cat-file -p 3b27636fd85f44b8ebd64e7ff051a3bd47ed5edc
first object
那么Git是如何管理文件版本的呢?其实Git是为每一个版本的文件生成一个对象。如下
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
warning: LF will be replaced by CRLF in test.txt.
The file will have its original line endings in your working directory
83baae61804e65cc73a7201a7252750c76066a30
生成文件名相同,但是内容不同的第二个版本的文件
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
warning: LF will be replaced by CRLF in test.txt.
The file will have its original line endings in your working directory
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
现在看一下objects文件夹的内容
$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/3b/27636fd85f44b8ebd64e7ff051a3bd47ed5edc
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
这里有三个对象,包括最初生成的对象。
现在可以把文件恢复到第一个版本
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1
再吧文件恢复到第二个版本
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 2
这就是Git管理文件版本的底层原理。
Git树对象
真实的情况肯定不是只管理一个或者几个文件。绝大多数的时候,需要管理文件夹,子文件夹,文件等等。所以Git提供了对文件夹的引用对象--树对象。树对象包含了一条或者多条树对象记录,每个记录含有一个指向它的子树对象或者指向数据对象的SHA-1指针。
$ git cat-file -p master^{tree}
100644 blob 8239193ceca410e0bb5f074d11d299deee7bd169 .gitignore
040000 tree 8ce6173fdfb491b151bd2e57153263f4df4aed14 lib
100644 blob 80d8906b159aebfc1638538c37645e54660b11d0 makeAChange.txt
100644 blob 8013df82c98d02380a7040b9b52266cc7d9a8650 normalCommit.txt
master^{tree} 语法表示 master 分支上最新的提交所指向的树对象。lib是一个子文件夹,命令输出的结果中指向lib的SHA-1就是一个指向lib树对象的指针。输出这个指针所指的内容,发现是lib文件夹下的文件。
$ git cat-file -p 8ce6173fdfb491b151bd2e57153263f4df4aed14
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 fileInLib.txt
下面使用Git的底层命令来建立一个树对象。
$ git init
Initialized empty Git repository in C:/study/gitee/TreeOjbectTest/.git/
首先,Git对树对象的操作都基于缓存区。先使用一个普通文件创建缓存区。通过hash-object创建一个数据对象,通过update-index --add命令使这个对象作为一个txt文件加入缓存。--cacheinfo表示这个文件的源存在于Git的数据库中。100644这样的数字意义如下:
- 100644:普通文件
- 100755:可执行文件
- 120000:符号链接
$ echo 'test' | git hash-object -w --stdin
9daeafb9864cf43055ae93beb0afd6c7d144bfa4
$ git update-index --add --cacheinfo 100644 \
> 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 test.txt
向缓存区中写入一个树对象,并且确认一下状态
$ git write-tree
2b297e643c551e76cfa1f93810c50811382f9117
$ git cat-file -t 2b297e643c551e76cfa1f93810c50811382f9117
tree
为test.txt文件创建第二个版本
$ echo 'test v2' > test.txt | git hash-object -w test.txt
warning: LF will be replaced by CRLF in test.txt.
The file will have its original line endings in your working directory
34a947a34775ff264a214c33a2a8a63cb2628429
下面再生成一个新的树对象,这个树对象包含一个new.txt文件和test.txt的版本二。
$ echo 'new file' > new.txt
$ git update-index --cacheinfo 100644 \
> 34a947a34775ff264a214c33a2a8a63cb2628429 test.txt
$ git update-index --add new.txt
warning: LF will be replaced by CRLF in new.txt.
The file will have its original line endings in your working directory
现在缓存区包含了new.txt文件和test.txt的新版本。基于它们建立数对象
$ git write-tree
e3523e8670cb97b03156aa66074cc06e51032c28
$ git cat-file -p e3523e8670cb97b03156aa66074cc06e51032c28
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 34a947a34775ff264a214c33a2a8a63cb2628429 test.txt
让我们把它变得稍微复杂一些,将第一次生成得树对象作为新树对象的子树
把第一个数读到缓存区并写入第二个树
git read-tree --prefix=child 2b297e643c551e76cfa1f93810c50811382f9117
$ git write-tree
fad3488dbc826de9be2f5676ab4d6af6198dfafd
确认状态
$ git cat-file -p fad3488dbc826de9be2f5676ab4d6af6198dfafd
040000 tree 2b297e643c551e76cfa1f93810c50811382f9117 child
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 34a947a34775ff264a214c33a2a8a63cb2628429 test.txt
到此为止,我们创建了一个两层的树对象。
提交对象
提交树对象
$ echo '1st commit' | git commit-tree 2b297e64
d29cc962d38b4fd47765d2e36967e7a3c9ca95b4
使用log命令确认状态
$ git log --stat d29cc962d
commit d29cc962d38b4fd47765d2e36967e7a3c9ca95b4
Author: kutilion <kutilion@gmail.com>
Date: Fri Apr 19 18:39:28 2019 +0800
1st commit
test.txt | 1 +
1 file changed, 1 insertion(+)
发现我们使用底层命令提交的树,实际上与我们直接使用commit命令提交的结果是一样的。其实在使用commit命令的时候,Git隐含执行了底层的commit-tree等命令。commit命令对用户来说更加友好,大家都使用而已。
对象存储
关于对象的存储,笔者认为过于底层,作为参考资料。感兴趣可以直接参照官方文档ProGit的Git内部原理章节的Git对象子节。
本文暂时没有评论,来添加一个吧(●'◡'●)