• git实际上是如何工作的?
  • 发布于 2个月前
  • 208 热度
    0 评论
前言
Git 是一个分布式的版本控制系统,这意味着它使用多个本地存储库,包括一个集中式存储库和服务器,它在从前端工作中抽象出底层机制方面做得非常出色。虽然 Git 已经演变成一个成熟的版本控制管理系统,但这并不是作者最初的意图,但并不影响它成为最为世界上最为出色、优雅的工具之一。Git 的好处在于,你可以在整个职业生涯中都不知道 Git 内部是如何工作的,但你依然可以和它相处得很好。但当你了解了 Git 如何管理您的存储库将有助于打开你的思维方式,并让您更深入地了解 Git 。

git的特性
区别
1.SVN 是集中式版本控制系统,它的版本库是集中放在中央服务器的,而干活的时候,用的都是自己的电脑,只有一台服务器来维护和控制代码,所以首先要从中央服务器那里得到最新的版本,干完活后需要把自己做完的活推送到中央服务器。
2.Git 是分布式版本控制系统,它没有中央服务器,每一台主机都当成一台服务器。
优势
1.Git 和其他版本控制系统的主要差别在于,Git 只关心文件数据的整体是否发生变化,而大多数其他系统则关心文件内容的具体差异。
2.Git 并不保存这些前后变化的差异数据。实际上,Git 更像是把变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件做快照,然后保存一个指向这次快照的索引。为提高性能,若文件没有变化,Git 不会再次保存,而是直接引用上次保存的快照链接。

3.Git 近乎所有操作都是本地执行,它的绝大多数操作都只需要访问本地文件和资源,不用连网。但如果用CVCS (集中式版本控制系统)的话,差不多所有操作都需要连接网络。且因为 Git 在本地磁盘上就保存着所有当前项目的历史更新,所以处理起来速度飞快。


git实际上是如何工作的
当我们要去探究 Git 是如何工作的时候我们该从何处下手呢?因为上文说过 Git 近乎所有操作都是本地执行的,在本地的文件中我们能找到他执行的记录,这就需要我们聚焦 本地文件的 Git 文件 —— .git 文件,那么接下来就来看看 Git 的本地文件都有些什么。

git对象
.git 文件作为一个隐藏文件并不经常出现在我们的目录中,现在我们打开一个从代码仓库拉取的项目,打开终端程序并导航到存储库的主目录,再导航到存储库的 .git 目录:
cd .git
拉出 .git 的目录列表,那么你至少能看到以下几个目录:
FETCH_HEAD/             
HEAD/
config/
objects/
refs/
现阶段我们需要聚焦的是objects目录,在objects中,我们最常见的对象是以下三种(具体的下文会详细说明这三者):
Commits: 这将树对象链接在一起以形成历史,保存有关您的提交的元数据的结构,以及指向父提交和下面文件的指针。
Tree: 这相当于一个目录,记录着目录树内容及其中各个文件对应 blob 对象索引。

Blobs: 这是文件的内容,用于表示文件快照内容。


Commits对象
直接进入object对象:
cd objects
ls
控制台展示:
// 堆代码 duidaima.com
// 每个人的项目都不同,文件自然也不同,此处以笔者的一个项目为例
0c      57      85      b3       
1b      60      94      c4      
2a      67      98      cb      
2c      6c      9a      info      
3c      73      a9      pack      
49      82      af      
52      83      b1
小朋友你是否有很多问号?在第一眼看到这么多两位字符的文件夹名时完全不知道这些是啥。那么我们就需要转头来解释一下 Git 的数据存储结构 了。

当 Git 存储对象(也就是我们提交的记录)时,它不会将它们全部转储到一个目录中,因为这样会使得目录在不断的迭代提交后变得笨拙,所以它会将它们整齐地构造成一棵树—— Git 将对象哈希的前 2 个字符用作目录名称,然后将剩余的 38 个字符用作对象标识符。当我们将以上的二位字符命名的文件夹展开时,我们就会得到这样一个树形结构的目录:
objects
├── 0c
│   ├── 8867d7e175f46d4bcd66698ac13f4ca00cf592
│   └── c8002da0403724dfaa6792885eaa97faa71bcf
├── 1b
│   └── 716fafdd3aeb3636222a0026d1d4971078db05
├── 2a
│   └── 14f7db6a6748cc98862960ff5d0e9b1d4a2f17
├── 2c
│   ├── 14f7db6a6748cc98862960ff5d0e9b1d4a2f17
├── 3c
│   ├── 121291ffc25ce6792f9350883b77cea2633048
.
.
.
为了验证上述 Git 存储对象的结构,我们可以查看当前最新的4次提交,并取第一条记录去提交记录的结构树中匹配:
command: git log -4 --oneline

