如何在没有Git的情况下将Git SHA1分配给文件?


138

据我了解,当Git将SHA1哈希分配给文件时,SHA1根据文件的内容对于文件是唯一的。

结果,如果文件从一个存储库移至另一个存储库,则文件的SHA1保持不变,因为其内容未更改。

Git如何计算SHA1摘要?是否对全部未压缩的文件内容执行此操作?

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




Answers:


255

这就是Git如何计算文件的SHA1的方式(或者用Git来表示“斑点”):

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()

这个答案是假设使用Python 2吗?当我在Python 3上尝试此操作TypeError: Unicode-objects must be encoded before hashing时,第一s.update()行出现异常。
Mark Booth

3
使用python 3,您需要对数据进行编码:s.update(("blob %u\0" % filesize).encode('utf-8'))避免使用TypeError
Mark Booth 2013年

可以使用utf-8进行编码,但是最好首先从一个字节字符串构建它(utf-8编码可以工作,因为所有Unicode字符都不是非ASCII)。
torek '16

值得一提的另一件事是git hash-object在数据内容中似乎也用“ \ n”替换了“ \ r \ n”。它可能完全去除了“ \ r”,我没有检查。
user420667

1
我在这里放置了文件树形哈希生成器的Python 2 + 3(两者合一)实现:github.com/chris3torek/scripts/blob/master/githash.py(树哈希读取目录树)。
torek

17

一个小东西:外壳

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

1
我正在与echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sum的输出进行比较git hash-object path-to-file,它们会产生不同的结果。然而,echo -e ...会产生正确的结果,不同之处有一个尾部-git hash-object产生拖尾字符)。这是我应该担心的事情吗?
FrustratedWithFormsDesigner 2015年

2
@FrustratedWithFormsDesigner:如果尾部-sha1sum从stdin而不是从文件中计算散列,则使用尾随。完全不用担心。尽管有关的事情很奇怪,但-n应该取消通常由echo附加的换行符。您的文件是否有空的最后一行,您忘记在CONTENTS变量中添加?
knittl

是的,你是正确的。而且我认为sha1sum的输出应该只是散列,但是用sed或类似的东西删除它并不难。
FrustratedWithFormsDesigner 2015年

@FrustratedWithFormsDesigner:如果您使用cat file | sha1sum而不是sha1sum file(尽管有更多的过程和管道)
则将

8

如果未安装git,则可以使bash shell函数非常容易地计算它。

git_id () { printf 'blob %s\0' "$(ls -l "$1" | awk '{print $5;}')" | cat - "$1" | sha1sum | awk '{print $1}'; }

1
短一点:(stat --printf="blob %s\0" "$1"; cat "$1") | sha1sum -b | cut -d" " -f1
sschuberth '16

4

看看git-hash-object的手册页。您可以使用它来计算任何特定文件的git哈希。我认为 git不仅将文件的内容提供给哈希算法,但我不确定,如果确实提供了额外的数据,我也不知道它是什么。


2
/// 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#中的解决方案。


我仍然有变音符号的问题:calcGitSHA1(“ü”)。ShouldBeEqualTo(“ 0f0f3e3b1ff2bc6722afc3e3812e6b782683896f”)但我的函数给出了0d758c9c7bc06c1e307f05d92d896aaf0a8a6d2。有什么想法git hash-object如何处理变音符号?
forki23 2010年

它应该将blob作为字节流进行处理,这意味着ü的长度可能为2(unicode),F♯的Length属性将返回长度1(因为它只是一个可见字符)
knittl 2010年

但是System.Text.Encoding.ASCII.GetBytes(“ü”)返回带有1个元素的字节数组。
forki23 2010年

使用UTF8和2作为字符串长度会得到一个字节数组:[98; 108; 111; 98; 32; 50; 0; 195; 188],因此SHA1为99fe40df261f7d4afd1391fe2739b2c7466fe968。这也不是git SHA1。
forki23 2010年

1
您绝对不能将摘要应用于字符串。相反,您必须将它们应用于字节字符串(字节数组),您可以通过使用显式编码将字符串转换为字节来获得这些字符串。
支石墓

2

完整的Python3实现:

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() 

2
您真正想要的是ASCII编码。UTF8只在这里工作,因为它是与ASCII兼容,“一滴X \ 0”只包含字符代码<= 127
费迪南德拜尔

1

在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

1

在Perl中(另请参见http://search.cpan.org/dist/Git-PurePerl/上的 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();

1

使用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

1

一个小Bash脚本,应该产生与以下命令相同的输出git hash-object

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

0

在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}`)
}

-4

有趣的是,很明显,Git在对数据进行哈希处理之前在数据末尾添加了换行符。不含“ Hello World!”的文件。得到一个980a0d5 ...的blob哈希值,与此相同:

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

4
该换行符是由您的文本编辑器而不是由添加的git hash-object。需要注意的是做echo "Hello World!" | git hash-object --stdin980a0d5...,同时使用echo -n给出的哈希c57eff5...来代替。
bdesham
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.