共计 13176 个字符,预计需要花费 33 分钟才能阅读完成。
原文作者为 Gitee 负责人周凯,原创于微信公众号「Zoker 随笔」
摘要:Git 是目前最风行的版本控制系统,从本地开发到生产部署,咱们每天都在应用 Git 进行咱们的版本控制,除了日常应用的命令之外,如果想要对 Git 有更深一步的理解,那么钻研下 Git 的底层存储原理将会对了解 Git 及其应用十分有帮忙,就算你不是一个 Git 开发者,也举荐你理解下 Git 的底层原理,你会对 Git 的弱小有一个全新的意识,并且将会在日常的 Git 应用过程中更加得心应手。
这篇文章面向的读者次要是对 Git 有肯定的理解的群体,并不会介绍具体 Git 的作用及其应用,也不会介绍与其它版本控制系统如 Subversion 之间的差别,次要是介绍下 Git 的实质以及他的存储实现的相干原理,旨在帮忙 Git 使用者更加清晰的理解在应用 Git 进行版本控制的时候其外部实现。
Git 实质是什么
Git 实质上是一个内容寻址的 Key-Value 数据库,咱们能够向 Git 仓库内插入任意类型的内容,Git 会返回给咱们一个惟一的键值,能够通过这个键取出过后咱们插入的值,咱们能够通过底层命令 git hash-object
命令来尝试:
➜ Zoker git:(master) ✗ cat testfile
Hello Git
➜ Zoker git:(master) ✗ git hash-object testfile -w
9f4d96d5b00d98959ea9960f069585ce42b1349a
能够看到咱们目录下有一个名为 testfile
的文件,内容是 Hello Git!
咱们应用git hash-object
命令将这个文件的内容写入到 Git 仓库,-w
选项通知 Git 把这个内容写到 Git 的 .git/objects
对象数据库目录,并且 Git 返回了一个 SHA 值,这个 SHA 值就是后续咱们要取出这个文件的键值:
➜ Zoker git:(master) ✗ git cat-file -p 9f4d96d5b00d98959ea9960f069585ce42b1349a
Hello Git
咱们应用了 git cat-file
命令取回刚刚存入到 Git 仓库的内容,尽管不像 Redis
的命令get
set
那么直观,然而它的确是一个 KV 数据库,不是吗?
咱们刚刚尝试插入的这种数据是根底的 blob
类型的对象,Git 还有其它如 tree
、commit
等对象类型,这些不同的对象类型之间有特定的关联关系,它们将不同的对象有逻辑的关联起来,才可能帮咱们进行不同版本的管制和检出。稍后会开展解说这几种不同的对象类型,咱们先来理解下 Git 的目录构造,看看在 Git 中数据是如何寄存的。
Git 目录构造
通过上一节的介绍,咱们晓得了 Git 实质就是一个 KV 数据库,而且还提到了内容都是写到 .git/objects
对象目录,那么这个目录放在哪里?Git 又是如何存储这些数据的呢?本节咱们重点介绍一下 Git 的存储目录构造,理解下 Git 是如何寄存不同类型的数据的。
更具体的介绍参见:https://github.com/git/git/bl…
通过 git init
咱们能够在当前目录初始化一个空的 Git 仓库,Git 会主动生成 .git
目录,这个 .git
目录就是后续所有的 Git 元数据的存储核心,咱们来看一下它的目录构造:
➜ Zoker git init
Initialized empty Git repository in /Users/zoker/tmp/Zoker/.git/
➜ Zoker git:(master) ✗ tree .git
.git
├── HEAD // 是一个符号援用,指明当前工作目录的版本援用信息,咱们平时执行 checkout 命令时就会扭转 HEAD 的内容
├── config // 配置以后存储库的一些信息,如:Proxy、用户信息、援用等,此处的配置项绝对于全局配置权重更高
├── description // 仓库形容信息
├── hooks // 钩子目录,执行 Git 相干命令后的回调脚本,默认会有一些模板
│ ├── update.sample
│ ├── pre-receive.sample
│ └── ...
├── info // 存储一些额定的仓库信息如 refs、exclude、attributes 等
│ └── exclude
├── objects // 元数据存储核心
│ ├── info
│ └── pack
└── refs // 寄存援用信息,也就是分支、标签
├── heads
└── tags
默认初始化生成的 Git 仓库就只有这些文件,除此之外还存在一些其它类型的文件和目录如 packed-refs
modules
logs
等,这些文件都有特定的用处,都是在特定的操作或者配置后才会呈现,这里咱们只关注外围存储的实现,这些额定文件或目录的作用及应用场景再可自行翻阅文档,这里仅介绍外围的一些文件。
hooks 目录
hooks 目录次要存储的是 Git 钩子,Git 钩子能够在很多事件产生后或者产生前触发,可能提供给咱们非常灵活的应用形式,默认状况下全部都是带 .sample
后缀的,须要移除这个后缀并赋予可执行权限方可失效,上面列举下罕用的一些钩子及其常见的用处:
客户端钩子
- pre-commit:提交前触发,比方查看提交信息是否标准,测试是否运行结束,代码格局是否符合要求
- post-commit:相同,这个是整个提交实现后触发,能够用来发告诉
服务端钩子
- pre-receive:服务端接管推送申请首先被调用的脚本,能够检测这些被推送的援用是否符合要求
- update:与 pre-receive 类似,然而 pre-receive 只会运行一次,而 update 将会为每一个推送的分支别离运行一次
- post-receive:整个推送过程实现后触发,能够用来发送告诉、触发构建零碎等
objects 目录
如上一节咱们提到的,Git 将所有接管到的内容生成对象文件存储在这个目录下,咱们通过 git hash-object
生成了一个对象并写入了 Git 仓库,这个对象的键值是9f4d96d5b00d98959ea9960f069585ce42b1349a
,这个时候咱们来查看下 objects
目录的构造:
➜ Zoker git:(master) ✗ git hash-object testfile -w
9f4d96d5b00d98959ea9960f069585ce42b1349a
➜ Zoker git:(master) ✗ tree .git/objects
.git/objects
├── 9f
│ └── 4d96d5b00d98959ea9960f069585ce42b1349a
├── info
└── pack
能够看到 objects
目录曾经有了新的内容,多了一个 9f
的文件夹以及其中的文件,这个文件就是插入到 Git 仓库的内容的对象文件,Git 取其键值的前两个字母作为文件夹,将前面的字母作为对象文件的文件名进行存储,这里(也就是 objects/[0-9a-f][0-9a-f]
)所存储的对象咱们个别称为loose objects
或者unpacked objects
,也就是涣散对象。
除了对象的存储文件夹,仔细的同学应该曾经留神到了 objects/pack
文件夹的存在,这里对应的是打包后的文件,为了节俭空间和晋升效率,当存储库中有过多的涣散对象文件或者手动执行 git gc
命令时,亦或是推送拉取的传输过程中,Git 都会将这些涣散的对象文件打包成 pack
文件来晋升效率,这里寄存的就是这些打包后的文件:
➜ objects git:(master) git gc
...
Compressing objects: 100% (75/75), done.
...
➜ objects git:(master) tree
.
├─ pack
├── pack-fe24a22b0313342a6732cff4759bedb25c2ea55d.idx
└── pack-fe24a22b0313342a6732cff4759bedb25c2ea55d.pack
└── ...
能够看到 objects
目录曾经没有了涣散对象,取而代之的是 pack
目录的两个文件,一个是打包后的文件,另一个是对这个打包的内容进行索引的 idx
文件,不便查问某个对象是否在这个对应的 pack
包内。
须要留神的是,如果在刚刚咱们手动创立的一个 blob
对象的仓库进行 GC,将不会产生任何成果,因为这个时候整个 Git 仓库并没有任何一个援用指向这个对象,咱们说这个对象是游离的,上面咱们来介绍下存储援用的目录。
refs 目录
refs 目录存储咱们的援用(references),援用能够看做是对一个版本号的别名,它存储的理论就是某一个 Commit 的 SHA 值,下面咱们用来测试的仓库并没有任何一个提交,所以只有一个空的目录构造
└── refs
├── heads
└── tags
咱们轻易找一个蕴含提交的仓库查看他的默认分支master
➜ .git git:(master) cat refs/heads/master
87e917616712189ecac8c4890fe7d2dc2d554ac6
能够看到这个 master
的援用只是存储了一个 Commit 的 SHA 值,益处当然就是咱们不须要记着那长长的一串 SHA 值,咱们只须要用 master
这个别名就能够获取到这个版本。同样的 tags 目录下存储的就是咱们的标签,与分支不同的是,标签的所记录的援用值个别是不会变动的,而分支能够咱们的版本变动而变动。除此之外,还可能会看到refs/remotes
refs/fetch
等目录,这些外面存储的是特定命名空间的援用。
还有一种状况,就是下面咱们讲到的 GC 机制,如果一个仓库执行了 GC,那么不仅 objects
目录下的涣散对象会被打包,refs
上面的援用同样也会被打包,只不过它寄存在裸仓库的根目录下.git/packed-refs
➜ .git git:(master) cat packed-refs
# pack-refs with: peeled fully-peeled sorted
87e917616712189ecac8c4890fe7d2dc2d554ac6 refs/heads/master
当咱们须要拜访分支 master
的时候,Git 会首先去 refs/heads
外面进行查找,如果找不到就会返回 .git/packed-refs
进行查找,将所有的援用打包到一个文件无疑晋升了不少效率。须要留神的是,如果咱们在这个时候往 master
分支上更新了一些提交,这个时候 Git 并不会间接批改 .git/packed-refs
文件,它会间接在 refs/heads/
下从新创立一个 master
援用,蕴含最新的提交的 SHA 值,依据刚刚咱们介绍的 Git 的机制,Git 会首先在 refs/heads/
查找,找不到才会去 .git/packed-refs
查找。
那么援用外面存储的 Commit 的 这串 SHA 值到底是指向什么内容呢,咱们能够应用之前查看 blob
对象内容的 cat-file
命令进行查看:
➜ .git git:(master) git cat-file -p 87e917616712189ecac8c4890fe7d2dc2d554ac6
tree aab1a9217aa6896ef46d3e1a90bc64e8178e1662 // 指向的 tree 对象
parent 7d000309cb780fa27898b4d103afcfa95a8c04db // 父提交
author Zoker <kaixuanguiqu@gmail.com> 1607958804 +0800 // 作者信息
committer Zoker <kaixuanguiqu@gmail.com> 1607958804 +0800 // 提交者信息
test ssh // 提交信息
它是一个 commit
类型的对象,次要的属性是它指向的 tree
对象,它的父提交(如果它是第一个提交,那么这里是 0000000…),以及作者和提交信息。
那么 commit
对象是什么?它所指向的 tree
对象又是什么?与之前咱们手工创立的 blob
对象有什么差异?接下来咱们来谈谈 Git 存储对象。
Git 存储对象
在 Git 的世界里,一共有四种类型的存储对象:文件(blob)、树(tree)、提交(commit)、标签(tag),这里咱们次要探讨头三种类型,因为这三种是最根底的 Git 元数据,而标签对象只是一个蕴含了额定属性信息的 Tag 而已,也就是附注标签(annotated tag),这里不再过多的介绍。
轻量标签(lightweight)与附注标签(annotated)介绍:https://git-scm.com/book/zh/v…
Blob 对象
在介绍 Git 实质的时候,为了演示 Git 是一个基于内容寻址的 KV 数据库,咱们向 Git 仓库插入了一个文件的内容:
➜ Zoker git:(master) ✗ cat testfile
Hello Git
➜ Zoker git:(master) ✗ git hash-object testfile -w
9f4d96d5b00d98959ea9960f069585ce42b1349a
这个 Key 为 9f4d96d5b00d98959ea9960f069585ce42b1349a
的 Git 对象实际上就是一个 Blob 对象,他存储了这个 testfile
文件的值,咱们能够应用 cat-file
命令来进行查看:
➜ Zoker git:(master) ✗ git cat-file -p 9f4d96d5b00d98959ea9960f069585ce42b1349a
Hello Git
每一次咱们批改文件,Git 都会残缺的保留一份这个文件的快照而非记录差别,所以如果咱们批改了 testfile
文件的内容再次存入到 Git 仓库中的时候,Git 会基于以后最新的内容来生成它的 Key,须要留神的是当内容不变的时候,它的 Key 值是固定的,毕竟咱们后面也说了,Git 是一个基于内容寻址的 KV 数据库。
另外,这里的 Blob 对象存储的是文本内容,它还能够是二进制内容,然而这里并不倡议应用 Git 治理二进制文件的版本。咱们 Gitee 平台在日常经营过程中遇到最多的问题就是用户仓库过大,这种状况个别都是用户提交了大的二进制文件导致的,因为每次文件的变更记录的是快照,所以这个二进制文件如果变更频繁,它占用的空间是倍增的。而且对于文本内容的 Blob,Git 在 GC 的过程中会只保留两次提交之间的文件差别,是能够达到节俭空间的成果的,然而对于二进制内容的 Blob 是无奈像文本内容的 Blob 那样解决的,所以尽量不要把频繁变动的二进制内容存储到 Git 仓库,能够应用 LFS 的形式进行存储。如果曾经存在了大量的二进制文件,能够应用 filter-branch
进行瘦身,新退出的共事在首次 Clone 仓库的时候必定会感谢你的。
LFS 的应用:https://gitee.com/help/articl… 大仓库的瘦身:https://gitee.com/help/articl… filter-branch:https://github.com/git/git/bl…
到了这里是不是感觉哪里不对劲?没错,这个 Blob 对象只存储了这个文件的内容,却没有记录文件名,那咱们该怎么晓得这个内容是属于哪个文件的啊?答案是 Git 的另外一个重要的对象:Tree 对象。
Tree 对象
在 Git 中,Tree 对象次要的作用是将多个 Blob 或者 子 Tree 对象组织到一起,所有的内容都是通过 Tree 和 Blob 类型的对象进行存储的。一个 Tree 对象蕴含了一个或者多个 Tree Entry(树对象记录),每个树对象记录都蕴含了一个指向 Blob 或者子 Tree SHA 值的指针,还有它们对应的文件名等信息,其实就能够了解为索引文件系统中的 \`inode\` 和 \`block\` 的关系,图示一个 Tree 对象的话,如下图:
这个 Tree 对象对应的目录构造就是上面这样的:
.
├── LICENSE
├── readme.md
└── src
├── libssl.so
└── logo.png
通过这种形式,咱们能够像组织 Linux 下目录的形式一样来结构化的存储咱们仓库的内容,把 Tree 看作目录构造,把 Blob 看作具体的文件内容。
那么该如何创立一个 Tree 对象呢?在 Git 中是依据暂存区的状态来创立对应的 Tree 对象的,这里的暂存区其实就是咱们日常在应用 Git 的过程中所了解的暂存区(Staged),个别咱们应用 git add
命令将某些文件增加到暂存区待提交。在没有任何提交的空仓库里,这个暂存区的状态就是你通过 git add
所增加的那些文件,如:
➜ Zoker git:(master) ✗ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: LICENSE
new file: readme.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
src/
这里以后的暂存区状态就是在根目录有两个文件,暂存区的状态是保留在 .git/index
文件的,咱们应用 file
命令来看看它是什么:
➜ Zoker git:(master) ✗ file .git/index
.git/index: Git index, version 2, 2 entries
能够发现在 index
文件中有两个 entry
,也就是根目录的两个文件LICENSE
和readme.md
。对于曾经有提交的仓库,如果暂存区没有任何内容,那么这个 index
示意的就是以后版本的目录树状态,如果批改或者增删了文件,并且退出了暂存区,那么 index
就会产生扭转,将相干文件的指针指向该文件新的 Blob 对象的 SHA 值。
所以如果想要创立一个 Tree 对象,咱们须要往暂存区放点货色,除了应用 git add
,咱们还能够应用底层命令update-index
来创立一个暂存区。接下来咱们依据下面曾经创立好的 testfile
文件来创立一个树对象,首先就是将文件 testfile
退出到暂存区:
➜ Zoker git:(master) ✗ git update-index --add testfile // 与 git add testfile 一样
➜ Zoker git:(master) ✗ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: testfile
这个过程 Git 次要是先把 testfile
的内容以 Blob 的模式插入到 Git 仓库,而后将返回的这个 Blob 的 SHA 值记录到 index
中,通知暂存区目前这个文件的内容是哪个。
➜ Zoker git:(master) ✗ tree .git/objects
.git/objects
├── 9f
│ └── 4d96d5b00d98959ea9960f069585ce42b1349a
├── info
└── pack
3 directories, 1 file
➜ Zoker git:(master) ✗ git cat-file -p 9f4d96d5b00d98959ea9960f069585ce42b1349a
Hello Git
Git 在执行 update-index
命令的时候,把指定文件的内容存储为 Blob 对象,并且记录在 index
文件状态内。因为在之前咱们曾经通过 git hash-object
命令将这个文件的内容插入过了,并且咱们能够发现因为内容不变,所以生成的这个 Blob 对象的 SHA 值也是统一的,如果像咱们这样曾经做过插入的动作,上面的命令是等效的:
git update-index --add --cacheinfo 9f4d96d5b00d98959ea9960f069585ce42b1349a testfile
这个命令其实就是把之前曾经生成的 Blob 对象放到暂存区,并且指定它的文件名字是 testfile
。因为咱们的暂存区曾经有一个文件testfile
,所以我接下来咱们能够应用git write-tree
命令来基于以后暂存区的状态来创立一个 Tree 对象了:
➜ Zoker git:(master) ✗ git write-tree
aa406ee8804971cf8edfd8c89ff431b0462e250c
➜ Zoker git:(master) ✗ tree .git/objects
.git/objects
├── 9f
│ └── 4d96d5b00d98959ea9960f069585ce42b1349a
├── aa
│ └── 406ee8804971cf8edfd8c89ff431b0462e250c
├── info
└── pack
执行完命令后,Git 会基于以后暂存区的状态生成一个 SHA 值为 aa406ee8804971cf8edfd8c89ff431b0462e250c
的 Tree 对象,并把这个 Tree 对象像 Blob 对象一样存储在 .git/objects
目录下。
➜ Zoker git:(master) ✗ git cat-file -p aa406ee8804971cf8edfd8c89ff431b0462e250c
100644 blob 9f4d96d5b00d98959ea9960f069585ce42b1349a testfile
应用 cat-file
命令查看这个 Tree 对象,能够看到这个对象下只有一个文件,名为testfile
咱们持续创立第二个 Tree 对象,咱们须要第二个 Tree 对象下有批改后的 testfile
文件,有新增的 testfile2
文件,并且须要把第一个 Tree 对象作为 第二个 Tree 对象的 duplicate
目录。首先咱们先把批改后的 testfile
和新增的 testfile2
文件退出到暂存区:
➜ Zoker git:(master) ✗ git update-index testfile
➜ Zoker git:(master) ✗ git update-index --add testfile2
➜ Zoker git:(master) ✗ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: testfile
new file: testfile2
紧接着咱们须要把第一个 Tree 对象挂到 duplicate
目录下,咱们能够应用 read-tree
命令来实现:
➜ Zoker git:(master) ✗ git read-tree --prefix=duplicate aa406ee8804971cf8edfd8c89ff431b0462e250c
➜ Zoker git:(master) ✗ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: duplicate/testfile
new file: testfile
new file: testfile2
而后咱们执行 write-tree
并通过 cat-file
查看第二个 Tree 对象:
➜ Zoker git:(master) ✗ git write-tree
64d62cef754e6cc995ed8d34f0d0e233e1dfd5d1
➜ Zoker git:(master) ✗ git cat-file -p 64d62cef754e6cc995ed8d34f0d0e233e1dfd5d1
040000 tree aa406ee8804971cf8edfd8c89ff431b0462e250c duplicate
100644 blob 106287c47fd25ad9a0874670a0d5c6eacf1bfe4e testfile
100644 blob 098ffe6f84559f4899edf119c25d276dc70607cf testfile2
胜利实现了,咱们不仅批改了 testfile
的文件内容,还新增了一个 testfile2
文件,并且还把第一个 Tree 对象当作第二个 Tree 对象的 duplicate
目录了,这个时候 Tree 对象看起来应该是这样的:
至此,咱们晓得了如何手动创立一个 Tree 对象,然而前面如果我须要这两个不同的 Tree 的快照该怎么办?总不能都记住这三个 Tree 对象的 SHA 值吧?没错,记起来费老大劲了,要害是还不晓得是谁在什么工夫为了什么而创立的这个快照,而 Commit 对象(提交对象)就可能帮咱们解决这个问题。
Commit 对象
Commit 对象次要是为了记录快照的一些附加信息,并且保护快照之间的线性关系。咱们能够通过 git commit-tree
命令来创立一个提交,这个命令看字面意思就晓得,它是用来将 Tree 对象提交为一个 Commit 对象的命令:
➜ Zoker git:(master) ✗ git commit-tree -h
usage: git commit-tree \[(-p <parent>)...\] \[-S\[<keyid>\]\] \[(-m <message>)...\] \[(-F <file>)...\] <tree>
-p <parent> id of a parent commit object
-m <message> commit message
-F <file> read commit log message from file
-S, --gpg-sign\[=<key-id>\]
GPG sign commit
要害的两个参数是 -p
和-m
,-p
是指定这个提交的父提交,如果是初始的第一个提交,那这里能够疏忽;-m
则是指定本次提交的信息,次要是用来形容提交的起因。咱们来把第一个 Tree 对象作为咱们的初始提交:
➜ Zoker git:(master) ✗ git commit-tree -m "init commit" aa406ee8804971cf8edfd8c89ff431b0462e250c
17ae181bd6c3e703df7851c0f7ea01d9e33a675b
应用 cat-file
来查看这个提交:
tree aa406ee8804971cf8edfd8c89ff431b0462e250c
author Zoker <kaixuanguiqu@gmail.com> 1613225370 +0800
committer Zoker <kaixuanguiqu@gmail.com> 1613225370 +0800
init commit
Commit 所存储的内容是一个 Tree 对象,并且记录了提交者、提交工夫以及提交信息。咱们基于这个 Commit 将第二个 Tree 对象作为援用:
➜ Zoker git:(master) ✗ git commit-tree -p 17ae181bd -m "add dir" 64d62cef754e6cc995ed8d34f0d0e233e1dfd5d1
de96a74725dd72c10693c4896cb74e8967859e58
➜ Zoker git:(master) ✗ git cat-file -p de96a74725dd72c10693c4896cb74e8967859e58
tree 64d62cef754e6cc995ed8d34f0d0e233e1dfd5d1
parent 17ae181bd6c3e703df7851c0f7ea01d9e33a675b
author Zoker <kaixuanguiqu@gmail.com> 1613225850 +0800
committer Zoker <kaixuanguiqu@gmail.com> 1613225850 +0800
add dir
咱们能够应用 git log
来查看这两个提交,这里增加 --stat
参数查看文件变更记录:
commit de96a74725dd72c10693c4896cb74e8967859e58
Author: Zoker <kaixuanguiqu@gmail.com>
Date: Sun Feb 13 22:17:30 2021 +0800
add dir
duplicate/testfile | 1 +
testfile | 2 +-
testfile2 | 1 +
3 files changed, 3 insertions(+), 1 deletion(-)
commit 17ae181bd6c3e703df7851c0f7ea01d9e33a675b
Author: Zoker <kaixuanguiqu@gmail.com>
Date: Sun Feb 13 22:09:30 2021 +0800
init commit
testfile | 1 +
1 file changed, 1 insertion(+)
这个时候整个对象的构造如下图:
练习:应用底层命令创立一个提交
仅应用咱们下面提到的 hash-object
write-tree
read-tree
commit-tree
等底层命令来创立一个提交,思考哪些过程是与 git add
git commit
等价的。
对象存储形式
咱们通过后面的介绍,晓得了 Git 是将数据以不同的对象类型演绎,并且依据内容计算出一个 SHA 值用来作为寻址,那么到底是如何计算的呢?以 Blob 对象为例,Git 次要是做了如下几步:
- 辨认对象的类型,结构头部信息,以
类型 + 内容字节数 + 空字节
作为头部信息如blob 151\u0000
- 将头部信息与内容拼接,并且计算 SHA-1 校验和
- 通过 zlib 压缩内容
- 通过 SHA 值将其内容放到对应的
objects
目录
整个过程就做了这些事件,Tree 对象和 Commit 对象也差不多,只是头部类型有所差别而已,这里不再赘述,《Pro Git 2》在 Git 外部原理章节中有介绍如何应用 Ruby 来实现等同的逻辑,感兴趣的能够自行翻阅。
Git- 外部原理:https://git-scm.com/book/zh/v…
Git 援用
咱们在下面通过 git log --stat 17ae181b
可能查看第一个版本的相干信息,并且能够通过这串 SHA 值拿到这个快照的内容,然而还是挺麻烦的,因为咱们要记住一串毫无意义的字符串,这个时候 Git 的援用就派上用场了,在 Git 目录构造章节咱们曾经介绍了 refs
目录,咱们晓得在援用中存储的就是 Commit 对象的键值,也就是这个对象的 SHA 值,既然如此,咱们就给咱们以后的版本起一个有意义的名字,个别咱们会拿 master
作为默认分支援用:
➜ Zoker git:(master) ✗ echo "17ae181bd6c3e703df7851c0f7ea01d9e33a675b" >> .git/refs/heads/master
➜ Zoker git:(master) ✗ tree .git/refs
.git/refs
├── heads
│ └── master
└── tags
这个时候,master
外面存储了咱们的第一个 Commit 的 SHA 值,咱们能够应用 master
来代替 17ae181b
这串毫无意义的字符串了
➜ Zoker git:(master) ✗ git cat-file -p master
tree aa406ee8804971cf8edfd8c89ff431b0462e250c
author Zoker <kaixuanguiqu@gmail.com> 1613916447 +0800
committer Zoker <kaixuanguiqu@gmail.com> 1613916447 +0800
init commit
然而,这个并不是咱们最新的版本,咱们最新的版本是第二个提交 de96a74725dd72c10693c4896cb74e8967859e58
,同样的,咱们能够把refs/heads/master
的内容更改为这个提交的 SHA 值,然而这里咱们应用一个底层命令来实现
➜ Zoker git:(master) ✗ git update-ref refs/heads/master de96a74725dd72c10693c4896cb74e8967859e58
➜ Zoker git:(master) ✗ cat .git/refs/heads/master
de96a74725dd72c10693c4896cb74e8967859e58
这个时候,分支 master
就指向了咱们最新的版本
总结
以上次要探讨了 Git 根底的存储原理以及一些实现,还有一些如 Pack 的打包、传输协商机制以及存储格局等,限于篇幅并没有说到,前面依据一些场景再另行探讨。