School

← To the program

Модель данных Git: объекты, коммиты, ссылки, индекс

Lesson from the course “Курс подготовки 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 часа)

Задание. Соберите коммит вручную из объектов и разберите настоящий репозиторий.

  1. Коммит без единой команды 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 показывает удалённый файл (подсказка: рабочий каталог и индекс не заполнены).

  2. Разбор своего репозитория конспектов. Начните с examples/03.01-git-data-model/inspect-repo.sh — он показывает объекты, ссылки, индекс и reflog и только читает, поэтому запускать можно и на рабочем репозитории. Возьмите репозиторий из урока 01.05 и ответьте с выводами команд: сколько объектов, сколько из них в packfile, каков размер .git против размера рабочего каталога (git count-objects -vH, du -sh .git).

  3. Эксперимент про снимок против изменений. Проверьте гипотезу: «коммит хранит снимок, а не diff». Сделайте два коммита, где второй меняет одну строку в большом файле, и покажите, что во втором коммите tree ссылается на новый blob целиком, а не на разницу. Оформите карточкой эксперимента из урока 01.07. Дополнительно: покажите через git count-objects -vH, что происходит с размером до и после git gc.

  4. Эксперимент про переименование. Проверьте: «переименование файла без изменения содержимого не создаёт новый blob». Покажите хеши до и после git mv и объясните через модель, почему Git не хранит «операцию переименования» и как тогда git log --follow отслеживает историю файла.

  5. Отсоединённый HEAD. Перейдите на коммит по хешу (git checkout <хеш>), сделайте коммит, вернитесь на ветку. Покажите: что говорит git status в этом состоянии, куда указывал HEAD (cat .git/HEAD), и как найти и сохранить сделанный коммит после возврата.

  6. Своя шпаргалка по низкоуровневым командам. Соберите 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. Проверка усвоения

  1. Что является именем объекта в Git и от чего оно вычисляется?
  2. Назовите четыре типа объектов и что хранит каждый.
  3. Где хранится имя файла — в blob или в tree? Что из этого следует при переименовании?
  4. Почему коммит — это снимок, а не набор изменений? Как тогда git show показывает diff?
  5. Два файла с одинаковым содержимым. Сколько объектов blob создаст Git и почему?
  6. Что физически представляет собой ветка? Сколько байт занимает её создание?
  7. Что такое HEAD и чем отличается обычное состояние от отсоединённого?
  8. Зачем нужен индекс, если можно было бы коммитить всё изменённое?
  9. Разберите вывод git status --short: что означают первый и второй символы?
  10. Чем отличаются git diff, git diff --cached и git diff HEAD?
  11. Вы сделали reset --hard и потеряли коммит. Опишите порядок восстановления.
  12. Что происходит при --amend со старым коммитом?
  13. Чем отличаются поля author и committer и когда они расходятся?
  14. Почему хранение больших бинарных файлов в 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. Повседневный цикл работы и отмена изменений →

Practice materials

Scripts, cheat sheets and starter files for the lesson tasks. The lesson text is on this page.