浅析Git存储—对象、打包文件及打包文件索引

存储是Git完成其他各种功能的基础,本质上是将系统中的文件按一定格式生成版本快照。对Git的存储机制进行一定程度的学习,可有效加深对Git的理解,帮助我们更高效的使用Git,同时也可作为进一步学习Git底层原理的基础。本文主要涉及Git的对象、打包文件及打包索引等内容。


Git的对象

在Git代码库中,commit、tag、目录(tree)、文件(blob)的内容经过压缩后,最终会被保存为Git对象(object)。

有两种形式的Git对象可用于保存这些压缩后的内容,一种是松散对象(loose object),另一种是打包对象(packed object)

松散对象的格式比较简单,每一个对象都会写入到一个独立的文件中,并以对象的SHA1值作为文件名,存储在GIT_DIR/objects目录中(实际会以SHA1的后38个字符[19字节]作为文件名,保存在以SHA1前两个字符[1字节]命名的子目录中)。松散对象存储的内容为zlib.deflate("${type} ${size}\0${data}"),可通过git cat-file -p ${objectId}命令还原指定对象中存储的内容(即data)。

基于松散对象执行Git操作效率较低,因此可通过git gc命令将松散对象转换为打包对象,并保存到GIT_DIR/objects/pack目录的打包文件中。一个打包文件可保存大量对象信息,相比于松散对象,使用打包对象具有以下优点。

  • 大量打包对象保存在一个或几个打包文件中,并配合对应的索引文件实现内容的快速检索,可有效减少Git操作过程中需要打开的文件句柄数。
  • 使用松散对象时,即使只对文件修改了一个字符,修改后的文件也会被保存为一个新的对象文件。使用打包对象时,Git会在内容相似的对象中选择一个作为base对象进行保存。针对其他相似文件,Git只会保存base对象的引用(对象id或相对偏移量),以及相较于base对象的变更内容(delta),从而节省存储空间。
  • 在执行push/fetch操作时,基于松散对象的网络通讯,会频繁发送大量小文件,效率低下。基于打包对象时,Git可使用智能协议一次性发送包含全部所需对象的打包文件,从而提高通讯效率。

Git的打包文件(packfile)

在代码库中,Git会通过单个打包文件保存大量(也可能是全部)打包对象。打包文件会以形如pack-4eb8b183eb6e64e1c9ccaabe91c96f83ad11a2c5.pack的方式命名,并保存在GIT_DIR/objects/pack目录下。打包文件的内容格式如下图所示。


(注意,上图中的红色区域仅用于描述其他区域所占用的空间大小,并不是文件中真实存在的部分)

打包文件中的内容可分为头部(header)数据区尾部(footer)。接下来对这三部分进行描述。

  • 头部:打包文件的头部由三部分组成,每部分占用4字节,所以头部的总长度是12字节。
    • 头部签名:内容为PACK
    • 版本号:Git可接受的版本号为整数23,但目前的打包文件格式都是V2的,V3可能用于Git后续的演进。
    • 打包文件中包含的对象数:由于是一个4字节的整数,因此单个打包文件中最多只能包含4G个打包对象。
  • 数据区:数据区包含了头部所指定的数量的数据块,每个数据块对应一个打包对象,数据块又分为数据头压缩数据两部分
    • 数据头:数据头可能由一个或多个字节组成。

      • 每个字节的第一位如果为1,则表示下一个字节还是数据头的部分。

      • 数据头第一个字节的2-4位,用于标识当前数据块的对象类型,类型与数值的对应关系如下(其中数值5为保留类型)

        对象类型 数值
        commit 1
        tree 2
        blob 3
        tag 4
        ofs_delta 6
        ref_delta 7
      • 数据头中,第一个字节的5-8位和后续每个字节的2-8位,均用于表示实际数据在未压缩状态下的长度。以图中第一个数据块为例,数据头中记录的数据长度为 C << 11 | B << 4 | A

      • 对于delta类型的对象,头部除了以上信息外,还需要声明对base对象的引用。ref_delta类型对象直接在数据头的末尾记录20字节的base对象id。ofs_delta类型对象会在数据头的末尾记录base数据块开始位置相对于当前数据块开始位置的偏移量信息。具体记录格式如下

      byte b = read();
      long ofs = b & 0x7f;
      while ((b & 0x80) != 0) {
          ofs += 1;
          b = read();
          ofs <<= 7;
          ofs += (b & 0x7f);
      }
      
    • 压缩数据:打包对象的文本内容按zlib算法压缩后的结果

      • 对于非delta类型的打包对象,压缩数据解压后即为原始内容
      • 对于delta类型的打包对象,压缩数据解压后为delta内容。基于delta内容和base内容,可还原出当前对象的原始内容。delta内容格式如下
      base数据长度 还原后数据长度 base数据片段(cmd;offset;length) delta数据片段(length) ...
      • 被引用的base对象可以是另一个delta类型的对象。
  • 尾部:尾部仅包含一个20字节的SHA1格式的校验和。

