Модель данных Git: объекты, коммиты, ссылки, индекс
Урок курса «Курс подготовки DevOps-инженеров»
Хронометраж: занятие 60 минут — теория 30 минут, практика 30 минут. Домашняя работа: 1–2 часа.
Кратко о чём урок: Blob/tree/commit/tag, DAG истории, HEAD и ветка как указатель, что такое staging area. Разбор через git cat-file — почему после этого понятны все остальные команды.
1. Что изучаем
После урока вы сможете:
- назвать четыре типа объектов Git и сказать, что хранит каждый;
- объяснить, почему коммит — это снимок, а не набор изменений, и что из этого следует;
- показать, что ветка — это файл с одной строкой, и объяснить, чем тогда является
HEAD; - объяснить назначение индекса и различить три состояния файла;
- найти и восстановить коммит, который «пропал» после неудачной команды.
2. Зачем это нужно и где применяется
Git можно использовать по заученным командам, и это работает, пока всё идёт по плану. Ломается на первой нестандартной ситуации — и тогда выясняется, что человек не знает, что делает.
Вы «потеряли» работу. Неудачный reset --hard, rebase не туда, случайный checkout. Тот, кто
понимает модель, знает: объекты никуда не исчезают, и восстановление занимает минуту. Тот, кто не
понимает, переписывает код заново.
Вы боитесь части команд. Страх перед rebase и reset — прямое следствие непонимания. Когда
видно, что происходит с указателями и объектами, эти команды перестают быть магией.
Вы разбираете конфликт или странное поведение. Вопросы вида «почему после rebase другие хеши», «откуда взялись эти изменения», «почему файл не попал в коммит» имеют точные ответы в модели данных.
На собеседовании. Вопрос «что такое коммит» отличает того, кто пользовался Git, от того, кто его понимает. Ответ «сохранённые изменения» — неверный, и это слышно сразу.
3. Теория (30 минут)
3.1. Git — база данных объектов, адресуемых по содержимому
Главная идея, из которой следует всё остальное: Git хранит объекты, и имя объекта — это хеш его содержимого. Не имя файла, не номер версии, а SHA-1 от самих байтов.
Проверим. Создадим репозиторий и посмотрим, что появится:
$ git init -b main
$ find .git -maxdepth 1
.git/HEAD
.git/config
.git/description
.git/hooks
.git/info
.git/objects
.git/refs
$ find .git/objects -type f | wc -l
0 ← объектов пока нет
Теперь добавим файл:
$ echo "привет" > readme.md
$ git add readme.md
$ find .git/objects -type f
.git/objects/d0/f56e135b2282bfdd87b1cae5d0238b6a193ab5
Появился один объект. Обратите внимание: он появился на git add, до коммита. Имя объекта —
это хеш, разбитый на каталог из двух первых символов и остальное; так сделано, чтобы в одном
каталоге не оказались миллионы файлов.
Что внутри:
$ git cat-file -t d0f56e13
blob
$ git cat-file -p d0f56e13
привет
Хеш действительно считается от содержимого, и это можно проверить без Git. Git добавляет к данным
заголовок вида blob <размер>\0 и хеширует всё вместе:
$ printf 'blob 13\0привет\n' | shasum | cut -c1-40
d0f56e135b2282bfdd87b1cae5d0238b6a193ab5 ← ровно тот же хеш
$ git hash-object readme.md
d0f56e135b2282bfdd87b1cae5d0238b6a193ab5
(Тринадцать байт, потому что каждая кириллическая буква в UTF-8 занимает два — знакомое из урока 02.06.)
Из адресации по содержимому следуют два практических вывода:
Одинаковое содержимое — один объект. Скопируем файл и добавим копию:
$ cp readme.md copy.md && git add copy.md
$ git ls-files -s
100644 d0f56e135b2282bfdd87b1cae5d0238b6a193ab5 0 copy.md
100644 d0f56e135b2282bfdd87b1cae5d0238b6a193ab5 0 readme.md
$ find .git/objects -type f | wc -l
3 ← новый blob НЕ создан
Два файла, один объект. Поэтому Git не раздувается от копий, а имя файла хранится отдельно от содержимого — в объекте другого типа.
Изменить историю, не меняя хеши, невозможно. Хеш коммита зависит от его содержимого, включая хеш родителя. Поменяли что-то в прошлом — изменились все хеши после. Это и есть основа целостности и причина, по которой перезапись истории всегда создаёт новые коммиты, а не правит старые.
3.2. Четыре типа объектов
| Тип | Что хранит | Что НЕ хранит |
|---|---|---|
| blob | содержимое файла, только байты | имя файла, права, путь |
| tree | список записей: права, тип, хеш, имя — то есть один каталог | содержимое файлов |
| commit | ссылку на один tree (корень проекта), ссылки на родителей, автора, дату, сообщение | сами файлы, изменения |
| tag | аннотированный тег: объект, на который указывает, автор, сообщение, подпись | — |
Посмотрим на все три после коммита:
$ git commit -m "первый коммит"
$ for o in $(find .git/objects -type f | sed 's|.git/objects/||;s|/||'); do
> printf '%s %-6s %s байт\n' "${o:0:8}" "$(git cat-file -t $o)" "$(git cat-file -s $o)"
> done
d0f56e13 blob 13 байт
a538c10a commit 184 байт
2f7e605d tree 37 байт
Три объекта на один файл: содержимое, каталог и коммит. Содержимое каждого:
$ git cat-file -p 2f7e605d # tree
100644 blob d0f56e135b2282bfdd87b1cae5d0238b6a193ab5 readme.md
$ git cat-file -p a538c10a # commit
tree 2f7e605d50d5adc45355e912cebdab4e8a61947a
author Student <student@example.com> 1788446773 +0300
committer Student <student@example.com> 1788446773 +0300
первый коммит
Здесь видна вся структура. Имя файла живёт в tree, а не в blob — поэтому переименование файла без изменения содержимого не создаёт новый blob, меняется только tree. Коммит ссылается на один tree — корневой каталог проекта целиком.
И самое важное следствие: коммит — это снимок всего проекта, а не набор изменений. В коммите нет
никакого «diff». Когда git show показывает изменения, он вычисляет их на ходу, сравнивая tree
этого коммита с tree родителя. Отсюда ответы на вопросы, которые иначе кажутся загадочными:
почему git log --follow для переименований работает эвристикой; почему cherry-pick может дать
конфликт, хотя «просто переносит коммит»; почему хранение больших бинарных файлов в Git — плохая
идея (каждая версия хранится целиком, дельты появляются только при упаковке).
Первые два поля коммита стоит различать: author — кто написал изменение, committer — кто его
применил. Обычно это одно лицо, но при rebase, cherry-pick и применении чужих патчей они
расходятся. Именно поэтому после rebase дата в git log может показаться «неизменившейся» —
по умолчанию показывается дата автора.
3.3. Ссылки: ветка — это файл с одной строкой
Объекты неизменны и адресуются хешами. Чтобы не запоминать хеши, есть ссылки (refs) — они лежат
в .git/refs/ и указывают на коммиты.
$ cat .git/refs/heads/main
a538c10a2906cae665449c205e5b963c20f5c29a
Вот и вся ветка: файл с одной строкой. Отсюда — почему в Git ветвление дешёвое: создать ветку значит записать 41 байт. Никакого копирования файлов не происходит, в отличие от систем контроля версий, где ветка была копией дерева.
HEAD — указатель на то, где вы находитесь. Обычно это символическая ссылка на ветку:
$ cat .git/HEAD
ref: refs/heads/main
$ git symbolic-ref HEAD
refs/heads/main
$ git rev-parse HEAD
a538c10a2906cae665449c205e5b963c20f5c29a
Из этого складывается точное понимание, что делает коммит: создаются объекты, ветка, на которую
указывает HEAD, переставляется на новый коммит. Всё.
А если HEAD указывает не на ветку, а прямо на коммит, это состояние называется отсоединённый
HEAD (detached HEAD). Ничего страшного в нём нет: вы просто находитесь на коммите, а не на ветке.
Опасность одна — коммиты, сделанные в этом состоянии, не привязаны ни к одной ветке, и после
переключения на них не останется ссылок. Их можно потерять из вида (но не уничтожить — см. 3.5).
Виды ссылок, которые встречаются:
| Ссылка | Что это |
|---|---|
refs/heads/<имя> |
локальная ветка |
refs/remotes/origin/<имя> |
состояние ветки в удалённом репозитории на момент последней связи |
refs/tags/<имя> |
тег |
HEAD |
где вы сейчас |
ORIG_HEAD |
где HEAD был до последней «опасной» операции — полезно для откатa |
3.4. Индекс: зачем нужна третья область
У файла в Git три возможных состояния, и это то, что путает в начале:
рабочий каталог ──git add──► индекс (staging) ──git commit──► репозиторий
что вижу что попадёт что сохранено
на диске в коммит навсегда
Индекс (он же staging area, он же cache) — это файл .git/index, в котором записано, каким
будет следующий коммит. Именно поэтому объект-blob создаётся уже на git add: содержимое
запоминается сразу.
$ git ls-files -s
100644 d0f56e135b2282bfdd87b1cae5d0238b6a193ab5 0 readme.md
Зачем нужна отдельная область, а не «сохранить всё, что изменилось». Затем, что коммит собирают
осознанно: из десяти правок в рабочем каталоге в коммит уходят три, относящиеся к одной задаче.
Это же даёт возможность добавить часть изменений одного файла (git add -p) — мы вернёмся к этому
в уроке 03.02.
Различие видно в выводе git status --short, и его надо научиться читать: два символа — это две
области. Первый — состояние в индексе, второй — в рабочем каталоге.
$ git status --short
A copy.md ← добавлен в индекс, в рабочем каталоге совпадает
M readme.md ← изменён в рабочем каталоге, в индекс не добавлен
Соответственно и git diff показывает разное в зависимости от того, какие две области сравнивать:
git diff # рабочий каталог против индекса — что ещё не добавлено
git diff --cached # индекс против последнего коммита — что попадёт в коммит
git diff HEAD # рабочий каталог против последнего коммита — всё вместе
Привычка проверять git diff --cached перед коммитом снимает целый класс ошибок вида «закоммитил
не то» и «забыл файл».
3.5. Объекты не исчезают: как найти «потерянное»
Самое практически ценное следствие модели. Объекты неизменны, и команды вроде reset --hard не
удаляют их — они лишь переставляют ссылки. Проверим на живом примере.
Есть три коммита, делаем жёсткий сброс:
$ git log --oneline
c092347 третий коммит
1aa6edf второй коммит
a538c10 первый коммит
$ git reset --hard HEAD~1
$ git log --oneline
1aa6edf второй коммит
a538c10 первый коммит ← третьего коммита в истории нет
Кажется, что работа потеряна. Но объект жив:
$ git cat-file -t c092347
commit
$ git cat-file -p c092347 | head -3
tree 32de4f58f94d23fa80d8d57c43e52bda18df4b9a
parent 1aa6edf133b3d953d20e1991102dac43661c79b9
author Student <student@example.com> 1788446817 +0300
Осталось узнать хеш, если вы его не записали. Для этого есть reflog — журнал того, где был
HEAD:
$ git reflog
1aa6edf HEAD@{0}: reset: moving to HEAD~1
c092347 HEAD@{1}: commit: третий коммит
1aa6edf HEAD@{2}: commit: второй коммит
a538c10 HEAD@{3}: commit (initial): первый коммит
Восстановление — одна команда:
$ git branch recovered c092347
$ git log --oneline recovered | head -1
c092347 третий коммит
Это тот навык, который стоит освоить один раз и запомнить навсегда: git reflog — первое, что
надо выполнить, когда «всё пропало». Работает для reset, rebase, merge, удалённой ветки,
отсоединённого HEAD.
Из той же логики понятно, что делает --amend: он не правит коммит, а создаёт новый, а старый
остаётся без ссылок.
$ git rev-parse HEAD # до amend
1aa6edf133b3d953d20e1991102dac43661c79b9
$ git commit --amend -m "исправлено"
$ git rev-parse HEAD # после — ДРУГОЙ хеш
99673ff1e048186fba0bdebd1c4c329cf16ffda2
$ git cat-file -t 1aa6edf # старый объект на месте
commit
Насколько долго живут такие объекты. Пока на них есть запись в reflog — по умолчанию 90 дней для
достижимых и 30 для недостижимых. Дальше их удаляет сборка мусора. То есть окно на восстановление
большое, но не бесконечное; принудительная git gc --prune=now закрывает его сразу.
3.6. Упаковка: почему .git не растёт бесконечно
Свободные объекты (loose objects) хранятся по одному файлу и сжаты каждый по отдельности. Когда их становится много, Git упаковывает их в packfile, где уже применяется дельта-сжатие: похожие версии хранятся как различия.
$ find .git/objects -type f -not -path '*/pack/*' | wc -l
10 ← свободных объектов
$ git gc --prune=now
$ find .git/objects -type f -not -path '*/pack/*' | wc -l
2
$ git count-objects -vH
count: 0
in-pack: 10
size-pack: 2.18 KiB
Это происходит автоматически (при git gc --auto, который вызывается из других команд), поэтому
специально запускать gc обычно не нужно. Знать про упаковку полезно по двум причинам: она
объясняет, почему .git иногда резко уменьшается, и почему дельты появляются только на этом
этапе — то есть каждая версия большого бинарного файла до упаковки лежит целиком, а после
упаковки сжимается плохо (у бинарных данных дельты неэффективны). Отсюда правило не хранить в Git
артефакты сборки и большие бинарники; для них есть отдельные решения, и в блоке 08 мы будем
складывать образы в реестр, а не в репозиторий.
3.7. Примеры и варианты реализации
Задача: получить репозиторий и понимать, что в нём лежит.
| Среда | Как решается | Что нужно иметь |
|---|---|---|
| Локальный стенд | git init на своей машине; все опыты урока — здесь |
git на рабочей машине |
| VPS / bare-metal | Свой сервер Git: голый репозиторий (git init --bare) плюс доступ по SSH |
сервер, SSH из урока 02.11 |
| Yandex Cloud | Управляемый сервис репозиториев либо тот же голый репозиторий на ВМ | облачный аккаунт |
Локальный стенд. Модель данных изучается только так: репозиторий целиком лежит в каталоге
.git, его можно вскрыть, поломать и создать заново. Ничего сетевого для этого не нужно.
VPS / bare-metal. Здесь появляется понятие голого репозитория — без рабочего каталога,
только каталог .git по сути. Именно так выглядит серверная сторона: git init --bare и доступ по
SSH дают полноценный удалённый репозиторий без всякого дополнительного софта. Полезно знать, что
для командной работы не обязательно нужен GitHub — достаточно сервера с SSH. Подробно — урок 03.04.
Yandex Cloud. Отличается не Git, а обвязка: управляемый сервис даёт веб-интерфейс, ревью и права доступа. Сама модель данных та же, и все команды этого урока работают одинаково.
Что выбрать. Для этого блока — локальный репозиторий; удалённые подключим в 03.04. Отдельно заметьте, что модель данных не зависит ни от среды, ни от хостинга: GitHub, GitLab и голый репозиторий на ВМ хранят те же объекты.
4. Практика на занятии (30 минут)
Курс проходят самостоятельно, поэтому каждый шаг говорит, что ввести, зачем, что должно получиться и что делать при неудаче. Работаем на своей машине, в отдельном каталоге, не в рабочих репозиториях — все опыты создают и ломают учебный репозиторий, который не жалко.
Шаг 1 (5 минут). Вскрыть пустой репозиторий.
- Что ввести:
mkdir -p ~/lab/03.01 && cd ~/lab/03.01
git init -b main
find .git -maxdepth 1
cat .git/HEAD
find .git/objects -type f | wc -l
- Зачем: увидеть, что репозиторий — это каталог
.gitс предсказуемой структурой, аHEADуказывает на ветку ещё до первого коммита. - Ожидаемый результат:
find .git/objects -type f | wc -lдаёт0(объектов нет);cat .git/HEADпечатаетref: refs/heads/main. - Если не так:
git initругается на-b— очень старый git; тогдаgit initи потомgit branch -m main. - Ответьте: сколько объектов в пустом репозитории и на что указывает
HEAD, когда коммитов ещё нет?
Шаг 2 (8 минут). Проследить появление объектов.
- Что ввести:
echo "привет" > readme.md
find .git/objects -type f | wc -l # ещё 0
git add readme.md
find .git/objects -type f # появился ОДИН объект — до коммита!
git cat-file -t $(git hash-object readme.md)
git cat-file -p $(git hash-object readme.md)
git commit -m "первый коммит"
find .git/objects -type f | wc -l # стало 3
for o in $(find .git/objects -type f | sed 's|.git/objects/||;s|/||'); do
printf '%s %-6s %s байт\n' "${o:0:8}" "$(git cat-file -t $o)" "$(git cat-file -s $o)"
done
git cat-file -p $(git rev-parse HEAD^{tree}) # дерево коммита
- Зачем: увидеть главное свойство модели — blob создаётся на
git add(до коммита), а один файл порождает три объекта: blob (содержимое), tree (каталог с именем), commit (снимок). - Ожидаемый результат: после
git addобъект один,cat-file -tпоказываетblob, содержимоепривет; после коммита объектов три (blob, tree, commit); в дереве видно имяreadme.md. - Если не так: после
git addобъектов ноль — файл не добавился (проверьтеgit status); имя автора не задано и коммит падает — задайтеgit config user.email/user.name. - Ответьте: где хранится имя файла
readme.md— в blob или в tree?
Шаг 3 (5 минут). Проверить, что хеш — это содержимое.
- Что ввести:
git hash-object readme.md
printf 'blob 13\0привет\n' | shasum | cut -c1-40 # тот же хеш
wc -c readme.md # почему 13, а не 7?
cp readme.md copy.md && git add copy.md
git ls-files -s # два файла, один хеш
find .git/objects -type f | wc -l # новый blob НЕ создан
- Зачем: убедиться, что имя объекта — это SHA от содержимого с заголовком, и что одинаковое содержимое даёт один объект (Git не дублирует копии).
- Ожидаемый результат:
git hash-objectиprintf ... | shasumдают один и тот же хеш; послеgit add copy.mdвls-filesдва файла с одинаковым хешем, а число объектов не выросло. - Если не так: хеши разошлись — в
printfдругой размер (не 13) или иная кодировка файла; проверьтеwc -c readme.md(должно быть 13: «привет» = 12 байт в UTF-8 плюс перевод строки).
Шаг 4 (5 минут). Ветка — это файл.
- Что ввести:
cat .git/refs/heads/main
git rev-parse HEAD
git branch feature
cat .git/refs/heads/feature # то же значение
ls -l .git/refs/heads/ # размер файла ветки
git symbolic-ref HEAD
- Зачем: увидеть, что ветка — это файл из одной строки (хеш коммита), поэтому ветвление в Git ничего не копирует и стоит копейки.
- Ожидаемый результат:
cat .git/refs/heads/mainиgit rev-parse HEADсовпадают; новый файлfeatureсодержит тот же хеш; размер файла ветки — около 41 байта. - Если не так: файла
.git/refs/heads/featureнет — в новых git ссылка может лежать в.git/packed-refs; тогдаgit rev-parse featureвсё равно её покажет. - Ответьте: сколько байт занимает создание ветки и почему в Git ветвление дешёвое?
Шаг 5 (7 минут). Потерять и восстановить коммит.
- Что ввести:
git commit -q -am "второй" --allow-empty
echo три > f3 && git add f3 && git commit -q -m "третий"
git log --oneline
git reset --hard HEAD~1 # «потеряли» третий коммит
git log --oneline
git reflog # где был HEAD
LOST=$(git reflog | awk '/commit: третий/{print $1; exit}')
git cat-file -t "$LOST" # объект жив
git branch recovered "$LOST"
git log --oneline recovered | head -2
- Зачем: закрепить самый ценный навык — после «всё пропало» коммит не уничтожен, он находится через
git reflogи возвращается одной командой. - Ожидаемый результат: после
reset --hardтретьего коммита нет вgit log, ноgit cat-file -tпо его хешу отвечаетcommit; послеgit branch recoveredон снова в истории. - Если не так:
LOSTпуст — измените фильтрawkпод своё сообщение или возьмите хеш из выводаgit reflogглазами (строкаcommit: третий).
Проверка результата. Вы можете назвать три объекта первого коммита и содержимое каждого,
показать, что имя файла лежит в tree, объяснить, почему копия файла не создала новый объект, и
восстановить коммит после reset --hard.
5. Домашняя работа (1–2 часа)
Задание. Соберите коммит вручную из объектов и разберите настоящий репозиторий.
-
Коммит без единой команды
git commit. Создайте объекты низкоуровневыми командами и соберите из них коммит:git init -b main manual && cd manual BLOB=$(echo "содержимое" | git hash-object -w --stdin) echo "100644 blob $BLOB file.txt" | git mktree # получите TREE TREE=<хеш из предыдущей команды> COMMIT=$(git commit-tree $TREE -m "собран вручную") git update-ref refs/heads/main $COMMIT git log --oneline && git ls-files -s && git statusОпишите в конспекте, что сделала каждая из пяти команд и почему
git statusпоказывает удалённый файл (подсказка: рабочий каталог и индекс не заполнены). -
Разбор своего репозитория конспектов. Начните с
examples/03.01-git-data-model/inspect-repo.sh— он показывает объекты, ссылки, индекс и reflog и только читает, поэтому запускать можно и на рабочем репозитории. Возьмите репозиторий из урока 01.05 и ответьте с выводами команд: сколько объектов, сколько из них в packfile, каков размер.gitпротив размера рабочего каталога (git count-objects -vH,du -sh .git). -
Эксперимент про снимок против изменений. Проверьте гипотезу: «коммит хранит снимок, а не diff». Сделайте два коммита, где второй меняет одну строку в большом файле, и покажите, что во втором коммите tree ссылается на новый blob целиком, а не на разницу. Оформите карточкой эксперимента из урока 01.07. Дополнительно: покажите через
git count-objects -vH, что происходит с размером до и послеgit gc. -
Эксперимент про переименование. Проверьте: «переименование файла без изменения содержимого не создаёт новый blob». Покажите хеши до и после
git mvи объясните через модель, почему Git не хранит «операцию переименования» и как тогдаgit log --followотслеживает историю файла. -
Отсоединённый HEAD. Перейдите на коммит по хешу (
git checkout <хеш>), сделайте коммит, вернитесь на ветку. Покажите: что говоритgit statusв этом состоянии, куда указывалHEAD(cat .git/HEAD), и как найти и сохранить сделанный коммит после возврата. -
Своя шпаргалка по низкоуровневым командам. Соберите 8–10 команд для разбора репозитория (
cat-file,ls-files,rev-parse,reflog,fsck,count-objects) с одной строкой о том, когда каждая нужна. Только те, которые вы выполняли. Заготовка —examples/03.01-git-data-model/plumbing-cheatsheet.md; в конце пять вопросов для самопроверки.
Критерии приёмки.
- Репозиторий
manualсодержит один коммит, созданный безgit commit;git logего показывает. - Объяснено назначение всех пяти низкоуровневых команд и причина вывода
git status. - Приведены
git count-objects -vHиdu -sh .gitдля своего репозитория с пояснением. - Карточка эксперимента показывает, что второй коммит ссылается на новый blob целиком.
- Показано изменение размера объектов до и после
git gc. - Показано, что при
git mvхеш blob не изменился; объяснено поведение--follow. - Для отсоединённого
HEADприведеныgit status, содержимое.git/HEADи способ сохранить сделанный там коммит. - Шпаргалка содержит 8–10 команд, и все они запускались.
Файлы для работы: blocks/03-git/examples/03.01-git-data-model/ — примеры к этому уроку в репозитории курса.
6. Частые ошибки и подводные камни
Считать коммит набором изменений. Симптом: непонимание, почему после rebase меняются хеши и
почему cherry-pick даёт конфликты. Причина: модель понята неверно. Что делать: помнить, что
коммит — снимок дерева, а diff вычисляется на ходу сравнением с родителем.
Думать, что reset --hard уничтожает коммиты. Симптом: работа переписывается заново. Причина:
незнание про reflog. Что делать: git reflog — первое действие при «всё пропало»; объект жив,
пока не прошла сборка мусора.
Считать, что --amend правит коммит. Симптом: удивление, что после amend нельзя просто
запушить, и что у коллег другая история. Причина: создаётся новый коммит с новым хешем.
Что делать: понимать, что любая «правка истории» — это новые объекты; последствия для общих
ветвей — урок 03.03.
Коммитить, не посмотрев индекс. Симптом: в коммите не то, что задумано, или забытый файл.
Причина: не проверили область подготовки. Что делать: git status --short и git diff --cached
перед каждым коммитом.
Путать git diff и git diff --cached. Симптом: «diff пустой, а изменения есть». Причина:
изменения уже в индексе, и git diff их не показывает. Что делать: помнить, какие две области
сравнивает каждая форма.
Хранить в Git большие бинарники и артефакты сборки. Симптом: репозиторий на гигабайты, клонирование минутами. Причина: каждая версия хранится целиком, дельты для бинарных данных неэффективны, а история не удаляется. Что делать: артефакты — в реестр или хранилище (блок 08); для больших файлов существуют отдельные механизмы.
Запускать git gc --prune=now, «чтобы почистить». Симптом: восстановить потерянное больше
нельзя. Причина: команда удаляет недостижимые объекты немедленно. Что делать: не запускать без
причины; автоматической сборки достаточно.
Бояться отсоединённого HEAD. Симптом: паника при сообщении detached HEAD. Причина:
непонимание, что это просто «HEAD указывает на коммит, а не на ветку». Что делать: если сделали
коммиты — создать ветку (git switch -c <имя>); если нет — просто вернуться на ветку.
7. Проверка усвоения
- Что является именем объекта в Git и от чего оно вычисляется?
- Назовите четыре типа объектов и что хранит каждый.
- Где хранится имя файла — в blob или в tree? Что из этого следует при переименовании?
- Почему коммит — это снимок, а не набор изменений? Как тогда
git showпоказывает diff? - Два файла с одинаковым содержимым. Сколько объектов blob создаст Git и почему?
- Что физически представляет собой ветка? Сколько байт занимает её создание?
- Что такое
HEADи чем отличается обычное состояние от отсоединённого? - Зачем нужен индекс, если можно было бы коммитить всё изменённое?
- Разберите вывод
git status --short: что означают первый и второй символы? - Чем отличаются
git diff,git diff --cachedиgit diff HEAD? - Вы сделали
reset --hardи потеряли коммит. Опишите порядок восстановления. - Что происходит при
--amendсо старым коммитом? - Чем отличаются поля
authorиcommitterи когда они расходятся? - Почему хранение больших бинарных файлов в Git — плохая идея? Назовите две причины.
8. Материалы
Первоисточники:
- Pro Git: «Git изнутри — Объекты Git» — та же модель данных подробнее и на русском: blob, tree, commit и низкоуровневые команды. Книга доступна бесплатно и является основным источником этого блока.
- Pro Git: «Git изнутри — Ссылки в Git» —
ветки,
HEAD, теги как ссылки на объекты. - Pro Git: «Git изнутри — Pack-файлы» — упаковка объектов и дельта-сжатие.
man git-cat-file,man git-hash-object,man git-mktree,man git-commit-tree— низкоуровневые команды из домашней работы.man gitrevisions— как записывать ссылки на коммиты:HEAD~1,HEAD^,@{1},main@{yesterday}. Короткая и очень полезная страница.man git-reflog— журнал перемещенийHEADи сроки хранения записей.
Для наглядности:
- Официальная документация Git — точка входа: книга, справочник команд, видео.
git log --graph --oneline --all --decorate— способ увидеть структуру ссылок и коммитов, не устанавливая ничего дополнительно. Понадобится в уроке 03.03.
← Предыдущий: 02.13. Изоляция и лимиты: ulimit, cgroups v2, namespaces · Блок 03 · Следующий: 03.02. Повседневный цикл работы и отмена изменений →
Материалы для практики
Скрипты, шпаргалки и заготовки к заданиям урока. Текст урока — на этой странице.
-
Материалы к уроку 03.01 «Модель данных Git объекты, коммиты, ссылки, индекс».zip