程序员开发实例大全宝库

网站首页 > 编程文章 正文

Git学习笔记 004 Git内部原理 part1 Git的对象

zazugpt 2024-08-18 01:22:57 编程文章 25 ℃ 0 评论

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对象子节。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表