浅谈GIT之底层对象理解

Posted jayhou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈GIT之底层对象理解相关的知识,希望对你有一定的参考价值。

一、基本概述

首先定下一个基调就是Git并不是比SVN任何方面都好。SVN在某些功能或方面或者某些场景下也优于Git。

只是这篇文章讨论涉及到的点要与SVN相比,然后突出Git的某些方面的优越性而已。

对于Git而言,它本身是一个内容寻址文件系统,会将需要存储的东西按元数据meta data方式存储在一个KV数据库里面,

类似HashMap的这样的键值对结构,然后会按照内容生成相应的唯一hashcode作为内容的key,

而对于SVN而言,则是按照文件方式存储。

所以,看出一个特点就是如果需要的功能中涉及到文本的对比的话,对于Git而言是毫不费力的,因为只要对比hashcode

是否一个就知道两个文本是否相同了。

在这里介绍Git底层相关的三类对象:Git存储对象,Git树对象和Git的提交对象

 

二、底层介绍

2.1、Git的存储对象

Git将文件内容保存到内置的kv数据库的时候会根据内容产生并返回一个hash键

下面在Git Bash中模拟Git保存内容以及回滚的内部实现

 1 hjjie@mrforever MINGW64 /e/git_study/jay4 (master)
 2 $ find .git/objects/ -type f
 3 
 4 hjjie@mrforever MINGW64 /e/git_study/jay4 (master)
 5 $ echo hello git1 > git.txt; git hash-object -w git.txt;
 6 warning: LF will be replaced by CRLF in git.txt.
 7 The file will have its original line endings in your working directory.
 8 665c1e58d062e749beac9d853c94cb435b711498
 9 
10 hjjie@mrforever MINGW64 /e/git_study/jay4 (master)
11 $ echo hello git2 > git.txt; git hash-object -w git.txt;
12 warning: LF will be replaced by CRLF in git.txt.
13 The file will have its original line endings in your working directory.
14 a8b60e8197d9f9025c13aaca6971a0738309d858
15 
16 hjjie@mrforever MINGW64 /e/git_study/jay4 (master)
17 $ find .git/objects/ -type f
18 .git/objects/66/5c1e58d062e749beac9d853c94cb435b711498
19 .git/objects/a8/b60e8197d9f9025c13aaca6971a0738309d858
20 
21 hjjie@mrforever MINGW64 /e/git_study/jay4 (master)
22 $ git cat-file -p ^C
23 
24 hjjie@mrforever MINGW64 /e/git_study/jay4 (master)
25 $ git cat-file -p a8b60e8197d9f9
26 hello git2
27 
28 hjjie@mrforever MINGW64 /e/git_study/jay4 (master)
29 $ git cat-file -p 665c1e58d062e > git.txt

解释上述内容:

第二行,$ find .git/objects/ -type f 这个命令是查找在.git/objects/这个目录下文件,结果是无

第五行,$ echo ‘hello git1‘ > git.txt; git hash-object -w git.txt;  这个命令是输入文本内容到

git.txt文件中,然后通过git hash-object -w保存起来

并返回一个hashcode:665c1e58d062e749beac9d853c94cb435b711498

通过两次保存的操作,再调用一次$ find .git/objects/ -type f 可以看到再.git/objects/

有两个目录的生成,可以看出目录名字数字的拼接就是生成的hashcode

.git/objects/66/5c1e58d062e749beac9d853c94cb435b711498

.git/objects/a8/b60e8197d9f9025c13aaca6971a0738309d858

第二十五行,$ git cat-file -p a8b60e8197d9f9,通过这个命令可以根据hashcode返回

保存的文本内容 “hello git2”

也正因此,$ git cat-file -p 665c1e58d062e > git.txt ,根据以前的hashcode找回旧的内容输入

到对应文件,完成回滚操作

那么在实际操作Git的过程中,保存在KV库就是对应于git add命令的执行,也就是我们执行

