Как назначить Git SHA1 в файл без Git?
Как я понимаю, когда Git назначает хэш SHA1 файлу, этот SHA1 уникален для файла на основании его содержимого.
В результате, если файл перемещается из одного хранилища в другое, SHA1 для файла остается тем же, поскольку его содержимое не изменилось.
Как Git рассчитывает дайджест SHA1? Это делает это на полном несжатом содержании файла?
Я хотел бы эмулировать назначение SHA1 вне Git.
13 ответов
Вот как 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()
Немного вкусного: в раковине
echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sum
Вы можете сделать функцию оболочки bash, чтобы вычислить ее довольно легко, если у вас не установлен git.
git_id () { printf 'blob %s\0' "$(ls -l "$1" | awk '{print $5;}')" | cat - "$1" | sha1sum | awk '{print $1}'; }
Взгляните на страницу man для git-hash-object. Вы можете использовать его для вычисления git-хеша любого конкретного файла. Я думаю, что git передает не только содержимое файла в алгоритм хеширования, но я точно не знаю, и если он действительно вводит дополнительные данные, я не знаю, что это такое.
Полная реализация Python3:
import os
from hashlib import sha1
def hashfile(filepath):
filesize_bytes = os.path.getsize(filepath)
s = sha1()
s.update(("blob %u\0" % filesize_bytes).encode('utf-8'))
with open(filepath, 'rb') as f:
s.update(f.read())
return s.hexdigest()
/// 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#.
Небольшой Bash-скрипт, который должен выдавать идентичный вывод git hash-object
:
#!/bin/sh
(
echo -en 'blob '"$(stat -c%s "$1")"'\0';
cat "$1"
) | sha1sum | cut -d\ -f 1
И в Perl (см. Также Git::PurePerl на http://search.cpan.org/dist/Git-PurePerl/)
use strict;
use warnings;
use Digest::SHA1;
my @input = <>;
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";
В качестве команды оболочки:
perl -MDigest::SHA1 -E '$/=undef;$_=<>;say Digest::SHA1->new->add("blob ".length()."\0".$_)->hexdigest' < file
Используя 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
То же самое можно применить и к файлам.
$ echo "foobar" > foo.txt
$ echo "$(cat foo.txt)"|(read f; echo -en "blob "$((${#f}+1))"\0$f\n" )|openssl sha1
323fae03f4606ea9991df8befbb2fca795e648fa
В 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}`)
}
Интересно отметить, что, очевидно, Git добавляет символ новой строки в конец данных перед их хэшированием. Файл, содержащий ничего, кроме "Hello World!" получает хэш BLOB-объекта 980a0d5..., который такой же, как этот:
$ php -r 'echo sha1("blob 13" . chr(0) . "Hello World!\n") , PHP_EOL;'