↓Перейти к содержанию
  1. Git: руководства и команды/

Как устроен Git изнутри: объекты, хранилище и структура .git

·6 минут·

Git — это не просто система отслеживания изменений. По своей сути Git является контентно-адресуемым хранилищем объектов, поверх которого построен пользовательский интерфейс для работы с версиями. Понимание внутреннего устройства Git объясняет, почему он работает именно так, и помогает решать нестандартные проблемы.

Структура папки .git #

Каждый Git репозиторий содержит скрытую папку .git — это и есть весь Git. Файлы проекта — лишь рабочая копия. Вся история, ветки и настройки хранятся в .git:

ls -la .git/
.git/
├── HEAD           — указатель на текущую ветку или коммит
├── config         — конфигурация репозитория
├── description    — описание (используется GitWeb)
├── index          — staging area (индекс)
├── COMMIT_EDITMSG — сообщение последнего коммита
├── objects/       — все объекты Git (blob, tree, commit, tag)
│   ├── info/
│   ├── pack/      — упакованные объекты
│   └── ab/
│       └── cdef...  — объект с хешем abcdef...
├── refs/
│   ├── heads/     — локальные ветки
│   │   └── main   — файл с хешем коммита ветки main
│   ├── remotes/   — удалённые ветки
│   │   └── origin/
│   │       └── main
│   └── tags/      — теги
├── logs/          — история изменений refs (reflog)
│   ├── HEAD
│   └── refs/heads/main
└── hooks/         — скрипты-хуки

Четыре типа объектов Git #

Git хранит всё как объекты четырёх типов. Каждый объект идентифицируется SHA-1 хешем своего содержимого.

Blob (содержимое файла) #

Blob хранит содержимое файла — только содержимое, без имени и метаданных:

# Создать blob объект вручную
echo "Hello, Git!" | git hash-object --stdin -w
# d670460b4b4aece5915caf5c68d12f560a9fe3e4

# Просмотреть объект
git cat-file -t d670460b
# blob

git cat-file -p d670460b
# Hello, Git!

Два файла с одинаковым содержимым ссылаются на один blob — Git автоматически дедуплицирует данные.

Tree (структура директории) #

Tree — это снимок директории: список blobs и вложенных trees с их именами и правами:

# Посмотреть tree текущего HEAD коммита
git cat-file -p HEAD^{tree}
100644 blob a8c4e7d3...  README.md
100644 blob f3b2c8a1...  package.json
040000 tree 9d2f1b4e...  src
040000 tree 5a8c3e2f...  tests

Формат строки: права тип хеш имя. Права 100644 — обычный файл, 040000 — директория, 100755 — исполняемый файл.

Commit (снимок проекта) #

Commit ссылается на tree (снимок всего проекта), родительский коммит и метаданные:

git cat-file -p HEAD
tree 9d2f1b4e5a8c3e2f1b4e5a8c3e2f1b4e5a8c3e2f
parent a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
author Иван Иванов <[email protected]> 1710000000 +0300
committer Иван Иванов <[email protected]> 1710000000 +0300

feat: add user authentication

Первый коммит в репозитории не имеет строки parent. Merge commit имеет две строки parent.

Tag (аннотированный тег) #

Аннотированный тег — отдельный объект, указывающий на коммит:

git cat-file -p v1.0.0
object a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
type commit
tag v1.0.0
tagger Иван Иванов <[email protected]> 1710000000 +0300

Release version 1.0.0

Лёгкий тег (lightweight tag) — просто файл в refs/tags/ с хешем коммита, без отдельного объекта.

Хранение объектов #

Объекты хранятся в .git/objects/. Хеш объекта разбивается: первые 2 символа — папка, остальные 38 — имя файла:

# Объект с хешем d670460b4b4aece5915caf5c68d12f560a9fe3e4
# хранится в:
ls .git/objects/d6/
# 70460b4b4aece5915caf5c68d12f560a9fe3e4

# Все объекты в репозитории
find .git/objects -type f | head -10

# Количество объектов
git count-objects
# 42 objects, 168 kilobytes

Объекты сжаты zlib. Git периодически упаковывает их в pack-файлы для экономии места:

# Принудительно упаковать объекты
git gc

# После gc объекты в pack/
ls .git/objects/pack/
# pack-abc123.idx
# pack-abc123.pack

Индекс (staging area) #

Индекс — бинарный файл .git/index, содержащий снимок staging area. Это промежуточный слой между рабочей директорией и репозиторием:

# Посмотреть содержимое индекса
git ls-files --stage
100644 a8c4e7d3... 0	README.md
100644 f3b2c8a1... 0	package.json
100644 9d2f1b4e... 0	src/index.js

Формат: права хеш-blob этап имя-файла. Этап (0/1/2/3) используется при конфликтах слияния.

