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

124

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

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

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

netvope
источник
12
См.
Назначение
1
Для получения дополнительной информации см. Также progit.org/book/ch9-2.html
netvope
5
Ссылка netvope теперь мертва. Я думаю, это новое местоположение: git-scm.com/book/en/Git-Internals-Git-Objects, который является §9.2 из git-scm.com/book
Rhubbarb

Ответы:

122

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

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

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

Лейф Грюнвольдт
источник
2
Также стоит упомянуть, что он заменяет "\ r \ n" на "\ n", но оставляет только изолированные "\ r".
user420667
8
^ исправление к приведенному выше комментарию: иногда git выполняет замену выше, в зависимости от настроек eol / autocrlf.
user420667
5
Вы также можете сравнить это с выводом echo 'Hello, World!' | git hash-object --stdin. При желании вы можете указать, --no-filtersчто преобразование crlf не происходит, или --path=somethi.ngразрешить git использовать фильтр, указанный через gitattributes(также @ user420667). И -wдействительно отправить blob .git/objects(если вы находитесь в репозитории git).
Тобиас Кинцлер
Выражая эквивалентность, чтобы иметь смысл: echo -e 'blob 16\0Hello, \r\nWorld!' | shasum ==, echo -e 'Hello, \r\nWorld!' | git hash-object --stdin --no-filters и это также будет эквивалентно \nи 15.
Питер Краусс
1
echoдобавляет к выводу новую строку, которая также передается в git. Вот почему в нем 14 символов. Чтобы использовать эхо без новой строки, напишитеecho -n 'Hello, World!'
Bouke Versteegh
36

Я только расширяю ответ @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 обрабатывает перемещение и переименование лучше, чем другие системы контроля версий.

Сделай сам (Ext)

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

Примечание:

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

Lordbalmon
источник
SHA1("blob" + <size_of_file>- есть ли дополнительный пробел между blob и размером? Размер десятичный? Это с нулевым префиксом?
osgx
1
@osgx Есть. Ссылка и мое тестирование это подтверждают. Я исправил ответ. Размер кажется целым числом байтов без префикса.
Сэмюэл Хармер
13

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, вот другие:

Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
Как упоминалось в предыдущем ответе, длину следует рассчитывать как $(printf "\0$s" | wc -c). Обратите внимание на добавленный пустой символ. То есть, если строка 'abc' с добавленным пустым символом впереди, длина даст 4, а не 3. Тогда результаты с sha1sum будут соответствовать git hash-object.
Майкл
Вы правы, они совпадают. Похоже, что использование здесь printf вместо echo -e имеет некоторый пагубный побочный эффект. Когда вы применяете git hash-object к файлу, содержащему строку 'abc', вы получаете 8baef1b ... f903, что вы получаете при использовании echo -e вместо printf. При условии, что echo -e добавляет новую строку в конец строки, кажется, что для соответствия поведению с printf вы можете сделать то же самое (т.е. s = "$ s \ n").
Майкл
3

Основываясь на ответе Лейфа Грюнвольдта , вот функция оболочки, заменяющая git hash-object:

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
Лукас Чимон
источник
3

Мне это было нужно для некоторых модульных тестов в 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 может также изменить окончание строк перед вычислением этого хэша, поэтому вам может понадобиться и .replace('\r\n', '\n')там.

Сэмюэл Хармер
источник