Git内部原理揭秘!从文件更改到代码储存,Git究竟是怎么实现的
新智元推荐
【新智元导读】本文以一个具体实例结合动图介绍了Git的内部原理,包括Git是如何储存我们的代码和变更历史的、在更改一个文件时,Git内部是怎么变化的、Git这样实现有什么好处等等。戳右边链接上 新智元小程序 了解更多!
近几年技术发展十分迅猛,让部分同学养成了一种学习知识停留在表面,只会调用一些指令的习惯。我们时常有一种“我会用这个技术、这个框架”的错觉,等到真正遇到问题,才发现事情没有那么简单。
而Git也是一个大部分人都知道如何去使用它,知道有哪些命令,却只有少部分人知道具体原理的东西。了解一些底层的东西,可以更好的帮你理清思路,知道你真正在操作什么,不会迷失在Git大量的指令和参数上面。
Git是怎么储存信息的
这里会用一个简单的例子让大家直观感受一下git是怎么储存信息的。首先我们先创建两个文件:
$ git init
$ echo '111' > a.txt
$ echo '222' > b.txt
$ git add *.txt
Git会将整个数据库储存在.git/目录下,如果你此时去查看.git/objects目录,你会发现仓库里面多了两个object。
$ tree .git/objects
.git/objects
├── 58
│ └── c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
├── c2
│ └── 00906efd24ec5e783bee7f23b5d7c941b0c12c
├── info
└── pack
好奇的我们来看一下里面存的是什么东西:
$ cat .git/objects/58/c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c
xKOR0a044K%
怎么是一串乱码?这是因为Git将信息压缩成二进制文件。但是不用担心,因为Git也提供了一个能够帮助你探索它的api git cat-file [-t] [-p], -t可以查看object的类型,-p可以查看object储存的具体内容。
$ git cat-file -t 58c9
blob
$ git cat-file -p 58c9
111
可以发现这个object是一个blob类型的节点,他的内容是111,也就是说这个object储存着a.txt文件的内容。
这里我们遇到第一种Git object,blob类型,它只储存的是一个文件的内容,不包括文件名等其他信息。然后将这些信息经过SHA1哈希算法得到对应的哈希值
58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c,作为这个object在Git仓库中的唯一身份证。
也就是说,我们此时的Git仓库是这样子的:

我们继续探索,创建一个commit。
$ git commit -am '[+] init'
$ tree .git/objects
.git/objects
├── 0c
│ └── 96bfc59d0f02317d002ebbf8318f46c7e47ab2
├── 4c
│ └── aaa1a9ae0b274fba9e3675f9ef071616e5b209
...
我们会发现当我们commit完成之后,Git仓库里面多出来两个object。同样使用cat-file命令,我们看看它们分别是什么类型以及具体的内容是什么。
$ git cat-file -t 4caaa1
tree
$ git cat-file -p 4caaa1
100644 blob 58c9bdf9d017fcd178dc8c0... a.txt
100644 blob c200906efd24ec5e783bee7... b.txt
这里我们遇到了第二种Git object类型——tree,它将当前的目录结构打了一个快照。从它储存的内容来看可以发现它储存了一个目录结构(类似于文件夹),以及每一个文件(或者子文件夹)的权限、类型、对应的身份证(SHA1值)、以及文件名。
此时的Git仓库是这样的:

$ git cat-file -t 0c96bf
commit
$ git cat-file -p 0c96bf
tree 4caaa1a9ae0b274fba9e3675f9ef071616e5b209
author lzane 李泽帆 1573302343 +0800
committer lzane 李泽帆 1573302343 +0800
[+] init
接着我们发现了第三种Git object类型——commit,它储存的是一个提交的信息,包括对应目录结构的快照tree的哈希值,上一个提交的哈希值(这里由于是第一个提交,所以没有父节点。在一个merge提交中还会出现多个父节点),提交的作者以及提交的具体时间,最后是该提交的信息。
此时我们去看Git仓库是这样的:
到这里我们就知道Git是怎么储存一个提交的信息的了,那有同学就会问,我们平常接触的分支信息储存在哪里呢?
$ cat .git/HEAD
ref: refs/heads/master
$ cat .git/refs/heads/master
0c96bfc59d0f02317d002ebbf8318f46c7e47ab2
在Git仓库里面,HEAD、分支、普通的Tag可以简单的理解成是一个指针,指向对应com。mit的SHA1值。
其实还有第四种Git object,类型是tag,在添加含附注的tag(git tag -a)的时候会新建,这里不详细介绍,有兴趣的朋友按照上文中的方法可以深入探究。
至此我们知道了Git是什么储存一个文件的内容、目录结构、commit信息和分支的。其本质上是一个key-value的数据库加上默克尔树形成的有向无环图(DAG)。这里可以蹭一下区块链的热度,区块链的数据结构也使用了默克尔树。
Git的三个分区
接下来我们来看一下Git的三个分区(工作目录、Index 索引区域、Git仓库),以及Git变更记录是怎么形成的。了解这三个分区和Git链的内部原理之后可以对Git的众多指令有一个“可视化”的理解,不会再经常搞混。
接着上面的例子,目前的仓库状态如下:
这里有三个区域,所储存的信息分别是:
工作目录 ( working directory ):操作系统上的文件,所有代码开发编辑都在这上面完成。
索引( index or staging area ):可以理解为一个暂存区域,这里面的代码会在下一次commit被提交到Git仓库。
Git仓库( git repository ):由Git object记录着每一次提交的快照,以及链式结构记录的提交变更历史。



运行git commit -m 'update'提交这次修改。如上图所示
Git首先根据当前的索引生产一个tree object,充当新提交的一个快照。
创建一个新的commit object,将这次commit的信息储存起来,并且parent指向上一个commit,组成一条链记录变更历史。
将master分支的指针移到新的commit结点。
一些有趣的问题
问题1:为什么要把文件的权限和文件名储存在tree object里面而不是blob object呢?
问题2:每次commit,Git储存的是全新的文件快照还是储存文件的变更部分?
问题3:Git怎么保证历史记录不可篡改?
参考资料:

关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

随时掌握互联网精彩
- 1 总书记的关切事 衣食住行五千年 7980252
- 2 又一个千万人口大市诞生了 7953595
- 3 台湾当局约谈马英九 7834808
- 4 住宅层高“长个”会带来哪些变化 7760415
- 5 微信朋友圈有新功能 7656601
- 6 小红书上的外国人都去哪儿了 7563768
- 7 00后用DeepSeek1天卖出3.3亿 7489459
- 8 劳力士暴跌 有人跌没一台保时捷 7390235
- 9 特朗普将封禁DeepSeek?中方回应 7299182
- 10 这些地方实现生育津贴发放至个人 7137418