Как git вычисляет хэши файлов?

Хэши SHA1, хранящиеся в объектах дерева (возвращенные git ls-tree), не соответствуют хешам SHA1 содержимого файла (возвращенным sha1sum)

$ git cat-file blob 4716ca912495c805b94a88ef6dc3fb4aff46bf3c | sha1sum
de20247992af0f949ae8df4fa9a37e4a03d7063e  -

Как git вычисляет хэши файлов? Сжимает ли он контент перед вычислением хеша?


person netvope    schedule 29.08.2011    source источник
comment
См. назначение Git SHA1 без Git   -  person sanmai    schedule 29.08.2011
comment
Для получения дополнительных сведений см. Также progit.org/book/ch9-2.html.   -  person netvope    schedule 30.08.2011
comment
Ссылка netvope теперь мертва. Думаю, это новое место: git-scm.com/book/ en / Git-Internals-Git-Objects, который является §9.2 из git-scm.com/book < / а>   -  person Rhubbarb    schedule 22.07.2014


Ответы (5)


Git ставит перед объектом префикс "blob", за которым следует длина (в виде удобочитаемого целого числа), за которым следует символ NUL.

$ echo -e 'blob 14\0Hello, World!' | shasum 8ab686eafeb1f44702738c8b0f24f2567c36da6d

Источник: http://alblue.bandlem.com/2011/08/git-tip-of-week-objects.html

person Leif Gruenwoldt    schedule 29.08.2011
comment
Также стоит упомянуть, что он заменяет \ r \ n на \ n, но оставляет только изолированные \ rs. - person user420667; 24.05.2016
comment
^ исправление к приведенному выше комментарию: иногда git выполняет замену, указанную выше, в зависимости от настроек eol / autocrlf. - person user420667; 26.05.2016
comment
Вы также можете сравнить это с выводом echo 'Hello, World!' | git hash-object --stdin. При желании вы можете указать --no-filters, чтобы исключить преобразование crlf, или указать --path=somethi.ng, чтобы позволить git использовать фильтр, указанный через gitattributes (также @ user420667). И -w, чтобы фактически отправить большой двоичный объект в .git/objects (если вы находитесь в репозитории git). - person Tobias Kienzler; 07.02.2017
comment
Выражая эквивалентность, чтобы иметь смысл: echo -e 'blob 16\0Hello, \r\nWorld!' | shasum == echo -e 'Hello, \r\nWorld!' | git hash-object --stdin --no-filters и он также будет эквивалентен \n и 15. - person Peter Krauss; 12.06.2017
comment
Разве длина не должна быть 13, а не 14? - person amn; 17.11.2018
comment
@amn символ nul \ 0 считается символом. - person Leif Gruenwoldt; 17.11.2018
comment
Это странно - я переварил текст blob <length>\0, где <length> - размер файла в байтах, за которым следует его содержимое, и результат совпадает с тем, что дает мне git hash-object --no-filters <file-path>. Когда я считаю нулевой байт дополнительным байтом для <length>, хеши, очевидно, больше не равны. - person amn; 17.11.2018
comment
echo добавляет к выводу новую строку, которая также передается в git. Вот почему в нем 14 символов. Чтобы использовать эхо без новой строки, напишите echo -n 'Hello, World!' - person Bouke Versteegh; 01.09.2019

Я лишь расширяю ответ на @Leif Gruenwoldt и подробно рассказываю, что находится в ссылка предоставлена ​​@Leif Gruenwoldt

Сделай сам ...

  • Шаг 1. Создайте пустой текстовый документ (имя не имеет значения) в вашем репозитории.
  • Шаг 2. Подготовьте и зафиксируйте документ.
  • Шаг 3. Определите хэш большого двоичного объекта, выполнив git ls-tree HEAD
  • Шаг 4. Найдите хэш большого двоичного объекта e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
  • Шаг 5. Избавьтесь от удивления и прочтите ниже.

Как GIT вычисляет хэши фиксации

    Commit Hash (SHA1) = SHA1("blob " + <size_of_file> + "\0" + <contents_of_file>)

Текст blob⎵ является постоянным префиксом, а \0 также является постоянным и является символом NULL. <size_of_file> и <contents_of_file> различаются в зависимости от файла.

