Git通讲-第二章(1):快照和不可变对象模型

前言

上一篇文章主要介绍些Git起源背后的一些故事背景,从这篇开始将逐渐讲解Git的设计理念,包括分布式控制、快照管理、不可变对象模型和分支模型。其实上述概念都不是孤立的,在讲解中会发现它们是相辅相成的有机整体,实现1+1大于2的效果。

接下来预计会按照快照模型与不可变对象模型分支模型分布式控制 这样的顺序讲解,其用意到后面自会明了。

本人总是从中能看到区块链的影子,之后会引入区块链的相关知识进行补充,也当是拓展了🫠。

快照存储模型

快照模型与传统的版本控制系统(如SVN等)不同。传统系统通常基于文件的差异(diff)来管理版本,而Git则使用快照(snapshot)的方式来记录项目的每个版本。

快照模型的主要特点:

  1. 快照而非差异
    • Git会在每次提交时保存项目的当前状态,称为"快照"。这意味着每次提交都是整个项目的完整状态,而不是仅保存变化部分。
  2. 只保存变更
    • 虽然每个提交实际上是一个完整的快照,但Git为了节省空间,内部实现上只存储更改过的文件的内容,而未改变的文件只保存一次。这使得Git非常高效。
  3. 对象存储
    • Git将文件和目录的快照存储为对象,每个对象都有一个唯一的SHA-1哈希值。这种方式不仅提供了数据的完整性,还能有效管理版本。
  4. 树(Tree)和Blob
    • 在Git中,目录结构被称为"树"(tree),每个文件被称为"Blob"(binary large object)。每次提交都会创建一个新的树对象,指向当前提交的文件状态。
  5. 提交(Commit)对象
    • 每次提交不仅包含快照,还包含提交信息、作者信息、时间戳和指向父提交的指针。这使得Git可以轻松地追踪历史和分支。
  6. 高效的分支管理
    • 因为每个提交都是一个独立的快照,创建分支实际上只需要创建一个新的指针,指向当前的提交。这使得分支操作非常轻量级,极大地方便了开发过程中的实验和合并。

不可变对象模型

其实这就是上述在快照模型中提到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对象的层层指向,构成了完整的项目快照。例如,每次提交时:
  1. Git 会生成一个 提交对象 ,指向项目当前的根 树对象
  2. 树对象 代表整个项目的目录结构,并指向多个文件对象(Blob)或子目录的树对象。
  3. 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(文件内容)
  1. 准备数据:首先,Git会将文件内容与其类型信息和长度信息组合在一起,形成一个字符串。格式如下:
bash 复制代码
	blob <size>\0<content>
- `<size>`是文件的字节数。
- `\0`是一个空字符,用于分隔大小和内容。
- `<content>`是文件的实际内容。
  1. 计算哈希:将上述字符串传递给SHA-1哈希函数,生成一个40字符的十六进制哈希值。
Tree(目录)
  1. 准备数据:一个tree对象由多个条目组成,每个条目包含文件模式、文件名和指向blob或子tree对象的SHA-1哈希值。格式如下:
bash 复制代码
	tree <size>\0<entries>
- `<size>`是tree中条目的数量。
- `<entries>`是所有条目的组合,每个条目都是类似于`<mode> <filename>\0<sha1>`的格式。
  1. 计算哈希:同样,将字符串传递给SHA-1哈希函数。
Commit(提交)
  1. 准备数据:一个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>`是提交信息。
  1. 计算哈希:将上述字符串传递给SHA-1哈希函数,生成该提交对象的哈希值。
3. 哈希值的存储

Git会将计算出来的哈希值作为对象的唯一标识符,存储在.git/objects目录中。每个对象的内容及其哈希值被保存在文件系统中,以便后续快速检索和验证。

SHA-1算法

看了这么多一定对SHA到底什么感到困惑吧,其实没什么高深的,Git就是根据相应对象的具体内容(本质是二进制)算出一个 40 字符的 16 进制字符串,这个字符串就被称为 "散列值" 或 "哈希值"。

Git 使用 SHA-1(Secure Hash Algorithm 1)作为其核心机制来唯一标识每个提交、文件对象和目录树。

哈希值具有以下特性:

  • 不可逆性:从哈希值无法反推原始数据。
  • 抗碰撞性:很难找到两个不同的输入产生相同的哈希值。
  • 小改动大变化 :输入数据的微小变化会导致哈希值的巨大变化。
    Git利用这个特性实现如下它的几个关键作用:
  1. 唯一标识对象:Git 中的每个文件、每次提交、以及每个目录树的状态都会生成一个 SHA-1 哈希值。Git 通过这个哈希值来区分每个提交,确保同样的文件内容和文件结构不会生成重复的 ID。

  2. 内容完整性校验 :SHA-1 的不可逆特性 使得它非常适合检测文件是否被篡改。Git 在传输和存储文件时使用 SHA-1 来校验数据的完整性,如果一个文件的内容被改动,它的哈希值也会发生变化,这样可以有效防止数据损坏。

  3. 分布式优势 :SHA-1 哈希值在 Git 中有一个不可忽视的好处:因为哈希值是根据内容生成的,不同的开发者对同样的内容会产生相同的哈希值,这使得 Git 的分布式模型更加高效。

  4. 不可逆性和冲突:虽然 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这个坑是越挖越大,越挖越深😓😓😓,或许能出本书呢。

相关推荐
好运yoo31 分钟前
git提交冲突的原因及解决方案
git
謬熙1 小时前
Git使用指南
git
没资格抱怨1 小时前
Git详细使用
git
匆匆整棹还1 小时前
git远程和本地创建分支并关联
git·github
shuangrenlong2 小时前
git 多账号配置
git
NewBee_Lxx2 小时前
gitmakegdb
git
丶213616 小时前
【Git】Git 远程仓库命令详解
git
InnovatorX16 小时前
Git 操作
大数据·git·elasticsearch
hillstream31 天前
一个git相关的cve:CVE-2024-32002
git
云间行者2 天前
Git使用全解析:10分钟精通版本控制!
git