← Назад к вопросам

В каком виде сохраняется commit в Git

1.3 Junior🔥 101 комментариев
#Git и VCS

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Как 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...

Почему хеши так важны:

  1. Целостность: изменение одного байта меняет весь хеш
  2. Идентификация: каждый коммит уникален
  3. 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 хранится как:

  1. Blob — содержимое файлов
  2. Tree — структура директорий
  3. Commit — ссылка на tree, автор, сообщение, parent
  4. Всё хранится как SHA-1 хеш в сжатом формате в .git/objects

Ключевые моменты:

  • Каждый объект идентифицируется SHA-1 хешем
  • SHA-1 вычисляется из содержимого (детерминировано)
  • Изменение коммита меняет его хеш и всех потомков
  • Распределённость: каждый репозиторий — полная копия
  • Ссылки (HEAD, branches) — просто указатели на хеши
  • Объекты хранятся сжатыми и могут быть упакованы

Эта архитектура делает Git:

  • Надёжным: сложно потерять данные или подделать коммиты
  • Быстрым: объекты могут быть найдены по хешу
  • Распределённым: легко синхронизировать репозитории
  • Эффективным: сжатие и пакирование экономят место