在上一篇文章中,将了数据对象、树对象和提交对象三种Git对象,每种对象会计算出一个hash值。那么,Git是如何计算出Git对象的hash值?本文的内容就是来解答这个问题。

Git对象的hash方法

Git中的数据对象、树对象和提交对象的hash方法原理是一样的,可以描述为:


header = "<type> " + content.length + "\0"
hash = sha1(header + content)

上面公式表示,Git在计算对象hash时,首先会在对象头部添加一个header。这个header由3部分组成:第一部分表示对象的类型,可以取值blob、tree、commit以分别表示数据对象、树对象、提交对象;第二部分是数据的字节长度;第三部分是一个空字节,用来将header和content分隔开。将header添加到content头部之后,使用sha1算法计算出一个40位的hash值。

在手动计算Git对象的hash时,有两点需要注意:
1.header中第二部分关于数据长度的计算,一定是字节的长度而不是字符串的长度;
2.header + content的操作并不是字符串级别的拼接,而是二进制级别的拼接。

各种Git对象的hash方法相同,不同的在于:
1.头部类型不同,数据对象是blob,树对象是tree,提交对象是commit;
2.数据内容不同,数据对象的内容可以是任意内容,而树对象和提交对象的内容有固定的格式。

接下来分别讲数据对象、树对象和提交对象的具体的hash方法。

数据对象

数据对象的格式如下:


blob <content length><NULL><content>

从上一篇文章中我们知道,使用git hash-object可以计算出一个40位的hash值,例如:


$ echo -n "what is up, doc?" | git hash-object --stdin
bd9dbf5aae1a3862dd1526723246b20206e5fc37

注意,上面在echo后面使用了-n选项,用来阻止自动在字符串末尾添加换行符,否则会导致实际传给git hash-object是what is up, doc?\n,而不是我们直观认为的what is up, doc?。

为验证前面提到的Git对象hash方法,我们使用openssl sha1来手动计算what is up, doc?的hash值:


$ echo -n "blob 16\0what is up, doc?" | openssl sha1
bd9dbf5aae1a3862dd1526723246b20206e5fc37

可以发现,手动计算出的hash值与git hash-object计算出来的一模一样。

在Git对象hash方法的注意事项中,提到header中第二部分关于数据长度的计算,一定是字节的长度而不是字符串的长度。由于what is up, doc?只有英文字符,在UTF8中恰好字符的长度和字节的长度都等于16,很容易将这个长度误解为字符的长度。假设我们以中文来试验:


$ echo -n "中文" | git hash-object --stdin
efbb13322ba66f682e179ebff5eeb1bd6ef83972
$ echo -n "blob 2\0中文" | openssl sha1
d1dc2c3eed26b05289bddb857713b60b8c23ed29

我们可以看到,git hash-object和openssl sha1计算出来的hash值根本不一样。这是因为中文两个字符作为UTF格式存储后的字符长度不是2,具体是多少呢?可以使用wc来计算:


$ echo -n "中文" | wc -c
6

中文字符串的字节长度是6,重新手动计算发现得出的hash值就能对应上了:


$ echo -n "blob 6\0中文" | openssl sha1
efbb13322ba66f682e179ebff5eeb1bd6ef83972

树对象

树对象的内容格式如下:


tree <content length><NUL><file mode> <filename><NUL><item sha>...

需要注意的是,<item sha>部分是二进制形式的sha1码,而不是十六进制形式的sha1码。

我们从上一篇文章摘出一个树对象做实验,其内容如下:


$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt

我们首先使用xxd把83baae61804e65cc73a7201a7252750c76066a30转换成为二进制形式,并将结果保存为sha1.txt以方便后面做追加操作:


$ echo -n &quot;83baae61804e65cc73a7201a7252750c76066a30&quot; | xxd -r -p &gt; sha1.txt
$ cat tree-items.txt
���a�Ne�s� rRu
vj0%

接下来构造content部分,并保存至文件content.txt:


$ echo -n &quot;100644 test.txt\0&quot; | cat - sha1.txt &gt; content.txt
$ cat content.txt
100644 test.txt���a�Ne�s� rRu
vj0%