# При конфликте слияния этапы:
# 1 — общий предок (base)
# 2 — наша версия (ours)
# 3 — их версия (theirs)
git ls-files --stage -- conflicted-file.txt
# 100644 abc... 1	conflicted-file.txt  (base)
# 100644 def... 2	conflicted-file.txt  (ours)
# 100644 ghi... 3	conflicted-file.txt  (theirs)

HEAD: текущее положение #

HEAD — файл .git/HEAD, указывающий на текущую ветку или коммит:

cat .git/HEAD
# ref: refs/heads/main   ← символическая ссылка на ветку

# В detached HEAD состоянии:
# a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0

Ветки — просто файлы в .git/refs/heads/:

cat .git/refs/heads/main
# a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0

# Это хеш последнего коммита ветки main

Что происходит при git commit #

При выполнении git commit Git делает следующее:

# Шаг 1: git add создаёт blob объекты и обновляет индекс
git add src/index.js
# → создаёт blob для содержимого src/index.js
# → записывает хеш в .git/index

# Шаг 2: git commit создаёт tree объекты
# → рекурсивно создаёт tree для каждой директории
# → корневой tree охватывает весь проект

# Шаг 3: создаёт commit объект
# → содержит: root tree, parent commit, author, message

# Шаг 4: обновляет ссылку ветки
# → .git/refs/heads/main = новый хеш коммита

Посмотреть созданные объекты:

# После коммита
git cat-file --batch-check --batch-all-objects | head -10
# a1b2c3d blob 12
# b2c3d4e tree 67
# c3d4e5f commit 234

Граф объектов #

Git хранит полные снимки (snapshots), а не дельты (разницы). Каждый коммит ссылается на полный tree всего проекта:

commit C3 → tree T3 → blob B1 (README.md, не изменился)
                    → blob B2 (package.json, новая версия)
                    → tree T2 (src/)
                           └→ blob B3 (index.js, изменился)

commit C2 → tree T2 → blob B1 (README.md, не изменился)  ← тот же объект!
                    → blob B4 (package.json, старая версия)
                    → tree T1 (src/)
                           └→ blob B5 (index.js, старая версия)

Неизменённые файлы повторно используют существующие blobs — поэтому Git эффективен по месту.

Хеширование и целостность #

Git использует SHA-1 (планируется переход на SHA-256). Хеш вычисляется из типа объекта, размера и содержимого:

# Формат хранения: "тип размер\0содержимое"
echo -e "blob 12\0Hello, Git!" | sha1sum
# d670460b4b4aece5915caf5c68d12f560a9fe3e4

# Это тот же хеш, что выдаёт git hash-object
echo "Hello, Git!" | git hash-object --stdin
# d670460b4b4aece5915caf5c68d12f560a9fe3e4

Благодаря хешированию Git гарантирует целостность: изменить объект незаметно невозможно — хеш изменится.

Практические инструменты для исследования #

# Посмотреть тип объекта
git cat-file -t <хеш>
# blob / tree / commit / tag

# Посмотреть размер объекта в байтах
git cat-file -s <хеш>

# Посмотреть содержимое объекта
git cat-file -p <хеш>

# Найти хеш объекта по пути в HEAD
git rev-parse HEAD:README.md

# Посмотреть tree коммита
git ls-tree HEAD
git ls-tree -r HEAD  # рекурсивно (все файлы)

# Статистика объектов
git count-objects -v

Часто задаваемые вопросы #

Почему Git такой быстрый? Git хранит объекты локально и использует контентную адресацию — поиск объекта по хешу это просто открытие файла по пути. Большинство операций не требуют сети.

Правда ли что Git хранит полные копии файлов? Технически да — каждый blob это полная копия. Но одинаковое содержимое хранится один раз. Pack-файлы используют дельта-сжатие, поэтому на диске занимают меньше.

Что будет если удалить файл из .git/objects? Репозиторий повредится. Git не сможет восстановить объект. Вот почему стоит делать резервные копии.

Как Git обнаруживает повреждение? git fsck проверяет все объекты — вычисляет хеши и сравнивает с именами файлов. Несоответствие означает повреждение.

Что такое pack-файлы? Git периодически упаковывает отдельные объекты в один pack-файл с дельта-сжатием. git gc делает это явно. Pack-файлы значительно уменьшают размер репозитория.

Заключение #

Git — это контентно-адресуемое хранилище четырёх типов объектов: blob (содержимое файла), tree (структура директории), commit (снимок проекта), tag (метка). Все объекты хранятся в .git/objects/ и идентифицируются SHA-1 хешем. Ветки — просто файлы с хешем последнего коммита. HEAD указывает на текущую ветку. Для изучения объектов используйте git cat-file и git ls-tree.

По теме #