如何分配一个 GitSHA1的文件没有 Git?

据我所知,当 Git 为文件分配 SHA1散列时,这个 SHA1基于其内容对文件是唯一的。

因此,如果文件从一个存储库移动到另一个存储库,文件的 SHA1保持不变,因为其内容没有更改。

Git 是如何计算 SHA1摘要的? 它是否计算完整的未压缩文件内容?

我想模拟在 Git 之外分配 SHA1的过程。

23862 次浏览

看一下 < em > git-hash-object 的手册页。您可以使用它来计算任何特定文件的 git 散列。我 好好想想,git 提供的不仅仅是文件的内容到散列算法,但我不确定,如果它确实提供额外的数据,我不知道它是什么。

这就是 Git 计算文件 SHA1(或者,用 Git 术语来说,一个“ blob”)的方法:

sha1("blob " + filesize + "\0" + data)

因此,您可以很容易地自己计算它,而不需要安装 Git。注意,“0”是 NULL 字节,而不是两个字符的字符串。

例如,空文件的哈希值:

sha1("blob 0\0") = "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"


$ touch empty
$ git hash-object empty
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

另一个例子:

sha1("blob 7\0foobar\n") = "323fae03f4606ea9991df8befbb2fca795e648fa"


$ echo "foobar" > foo.txt
$ git hash-object foo.txt
323fae03f4606ea9991df8befbb2fca795e648fa

下面是一个 Python 实现:

from hashlib import sha1
def githash(data):
s = sha1()
s.update("blob %u\0" % len(data))
s.update(data)
return s.hexdigest()

如果没有安装 git,您可以创建一个 bash shell 函数来轻松计算它。

git_id () { printf 'blob %s\0' "$(ls -l "$1" | awk '{print $5;}')" | cat - "$1" | sha1sum | awk '{print $1}'; }
/// Calculates the SHA1 for a given string
let calcSHA1 (text:string) =
text
|> System.Text.Encoding.ASCII.GetBytes
|> (new System.Security.Cryptography.SHA1CryptoServiceProvider()).ComputeHash
|> Array.fold (fun acc e ->
let t = System.Convert.ToString(e, 16)
if t.Length = 1 then acc + "0" + t else acc + t)
""
/// Calculates the SHA1 like git
let calcGitSHA1 (text:string) =
let s = text.Replace("\r\n","\n")
sprintf "blob %d%c%s" (s.Length) (char 0) s
|> calcSHA1

这是 F # 中的一个解。

一点好东西,在贝壳里

echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sum

在 Perl 中(也可以参见 Git: : PurePerl at < a href = “ http://search.cpan.org/dist/Git-PurePerl/”rel = “ nofollow noReferrer”> http://search.cpan.org/dist/Git-PurePerl/ )

use strict;
use warnings;
use Digest::SHA1;


my @input = &lt;&gt;;


my $content = join("", @input);


my $git_blob = 'blob' . ' ' . length($content) . "\0" . $content;


my $sha1 = Digest::SHA1->new();


$sha1->add($git_blob);


print $sha1->hexdigest();

在 Perl 中:

#!/usr/bin/env perl
use Digest::SHA1;


my $content = do { local $/ = undef; <> };
print Digest::SHA1->new->add('blob '.length($content)."\0".$content)->hexdigest(), "\n";

作为 shell 命令:

perl -MDigest::SHA1 -E '$/=undef;$_=<>;say Digest::SHA1->new->add("blob ".length()."\0".$_)->hexdigest' < file

完整的 Python 3实现:

import os
from hashlib import sha1


def hashfile(filepath):
filesize_bytes = os.path.getsize(filepath)


s = sha1()
s.update(b"blob %u\0" % filesize_bytes)


with open(filepath, 'rb') as f:
s.update(f.read())


return s.hexdigest()

值得注意的是,Git 显然在数据末尾添加了一个换行符,然后才对其进行散列。一个只包含“ Hello World!”的文件得到一个980a0d5的杂凑,和这个一样:

$ php -r 'echo sha1("blob 13" . chr(0) . "Hello World!\n") , PHP_EOL;'

使用 Ruby,您可以执行以下操作:

require 'digest/sha1'


def git_hash(file)
data = File.read(file)
size = data.bytesize.to_s
Digest::SHA1.hexdigest('blob ' + size + "\0" + data)
end

一个小小的 Bash 脚本可以产生与 git hash-object相同的输出:

#!/bin/sh
(
echo -en 'blob '"$(stat -c%s "$1")"'\0';
cat "$1"
) | sha1sum | cut -d\  -f 1

在 JavaScript 中

const crypto = require('crypto')
const bytes = require('utf8-bytes')


function sha1(data) {
const shasum = crypto.createHash('sha1')
shasum.update(data)
return shasum.digest('hex')
}


function shaGit(data) {
const total_bytes = bytes(data).length
return sha1(`blob ${total_bytes}\0${data}`)
}

您也可以对文件应用相同的方法

$ echo "foobar" > foo.txt
$ echo "$(cat foo.txt)"|(read f; echo -en "blob "$((${#f}+1))"\0$f\n" )|openssl sha1
323fae03f4606ea9991df8befbb2fca795e648fa