计算content的长度:


$ cat content.txt | wc -c
36

那么最终该树对象的内容为:


$ echo -n &quot;tree 36\0&quot; | cat - content.txt
tree 36100644 test.txt���a�Ne�s� rRu
vj0%

最后使用openssl sha1计算hash值,可以发现和实验的hash值是一样的:


$ echo -n &quot;tree 36\0&quot; | cat - content.txt | openssl sha1
d8329fc1cc938780ffdd9f94e0d364e0ea74f579

提交对象

提交对象的格式如下:


commit &lt;content length&gt;&lt;NUL&gt;tree &lt;tree sha&gt;
parent &lt;parent sha&gt;
[parent &lt;parent sha&gt; if several parents from merges]
author &lt;author name&gt; &lt;author e-mail&gt; &lt;timestamp&gt; &lt;timezone&gt;
committer &lt;author name&gt; &lt;author e-mail&gt; &lt;timestamp&gt; &lt;timezone&gt;
&lt;commit message&gt;

我们从上一篇文章摘出一个提交对象做实验,其内容如下:


$ echo 'first commit' | git commit-tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
db1d6f137952f2b24e3c85724ebd7528587a067a
$ git cat-file -p db1d6f137952f2b24e3c85724ebd7528587a067a
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author jingsam &lt;jing-sam@qq.com&gt; 1528022503 +0800
committer jingsam &lt;jing-sam@qq.com&gt; 1528022503 +0800
first commit

这里需要注意的是,由于echo 'first commit'没有添加-n选项,因此实际的提交信息是first commit\n。使用wc计算出提交内容的字节数:


$ echo -n &quot;tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author jingsam &lt;jing-sam@qq.com&gt; 1528022503 +0800
committer jingsam &lt;jing-sam@qq.com&gt; 1528022503 +0800
first commit\n&quot; | wc -c
163

那么,这个提交对象的header就是commit 163\0,手动把头部添加到提交内容中:


commit 163\0tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author jingsam &lt;jing-sam@qq.com&gt; 1528022503 +0800
committer jingsam &lt;jing-sam@qq.com&gt; 1528022503 +0800
first commit\n

使用openssl sha1计算这个上面内容的hash值:


$ echo -n &quot;commit 163\0tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author jingsam &lt;jing-sam@qq.com&gt; 1528022503 +0800
committer jingsam &lt;jing-sam@qq.com&gt; 1528022503 +0800
first commit\n&quot; | openssl sha1
db1d6f137952f2b24e3c85724ebd7528587a067a

可以看见,与实验的hash值是一样的。

总结

这篇文章详细地分析了Git中的数据对象、树对象和提交对象的hash方法,可以发现原理是非常简单的。数据对象和提交对象打印出来的内容与存储内容组织是一模一样的,可以很直观的理解。对于树对象,其打印出来的内容和实际存储是有区别的,增加了一些实现上的难度。例如,使用二进制形式的hash值而不是直观的十六进制形式,我现在还没有从已有资料中搜到这么设计的理由,这个问题留待以后解决。

原文地址:https://jingsam.github.io/2018/06/10/git-hash.html