git add xxx就相当于执行 git hash-object -w xxx的操作

记住一个点就是生成Git的存储对象是根据文件的内容而生成的,

也就是在存储对象中并没有保存Git文件的名字,也就是还不清楚文件内容与文件名称的映射关系,

所以假如需要进行回滚操作并不清楚该文件能够回滚到什么文件内容了。

而树对象则解决了这个问题。

 

2.2、Git的树对象

因为保存到Git的时候肯定文件中有相应的文件与文件夹的结构

Git的树对象其实是为了组织或者映射保存时的结构,类Unix的文件系统层次。可以从中看到对保存的内容的映射关系

技术图片

 

 如上图所属,一个树对象(tree object) 可能会包含多个文件(也就是 blob object,内部以二进制文件进行处理保存)

以及多个树对象(tree object,可以理解为文件夹)

由于每一个分支都有一个指向一个树对象,

因此可以通过以下命令根据key查看master分支中树对象的内容  master^{tree}指对应树对象的key

git cat-file -p master^{tree}

通过以下命令解说一下

 1 hjjie@mrforever MINGW64 /e/git_study/jay (master)
 2 $ mkdir -p src/main/
 3 
 4 hjjie@mrforever MINGW64 /e/git_study/jay (master)
 5 $ echo hi git > ./src/main/demo.java
 6 
 7 hjjie@mrforever MINGW64 /e/git_study/jay (master)
 8 $ git add -A
 9 warning: LF will be replaced by CRLF in git.txt.
10 The file will have its original line endings in your working directory.
11 warning: LF will be replaced by CRLF in src/main/demo.java.
12 The file will have its original line endings in your working directory.
13 
14 hjjie@mrforever MINGW64 /e/git_study/jay (master)
15 $ git commit -am tree object;
16 [master (root-commit) f3484a7] tree object
17  2 files changed, 3 insertions(+)
18  create mode 100644 git.txt
19  create mode 100644 src/main/demo.java
20 
21 hjjie@mrforever MINGW64 /e/git_study/jay (master)
22 $ git log
23 commit f3484a7829d2b32f5c6b4579575cacdf76abe588 (HEAD -> master)
24 Author: hjj <hjjie@aa.com>
25 Date:   Mon Feb 3 22:17:12 2020 +0800
26 
27     tree object
28 
29 hjjie@mrforever MINGW64 /e/git_study/jay (master)
30 $ git cat-file -p f3484a7829d2b32f5c6b4579575cacdf76abe588
31 tree a6f44ae816a4c755d2a568cb9055a7eb860a1776
32 author hjj <hjjie@aa.com> 1580739432 +0800
33 committer hjj <hjjie@aa.com> 1580739432 +0800
34 
35 tree object
36 
37 hjjie@mrforever MINGW64 /e/git_study/jay (master)
38 $ git cat-file -p a6f44ae816a4c755d2a568cb9055a7eb860a1776
39 100644 blob 5b317be9a8665279f1d3b01e009211380442e337    git.txt
40 040000 tree 9451f6d961aa89ae596202edff4ee0a57e528e15    src

在上述中我先创建文件夹src,main,然后创建一个文件demo.java

然后再对文件进行git add和git commit操作

接着再通过git log查看这次的提交日志,从中得到提交信息

1 commit f3484a7829d2b32f5c6b4579575cacdf76abe588 (HEAD -> master)
2 Author: hjj <hjjie@aa.com>
3 Date:   Mon Feb 3 22:17:12 2020 +0800

主要是获取提交对象(commit object)的key

然后在30行,通过命令 $ git cat-file -p f3484a7829d2b32f5c6b4579575cacdf76abe588、

得到以下信息,从中可以得到这次提交中所指向的树对象的key

1 tree a6f44ae816a4c755d2a568cb9055a7eb860a1776
2 author hjj <hjjie@aa.com> 1580739432 +0800
3 committer hjj <hjjie@aa.com> 1580739432 +0800

