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

Git对象的hash方法

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


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

上面公式表示,Git在计算对象hash时,首先会在对象头部添加一个header。这个header由3部分组成:第一部分表示对象的类型,可以取值blobtreecommit以分别表示数据对象、树对象、提交对象;第二部分是数据的字节长度;第三部分是一个空字节,用来将headercontent分隔开。将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-objectwhat 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-objectopenssl 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

我们首先使用xxd83baae61804e65cc73a7201a7252750c76066a30转换成为二进制形式,并将结果保存为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. java——String、StringBuffer、StringBuilder、包装类、单双引号

    String: String是一个特殊的类,被定义为final类型,为字符串常量,同样的字符串在常量池中不能重复. 但是由于使用关键字new创建新的字符串,java会在对中分配新的空间,这样即使字符串 ...

  2. 物理机和虚拟机互相可以ping通,还是无法连接

    关闭防火墙服务 CentOS # systemctl stop firewalld.service Debian # iptables -F Ubuntu # ufw disable 安装SSH服务 ...

  3. Ubuntu同屏多终端

    sudo apt-get install terminator

  4. D. Beautiful numbers

    题目链接:http://codeforces.com/problemset/problem/55/D D. Beautiful numbers time limit per test 4 second ...

  5. URAL 2080 Wallet

    找规律发现只要找到两个相同数字之间,有多少个不同的数字,即为答案. 可以用树状数组离线处理. 坑点是卡有很多张,没用完的情况,后面的卡直接放在哪里, 就是 10 5 1 2 3 4 5 这样 开始数据 ...

  6. leetcode--Learn one iterative inorder traversal, apply it to multiple tree questions (Java Solution)

    will show you all how to tackle various tree questions using iterative inorder traversal. First one ...

  7. linux下mysql-5.5.27.tar.gz源程序包安装实例

    研究了好几天,终于把mysql装上了,现在来做下小结. 系统环境:fedora8 虚拟机. 1.检查安装使用的编译工具gcc是否存在,如果不存在则要下载安装 # gcc -v 2.卸载低版本的mysq ...

  8. <rhel6 mysql replication>

    MySQL 支持单向.异步复制,复制过程中一个服务器充当主服务器,而一个或多个其它服务器充当从服务器.主服务器将更新写入二进制日志文件,并维护文件的一个索引以跟踪日志循环.这些日志可以记录发送到从服务 ...

  9. js中的load先执行还是Jquery的ready先执行问题

    onload需要页面上所有的资源都加载上之后执行,而ready则是DOM文档树已经解析完成时,说ready比onload快最显著的是比如一个页面上有一个很大的图片,加载要好久,onload只有在图片加 ...

  10. 页面中引入百度地图,实例化后影响html5的表单元素date的上下箭头

    复现步骤: 使用百度地图的JavaScript的API,引入文件地址"http://api.map.baidu.com/api?key=&v=1.1&services=tru ...