Git 内部原理之 Git 对象哈希的更多相关文章

  1. Git 内部原理--初探 .git

    说到Git大家应该都非常熟悉,几乎每天都会用到它.在日常使用过程中,我们貌似并不需要关注其内部的原理,只需要记住那几个常用的命令,就可以说自己是会Git的人了.可是,事实真的是这样子的吗?今天我们就来 ...

  2. Git 内部原理 - (3) Git 引用 (4)包文件

    Git 引用 我们可以借助类似于 git log 1a410e 这样的命令来浏览完整的提交历史,但为了能遍历那段历史从而找到所有相关对象,你仍须记住 1a410e 是最后一个提交. 我们需要一个文件来 ...

  3. Git详解之九:Git内部原理

    Git 内部原理 不管你是从前面的章节直接跳到了本章,还是读完了其余各章一直到这,你都将在本章见识 Git 的内部工作原理和实现方式.我个人发现学习这些内容对于理解 Git 的用处和强大是非常重要的, ...

  4. git内部原理

    Git 内部原理 无论是从之前的章节直接跳到本章,还是读完了其余章节一直到这——你都将在本章见识到 Git 的内部工作原理 和实现方式. 我们发现学习这部分内容对于理解 Git 的用途和强大至关重要. ...

  5. Git详解之九 Git内部原理

    以下内容转载自:http://www.open-open.com/lib/view/open1328070620202.html Git 内部原理 不管你是从前面的章节直接跳到了本章,还是读完了其余各 ...

  6. Git 内部原理 - (7)维护与数据恢复 (8) 环境变量 (9)总结

    维护与数据恢复 有的时候,你需要对仓库进行清理 - 使它的结构变得更紧凑,或是对导入的仓库进行清理,或是恢复丢失的内容. 这个小节将会介绍这些情况中的一部分. 维护 Git 会不定时地自动运行一个叫做 ...

  7. Git内部原理(1)

    Git本质上是一套内容寻址文件系统,在此之上提供了VCS的用户界面. Git底层命令(plumbing) vs 高层命令(porcelain) Git的高层命令包括checkout.branch.re ...

  8. git内部原理-第一篇

    本人计划写一些关于<git内部原理>的文章 计划每周一篇

  9. Git 内部原理 - (1)底层命令和高层命令 (2Git 对象

    文章摘选自git官网,这里复制下来表示我已阅读并学习过一次这些内容: 无论是从之前的章节直接跳到本章,还是读完了其余章节一直到这——你都将在本章见识到 Git 的内部工作原理和实现方式. 我们发现学习 ...

随机推荐

  1. 宋宝华:swappiness=0究竟意味着什么?

    http://mp.weixin.qq.com/s/BixMISiPz3sR9FDNfVSJ6w 本文解释swappiness的作用,以及swappiness=0究竟意味着什么. 内存回收 我们都知道 ...

  2. iOS开发 - 3D Touch 应用系列一 - Quick Actions 创建桌面 Icon 快捷方式

    个言 很久没发随笔了,有一年多了吧.期间也曾想继续去写随笔,但是因为各种原因而耽搁了.最近又想了一下,还是有很多东西想要写,想要分享,想要记录下来的东西.之后我也会不断写随笔,但不止于 iOS 的方向 ...

  3. Oracle 取上周一到周末的sql

    -- 这样取的是 在一周内第几天,是以周日为开始的 select to_char(to_date('20131005','yyyymmdd'),'d') from dual; --结果:7 注释:20 ...

  4. C#语言使用习惯

    1.使用属性而不是可访问的数据成员 2.用运行时常量(readonly)而不是编译期常量(const) 编译期常量与运行时常量行为的不同之处在于对他们的访问方式不同,编译期常量的值是在目标代码中进行替 ...

  5. SpringMVC10 InitBinder 注册自定义编辑器

    1.配置web.xml文件 <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3// ...

  6. webpack.config.js====配置babel

    参考:https://www.jianshu.com/p/9808f550c6a91. 安装依赖babel-loader: 负责 es6 语法转化babel-preset-env: 包含 es6.7 ...

  7. Web框架的应用

    从今天开始,我们将要学习有关Web框架的一些内容,在学习之前先来学习一下http协议,即基于http是如何通信的. http 概要:http是基于tcp/ip通信协议来传输数据的. 优点: 1.简单快 ...

  8. JQuery初识(二)

    一丶链式编程 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UT ...

  9. HTTP缓存技术,304和200有何区别

    为什么有的缓存是 200 OK (from cache),有的缓存是 304 Not Modified 呢?很简单,看运维是否移除了 Entity Tag.移除了,就总是 200 OK (from c ...

  10. 海海DRM视频保护解密流程分析

    环境及工具 手机    :小米手机 MI 2A 系统版本: Android 4.1.1 工具    : IDA pro 6.6 .C32Asm .VS2005 一:第一次打开加密视频会出现如下验证: ...