然后在38行通过命令 $ git cat-file -p a6f44ae816a4c755d2a568cb9055a7eb860a1776

可以查看到树对象包含的信息:

类型编码  类型  key  对象的名称

100644   blob   5b317be9a8665279f1d3b01e009211380442e337   git.txt

040000   tree   9451f6d961aa89ae596202edff4ee0a57e528e15   src

因此在树对象中映射相应的文件结构以及文件名与存储对象的key的映射

再往下进行解读,如下图所示:

每个树对象(文件夹)和每个存储对象(文件)与对应的hash key以及相应的文件结构

技术图片

 

 

 

2.3、Git的提交对象

在讲述Git的树对象中,还涉及到一个提交对象(commit object)。其实在Git中每次提交都会生成一个提交对象,该对象内容存储着

一个顶级树对象的key,上次提交的顶级树对象key,提交人的名称邮箱时间戳,以及提交评论

$ git cat-file -p f38854cec27f8614fd7bea3e8fa256724302d7c8

tree 84e18d1503ab30cf5443af78a08d95ee56fb34ac          ----- 当前顶级树对象的key
parent f3484a7829d2b32f5c6b4579575cacdf76abe588     ----- 上次提交顶级树对象的key
author hjj <hjjie@aa.com> 1580742512 +0800                   -----  提交人的名称邮箱时间戳
committer hjj <hjjie@aa.com> 1580742512 +0800             ----- 提交人的名称邮箱时间戳

commit object                                           ----- 提交评论


以上看出最关键的是提交对象,通过每一次提交就会生成一个提交对象,然后将

关联的当前版本中的所有文件的blob object的key通过树对象组织和关联起来形成这一次的版本

也就是每次提交只是保存对应文件的key的组织集合,所以成本很小。

同时每次做版本回滚只要比对key是否相同,然后如果不同,就将旧版本key对应的内容找出来切换,就完成了,

所以速度很快。

 

2.4、分支的创建

在git中创建分支,可以利用 git branch <branch name> 基于当前分支创建,或者可以通过指定的分支名/commit id/远程分支名/tag名创建。

其实创建一个分支相当于在.git efsheads下创建一个引用的文件

 1 hjjie@mrforever MINGW64 /e/git_study/jay (master)
 2 $ find .git/refs/heads/ -type f
 3 .git/refs/heads/master
 4 
 5 hjjie@mrforever MINGW64 /e/git_study/jay (master)
 6 $ git branch dev
 7 
 8 hjjie@mrforever MINGW64 /e/git_study/jay (master)
 9 $ find .git/refs/heads/ -type f
10 .git/refs/heads/dev
11 .git/refs/heads/master
12 
13 hjjie@mrforever MINGW64 /e/git_study/jay (master)
14 $ git log
15 commit f38854cec27f8614fd7bea3e8fa256724302d7c8 (HEAD -> master, dev)
16 Author: hjj <hjjie@aa.com>
17 Date:   Mon Feb 3 23:08:32 2020 +0800
18 
19     commit object
20 
21 commit f3484a7829d2b32f5c6b4579575cacdf76abe588
22 Author: hjj <hjjie@aa.com>
23 Date:   Mon Feb 3 22:17:12 2020 +0800
24 
25     tree object

以上操作就基于master分支创建一个dev分支,并看到在.git/refs/heads目录下创建了一个dev文件,文件内容是

技术图片

可以看出dev中保存的就是master分支所指向最新那次提交的提交对象的key。

所以至少看出一点就是在Git中创建分支几乎是没什么成本,就是创建一个文件保存引用

 

本文作者:hjjay
原文出处:https://www.cnblogs.com/jayhou/p/12250859.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

以上是关于浅谈GIT之底层对象理解的主要内容,如果未能解决你的问题,请参考以下文章

浅谈ios之kvc底层执行原理

浅谈JS面向对象之创建对象

浅谈GIT之通讯协议

37.浅谈js原型的理解

浅谈对express中间件的的理解。

浅谈Js原型的理解