Как назначить Git SHA1 файлу без Git?

139

Насколько я понимаю, когда Git назначает хеш SHA1 файлу, этот SHA1 уникален для файла в зависимости от его содержимого.

В результате, если файл перемещается из одного репозитория в другой, SHA1 для файла остается прежним, поскольку его содержимое не изменилось.

Как Git вычисляет дайджест SHA1? Это происходит с полным несжатым содержимым файла?

Я хотел бы подражать присвоению SHA1 вне Git.

мерзавец
источник

Ответы:

256

Вот как 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()
Фердинанд Бейер
источник
Предполагает ли этот ответ Python 2? Когда я пробую это на Python 3, я получаю TypeError: Unicode-objects must be encoded before hashingисключение в первой s.update()строке.
Марк Бут
3
С python 3 вам нужно кодировать данные: s.update(("blob %u\0" % filesize).encode('utf-8'))чтобы избежать TypeError.
Марк Бут
Кодирование как utf-8 будет работать, но, вероятно, лучше сначала просто построить его из байтовой строки (кодировка utf-8 работает, потому что ни один из символов Unicode не является ASCII).
torek
Еще одна вещь, о которой стоит упомянуть, это то, что git hash-object, похоже, также заменяет "\ r \ n" на "\ 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
2
@FrustratedWithFormsDesigner: трейлинг -используется, sha1sumесли он вычисляет хэш из стандартного ввода, а не из файла. Не о чем беспокоиться. Странная вещь о том -n, что это должно подавлять новую строку, обычно добавляемую echo. В вашем файле случайно есть пустая последняя строка, которую вы забыли добавить в свою CONTENTSпеременную?
knittl
Да, ты прав. И я думал, что вывод sha1sum должен быть только хешем, но его несложно удалить с помощью sed или чего-то еще.
FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner: вы получите тот же результат, если будете использовать cat file | sha1sumвместо sha1sum file(хотя больше процессов и трубопроводов)
knittl
9

Если у вас не установлен git, вы можете создать функцию оболочки bash для ее вычисления.

git_id () { printf 'blob %s\0' "$(ls -l "$1" | awk '{print $5;}')" | cat - "$1" | sha1sum | awk '{print $1}'; }
CB Bailey
источник
1
Немного короче: (stat --printf="blob %s\0" "$1"; cat "$1") | sha1sum -b | cut -d" " -f1.
sschuberth
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 #.

forki23
источник
У меня все еще проблемы с умляутами: calcGitSHA1 ("ü"). ShouldBeEqualTo ("0f0f3e3b1ff2bc6722afc3e3812e6b782683896f") Но моя функция дает 0d758c9c7bc06c1e307f05d92a8a6aaf Есть идеи, как git hash-object обрабатывает умляуты?
forki23
он должен обрабатывать blob как поток байтов, это означает, что ü, вероятно, имеет длину 2 (unicode), свойство Length F♯ вернет длину 1 (потому что это только один видимый символ)
knittl
Но System.Text.Encoding.ASCII.GetBytes ("ü") возвращает массив байтов с 1 элементом.
forki23,
Использование UTF8 и 2 в качестве длины строки дает массив байтов: [98; 108; 111; 98; 32; 50; 0; 195; 188] и для этого SHA1 99fe40df261f7d4afd1391fe2739b2c7466fe968. Это тоже не git SHA1.
forki23,
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, а «blob 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";

В качестве команды оболочки:

perl -MDigest::SHA1 -E '$/=undef;$_=<>;say Digest::SHA1->new->add("blob ".length()."\0".$_)->hexdigest' < file
дольмен
источник
1

И в Perl (см. Также Git :: PurePerl на 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();
Алек Компьютерщик
источник
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
Fordi
источник
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}`)
}
EnZo
источник
-4

Интересно отметить, что очевидно, что Git добавляет символ новой строки в конец данных перед их хешированием. Файл, не содержащий ничего, кроме "Hello World!" получает хэш blob-объекта 980a0d5 ..., который совпадает с этим:

$ php -r 'echo sha1("blob 13" . chr(0) . "Hello World!\n") , PHP_EOL;'
Подтолкнуть
источник
4
Эта новая строка добавляется вашим текстовым редактором, а не git hash-object. Обратите внимание, что выполнение echo "Hello World!" | git hash-object --stdinдает 980a0d5..., а использование вместо этого echo -nдает хеш c57eff5....
bdesham