前言
上一篇文章主要介绍些Git起源背后的一些故事背景,从这篇开始将逐渐讲解Git的设计理念,包括分布式控制、快照管理、不可变对象模型和分支模型。其实上述概念都不是孤立的,在讲解中会发现它们是相辅相成的有机整体,实现1+1大于2的效果。
接下来预计会按照快照模型与不可变对象模型 、分支模型 、分布式控制 这样的顺序讲解,其用意到后面自会明了。
本人总是从中能看到区块链的影子,之后会引入区块链的相关知识进行补充,也当是拓展了🫠。
快照存储模型
快照模型与传统的版本控制系统(如SVN等)不同。传统系统通常基于文件的差异(diff)来管理版本,而Git则使用快照(snapshot)的方式来记录项目的每个版本。
快照模型的主要特点:
- 快照而非差异 :
- Git会在每次提交时保存项目的当前状态,称为"快照"。这意味着每次提交都是整个项目的完整状态,而不是仅保存变化部分。
- 只保存变更 :
- 虽然每个提交实际上是一个完整的快照,但Git为了节省空间,内部实现上只存储更改过的文件的内容,而未改变的文件只保存一次。这使得Git非常高效。
- 对象存储 :
- Git将文件和目录的快照存储为对象,每个对象都有一个唯一的SHA-1哈希值。这种方式不仅提供了数据的完整性,还能有效管理版本。
- 树(Tree)和Blob :
- 在Git中,目录结构被称为"树"(tree),每个文件被称为"Blob"(binary large object)。每次提交都会创建一个新的树对象,指向当前提交的文件状态。
- 提交(Commit)对象 :
- 每次提交不仅包含快照,还包含提交信息、作者信息、时间戳和指向父提交的指针。这使得Git可以轻松地追踪历史和分支。
- 高效的分支管理 :
- 因为每个提交都是一个独立的快照,创建分支实际上只需要创建一个新的指针,指向当前的提交。这使得分支操作非常轻量级,极大地方便了开发过程中的实验和合并。
不可变对象模型
其实这就是上述在快照模型中提到Git对象数据库的具体展开,主要就是三大对象类型结合SHA-1来保证数据的不可变性。
Git的内部数据结构依赖于 [[加密哈希函数|SHA-1]],为每个对象(如文件和提交)生成唯一标识。这种设计确保了数据的不可变性:
- 数据完整性:每个对象的内容会被哈希处理,任何对内容的修改都会导致哈希值的变化,从而提示开发者数据已被更改。
- 一致性:由于对象的内容与其哈希值紧密关联,Git能够保证版本库的一致性,防止数据丢失或损坏。这种机制使得任何用户都可以安全地信任本地的Git仓库。
Git中的哈希值通过对对象内容和元数据进行特定格式的组合,然后应用SHA-1哈希函数生成。这个过程确保了每个对象都有唯一的标识符,同时也保证了数据的完整性和安全性。我们先来看下它们的具体的步骤和原理:
三大对象
1. 对象类型
Git中的对象主要有三种类型:
- Blob:表示文件内容。
- Tree:表示目录,可以包含指向其他blob或tree对象的指针。
- Commit:表示一次提交,包含指向tree对象的指针、作者信息、提交信息等。
提交对象(commit)
提交对象是每次提交时生成的一个对象,记录了:
- 提交元数据:如提交信息、作者、时间戳等。
- 父提交:上一个提交的引用(一个提交可以有多个父提交,用于表示合并)。
- 树对象的引用 :提交对象会指向一个根树对象,代表当时项目的整体文件系统快照。
提交对象是 Git 的历史记录,通过它可以追踪项目的每个版本、作者、提交时间等信息。
树对象(tree)
树对象表示文件系统的目录结构。它:
- 包含多个引用:每个引用要么指向一个文件对象(blob),要么指向另一个子目录的树对象。
- 记录文件名和路径 :树对象会保存项目中所有文件和子目录的结构信息,包括文件的名字、类型(文件或目录),以及它们在仓库中的位置。
树对象可以嵌套,类似文件系统中的目录结构,一个树对象可以包含其他树对象(子目录),也可以包含文件对象(表示具体的文件内容)。
文件对象(Blob)
Blob(Binary Large Object)对象保存了实际的文件内容,Blob 对象本身不包含文件名或路径等元数据。它:
- 存储文件内容:无论是文本文件还是二进制文件,Blob 对象只存储文件的内容本身。
- 与文件名、路径无关:文件名和路径信息都在树对象中管理,因此相同内容的文件在不同的目录或提交中,只需要存储一份 Blob 对象。
关系总结:如何构成一个提交
- 提交对象(commit) 是顶层对象,指向一个 树对象(tree),代表当时项目的目录结构。
- 树对象(tree) 维护着目录和文件的结构,并通过引用指向文件对象或子目录(另一个树对象)。
- 文件对象(Blob) 存储文件的实际内容,树对象通过引用连接到 Blob 对象。
因此,Git 通过提交对象、树对象和Blob对象的层层指向,构成了完整的项目快照。例如,每次提交时:
- Git 会生成一个 提交对象 ,指向项目当前的根 树对象。
- 树对象 代表整个项目的目录结构,并指向多个文件对象(Blob)或子目录的树对象。
- Blob 对象 保存文件内容。
这三者相互关联,形成了完整的项目历史和快照。
图示:对象关系
sql
提交对象(commit)
|
└── 树对象(tree)
├── Blob 对象(文件1内容)
├── Blob 对象(文件2内容)
└── 子树对象(子目录)
├── Blob 对象(子目录中的文件1内容)
└── Blob 对象(子目录中的文件2内容)
通过这种分层结构,Git 能够快速检索项目的历史版本,并且因为Blob对象可以被多个树对象引用,所以相同的文件内容在不同的提交中只需要存储一次,大大提高了存储效率。
可以通过 git cat-file -p
命令查看这些对象的关系和详细内容,比如查看提交、树对象和Blob对象的内容:
bash
git cat-file -p <commit-hash>
git cat-file -p <tree-hash>
git cat-file -p <blob-hash>
可以通过 git cat-file -t
命令查看这些对象的实际类型
bash
git cat-file -t <commit-hash>
git cat-file -t <tree-hash>
git cat-file -t <blob-hash>
2. 计算哈希值的步骤
在让我们对于每种对象,Git都是如何生成一个唯一的哈希值。计算哈希值的步骤如下:
Blob(文件内容)
- 准备数据:首先,Git会将文件内容与其类型信息和长度信息组合在一起,形成一个字符串。格式如下:
bash
blob <size>\0<content>
- `<size>`是文件的字节数。
- `\0`是一个空字符,用于分隔大小和内容。
- `<content>`是文件的实际内容。
- 计算哈希:将上述字符串传递给SHA-1哈希函数,生成一个40字符的十六进制哈希值。
Tree(目录)
- 准备数据:一个tree对象由多个条目组成,每个条目包含文件模式、文件名和指向blob或子tree对象的SHA-1哈希值。格式如下:
bash
tree <size>\0<entries>
- `<size>`是tree中条目的数量。
- `<entries>`是所有条目的组合,每个条目都是类似于`<mode> <filename>\0<sha1>`的格式。
- 计算哈希:同样,将字符串传递给SHA-1哈希函数。
Commit(提交)
- 准备数据:一个commit对象包含指向tree对象的指针、作者信息、提交时间和提交信息。格式如下:
bash
commit <size>\0<tree>\n<parent>\n<author>\n<committer>\n<timestamp>\n<message>
- `<size>`是commit对象的字节数。
- `<tree>`是指向tree对象的SHA-1。
- `<parent>`(如果存在)指向父提交的SHA-1。
- `<author>`和`<committer>`包含提交者的信息。
- `<timestamp>`是提交时间。
- `<message>`是提交信息。
- 计算哈希:将上述字符串传递给SHA-1哈希函数,生成该提交对象的哈希值。
3. 哈希值的存储
Git会将计算出来的哈希值作为对象的唯一标识符,存储在.git/objects
目录中。每个对象的内容及其哈希值被保存在文件系统中,以便后续快速检索和验证。
SHA-1算法
看了这么多一定对SHA到底什么感到困惑吧,其实没什么高深的,Git就是根据相应对象的具体内容(本质是二进制)算出一个 40 字符的 16 进制字符串,这个字符串就被称为 "散列值" 或 "哈希值"。
Git 使用 SHA-1(Secure Hash Algorithm 1)作为其核心机制来唯一标识每个提交、文件对象和目录树。
哈希值具有以下特性:
- 不可逆性:从哈希值无法反推原始数据。
- 抗碰撞性:很难找到两个不同的输入产生相同的哈希值。
- 小改动大变化 :输入数据的微小变化会导致哈希值的巨大变化。
Git利用这个特性实现如下它的几个关键作用:
-
唯一标识对象:Git 中的每个文件、每次提交、以及每个目录树的状态都会生成一个 SHA-1 哈希值。Git 通过这个哈希值来区分每个提交,确保同样的文件内容和文件结构不会生成重复的 ID。
-
内容完整性校验 :SHA-1 的不可逆特性 使得它非常适合检测文件是否被篡改。Git 在传输和存储文件时使用 SHA-1 来校验数据的完整性,如果一个文件的内容被改动,它的哈希值也会发生变化,这样可以有效防止数据损坏。
-
分布式优势 :SHA-1 哈希值在 Git 中有一个不可忽视的好处:因为哈希值是根据内容生成的,不同的开发者对同样的内容会产生相同的哈希值,这使得 Git 的分布式模型更加高效。
-
不可逆性和冲突:虽然 SHA-1 理论上会有哈希冲突的可能,但在实际开发中,这种冲突几乎不可能影响到项目的正常运作。SHA-1 的设计保证了它的安全性和性能。
我之前了解过一些区块链的知识,就感觉似曾相识。Git是使用SHA-1而区块链使用的SHA-256,但SHA-256和SHA-1都是加密哈希函数,可以简单的理解为SHA-1是简单版而SHA-256是plus版。因为比特币算是金融领域,需要更高的安全性,区块链算是把hash玩出了花,相较Git有更复杂的数据结构和运算方法。
Git诞生于2005年,是由Linus Torvalds开发的一种分布式版本控制系统,设计之初主要是为了解决Linux内核开发的版本管理需求。它的核心理念是去中心化的分布式存储:每个人可以有一个完整的项目历史副本,且各副本通过哈希值(SHA-1)确保每次变更的唯一性和完整性。
区块链则是在2008年中本聪提出的比特币白皮书中被首次介绍的技术,它建立在许多类似Git的去中心化和不可篡改的核心理念上。
后记
这篇文章大致讲解了git中的两个主要模型快照存储模型 和不可变对象模型 ,略微引出了下哈希,而这两个模型理念的集中应用就是在 .git/objects
目录,下一篇文章会围绕其展开。
也是写爽了,发现Git这个坑是越挖越大,越挖越深😓😓😓,或许能出本书呢。