9a5bf36 (HEAD -> master) feat: third commit
2c5331f feat: second commit
60814e1 feat: first commit
49942f3 Initial commit
我们能看到最近的4次提交,并且每次提交都会有一个 7 位长的哈希值以及提交时的描述。以 9a5bf36 这次提交为例,我们可能会有个疑问:这只有 7 位似乎跟我们说的不太一样呀。别急!我们需要转换一下,将他转换成完整的长哈希值,因为在树结构中是以长哈希值构建生成的。
git rev-parse 9a5bf36
Git 以等效的长哈希值响应:9a5bf367f10390c64a3f7b3e738b78bd78a3d781.

将其分解为目录名称和对象标识符:
目录:9a
对象标识符:5bf367f10390c64a3f7b3e738b78bd78a3d781
我们很容易就能看到找到:
objects
├── 0c
│   ├── 8867d7e175f46d4bcd66698ac13f4ca00cf592
│   └── c8002da0403724dfaa6792885eaa97faa71bcf
├── 1b
│   └── 716fafdd3aeb3636222a0026d1d4971078db05
.
.
.
├── 98
│   ├── ed6b3f02409778bc864d8897bc230c90cae445
├── 9a
│   ├── 5bf367f10390c64a3f7b3e738b78bd78a3d781   //====>在这
.
.
既然我们知道了它的存储结构,那么我们自然就应该打开这个文件查看文件的内容,但是我们不能直接查看此对象,因为 Git 中的对象是经过压缩的。如果您尝试使用cat 5bf367f10390c64a3f7b3e738b78bd78a3d781或类似方式查看它,您可能会看到一堆像这样的乱码,以及计算机尝试从二进制对象读取控制字符时发出的咔呲声:
6?$?(?E9?z??nUmV?Em]?p??3?`??????q?Ţqjw????VR?O? q?.r???e|lN?p??Gq?)?????#???85V?W6?????
)|Wc*??8?1a?b?=?f*??pSvx3??;??3??^??O?S}??Z4?/?%J?
xu?Ko?0??̯?51??Ԯ
yB
    ??f?y?cBɯo?{ݝ?|ҌFL?:?@??_?0Td5?D2Br?D$??f?B??b?5W?HÁ?H*?&??(fbꒉdC!DV%?????D@?(???u0??8{?w
    ????0?IULC1????@(<?s '
mO????????ƶe?S????>?K8                  89_vxm(#?jxOs?u?b?5m????=w\l?
%?O??[V?t]?^??????G6.n?Mu?%
                           ?̉?X??֖X
                          v??x?EX???:sys???G2?y??={X?Ռe?X?4u???????4o'G??^"qݠ???$?Ccu?ml???vB_)?I?
`??*ގF?of??O
我们可以使用命令:
git cat-file -p 9a5bf36
sanqius-MacBook-Pro:3c zcy$ git cat-file -p 9a5bf36
tree 85b9416a23f8fb018181f96e5c01ba4bd923b965
parent 2c5331fd7046e561aad8fdde3e3f21375a17549c
author 三秋 <sanqiu@***.com> 1665729807 +0800
committer 三秋 <sanqiu@***.com> 1665729807 +0800

feat: third commit
我们看到的这个文件内部的这些内容其实就是一个对象,一个包含了 tree、parent、author... 等数据的对象,这个对象就是 Commits 了。

Commits 对象是以键值对的形式展示的,这个 Commits 指向一个 Hash 值为 2c5331fd7046e561aad8fdde3e3f21375a17549c 的 parent ,其实这个parent同样是一个 Commits 对象,这很好理解。但是这个 Commits 还有一个 Hash 值为 85b9416a23f8fb018181f96e5c01ba4bd923b965 的 tree 属性,也就是我们上面所说的第二个常用对象 Tree 。接下来我们需要聚焦的是 Commits 对象中的 Tree。

Tree对象
这个提交的文章目录里面有什么?我们使用相同的命令打开这个哈希值指向的文件:
git cat-file -p 85b9416a23f8fb018181f96e5c01ba4bd923b965
100644 blob 0cc8002da0403724dfaa6792885eaa97faa71bcf    README.md
040000 tree 3c121291ffc25ce6792f9350883b77cea2633048    src
我们发现这个 Tree 对象下有两个,一个是 Blob 类型的 README.md 文件和 Tree 类型的 src 的文件夹,可以看出 Tree 是可以嵌套的,并且这个结构似乎有点眼熟,没错这就是我们项目的目录结构,这也就能解释为什么说 Commits 对象下的 Tree 就是对应着这个代码版本的文件快照了。(100644 代表它是一个普通的文件,100755 表示一个可执行文件,120000 仅仅是一符号链接)

Blobs对象
接上文,现在这个 Tree 文件类型已经出现我们的第三类对象 Blob 了,打破砂锅问到底,继续看看这个Blob 是啥:
git cat-file -p 0cc8002da0403724dfaa6792885eaa97faa71bcf
MIT License

Copyright (c) 2019

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell...
<snip>
我们可以看到其实这就是我们在这个代码版本下的文件内容,这也就意味着 Blob 其实就是存放文件的内容。

总结

放一张图用来总结一下 Commits、Tree、Blob 三者之间的关系:

用户评论