- Андрей Куманяев/
- Git: руководства и команды/
- Git хеши: SHA-1, SHA-256 и как Git идентифицирует объекты/
Git хеши: SHA-1, SHA-256 и как Git идентифицирует объекты
В Git каждый объект — коммит, файл, папка, тег — идентифицируется уникальным хешем. Это основа архитектуры Git: хеш является одновременно именем объекта и проверкой его целостности. Невозможно изменить содержимое объекта без изменения его хеша.
Что такое хеш в Git #
Хеш — это результат криптографической функции, которая превращает данные любого размера в строку фиксированной длины:
SHA-1: 40 шестнадцатеричных символов
Пример: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
SHA-256: 64 шестнадцатеричных символа (в новых репозиториях)
Пример: a1b2c3d4e5f6...64 символа...
Хеш:
- Детерминированный — одни и те же данные дают один и тот же хеш
- Уникальный — разные данные дают разные хеши (коллизии практически невозможны)
- Однонаправленный — из хеша нельзя получить исходные данные
Типы объектов Git #
Git хранит четыре типа объектов:
blob — содержимое файла:
# Вычислить хеш содержимого файла
git hash-object README.md
# a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
# Просмотреть содержимое объекта
git cat-file -p a1b2c3d4
# # My Project
# This is the README file
tree — структура папки (аналог директории):
# Посмотреть дерево текущего коммита
git ls-tree HEAD
# 100644 blob a1b2c3d4 README.md
# 100644 blob e5f6a7b8 package.json
# 040000 tree c9d0e1f2 src/
# Дерево подпапки
git ls-tree HEAD src/
# 100644 blob f3a4b5c6 index.js
# 100644 blob d7e8f9a0 utils.js
commit — коммит с метаданными:
# Просмотреть объект коммита
git cat-file -p HEAD
# tree c9d0e1f2a3b4c5d6e7f8a9b0a1b2c3d4e5f6a7b8
# parent a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
# author John Doe <[email protected]> 1700000000 +0300
# committer John Doe <[email protected]> 1700000000 +0300
#
# feat: add user authentication
tag — аннотированный тег:
# Просмотреть объект тега
git cat-file -p v1.0
# object a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
# type commit
# tag v1.0
# tagger John Doe <[email protected]> 1700000000 +0300
#
# Release version 1.0
Как Git вычисляет хеш #
Git вычисляет SHA-1 от строки вида <type> <size>\0<content>:
# Для blob (содержимое файла):
# sha1("blob 12\0Hello World\n")
# Для коммита:
# sha1("commit <size>\0tree <hash>\nauthor ...\n...")
# Проверить вручную (bash)
echo -n "blob 12\0Hello World\n" | sha1sum
# (технически немного другой формат)
# Git способ:
echo "Hello World" | git hash-object --stdin
# Возвращает SHA-1 хеш этого содержимого
Сокращённые хеши #
Хеш в 40 символов неудобен для ввода. Git поддерживает сокращённые версии:
# Полный хеш
git show a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
# Сокращённый (7+ символов, уникальность гарантируется)
git show a1b2c3d
# git log выводит сокращённые хеши
git log --oneline
# a1b2c3d feat: add authentication
# e5f6a7b fix: resolve login bug
# c9d0e1f initial commit
# Минимальная длина сокращения
git log --oneline --abbrev=4 # 4 символа (рискованно)
git log --oneline --abbrev=12 # 12 символов (надёжно)
Обычно 7 символов достаточно для небольших репозиториев. В больших проектах (ядро Linux) используют 12-15 символов.
Хеш как гарантия целостности #
Хеши связывают коммиты в цепочку:
Commit C: hash = SHA1(tree, parent=B, message)
Commit B: hash = SHA1(tree, parent=A, message)
Commit A: hash = SHA1(tree, parent=null, message)
Изменение любого коммита меняет его хеш. Поскольку следующий коммит содержит хеш предыдущего — меняются все следующие хеши тоже. Это делает историю Git неизменяемой без перезаписи всех последующих коммитов.
SHA-256: будущее Git #
Git 2.29+ поддерживает SHA-256 как альтернативу SHA-1:
# Создать репозиторий с SHA-256
git init --object-format=sha256 my-project
# Проверить формат существующего репозитория
git rev-parse --show-object-format
# sha1 (или sha256 для нового формата)
SHA-256 используется, так как SHA-1 теоретически уязвим к коллизионным атакам (SHAttered атака 2017 года). Для большинства задач SHA-1 всё ещё достаточно безопасен, но переход на SHA-256 — правильное направление.
Практические команды для работы с хешами #
# Получить хеш HEAD (последнего коммита)
git rev-parse HEAD
# a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
# Получить сокращённый хеш HEAD
git rev-parse --short HEAD
# a1b2c3d
# Хеш конкретной ветки
git rev-parse main
git rev-parse origin/main
# Хеш тега
git rev-parse v1.0
# Тип объекта по хешу
git cat-file -t a1b2c3d4
# commit (или blob, tree, tag)
# Размер объекта
git cat-file -s a1b2c3d4
# 256 (байт)
# Создать blob объект из файла
git hash-object -w myfile.txt
# Создаёт объект в .git/objects/ и возвращает хеш
Расположение объектов в .git #
.git/objects/
├── a1/
│ └── b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 ← хранится здесь
├── e5/
│ └── f6a7b8...
├── pack/
│ ├── pack-abc123.idx ← индекс packfile
│ └── pack-abc123.pack ← сжатые объекты
└── info/
Первые 2 символа хеша — имя папки, оставшиеся 38 — имя файла. Git периодически упаковывает объекты в packfiles (git gc).
Часто задаваемые вопросы #
Могут ли два разных файла иметь одинаковый хеш? Теоретически да (коллизия), но вероятность ничтожно мала для SHA-1 (1 из 2^80) и ещё меньше для SHA-256. На практике не происходит.
Что значит “хеш коммита” в сообщениях об ошибках? Это уникальный идентификатор коммита — строка из 40 (SHA-1) или 64 (SHA-256) шестнадцатеричных символов. По хешу можно найти любой коммит через git show <hash>.
Почему изменение коммита меняет все последующие хеши? Хеш коммита вычисляется из его содержимого + хеша родительского коммита. Если родитель изменился, его хеш изменился → дочерний коммит тоже пересчитывается с новым хешем родителя.
Что такое short hash и когда он недостаточен? Short hash — первые 7+ символов полного хеша. Достаточен когда уникален в репозитории. В очень больших репозиториях (Linux kernel) иногда нужно 12 символов. git log --abbrev-commit автоматически выбирает минимально достаточную длину.
Заключение #
Хеши — основа архитектуры Git. Каждый объект (blob, tree, commit, tag) идентифицируется SHA-1 или SHA-256 хешем. Связанность через хеши делает историю Git неизменяемой. Для просмотра объектов используйте git cat-file, для вычисления хешей — git hash-object. Подробнее об устройстве Git изнутри — объекты Git.