由以上对数据区的描述可知,我们无法根据数据头中记录的未压缩数据长度得知后面压缩数据的结束位置。同时,我们也无法直接通过打包文件中这种略显复杂的数据组织格式实现对打包对象的快速查找。因此需要进一步基于打包文件构造索引文件,才能实现打包对象的快速检索。


Git的打包文件索引(packfile index)

打包文件索引会以形如pack-4eb8b183eb6e64e1c9ccaabe91c96f83ad11a2c5.idx的方式命名,并保存在GIT_DIR/objects/pack目录下。Git可以通过git index-pack ${packfile}命令为打包文件构造索引文件,因此在任何通讯交互场景下,Git均不需要传递打包索引文件,只需传递打包文件即可。

到目前为止,打包索引文件的内容格式有两个版本,V1格式用于Git1.6版本之前,V2格式用于Git1.6版本之后。V2格式的索引文件中,包含了每个对象的CRC校验值,因此在重新打包的过程中,压缩过的对象可以直接进行包间拷贝而不用担心数据损坏。此外V2格式的索引文件可支持大于4G的打包文件。有鉴于此,这里只对V2版本的打包文件索引做进一步描述。


如上图所示,V2版本的打包文件索引可分为头部、fanout表、对象id区、校验和区、偏移量区(包括整形偏移量区和长整型偏移量区)和尾部,各部分内容具体描述如下

  • 头部
    • magic number:内容为[-1, 't', 'O', 'c'],用于表示当前索引文件不是V1版本。
    • 版本号:标识当前索引的格式版本。对于V2版本,值为整数2。
  • fanout表:一共256个槽位,对应SHA1格式id的前两个字符(1字节)的有序排列(即00至ff)。每个槽位里记录了索引中SHA1值前两位小于等于当前槽位值的对象数量。在查找指定对象id时,可先通过fanout表做一次基于hash的范围检索,从而缩小后续二分查找的范围。这里每个槽位占用4字节,同样也限制了一个打包文件中最多只能包含4G个打包对象。
  • 对象id区:打包文件中的对象id有序的排列在此区域中
  • 校验和区:按对象id的顺序,排列存放对应的CRC校验值信息
  • 偏移量区:按对象id的顺序,排列存放打包对象在打包文件中的偏移量。此处的偏移量对应的是打包对象压缩后在打包文件中的偏移量。
    • 整形偏移量区:记录2G以下的偏移量。当偏移量大于2G时,将记录值的最高位标记为1(即使用负值),后31位保存指向长整型偏移量区的指针。
    • 长整型偏移量区:此区域只记录大小超过2G的偏移量。正是由于引入了长整型偏移量区,V2版本的索引才得以支持存储空间大于4G的打包文件。
  • 尾部
    • 打包文件的SHA1校验和
    • 索引文件的SHA1校验和

本文阐述了Git对象、打包文件、打包文件索引的相关概念和内部结构,可作为进一步了解Git其他底层原理的基础。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 公司有自己的电商平台和品牌。 开发了属于自己特色的产品:musely.店铺中卖的产品大多数是面膜类,高分子颈膜贴,...
    羊羊羊羊羊阳阅读 401评论 0 0
  • 忙碌的一天 对于宇哲来说,一到周末也不是很轻松的,周一到周五也就是完成基本的学习作业,没有...
    宇哲妈妈阅读 219评论 0 2
  • 每当我不开心或是难过的时候,我会听飞轮海的歌,只要听着飞轮海的歌我的心情就会变好;我开心的时候,也会听飞轮海的歌,...
    钟情于尊阅读 190评论 0 0