- Андрей Куманяев/
- Git: руководства и команды/
- Как устроен Git изнутри: объекты, хранилище и структура .git/
Как устроен Git изнутри: объекты, хранилище и структура .git
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.