Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как Git сохраняет commits: внутреннее устройство
Краткий ответ
Commit в Git сохраняется как объект в хранилище объектов (object database), расположенном в .git/objects. Каждый commit кодируется в виде SHA-1 хеша и хранится в сжатом (compressed) формате с использованием zlib.
Git Objects: 4 типа
Git работает с 4 основными типами объектов:
1. Blob (Binary Large Object)
Хранит содержимое файла. Каждый файл — это отдельный blob.
$ echo "Hello, World!" > hello.txt
$ git add hello.txt
$ git hash-object hello.txt
557db03de997c86a4a028e1ebd3a1ceb225be238
Структура blob:
blob <size>\0<content>
Пример:
blob 13\0Hello, World!\n
↓ (SHA-1 хеш)
557db03de997c86a4a028e1ebd3a1ceb225be238
Блобы хранятся в файловой системе:
.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
первые 2 буквы остаток хеша
2. Tree
Представляет директорию. Содержит список файлов и поддиректорий с их хешами.
$ git ls-tree HEAD
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 empty.txt
100644 blob 557db03de997c86a4a028e1ebd3a1ceb225be238 hello.txt
040000 tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 subdir
Структура tree:
tree <size>\0<entry1><entry2>...
Где каждый entry:
<mode> <type> <sha-1><name>\0
Пример в бинарном формате:
tree 87\0
100644 hello.txt\0\x55\x7d\xb0\x3d...(20 байт SHA-1)
100644 readme.md\0\x72\xaa\xcc\x51...(20 байт SHA-1)
3. Commit
Оъект, содержащий ссылку на tree, автора, сообщение и родительский коммит(ы).
Структура commit:
commit <size>\0
tree <sha-1>
parent <sha-1> (может быть несколько или отсутствовать)
author <name> <email> <timestamp> <timezone>
committer <name> <email> <timestamp> <timezone>
<commit message>
Пример реального commit'а:
commit 148\0tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
parent c1d5375991c42535b8cc4cdfa79c5ccceb66c116
author John Doe <john@example.com> 1609459200 +0000
committer John Doe <john@example.com> 1609459200 +0000
Initial commit
This is the first commit.
Ш-1 хеш commit'а вычисляется из всего содержимого, включая время. Поэтому один и тот же коммит в разные времена будет иметь разные хеши.
$ git log --oneline
e5c562f (HEAD -> main) Initial commit
^^^^^^^^ SHA-1 хеш (сокращённо)
4. Tag
Неподвижная ссылка на коммит с дополнительной информацией (аннотированный tag).
# Лёгкий tag (lightweight tag) — просто ссылка на commit
$ git tag v1.0
# Аннотированный tag — отдельный объект
$ git tag -a v1.0 -m "Version 1.0"
Структура .git/objects
$ ls -la .git/objects/
drwxr-xr-x objects
drwxr-xr-x 00
drwxr-xr-x 0a
drwxr-xr-x 1f
...
drwxr-xr-x ff
-rw-r--r-- pack-*.pack
-rw-r--r-- pack-*.idx
Каждый объект хранится в отдельном файле:
- Первые 2 символа SHA-1 → имя директории
- Оставшиеся 38 символов → имя файла
Пример:
SHA-1: a1b2c3d4e5f6...
↓
.git/objects/a1/b2c3d4e5f6...
Содержимое commit'а: практический пример
# Создаём репозиторий и коммит
$ mkdir test-repo && cd test-repo
$ git init
$ echo "Hello" > hello.txt
$ git add hello.txt
$ git commit -m "First commit"
# Посмотрим, что произошло
$ git log
commit e5c562f7a... (SHA-1 хеш коммита)
Author: John Doe <john@example.com>
Date: Mon Jan 1 12:00:00 2024 +0000
First commit
# Изучим структуру
$ git cat-file -p e5c562f7a
tree a1b2c3d4e5f6
author John Doe <john@example.com> 1704110400 +0000
committer John Doe <john@example.com> 1704110400 +0000
First commit
# Посмотрим на tree
$ git cat-file -p a1b2c3d4e5f6
100644 blob 65a8e27d8d91... hello.txt
# Посмотрим на blob
$ git cat-file -p 65a8e27d8d91
Hello
Сжатие (Compression)
Данные в .git/objects сжимаются с использованием zlib:
# Как Git сжимает объект
import zlib
content = b"blob 5\x00Hello"
compressed = zlib.compress(content)
print(repr(compressed))
# b'x\x9c+T\xc8H\xcd\xc9I\x04\x00\x14\x1e\x04\x01'
Все объекты в .git/objects хранятся в этом сжатом формате.
Пакирование объектов (Git Pack)
Для экономии места Git может упаковать объекты в pack-файлы:
$ git gc # запустить сборку мусора и упаковку
$ ls .git/objects/pack/
pack-a1b2c3d4e5f6.pack
pack-a1b2c3d4e5f6.idx
Структура pack-файла:
.pack— содержит упакованные объекты.idx— индекс для быстрого поиска объектов в .pack
Pack-файлы используют дельта-компрессию для хранения различий между похожими объектами.
Вычисление SHA-1 хеша commit'а
Хеш commit'а вычисляется из его содержимого:
$ git hash-object -t commit commit.txt
e5c562f7a1f...
Почему хеши так важны:
- Целостность: изменение одного байта меняет весь хеш
- Идентификация: каждый коммит уникален
- Distributed: легко синхронизировать репозитории
import hashlib
# Как вычислить SHA-1 коммита
content = b"""tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
author John Doe <john@example.com> 1609459200 +0000
committer John Doe <john@example.com> 1609459200 +0000
Initial commit"""
# Git вычисляет: SHA-1(type length\0 + content)
data = b"commit " + str(len(content)).encode() + b"\0" + content
sha1 = hashlib.sha1(data).hexdigest()
print(sha1) # e5c562f7a1f...
Ссылки на commit'ы
refs (references)
Вместо запоминания 40-символьных хешей, Git использует ссылки:
# HEAD указывает на текущий коммит
.git/HEAD
ref: refs/heads/main
# Branch указывает на коммит
.git/refs/heads/main
e5c562f7a1f8d91d1b9c7a0e5f6d8c9b1a2c3d4e5
# Remote branch
.git/refs/remotes/origin/main
e5c562f7a1f8d91d1b9c7a0e5f6d8c9b1a2c3d4e5
# Tag
.git/refs/tags/v1.0
e5c562f7a1f8d91d1b9c7a0e5f6d8c9b1a2c3d4e5
История одного commit'а
# Создаём файл
$ echo "content" > file.txt
# Staging: создаёт blob
$ git add file.txt
# .git/objects/c0/nde58c3ce70e6b24ae29bc41d2b3ff8bb8f7a
# Commit: создаёт tree и commit
$ git commit -m "Add file"
# .git/objects/ae/bdeef8a6ac42f2eb8b54ca1baad9d13ba4e6
# .git/objects/c0/debeef7c1b8c3a5d3e6c7b0a9e8c3d4e5f6a7b
# HEAD обновляется
# .git/refs/heads/main
# e5c562f7a1f8d91d1b9c7a0e5f6d8c9b1a2c3d4e5
Распределённая природа
После git push или git pull, git просто обменивается объектами и ссылками:
$ git push origin main
# 1. Вычисляет, какие объекты есть локально, но нет на сервере
# 2. Отправляет blob'ы, tree'ы, commit'ы
# 3. Обновляет refs/remotes/origin/main на сервере
Это почему Git децентрализован — каждый репозиторий — полная копия истории.
Итоги
Git commit хранится как:
- Blob — содержимое файлов
- Tree — структура директорий
- Commit — ссылка на tree, автор, сообщение, parent
- Всё хранится как SHA-1 хеш в сжатом формате в
.git/objects
Ключевые моменты:
- Каждый объект идентифицируется SHA-1 хешем
- SHA-1 вычисляется из содержимого (детерминировано)
- Изменение коммита меняет его хеш и всех потомков
- Распределённость: каждый репозиторий — полная копия
- Ссылки (HEAD, branches) — просто указатели на хеши
- Объекты хранятся сжатыми и могут быть упакованы
Эта архитектура делает Git:
- Надёжным: сложно потерять данные или подделать коммиты
- Быстрым: объекты могут быть найдены по хешу
- Распределённым: легко синхронизировать репозитории
- Эффективным: сжатие и пакирование экономят место