Git版本管理原理:从快照到内容寻址
Git版本管理原理:从快照到内容寻址
Git是如何通过一个小小的仓库实现庞大文件的版本管理?为什么明明没有倍增式存储却还能轻松地完成合并与分支操作?本文将从Git的快照机制入手,深入浅出地介绍Git底层机制的一部分。
Git与SVN的存储方式对比
在版本控制系统中,Git和SVN的存储方式存在本质区别:
SVN:以文件为水平维度,记录每个文件在每个版本下的变化(delta改变)。每次提交只记录文件的变化部分,而不是整个文件。这使得SVN存储的数据相对较小,因为它只存储了每个文件的变化,而不是整个文件的副本。
Git:以每次提交为一次快照,即每次提交时,对当前所有文件做一次全量快照,然后将该快照作为一个整体存储。这意味着每次提交都包含了整个项目的状态,而不仅仅是文件的变化部分。Git存储的数据量相对较大,因为每次提交都包含完整的文件副本,但这也使得Git在操作上更加灵活,例如,可以轻松地进行分支、合并等操作。
以下是两张图,可以比较明确地显示出两者之间的区别:
快照机制详解
快照(snapshot)
这个概念源于摄影学,在摄影中,snapshot可以翻译成:抓拍,指记录某一瞬间的影像。从英文原意来看,Snap指的是相机的快门,在快速开合时发出的咔嗒声。Snapshot意为抓拍——捕捉特殊的瞬间。
在计算机领域中,我们提到的快照多半为存储快照,百度百科对存储快照的解释是这样的:
存储快照是计算机科学领域术语。存储网络行业协会SNIA(StorageNetworking Industry Association)对快照(Snapshot)的定义是:关于指定数据集合的一个完全可用拷贝,该拷贝包括相应数据在某个时间点(拷贝开始的时间点)的映像。快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。
全量快照(full snapshot)与增量快照(incremental snapshot)
快照有全量快照 (full snapshot) 和增量快照 (incremental snapshot) 两种类型。
全量快照(Full Snapshot):在全量快照中,整个数据集或文件系统的状态被完整地复制并保存。无论是第一次备份还是后续备份,都会复制整个数据集,这样每次备份都会占用大量的存储空间。
增量快照(Incremental Snapshot):在增量快照中,只有自上次备份以来发生更改的部分被复制并保存。这意味着每次备份只会存储自上次备份以来的变化,因此它们通常需要更少的存储空间,并且备份速度更快。
内容重用
Git通过内容重用来解决存储问题:
Git会对每一个文件进行哈希值的计算。在 Git 中,每个文件和目录都被视为一个对象。当你向 Git 添加文件时,Git 首先计算文件的哈希值。这个哈希值是根据文件内容计算得出的,即使文件的文件名和路径不同,只要文件内容相同,其哈希值也会相同。
当你提交一个新的版本时,Git 不会简单地复制整个文件内容并创建一个新的对象。相反,Git 首先计算新版本文件的哈希值。如果该哈希值已经存在于仓库中,说明相同的文件内容已经被存储过了,Git 将会重用这个已经存在的对象,而不是重复存储相同的内容。
例如,如果某次更新只修改了README文件里面的标点符号,而没有修改其他大型文件,此时创建提交,git会计算出所有文件的哈希值,发现仅仅多了一个文件的哈希值,而其他值并无变化,则仓库内仅仅多了一个新的README文件,存储模式上大大优化了。
快照的优点
快照的优点主要体现在以下几个方面:
性能优化:在 Git 中,大多数操作都需要访问整个文件内容,例如查看历史记录、切换分支等。如果存储的是文件差异而不是快照,那么在执行这些操作时就需要不断地重新计算文件的内容,这会严重影响性能。
完整性保障:Git 是一个分布式版本控制系统,每个本地仓库都包含完整的项目历史。如果仅存储文件差异,而不是快照,那么在某些情况下可能会丢失文件的完整性。例如,如果某个差异文件损坏或丢失,那么整个项目的完整性就无法恢复。
哈希与内容寻址
哈希值(Hash)
Git 使用 SHA-1 哈希算法来计算对象的唯一标识符。每个 Git 对象,包括文件内容(blob)、目录结构(tree)、提交信息(commit)等,都会被计算出一个唯一的哈希值。哈希值是根据对象的内容计算得出的,只要对象内容不同,哈希值就会不同。这确保了 Git 中的每个对象都具有唯一性。
内容寻址(Content Addressing)
Git 使用哈希值来寻址对象。这意味着 Git 的对象数据库(Object Database)是一个键值对结构,其中键是对象的哈希值,而值则是对象的内容。通过使用哈希值作为对象的唯一标识符,Git 可以确保对象在存储和检索时是完整和一致的。当你需要访问某个对象时,Git 只需根据该对象的哈希值在数据库中查找相应的内容。
以一般的流程为例,我们会先用git add指令将文件添加到暂存区,而后用git commit来提交。现在假设我们在工作目录下新增俩文件Ham, Bur。同时执行了一次
git add
添加了这三份文件。那么此时
git add
做了如下两件事:
首先,它给这三个文件分别创建2个索引添加到暂存区中。git通过SHA-1这种哈希算法,遍历每一个文件,根据文件内容等信息,为文件创建索引。以后,只要根据这个索引,我们就可以取出一个文件中的完整内容。
然后,git对当前的暂存区拍了一张照片,也就是我们所说的快照, 并将快照放入版本库。快照里包括什么内容呢?快照里包括我们刚才说的文件索引和文件完整内容(类似于key-value的结构)。同时,git采用内置的blob对象来存储这三个文件的快照。
接下来,我们执行
git commit
,这个命令又做了两件事:
首先,零散的文件得有一个目录结构吧?所有它用一个内置的tree对象,把文件的目录结构保存下来。
然后,git在这个tree对象上又包了一层,创建了一个commit对象,这个commit对象也是我们说的git进行版本管理的最终对象。commit对象里包含了tree对象,还包含作者、提交评论等信息。
在执行
git commit
时对当前暂存区的情况进行状态记录。这个状态记录包含了文件的索引、文件的完整内容以及文件的目录结构。这些信息将被存放到Git版本库下,并用Git内置的blob(文件内容)、tree(目录结构)、commit(提交信息)对象进行存储。
总结
Git通过快照机制和内容寻址实现了高效的版本管理。虽然每次提交都创建全量快照,但通过内容重用和哈希算法,Git能够有效地节省存储空间并保持数据完整性。这种设计使得Git在处理大型项目时也能保持高性能和灵活性。