git object 很大_这才是真正的Git——Git内部原理

本文以一个具体例子结合动图介绍了Git的内部原理,包括Git是什么储存我们的代码和变更历史的、更改一个文件时,Git内部是怎么变化的、Git这样实现的好处等等。

TL;DR

本文以一个具体例子结合动图介绍了Git的内部原理,包括Git是什么储存我们的代码和变更历史的、更改一个文件时,Git内部是怎么变化的、Git这样实现的有什么好处等等。

9f6dc64f082b8619fcc287fc9ba551af.gif

通过例子解释清楚上面这张动图,让大家了解Git的内部原理。如果你已经能够看懂这张图了,下面的内容可能对你来说会比较基础。

本文是2019/11/24在深圳腾讯大厦2楼多功能厅举办的FCC前端分享会(freeCodeConf 2019 深圳站)上分享的文字版。

视频:https://www.bilibili.com/video/av77252063

PPT:https://www.lzane.com/slide/git-under-the-hood

前言

近几年技术发展十分迅猛,让部分同学养成了一种学习知识停留在表面,只会调用一些指令的习惯。我们时常有一种“我会用这个技术、这个框架”的错觉,等到真正遇到问题,才发现事情没有那么简单。

而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仓库是这样子的:

c1530f49c652d6056bc41c3521d1c4de.png

我们继续探索,我们创建一个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仓库是这样的:

15e92dd9aeedb1e706715811a20ace45.png
$ 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仓库是这样的:

3cfff3c6659e28c1ae40bebe382c273d.png

到这里我们就知道Git是怎么储存一个提交的信息的了,那有同学就会问,我们平常接触的分支信息储存在哪里呢?

$ cat .git/HEAD
ref: refs/heads/master

$ cat .git/refs/heads/master
0c96bfc59d0f02317d002ebbf8318f46c7e47ab2

在Git仓库里面,HEAD、分支、普通的Tag可以简单的理解成是一个指针,指向对应commit的SHA1值。

b6ccd3eae884cf79058dbce66de7402e.png

其实还有第四种Git object,类型是tag,在添加含附注的tag(git tag -a)的时候会新建,这里不详细介绍,有兴趣的朋友按照上文中的方法可以深入探究。

至此我们知道了Git是什么储存一个文件的内容、目录结构、commit信息和分支的。其本质上是一个key-value的数据库加上默克尔树形成的有向无环图(DAG)。这里可以蹭一下区块链的热度,区块链的数据结构也使用了默克尔树。

Git的三个分区

接下来我们来看一下Git的三个分区(工作目录、Index 索引区域、Git仓库),以及Git变更记录是怎么形成的。了解这三个分区和Git链的内部原理之后可以对Git的众多指令有一个“可视化”的理解,不会再经常搞混。

接着上面的例子,目前的仓库状态如下:

a369fe7c483fbcc14f472ab6666f2c79.png

这里有三个区域,他们所储存的信息分别是:

  • 工作目录 ( working directory ):操作系统上的文件,所有代码开发编辑都在这上面完成。

  • 索引( index or staging area ):可以理解为一个暂存区域,这里面的代码会在下一次commit被提交到Git仓库。

  • Git仓库( git repository ):由Git object记录着每一次提交的快照,以及链式结构记录的提交变更历史。

我们来看一下更新一个文件的内容这个过程会发生什么事。

a303b59d7236a5765f85b4873565c1e9.gif

运行echo "333" > a.txt将a.txt的内容从111修改成333,此时如上图可以看到,此时索引区域和git仓库没有任何变化。

80b612177d2f37f2f05c6cc80eccebaf.gif

运行git add a.txt将a.txt加入到索引区域,此时如上图所示,git在仓库里面新建了一个blob object,储存了新的文件内容。并且更新了索引将a.txt指向了新建的blob object。

2715d0d76ffc6b2259e98619135f6dee.gif

运行git commit -m 'update'提交这次修改。如上图所示

  1. Git首先根据当前的索引生产一个tree object,充当新提交的一个快照。

  2. 创建一个新的commit object,将这次commit的信息储存起来,并且parent指向上一个commit,组成一条链记录变更历史。

  3. 将master分支的指针移到新的commit结点。

至此我们知道了Git的三个分区分别是什么以及他们的作用,以及历史链是怎么被建立起来的。基本上Git的大部分指令就是在操作这三个分区以及这条链。可以尝试的思考一下git的各种命令,试一下你能不能够在上图将它们“可视化”出来,这个很重要,建议尝试一下。

如果不能很好的将日常使用的指令“可视化”出来,推荐阅读 图解Git

一些有趣的问题

有兴趣的同学可以继续阅读,这部分不是文章的主要内容

