做购物网站是怎么链接银行,福州微网站开发,网络营销与直播电商专业学什么就业方向是什么,微网站的建设模板有哪些内容一、引言
在我们日常使用 Git 时#xff0c;通常的操作是#xff1a;
在写完一段代码后#xff0c;执行 git add命令#xff0c;将这段代码添加到暂存区中然后再执行 git commit和 git push 命令#xff0c;将 本地 Git 版本库中的提交同步到服务器中的版本库中
Git 在…一、引言
在我们日常使用 Git 时通常的操作是
在写完一段代码后执行 git add命令将这段代码添加到暂存区中然后再执行 git commit和 git push 命令将 本地 Git 版本库中的提交同步到服务器中的版本库中
Git 在中间做了什么它如何存储不同的文件和内容以及如何区分不同分支下的文件版本呢日常操作对这些自动的操作都是无感的。 但是如果哪天一旦上述操作中出现了错误需要找回自己的代码时如果不懂 Git 其内部存储原理是没法找回的因此为了避免这种情况就有必要去了解其内部的存储——Git 对象的原理。
二、Git 对象
2.1. Git 对象概述
我们知道Git 是一个内容寻址文件系统其核心部分是一个键值对数据库。 当我们向 Git 仓库中插入任意类型的内容时它会返回一个唯一的键。我们可以通过该键在任意时刻再次取回插入的内容。 比如我们初始化 GitDemo 发现 Git 对 objects 目录进行了初始化并创建了 pack 和 info 子目录但均为空
$ git init GitDemo
Initialized empty Git repository in D:/GitDemo/.git/
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f然后创建一个 readme.txt 文本执行 git add后会发现在 .git/objects 中新增了一个文件夹 89 和文件 dab47ae90ebdfee4e6cb3d64708cd73e9c5472
$ echo read me please readme.txt
$ git add readme.txt
$ find .git/objects -type f
.git/objects/89/dab47ae90ebdfee4e6cb3d64708cd73e9c5472查看其文件内容类型和大小
$ git cat-file -p 89dab47ae90ebdfee4e6cb3d64708cd73e9c5472
read me please
$ git cat-file -t 89dab47ae90ebdfee4e6cb3d64708cd73e9c5472
blob
$ git cat-file -s 89dab47ae90ebdfee4e6cb3d64708cd73e9c5472
15这个键值为 89dab47ae90ebdfee4e6cb3d64708cd73e9c5472的对象就是 Git 对象中的 blob 对象。而且 Git 中所有的对象都存储在 .git/objects目录也叫做对象库中。 这个键值是一个 SHA-1 的哈希值由 40 个十六进制的数字组成。它是通过一个将待存储的数据外加一个头部信息一起做 SHA 算法运算而得到的校验和。40 个十六进制数字就相当于 160 比特当用 SHA-1 对不同对象进行区分和识别时冲突的概率就会极低不用存储文件的具体类型用 blob 和 SHA-1 就足以分辨不同文件内容了。 下面来看看 Git 对象的类型
2.2. Git 对象类型
2.2.1. Blob 对象
1. Blob 对象的定义和作用
BlobBinary Large Object二进制大对象是Git中的一种对象类型用来指代某些可以包含任意数据的变量或文件。它是Git对文件内容的一种抽象表示。每个文件在Git仓库中都被表示为一个独立的Blob对象。Blob对象保存了文件的原始二进制数据无论文件是文本文件还是二进制文件Git都以Blob对象的形式存储它们。 比如在上一节中的 readme.txt 文本在 Git 中就是以 blob 对象存储的
$ git cat-file -t 89dab47ae90ebdfee4e6cb3d64708cd73e9c5472
blob
$ git cat-file -p 89dab47ae90ebdfee4e6cb3d64708cd73e9c5472
read me please**当在Git仓库中添加、修改或删除某个文件时Git会创建一个新的Blob对象来存储这个文件的内容。**这样就可以跟踪文件的变化历史并且可以在需要时恢复到特定的文件版本。
比如我们修改 readme.txt 文本会发现有两个 blob 对象存储 readme.txt 的两个版本
//新增一行文本: reading
$ vi readme.txt
$ git add readme.txt
//原来版本的readme.txt内容还存在
$ git cat-file -p 89dab47ae90ebdfee4e6cb3d64708cd73e9c5472
//新版本的readme.txt内容
$ git cat-file -p b0530c9
read me please
reading因为在修改内容后创建了新的 Blob 对象因此 Git 可以使用 Blob 对象来进行文件比较操作。通过比较两个Blob对象的哈希值Git可以快速确定文件内容是否发生了变化从而进行版本控制和合并操作。
2. Blob 对象的存储方式
Blob对象在Git中的存储方式是使用对象哈希值来进行索引和存储。具体的存储方式如下
当在 Git 仓库中添加文件并执行 git add时Git 就会提取该内容然后将内容进行 SHA-1 哈希计算得到一个40个十六进制字符的哈希值。这个哈希值就是Blob对象的唯一标识符也就是我们上节提到的键。而后如果Blob对象是新的Git会将它以哈希值上面由 SHA-1 哈希计算得到的标识符为文件名存储在对象数据库中(也就是 .git/objects 目录下)。存储时Git将Blob对象的内容写入一个临时文件并将该文件的路径与哈希值相关联。一般来说取前两位作为文件目录剩下的 38 位作为文件名。比如 readme.txt 文件目录是 89文件名为 dab47ae90ebdfee4e6cb3d64708cd73e9c5472。此外如果 blob 对象过大Git会对存储的Blob对象进行压缩并将压缩后的数据写入真正的对象文件中。这些压缩的文件存储在 .git/objects/pack 中
2.2.2. Tree 对象
1. Tree 对象的定义和作用
Tree 对象是Git中的一种对象类型用于表示文件和目录的组织结构。每当向Git仓库中添加一个目录时Git会创建一个新的Tree对象来表示该目录的结构。Tree对象包含了目录中的文件和子目录的元数据以及它们对应的Blob或Tree对象的哈希值。 比如我们接着在 GitDemo 仓库中添加目录 lib和文件 readme2.txt 并提交后当前目录为:
│ readme.txt
│
└─libreadme2.txt在 git 中的存储如下
$ git cat-file -p master^{tree}
040000 tree dbff68a947c7cc60653ff64260b372a405939ae2 lib
100644 blob b0530c9b7360a8cea0e4af86475cac70a2985138 readme.txtmaster^{tree} 语法表示 master 分支上最新的提交所指向的树对象。lib 子目录所对应的那条树对象记录并不是一个数据对象而是一个指针其指向的是另一个树对象
$ git cat-file -p dbff68a947c7cc
//模式 对象类型 对象的SHA-1值 文件名
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 readme2.txt从上面可以发现一个 Tree 对象包含一条或多条树对象记录。每条记录含有一个指向数据对象或者子目录对象的 SHA-1 指针以及相应的模式、类型、对象名(SHA-1 值)、文件名信息
模式Mode模式表示文件或目录的类型和权限。它是一个八进制数字通常以4位表示。常见的模式包括
100644表示普通文件即Blob对象。100755表示可执行文件即Blob对象。040000表示目录即Tree对象。
类型Type类型表示列表项所指向的对象类型即是Blob对象还是Tree对象。对象名Object Name对象名是对应的Blob或Tree对象的哈希值。它是一个40个十六进制字符的字符串用于唯一标识对象。文件名或目录名File/Directory Name文件名或目录名是列表项所代表的文件或目录的名称。
如果记录中的类型为 Blob表示该项是一个文件如果该类型为 Tree表示该项是一个子目录。
2. Tree 对象的存储方式
和 blob 对象的存储方式类似Tree对象在Git中的存储方式是使用对象哈希值来进行索引和存储。当执行 git add 时 Git 内部的操作有
构建树对象当你在Git仓库中添加、修改或删除文件时Git会根据当前目录结构构建一个Tree对象。该Tree对象包含了目录中的文件和子目录的元数据以及它们对应的Blob或Tree对象的哈希值。哈希计算Git会对Tree对象的内容进行哈希计算生成一个40个十六进制字符的哈希值。这个哈希值就是Tree对象的唯一标识符。检查存储Git会检查对象数据库中是否已存在具有相同哈希值的Tree对象。如果存在则直接引用已有的对象如果不存在则进行存储。存储对象如果Tree对象是新的Git会将它以哈希值为文件名存储在对象数据库中。对象数据库通常位于.git/objects目录下。存储时Git将Tree对象的内容以特定的格式写入一个临时文件并将该文件的路径与哈希值相关联。压缩和索引Git会对存储的Tree对象进行压缩并将压缩后的数据写入真正的对象文件中。同时Git会更新索引文件将Tree对象的哈希值与文件路径进行映射。
2.2.3. Commit 对象
1. Commit 对象的定义和作用
Commit对象是Git中的一种对象类型用于记录代码仓库中的提交操作也就是执行 git commit命令。每个Commit对象代表一个特定的提交操作包含了提交的元数据和指向代码快照的引用。通过Commit对象Git能够跟踪代码修改的历史并实现版本控制和代码回溯等功能。 如下图所示每个 Commit 对象就是一个 version 版本Commit 对象通过指向代码快照也就是一个 Tree 对象的引用记录了代码仓库在某个特定时间点的状态。 我们再次引用上面 Tree 对象中的 GitDemo 仓库案例此时仓库中有如下文件和目录
│ readme.txt
│
└─libreadme2.txt可以通过 git cat-file master命令查看此时的 commit 对象
//master就是一个指向commit对象的指针, 其内部存储Commit对象的SHA-1值
$ cat .git/refs/heads/master
2b2af66549827bd6a466fe43081f406c2a12900b
$ git cat-file -p 2b2af665498
tree 2503e9e0c4f774fc5ce298f4972f0e6d3a800d6f
parent 7b34a1e750918570ed610ee1f228e83b43a1192e
author wangJw wangJw163.com 1705458723 0800
committer wangJw wangJw163.com 1705458723 0800second commit
从上面可知一个 commit 对象由这样几个部分组成
指向代码快照的引用treeCommit对象包含一个指向代码快照的引用通常是指向一个Tree对象。该Tree对象记录了提交时代码仓库中文件和目录的状态。父提交parentCommit对象可以有一个或多个父提交指向前一个或多个Commit对象。这构成了提交历史的链式结构。通常一个Commit对象的父提交是它之前的一个Commit对象除非进行了分支合并等操作。作者authorCommit对象记录了提交的作者信息包括姓名和电子邮件。作者是指实际进行代码更改的人。提交者CommitterCommit对象还包含了提交者信息通常与作者相同。提交者是指将更改提交到代码仓库的人。提交消息Commit Message也就是 second commit 中的内容 Commit对象包含了提交时附加的可选消息用于描述提交的目的、更改的内容、修复的问题等。提交消息可以提供其他开发者和团队成员了解提交的背景和目的。Commit对象的哈希值SHA-1 值每个Commit对象都有一个唯一的哈希值用于标识该对象。
加上 commit 对象和 master 指针可以完善在 tree 对象中的图
2. Commit 对象的存储方式
当执行 git commit命令提交代码时commit 对象随之创建Git 的内部操作有
内容提取执行git commit命令提交代码时Git会提取提交的相关信息包括作者、提交者、提交时间、提交消息和父提交等。创建对象Git会将提交信息和父提交的引用等数据组合成一个Commit对象。哈希计算Git会对Commit对象的内容进行SHA-1哈希计算得到一个40个十六进制字符的哈希值。这个哈希值就是Commit对象的唯一标识符。检查存储Git会检查对象数据库中是否已存在具有相同哈希值的Commit对象。如果存在则直接引用已有的对象如果不存在则进行存储。存储对象如果Commit对象是新的Git会将它以哈希值为文件名存储在对象数据库中。对象数据库通常位于 .git/objects 目录下。存储时Git将Commit对象的内容写入一个临时文件并将该文件的路径与哈希值相关联。压缩和索引Git会对存储的Commit对象进行压缩并将压缩后的数据写入真正的对象文件中。同时Git会更新索引文件将Commit对象的哈希值与文件路径进行映射。
2.2.4. Tag 对象
1. Tag 对象的定义和作用
Tag对象是Git中的一种对象类型用于给特定的提交打上标记。Tag对象的主要作用是标记代码仓库中的特定提交或里程碑。它可以用于记录发布版本、重要的里程碑、稳定的代码快照等。git 标签分为两种类型轻量标签和附注标签。
轻量标签
轻量标签是指向提交对象的引用
//创建轻量级标签
$ git tag firstTag
//查看标签
$ git tag
firstTag当创建了 firstTag 后会在.git/refs/tags 目录下创建一个名为 firstTag 的文件其内容指向当前的 commit 对象的 SHA-1 值
$ cat .git/refs/tags/firstTag
2b2af66549827bd6a466fe43081f406c2a12900b
//轻量标签指向提交对象的引用
$ git cat-file -t firstTag
commit
$ git cat-file -p firstTag
tree 2503e9e0c4f774fc5ce298f4972f0e6d3a800d6f
parent 7b34a1e750918570ed610ee1f228e83b43a1192e
author wangJw wangJw163.com 1705458723 0800
committer wangJw wangJw163.com 1705458723 0800second commit
我们发现轻量标签 firstTag 中的内容只有一个指向提交对象的 SHA-1 值。没有其他内容因此无法知道何人什么时间创建的标签。在团队开发中很容易发生混淆因此可以用另外一种打标签的方式附注标签
附注标签
附注标签则是仓库中的一个独立对象使用带参数 -a 或 -m msg 的 git tag 命令
//创建一个空提交
$ git commit --allow-empty -m empty commit for tagTest
[master 8a4678f] empty commit for tagTest
//创建一个附注标签
$ git tag -m secondTag secondTag
//查看所有标签
$ git tag
firstTag
secondTag
这个时候再来看看 .git/refs/tags中的 secondTag 标签内容
//查看该标签的类型
$ git cat-file -t secondTag
tag
//再来看看secondTag标签的内容
$ git cat-file -p secondTag
object 8a4678fae181c16c6f4ff0e6a618991128d86da2
type commit
tag secondTag
tagger wangJw wangJw163.com 1705480524 0800secondTag
主要由这样几个部分组成
标签指向的提交对象object附注标签对象中包含一个指向特定提交的引用。此处 object 中的值为我上面的提交对象的 SHA-1 值标签指向对象类型type指向的提交对象的类型标签名称tag附注标签对象包含一个唯一的标签名称用于标识和引用该标签。标签作者tagger附注标签对象记录了标签的作者信息包括姓名和电子邮件地址。作者是指创建该标签的人。标签消息Tag Message其中的 secondTag内容附注标签对象包含一个可选的标签消息用于描述标签的目的、里程碑或其他相关信息。标签消息可以提供其他开发者和团队成员了解标签的背景和用途。
我们再来看看 Tag 对象的存储方式
2. Tag 对象的存储方式
当执行带有 -a 或 -m msg 的 git tag命令时Git 就会由如下操作
创建对象当你执行创建标签的操作如git tag命令时Git会创建一个Tag对象。内容提取Tag对象包含标签的名称、类型、标签指向的提交、标签作者、标签创建时间、标签消息等信息。哈希计算Git会对Tag对象的内容进行SHA-1哈希计算得到一个40个十六进制字符的哈希值。这个哈希值就是Tag对象的唯一标识符。检查存储Git会检查对象数据库中是否已存在具有相同哈希值的Tag对象。如果存在则直接引用已有的对象如果不存在则进行存储。存储对象如果Tag对象是新的Git会将它以哈希值为文件名存储在对象数据库中。对象数据库通常位于 .git/objects 目录下。存储时Git将Tag对象的内容写入一个临时文件并将该文件的路径与哈希值相关联。压缩和索引Git会对存储的Tag对象进行压缩并将压缩后的数据写入真正的对象文件中。同时Git会更新索引文件将Tag对象的哈希值与文件路径进行映射。
三. Git 对象的存储
3.1 SHA-1 算法如何生成哈希值
SHA-1Secure Hash Algorithm 1是一种用于生成哈希值的加密算法。该算法将任意长度的输入经过散列运算转换为固定长度的输出。这个固定长度的输出就叫做对应输入内容的数字摘要或者哈希值。 那么对于 Git 对象中的 SHA-1 哈希值是如何生成的 在 《Pro Git 2nd》这本书提到SHA-1 哈希值是通过将待存储的数据一个头部信息header一起做 SHA-1 校验运算而得到的。 而在头部信息由这些部分组成
对象类型字符串比如blob, “tree”, “commit”, “tag”空格数组内容的字节数空字节null byte
Git 会将上述的头部信息和文件原始数据拼接来计算出 SHA-1 校验和。在 Linux 中有 sha1sum 命令可以生成 SHA1 哈希值下面来验证一下我们生成的 SHA1 哈希值和 Git 是不是相同的
//当前目录结构
│ a.txt
│
└─bc.txt3.1.1. blob 对象的 SHA1 哈希值
先来看看 blob 对象也就是 a.txt 对应的文件内容的 SHA1 哈希值生成过程
//a.txt中的内容为
$ cat a.txt
123
//字符数为3
$ git cat-file blob HEAD:a.txt | wc -c
3其头部信息为 blob 3\000 在文件内容上加上头部信息然后对新文件内容执行 SHA-1 哈希算法
$ (printf blob 3\000; git cat-file blob HEAD:a.txt) | sha1sum
d800886d9c86731ae5c4a62b0b77c437015e00d2 *-查看在 Git 仓库中是否找到该 SHA-1 值对应的 blob 对象
$ git cat-file -p d80088
123说明执行 sha1 算法和 Git 操作算法得到的结果一致验证了 Git 中 SHA-1 哈希值的生成过程
3.1.2. commit 对象的 SHA1 哈希值
此时在提交链最末端的 commit 对象内容是
$ git cat-file commit master
tree 46bda27c4834d428a388841808fdaa7ca15a7bc1
parent 61b04b17412e1d9639db2a6b1b4e83319473a14a
author wangJW 1w163.com 1679818066 0800
committer wangJW 1w163.com 1679818066 0800second commit根据头部信息的组成需要知道 commit 中的字符数
$ git cat-file commit HEAD | wc -c
218然后加上空格以及空字符串commit 218\000然后与 commit 对象内容拼接将拼接后的内容计算 SHA1 校验和
$ (printf commit 218\000; git cat-file commit HEAD) | sha1sum
2514fb61430ad5beea4f80e2548f1fbdfd97d74d *-再来看看 HEAD 文件中对应的 Commit 对象以及其内容是不是与上面的 SHA1 相符
$ cat .git/HEAD
2514fb61430ad5beea4f80e2548f1fbdfd97d74d
$ git cat-file -p 2514fb6
tree b79d07773ea2d47125f1e7078bbc8113a74a2fa7
parent 61b04b17412e1d9639db2a6b1b4e83319473a14a
author wangJW 1w163.com 1705493204 0800
committer wangJW 1w163.com 1705493204 0800second commit
从结果可知说明 Git 内部就是采用头部信息内容利用 SHA1 算法得到的哈希值。 再来看看 tree 对象
3.1.3 tree 对象的 SHA1 哈希值
直接拿上面 commit 对象中的 tree 对象来做实验首先查看 tree 对象中的内容和其中的字节数
$ git cat-file -p b79d0777
100644 blob d800886d9c86731ae5c4a62b0b77c437015e00d2 a.txt
040000 tree ceb3bfbba0a2f151a88628549113aa5c1be65bf5 b
//此时就是对应HEAD指针指向的树
$ git cat-file tree HEAD^{tree} | wc -c
61然后根据头部信息tree 对象内容信息计算 SHA-1 值
$ (printf tree 61\000; git cat-file tree HEAD^{tree}) | sha1sum
b79d07773ea2d47125f1e7078bbc8113a74a2fa7 *-发现此时计算出的 SHA-1 值和 commit 对象所指向的值完全相同再次验证 SHA-1 生成方式。最后再来看看 tag 对象
3.1.4 tag 对象的 SHA1 哈希值
首先创建一个 tag 对象
$ git tag -m firstTag firstTag
//创建成功
$ git tag
firstTag获取这个 tag 对象的字节数并执行 SHA1 哈希算法
$ git cat-file tag firstTag | wc -c
136$ (printf tag 136\000; git cat-file tag firstTag) | sha1sum
d0c8f7e57f23b368152094bf3e57e70b3569cb13 *-从 tag 对象的执行结果说明SHA1 哈希值生成方式正确。
3.2 Git 对象的存储位置
从前面查看 blob 对象内容时提到过在 Git 中的对象存储在 Git 仓库的 .git/objects 目录下。 在下列情况中会触发 Git 存储对象的操作
git add在执行 git add 命令暂存某个文件 时Git 将会将文件的内容转换为一个 Blob 对象并将该对象存储在本地对象数据库中。这个操作将文件添加到暂存区Staging Area为接下来的提交做准备。git commit执行 git commit 命令时Git 首先会创建一个新的 Commit 对象。这个 Commit 对象包含了提交的元数据信息如作者、提交时间、提交信息等。同时Git 会创建一个对应的根目录的 Tree 对象记录了当前提交时仓库中所有文件的快照。最后Git 将这个 Commit 对象存储在本地对象数据库中并将当前分支指向该 Commit 对象表示当前的工作状态。git tag执行创建附注标注命令时Git 会创建一个 Tag 对象该对象包含标签的元数据信息并指向一个特定的 Commit 对象。这个 Tag 对象会被存储在本地对象数据库中以便后续引用。git merge执行 git merge 命令时Git 会创建一个新的 Commit 对象该对象包含合并的元数据信息并引用两个或多个合并的分支的 Commit 对象。Git 会将这个新的 Commit 对象存储在本地对象数据库中并将当前分支指向该新的 Commit 对象。
四、 总结
本文通过 .git 目录角度解析 Git对象
Git 对象主要有以下四种类型Blob存储文件内容,Tree记录文件结构,Commit记录历史,Tag添加标签。Git 通过提取对象内容加头信息使用 SHA-1 算法生成哈希值作为唯一 IDGit 对象存储于 .git/objects目录下其中对象 ID 值前两位作为目录名后 38 位作为文件名在在执行暂存add、提交commit、合并merge、打标签tag等操作时都会触发 Git 对象的存储
参考资料
《Git 权威指南》
《Git Pro》