См .: Какой формат файла у объекта фиксации git?

И все, ребята!

Но подождите! вы заметили, что <filename> не является параметром, используемым для вычисления хэша? Два файла потенциально могут иметь один и тот же хэш, если их содержимое одинаково безразлично к дате и времени создания и их имени. Это одна из причин, по которой Git обрабатывает перемещение и переименование лучше, чем другие системы контроля версий.

Сделай сам (внешнее)

  • Шаг 6. Создайте еще один пустой файл с другим filename в том же каталоге.
  • Шаг 7. Сравните хэши обоих файлов.

Примечание.

В ссылке не упоминается, как хешируется объект tree. Я не уверен в алгоритме и параметрах, однако, по моим наблюдениям, он, вероятно, вычисляет хэш на основе всех blobs и trees (вероятно, их хэшей), которые он содержит

person Lordbalmon    schedule 05.03.2015
comment
SHA1("blob" + <size_of_file> - есть ли дополнительный пробел между blob и размером? Размер десятичный? Это с нулевым префиксом? - person osgx; 18.07.2016
comment
@osgx Есть. Ссылка и мое тестирование это подтверждают. Я исправил ответ. Размер выглядит как целое число байтов без префикса. - person Samuel Harmer; 14.05.2017

git hash-object

Это быстрый способ проверить свой метод тестирования:

s='abc'
printf "$s" | git hash-object --stdin
printf "blob $(printf "$s" | wc -c)\0$s" | sha1sum

Выход:

f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f
f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f  -

где sha1sum находится в GNU Coreutils.

Затем все сводится к пониманию формата каждого типа объекта. Мы уже рассмотрели тривиальный blob, вот другие:

person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 16.05.2016
comment
Как упоминалось в предыдущем ответе, длину следует рассчитывать как $(printf "\0$s" | wc -c). Обратите внимание на добавленный пустой символ. То есть, если строка - «abc» с добавленным пустым символом впереди, длина даст 4, а не 3. Тогда результаты с sha1sum соответствуют git hash-object. - person Michael Ekoka; 11.04.2017
comment
Вы правы, они совпадают. Похоже, что использование здесь printf вместо echo -e имеет некоторый пагубный побочный эффект. Когда вы применяете хэш-объект git к файлу, содержащему строку 'abc', вы получаете 8baef1b ... f903, что вы получаете при использовании echo -e вместо printf. При условии, что echo -e добавляет новую строку в конец строки, кажется, что для соответствия поведению с printf вы можете сделать то же самое (т.е. s = $ s \ n). - person Michael Ekoka; 11.04.2017

На основании ответа Лейфа Грюнвольдта, вот функция оболочки, заменяющая _ 1_:

git-hash-object () { # substitute when the `git` command is not available
    local type=blob
    [ "$1" = "-t" ] && shift && type=$1 && shift
    # depending on eol/autocrlf settings, you may want to substitute CRLFs by LFs
    # by using `perl -pe 's/\r$//g'` instead of `cat` in the next 2 commands
    local size=$(cat $1 | wc -c | sed 's/ .*$//')
    ( echo -en "$type $size\0"; cat "$1" ) | sha1sum | sed 's/ .*$//'
}

Тестовое задание:

$ echo 'Hello, World!' > test.txt
$ git hash-object test.txt
8ab686eafeb1f44702738c8b0f24f2567c36da6d
$ git-hash-object test.txt
8ab686eafeb1f44702738c8b0f24f2567c36da6d
person Lucas Cimon    schedule 27.06.2016

Мне это было нужно для некоторых модульных тестов в Python 3, поэтому я решил оставить его здесь.

def git_blob_hash(data):
    if isinstance(data, str):
        data = data.encode()
    data = b'blob ' + str(len(data)).encode() + b'\0' + data
    h = hashlib.sha1()
    h.update(data)
    return h.hexdigest()

Я везде придерживаюсь \n окончаний строк, но в некоторых случаях Git также может быть измените окончания строк перед вычислением этого хэша, поэтому вам может понадобиться _ 3_ тоже.

person Samuel Harmer    schedule 14.05.2017