问题1:为什么要把文件的权限和文件名储存在tree object里面而不是blob object呢?

想象一下修改一个文件的命名。

如果将文件名保存在blob里面,那么Git只能多复制一份原始内容形成一个新的blob object。而Git的实现方法只需要创建一个新的tree object将对应的文件名更改成新的即可,原本的blob object可以复用,节约了空间。

问题2:每次commit,Git储存的是全新的文件快照还是储存文件的变更部分?

由上面的例子我们可以看到,Git储存的是全新的文件快照,而不是文件的变更记录。也就是说,就算你只是在文件中添加一行,Git也会新建一个全新的blob object。那这样子是不是很浪费空间呢?

这其实是Git在空间和时间上的一个取舍,思考一下你要checkout一个commit,或对比两个commit之间的差异。如果Git储存的是问卷的变更部分,那么为了拿到一个commit的内容,Git都只能从第一个commit开始,然后一直计算变更,直到目标commit,这会花费很长时间。而相反,Git采用的储存全新文件快照的方法能使这个操作变得很快,直接从快照里面拿取内容就行了。

当然,在涉及网络传输或者Git仓库真的体积很大的时候,Git会有垃圾回收机制gc,不仅会清除无用的object,还会把已有的相似object打包压缩。

问题3:Git怎么保证历史记录不可篡改?

通过SHA1哈希算法和哈系树来保证。假设你偷偷修改了历史变更记录上一个文件的内容,那么这个问卷的blob object的SHA1哈希值就变了,与之相关的tree object的SHA1也需要改变,commit的SHA1也要变,这个commit之后的所有commit SHA1值也要跟着改变。又由于Git是分布式系统,即所有人都有一份完整历史的Git仓库,所以所有人都能很轻松的发现存在问题。


希望大家读完有所收获,下一篇文章会写一些我日常工作中觉得比较实用的Git技巧、经常被问到的问题、以及发生一些事故时的处理方法。

参考

  • Scott Chacon, Ben Straub - Pro Git-Apress (2014) 免费,有兴趣继续深入的同学推荐阅读这本书

  • Jon Loeliger, Matthew McCullough - Version Control with Git, 2nd Edition - O'Reilly Media (2012) 作为上面那本书的补充

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/441430.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【CodeForces - 195D】Analyzing Polyline (思维,卡精度的处理方式)

题干: As Valeric and Valerko were watching one of the last Euro Championship games in a sports bar, they broke a mug. Of course, the guys paid for it but the barman said that he will let them watch football in his bar only if they help his son …

【CodeForces - 985D】Sand Fortress (二分,贪心,思维构造,技巧,有坑)

题干: You are going to the beach with the idea to build the greatest sand castle ever in your head! The beach is not as three-dimensional as you could have imagined, it can be decribed as a line of spots to pile up sand pillars. Spots are numbe…

mysql多实例脚本_mysql多实例脚本

mysql多实例脚本##mariadb和mysql-server的通用多实例脚本。vi mdp.sh 脚本内容参考内容如下#!/bin/bashecho ‘等待mariadb-server或mysql-server服务软件安装完毕’while truedoyum install -y mariadb-server mariadb &>/dev/nullyum install -y mysql-serv…

scrapy 分布式 mysql_Scrapy基于scrapy_redis实现分布式爬虫部署的示例

准备工作1.安装scrapy_redis包,打开cmd工具,执行命令pip install scrapy_redis2.准备好一个没有BUG,没有报错的爬虫项目3.准备好redis主服务器还有跟程序相关的mysql数据库前提mysql数据库要打开允许远程连接,因为mysql安装后root用户默认只允许本地连接,详情请看此文章部署过程…

(精)DEVC++的几个实用小技巧

依赖 DEV C 5.11 最新版 下载安装DEV C后,使用DEV C打开一个随便的cpp文件,你看到的应该是这样的界面。(为了节约读者的流量,图片进行了有损压缩,但是字看得清楚) 重点是确认工具栏有AStyle选项。 相信…

win10一按右键就闪屏_升级Win10正式版后屏幕一直闪烁正确的解决办法

Win10正式版屏幕一直闪烁怎么办呢?升级到Win10正式版并进入Windows桌面后,发现屏幕一直不断的闪烁,此时无法执行任务操作。小编最近在升级到Win10正式版后才遇到了这个问题,后台经过反复思考和探索,终于解决了问题&…

KMP 常用模板

写在前面: 代码中有的是i,j,有的是j,k,总之不唯一啊!!!这个一定要弄清楚!!!我没有统一。。 从0开始的字符串,加速版。 void getnext…

在spark的软件栈中_问:Spark Streaming是什么软件栈中的流计算?

