Как git вычисляет хэши файлов?
Хэши SHA1, хранящиеся в объектах дерева (как возвращено git ls-tree
) не совпадают с хэшем SHA1 содержимого файла (как возвращено sha1sum
)
$ git cat-file blob 4716ca912495c805b94a88ef6dc3fb4aff46bf3c | sha1sum
de20247992af0f949ae8df4fa9a37e4a03d7063e -
Как git вычисляет хэши файлов? Сжимает ли содержимое перед вычислением хеша?
6 ответов
Git ставит перед объектом префикс "blob ", за которым следует длина (в виде читаемого человеком целого числа), за которой следует символ NUL
$ echo -e 'blob 14\0Hello, World!' | shasum
8ab686eafeb1f44702738c8b0f24f2567c36da6d
Источник: http://alblue.bandlem.com/2011/08/git-tip-of-week-objects.html
Я только расширяю ответ @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>
варьироваться в зависимости от файла.
И это все люди!
Но ждать! Вы заметили, что <filename>
параметр не используется для вычисления хеша? Два файла могут иметь одинаковый хэш, если их содержимое одинаково независимо от даты и времени их создания и их имени. Это одна из причин, по которой Git обрабатывает перемещения и переименовывает лучше, чем другие системы контроля версий.
Сделай сам (Ext)
- Шаг 6. Создайте еще один пустой файл с другим
filename
в том же каталоге- Шаг 7. Сравните хэши обоих ваших файлов.
Замечания:
Ссылка не упоминает, как tree
Объект хешируется. Я не уверен в алгоритме и параметрах, однако, по моим наблюдениям, он вычисляет хеш на основе всех blobs
а также trees
(вероятно, их хеши)
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
вот другие:
Мне это нужно для некоторых юнит-тестов в 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')
и там тоже.
Основываясь на ответе Leif Gruenwoldt, здесь есть функция оболочки, заменяющая 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
Это версия Python3 для расчета двоичного хеша (пример выше предназначен для текста)
Для удобства чтения поместите этот код в свой файл def. Также обратите внимание: код представляет собой фрагмент, а не полный скрипт. Для вашего вдохновения.
targetSize: int
exists: bool
if os.path.exists(targetFile):
exists = True
targetSize = os.path.getsize(targetFile)
else:
exists = False
targetSize = 0
openMode: str
if exists:
openMode = 'br+'
else:
openMode = 'bw+'
with open(targetFile, openMode) as newfile:
if targetSize > 0:
header: str = f"blob {targetSize}\0"
headerBytes = header.encode('utf-8')
headBytesLen = len(headerBytes)
buffer = bytearray(headBytesLen + targetSize)
buffer[0:0+headBytesLen] = headerBytes
buffer[headBytesLen:headBytesLen+targetSize] = newfile.read()
sha1Hash = hashlib.sha1(buffer).hexdigest()
if not sha == sha1Hash:
newfile.truncate()
else:
continue
with requests.get(fullFile) as response2:
newfile.write(response2.content)