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这个坑是越挖越大,越挖越深😓😓😓,或许能出本书呢。

相关推荐
deja vu水中芭蕾3 小时前
git push origin HEAD:refs/for/分支名
git
海岛日记7 小时前
git常用操作
git
喝鸡汤7 小时前
一起学Git【番外篇:如何在Git中新建文件】
git
“αβ”7 小时前
Windows下使用git配置gitee远程仓库
git
谢家小布柔12 小时前
Git图形界面以及idea中集合Git使用
java·git
winner888113 小时前
git merge 冲突 解决 show case
java·git·git merge·git冲突
玩电脑的辣条哥17 小时前
怎么给git动图扣除背景?
git·抠图
谢家小布柔19 小时前
git中的多人协作
git
isolusion20 小时前
git分支管理及策略
git
isolusion20 小时前
git仓库的基本概念和流程以及一些基本命令
git