问:Spark Streaming是什么软件栈中的流计算?A:不能确定,B:,C:,D:正确答案:绛旓細姣斿皵路鐩栬尐解析:问:Spark Streaming是什么软件栈中的流计算?A:不能确定,B:,C:,D:相关问题:“逻辑与”的运算符是( )。A:&&B:&C:…

【CodeForces - 195A】Let's Watch Football (追及问题,模拟)

题干: Valeric and Valerko missed the last Euro football game, so they decided to watch the games key moments on the Net. They want to start watching as soon as possible but the connection speed is too low. If they turn on the video right now, i…

下面不是mysql特性_下面( )不是MySQL的特性。_学小易找答案

【单选题】如何检测 2型糖尿病【填空题】<5>以下do-while语句中循环体的执行次数是( ). a10; b0; do { b2; a-2b; } while (a>0);【单选题】2 型糖尿病 多 发在什么年龄段【填空题】<5>以下程序的输出结果为( )。 #include "stdio.h" main() {int a; …

*【CodeForces - 195B】After Training (多解,模拟)

题干&#xff1a; After a team finished their training session on Euro football championship, Valeric was commissioned to gather the balls and sort them into baskets. Overall the stadium has n balls and m baskets. The baskets are positioned in a row from l…

pandas打印全部列_python——pandas练习题1-5

练习1-开始了解你的数据探索Chipotle快餐数据相应数据集&#xff1a;chipotle.tsvimport pandas as pd chipopd.read_csv("exercise_data/chipotle.tsv",sept) chipo.head(5)chipo.shape[0] #查看有多少行4622chipo.shape[1] #查看有多少列5chipo.columns #打印所…

【POJ - 2406】Power Strings (KMP,最小循环节)

题干&#xff1a; Given two strings a and b we define a*b to be their concatenation. For example, if a "abc" and b "def" then a*b "abcdef". If we think of concatenation as multiplication, exponentiation by a non-negative in…

python wmi mac变动_Python WMI参数反转

使用python的wmi模块创建vss快照&#xff0c;我发现除非将它们反向&#xff0c;否则这些参数将不起作用&#xff1a;importwmidefvss_create():shadow_copy_servicewmi.WMI(monikerwinmgmts:\\\\.\\root\\cimv2:Win32_ShadowCopy)resshadow_copy_service.Create(ClientAccessib…

【 HDU - 2594 】Simpsons’ Hidden Talents(KMP应用,求最长前缀后缀公共子串)

题干&#xff1a; Homer: Marge, I just figured out a way to discover some of the talents we weren’t aware we had. Marge: Yeah, what is it? Homer: Take me for example. I want to find out if I have a talent in politics, OK? Marge: OK. Homer: So I take…

python两次调用write连续写入的数据之间_两次调用文件的write 方法,以下选项中描述正确的是...

两次调用文件的write 方法&#xff0c;以下选项中描述正确的是答&#xff1a;连续写入的数据之间无分隔符中国大学MOOC: 斜弯曲、拉(压)弯曲组合变形的危险点都是单向应力状态。答&#xff1a;对急性宫外孕破裂或的最主要症状是答&#xff1a;突然一侧下腹部撕裂样疼痛Some thi…

拉格朗日差值 - 杜教板子

牛客网暑期ACM多校训练营(第一场) F Sum of Maximum 杜教板子&#xff1a; 证明https://blog.csdn.net/Lee_w_j__/article/details/81135539 #include <cstdio> #include <iostream> #include <vector> #include <cstring> #include <algorithm&…

python归并排序 分词_python实现归并排序,归并排序的详细分析

学习归并排序的过程是十分痛苦的。它并不常用&#xff0c;看起来时间复杂度好像是几种排序中最低的&#xff0c;比快排的时间复杂度还要低&#xff0c;但是它的执行速度不是最快的。很多朋友不理解时间复杂度低为什么运行速度不一定快&#xff0c;这个不清楚的伙伴可以看下我之…

CCFCSP 2018年9月 -- 部分题目

CCF201809 -- 第一题 &#xff1a;买菜 问题描述   在一条街上有n个卖菜的商店&#xff0c;按1至n的顺序排成一排&#xff0c;这些商店都卖一种蔬菜。   第一天&#xff0c;每个商店都自己定了一个正整数的价格。店主们希望自己的菜价和其他商店的一致&#xff0c;第二天…

servlet中显示mysql字段_Java Servlet:从数据库获取信息并在屏幕上显示它

创建它代表了表的每一个项目(行)一个JavaBean类。创建一个使用JDBC返回这些项目列表的DAO类。然后在servlet中&#xff0c;只需使用HttpServletRequest#setAttribute()将请求范围中的项目列表&#xff0c;使用RequestDispatcher#forward()将请求转发到JSP文件&#xff0c;并使用…