Ментальная модель Git: три состояния, коммит-снимок и граф

Рейтинг: 67.6% · 8 голосов
Самый подробный курс по Git - от первого коммита до объектной базы и packfiles. Терминал и первый репозиторий, ветки и слияние, конфликты, удалённая работа и форджи (GitHub, GitLab), rebase и переписывание истории, спасение кода через reflog и fsck, stash и worktree, bisect и blame, подмодули и монорепо, хуки и подпись коммитов, внутреннее устройство и масштаб. Для новичков и профи. Актуально на 2026 (Git 2.54, дорожная карта 3.0).
Ответить
Аватара пользователя
Pavel_Git
Сообщения: 52
Зарегистрирован: 11 май 2026, 05:31

Ментальная модель Git: три состояния, коммит-снимок и граф

Сообщение Pavel_Git »

Оглавление курса (52)
  1. Что такое контроль версий и почему победил Git
  2. Установка Git на Windows, macOS и Linux и первая настройка
  3. Терминал и как спрашивать у Git: выживание в командной строке
  4. Ментальная модель Git: три состояния, коммит-снимок и граф (вы здесь)
  5. Первый репозиторий: init, add, commit, status
  6. Просмотр изменений: status, diff и индексация по частям
  7. История проекта: git log, диапазоны и поиск
  8. .gitignore: что не должно попасть в репозиторий
  9. .gitattributes: окончания строк, бинарники и атрибуты
  10. Ветки как указатели: branch, switch и detached HEAD
  11. Слияние веток: fast-forward против трёхстороннего merge
  12. Конфликты слияния: анатомия и уверенное разрешение
  13. Практикум: твой первый день с Git от нуля до push
  14. Удалённые репозитории: remote, fetch, базовый pull и refspec
  15. git push: безопасная отправка и защита от перезаписи
  16. Клонирование: shallow, partial clone, bundle и backfill
  17. GitHub, GitLab и форки: pull request, rulesets и защита веток
  18. Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
  19. Merge queue и merge trains: безопасное вливание в trunk
  20. Культура code review и Conventional Commits
  21. git reset: три дерева и отмена индексации и коммитов
  22. git revert: безопасная отмена в общей истории
  23. Правка последнего коммита: git commit --amend
  24. git rebase: пересадка истории и золотое правило
  25. Интерактивный rebase: переписать серию коммитов
  26. git cherry-pick: перенос отдельных коммитов
  27. Stacked diffs: цепочка зависимых PR
  28. Массовое переписывание истории: git filter-repo и git replace
  29. git reflog: журнал, который помнит всё
  30. Восстановление утерянных коммитов, веток и файлов
  31. Откат опасных операций: reset --hard, сломанный rebase, плохой merge
  32. git stash: тайник для незавершённой работы
  33. git worktree: несколько рабочих деревьев одного репозитория
  34. git bisect: бинарный поиск регрессии
  35. git blame, mailmap и археология кода
  36. Теги, релизы, archive и bundle
  37. Подмодули: внешние репозитории внутри проекта
  38. Subtree, монорепозитории и sparse-checkout
  39. Git LFS и большие файлы
  40. Вспомогательные команды: clean, mv, rm, restore
  41. Псевдонимы и кастомизация .gitconfig
  42. Хуки Git: автоматизация на клиенте и сервере
  43. Подпись коммитов и тегов: SSH, GPG и Sigstore
  44. Внутреннее устройство I: объектная база Git
  45. Внутреннее устройство II: ссылки, HEAD, notes и reftable
  46. Внутреннее устройство III: индекс, packfiles, gc и обслуживание
  47. От SHA-1 к SHA-256: переход на новый хеш
  48. Огромные репозитории: partial clone, FSMonitor, Scalar
  49. Git в CI/CD: shallow, кеш, merge_group и безопасность
  50. Куда движется Git: дорожная карта 3.0 и экосистема
  51. Профессиональный setup: собираем рабочее окружение Git
  52. 30+ типичных ошибок Git и как из них выбираться
Если ты раньше работал в SVN, копировал папки с суффиксами _final, _final2, _реально_final, или просто впервые открыл Git и не понимаешь, почему добавить файл это аж две команды (add и commit), то этот урок снимет туман. Тут мы не лезем в хеши и объекты под капотом - это будет позже, в модуле внутренностей. Сейчас задача другая и более важная: собрать в голове правильную картинку. Когда ментальная модель git верная, команды перестают быть заклинаниями и становятся очевидными. Когда модель кривая - ты будешь годами бояться git и копировать проект перед каждым git pull. Поэтому потрать эти 16 минут серьёзно. Это фундамент всего курса.

Три состояния файла: как устроен git на самом деле

Главное, что отличает Git от копирования папок и от старых систем - у каждого файла в твоём проекте есть три места, где он может жить одновременно, и в каждом из них может быть своя версия. Это и есть знаменитые три состояния.
  • Рабочий каталог (working directory) - обычные файлы на диске, которые ты редактируешь в редакторе. То, что видишь в проводнике. Тут ты ломаешь, чинишь, экспериментируешь.
  • Индекс (staging area, он же staging) - промежуточная зона. Сюда ты складываешь то, что хочешь положить в следующий коммит. Это твоя "корзина перед кассой": набрал, проверил, потом оплатил.
  • Репозиторий (HEAD) - зафиксированная история. Тут лежат коммиты - снимки, которые уже сделаны и никуда не денутся.
Изменения движутся по этой цепочке слева направо. Поправил файл в редакторе - он изменился в рабочем каталоге. Сделал

Код: Выделить всё

git add file.txt
- скопировал текущее содержимое файла в индекс git. Сделал

Код: Выделить всё

git commit
- всё, что лежало в индексе, превратилось в новый снимок в репозитории, и HEAD сдвинулся на него.

Зачем вообще нужна эта средняя зона, индекс? Почему нельзя коммитить прямо из рабочего каталога, как делает SVN? Затем, что индекс даёт тебе контроль над тем, что именно войдёт в коммит. Ты поправил три файла, но логически это две разные задачи. Ты добавляешь в индекс только два файла по первой задаче, коммитишь их с понятным сообщением, потом добавляешь третий и коммитишь отдельно. Рабочий каталог при этом не трогался. Staging area - это не бюрократия, это инструмент для аккуратных, атомарных коммитов. На поздних уроках мы будем добавлять в индекс даже отдельные куски одного файла через

Код: Выделить всё

git add -p
- и тогда ты оценишь staging по-настоящему.

Изображение

Смотрим состояния глазами: git status и diff

Слова словами, давай увидим три состояния вживую. Создадим репозиторий и поиграем.

Код: Выделить всё

$ git init demo
Initialized empty Git repository in /home/user/demo/.git/
$ cd demo
$ git switch -c main
Switched to a new branch 'main'
$ echo "привет" > readme.txt
$ git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        readme.txt

nothing added to commit but untracked files present (use "git add" to track)
Файл есть в рабочем каталоге, но Git его пока не отслеживает - он untracked. Заметь: в ванильном Git на 2026 год при

Код: Выделить всё

git init
ветка по умолчанию всё ещё master (плюс подсказка-hint в выводе), поэтому я сразу переключился на main руками через

Код: Выделить всё

git switch -c main
. Встроенным дефолтом main станет только в Git 3.0 в конце 2026 года. Добавим файл в индекс и снова посмотрим статус.

Код: Выделить всё

$ git add readme.txt
$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   readme.txt
Видишь формулировку "Changes to be committed"? Это и есть содержимое индекса - то, что уйдёт в следующий снимок. Теперь сделаем хитрость: изменим файл в рабочем каталоге после того, как добавили в индекс.

Код: Выделить всё

$ echo "вторая строка" >> readme.txt
$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   readme.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
        modified:   readme.txt
Вот он, момент истины. Один и тот же файл readme.txt сейчас фигурирует дважды. В индексе лежит версия с одной строкой ("Changes to be committed"). В рабочем каталоге - версия с двумя строками ("Changes not staged"). Это наглядно доказывает: индекс и рабочий каталог - реально разные хранилища. Чтобы увидеть разницу прицельно:
  • Код: Выделить всё

    git diff
    - показывает разницу между рабочим каталогом и индексом (что ты ещё не добавил).
  • Код: Выделить всё

    git diff --staged
    - показывает разницу между индексом и последним коммитом (что уйдёт в коммит).
Это две разные команды, и путаница между ними - классическая боль новичка. Запомни простое правило: diff без флага смотрит "вперёд" от индекса к диску, diff --staged смотрит "назад" от индекса к истории.

Коммит - это снимок, а не разница (git снимок против SVN)

Теперь самое контринтуитивное и самое важное. Что такое commit? Большинство приходит из мира, где система версий хранит разницы (дельты): "в этой ревизии добавили строку 5, удалили строку 12". Так работал SVN, так работал CVS. Git устроен принципиально иначе.

Коммит в Git - это снимок (snapshot) всего проекта целиком на момент фиксации, плюс ссылка на родительский коммит, плюс метаданные (автор, дата, сообщение). Не разница. Снимок.

Звучит расточительно: неужели Git хранит полную копию всех файлов в каждом коммите? На пальцах - да, концептуально каждый коммит знает полное состояние проекта. Но Git не дурак: если файл между коммитами не менялся, новый снимок просто ссылается на ту же самую версию файла, что и раньше, а не дублирует её. Как именно это сделано - через хеши и объекты - мы разберём в модуле внутренностей. Сейчас держи в голове правильную интуицию: коммит = полная фотография проекта, а экономия места - забота Git, не твоя.

Почему снимки лучше дельт? Несколько причин, которые ты почувствуешь руками:
  • Скорость. Чтобы показать тебе состояние проекта на любом коммите, Git не пересобирает его, складывая сотни дельт от начала истории. Он просто берёт готовый снимок. Переключение веток поэтому почти мгновенное.
  • Целостность. Снимок зафиксирован весь и сразу. Нет ситуации "дельта 47 побилась, и всё после неё нечитаемо".
  • Простота слияний. Когда Git мержит ветки, он сравнивает три снимка (две ветки и их общий предок) - это надёжнее, чем накатывать цепочки дельт.
Эта разница между "git снимок" и "SVN дельта" - не академическая придирка. Из неё растёт вообще всё поведение Git: дешёвые ветки, быстрый переход по истории, локальность. Усвоишь снимки - половина "магии" Git станет очевидной.

Граф коммитов git: ветка - это указатель, HEAD - где я сейчас

Коммиты не висят в воздухе - они связаны. Каждый коммит (кроме самого первого) хранит ссылку на своего родителя. Сделал коммит A, потом коммит B - B указывает на A как на родителя. Получается цепочка, направленная назад во времени. Это и есть граф коммитов git. В общем случае это не просто линия, а DAG - направленный ациклический граф (когда есть ветвления и слияния, у коммита может быть два родителя), но начинать всегда удобнее с простой цепочки.

Введём единую легенду графа, которую будем использовать весь курс. Запомни её сейчас раз и навсегда:
  • Кружок (узел) - это коммит-снимок.
  • Стрелка - связь "коммит указывает на своего родителя". Стрелка всегда смотрит назад, в прошлое.
  • Ярлык-узел (прямоугольная метка, привязанная к кружку) - это ветка, HEAD или тег. Указатель на конкретный коммит.

Код: Выделить всё

                       main
                        |
   C1  <----  C2  <---- C3
                        ^
                       HEAD
Здесь три коммита. C3 указывает на родителя C2, C2 на C1. Ярлык main привязан к C3 - значит, ветка main "сейчас находится" на коммите C3. И вот ключевая идея, которая ломает мозг новичкам, но потом освобождает:

Ветка в Git - это не папка с файлами и не копия проекта. Ветка - это просто подвижный указатель на один коммит. Лёгкий ярлык, ничего больше.

Создать ветку = создать ещё один такой ярлык (это операция в доли миллисекунды, поэтому ветки в Git дешёвые, в отличие от SVN, где ветка - это копирование). Сделать коммит на ветке = создать новый снимок и передвинуть ярлык ветки вперёд, на свежий коммит.

А что такое HEAD? Это специальный указатель "где я сейчас". Обычно HEAD указывает не прямо на коммит, а на ветку ("я сейчас на ветке main"). Когда ты коммитишь, двигается ветка, а HEAD едет вместе с ней, потому что прикреплён к ней. Посмотрим реально:

Код: Выделить всё

$ git log --oneline --decorate
a1b2c3d (HEAD -> main) добавил вторую строку
9f8e7d6 первый коммит
Запись (HEAD -> main) читается дословно: "HEAD указывает на ветку main, а main указывает на коммит a1b2c3d". Стрелка тут - тот самый смысл из легенды. Когда ты слышишь страшное "detached HEAD" - это всего лишь ситуация, когда HEAD прицепили напрямую к коммиту, минуя ветку. Не катастрофа, просто "я стою на снимке, но ни на какой ветке". Разберём детально позже.

Мини-глоссарий: HEAD, индекс, ветка, ref, upstream, base

Эти шесть слов будут звучать в каждом уроке. Зафиксируем короткие, честные определения, без воды.
  • HEAD - указатель "где я сейчас". Почти всегда указывает на текущую ветку, а через неё - на коммит. Твоё "ты находишься здесь" на карте истории.
  • Индекс (staging area) - промежуточная зона между рабочим каталогом и репозиторием. Черновик следующего коммита. Физически лежит в файле .git/index.
  • Ветка (branch) - подвижный лёгкий указатель на коммит. Не копия файлов. Двигается вперёд при каждом коммите.
  • Ref (ссылка) - общее имя для любого указателя на коммит. Ветка - это ref, тег - это ref, HEAD - тоже ref. Ветки лежат под именами вида refs/heads/main. Считай ref "именованной закладкой".
  • Upstream (вышестоящая ветка) - удалённая ветка, которую отслеживает твоя локальная. Например, локальная main отслеживает origin/main. Именно по upstream Git понимает, куда пушить и насколько ты отстал или опередил ("ahead 2, behind 1").
  • Base (база, merge base) - общий предок двух веток. Тот последний коммит, от которого ветки разошлись. Git опирается на base при слияниях и rebase, чтобы понять, что в каждой ветке поменялось относительно общей точки.
Тизер на будущее: всё, что ты сейчас представляешь кружками и ярлыками, под капотом - это хеши (длинные идентификаторы вроде a1b2c3d) и четыре типа объектов в каталоге .git/objects. Снимок-коммит ссылается на снимок дерева файлов, тот - на содержимое. Мы вскроем это в модуле внутренностей и ты увидишь, что "магии" там нет - чистая инженерия. А на 2026 год отмечу: новые репозитории на Git 2.51+ уже по умолчанию используют хеш SHA-256 вместо старого SHA-1, и в Git 3.0 это закрепится. Но это деталь реализации - ментальная модель из снимков, графа и трёх состояний от смены хеша не меняется ни на йоту.

Типичные грабли ментальной модели
  • "Я сделал git add, теперь можно править файл, изменения подхватятся". Нет. add делает снимок файла в индекс в момент команды. Поправил после add - правка осталась только в рабочем каталоге, в коммит не попадёт, пока не сделаешь add ещё раз. Мы это видели выше в git status: один файл в двух зонах.
  • "Ветка - это отдельная папка/копия проекта". Нет. Ветка - ярлык на коммит. Все ветки делят одну рабочую папку, ты просто переключаешь, какой снимок в ней разложен.
  • "Коммит хранит мои изменения (дельту)". Нет, коммит хранит полный снимок состояния. "Изменения" - это то, что Git вычисляет на лету, сравнивая два снимка, когда ты просишь git diff или git show.
  • "HEAD - это последний коммит". Не совсем. HEAD - это где ты сейчас. Обычно это вершина текущей ветки, но если ты переключился на старый коммит, HEAD будет там, а вершина ветки останется впереди.
Мини-лаба: пройди три состояния руками

Открой терминал и повтори по шагам. Цель - своими глазами увидеть, как файл движется рабочий каталог -> индекс -> репозиторий.
  • Шаг 1. Создай чистый репозиторий и ветку main:

    Код: Выделить всё

    git init lab && cd lab && git switch -c main
  • Шаг 2. Создай файл и посмотри статус - файл untracked, он только в рабочем каталоге:

    Код: Выделить всё

    echo "строка 1" > notes.txt
    git status
  • Шаг 3. Положи файл в индекс и проверь, что он "Changes to be committed":

    Код: Выделить всё

    git add notes.txt
    git status
  • Шаг 4. Дополни файл и снова глянь статус. Файл должен появиться в обеих секциях - в индексе старая версия, в рабочем каталоге новая:

    Код: Выделить всё

    echo "строка 2" >> notes.txt
    git status
  • Шаг 5. Сравни два diff и почувствуй разницу зон:

    Код: Выделить всё

    git diff
    git diff --staged
  • Шаг 6. Добавь свежую версию и сделай снимок-коммит:

    Код: Выделить всё

    git add notes.txt
    git commit -m "первые заметки"
  • Шаг 7. Посмотри граф и найди легенду в выводе - кружок-коммит и ярлыки HEAD и main:

    Код: Выделить всё

    git log --oneline --decorate --graph
Если на шаге 4 ты увидел notes.txt дважды и понял, почему - значит, три состояния улеглись в голове. Это главный чекпойнт урока.

Контрольные вопросы
  • Чем индекс (staging area) отличается от рабочего каталога, и зачем вообще нужна эта промежуточная зона?
  • Коммит хранит снимок всего проекта или только разницу с прошлым коммитом? Какое практическое преимущество даёт выбранный Git подход?
  • Что физически представляет собой ветка в Git и почему создание ветки - почти бесплатная операция?
  • Что такое HEAD и чем "HEAD указывает на main" отличается от "detached HEAD"?
Итог

Ты собрал ментальную модель git, на которую будет опираться весь курс. Три состояния: рабочий каталог (правишь), индекс (готовишь следующий коммит), репозиторий (зафиксированная история), и изменения движутся между ними через add и commit. Коммит - это снимок всего проекта плюс ссылка на родителя, а не дельта, и отсюда растёт вся скорость и надёжность Git. История - это граф из коммитов-кружков со стрелками на родителей, ветка - лёгкий подвижный ярлык на коммит, HEAD - "ты находишься здесь". Эту легенду графа (кружок, стрелка, ярлык) мы пронесём через весь курс. Дальше будет только конкретнее: следующие уроки - про коммиты, ветки и переключение на практике, а в модуле внутренностей вскроем, как снимки превращаются в хеши и объекты.
👍1 ❤️2 🔥3 😄 🤔1
Аватара пользователя
ppbdok
Сообщения: 1
Зарегистрирован: 12 май 2026, 22:25

Re: Ментальная модель Git: три состояния, коммит-снимок и граф

Сообщение ppbdok »

Наконец-то дошло, почему один файл в git status светится сразу в двух секциях. Думал баг, а это я после add ещё раз поправил. Спасибо за пример со второй строкой!
👍 ❤️ 🔥 😄 🤔1
Аватара пользователя
deno_lord
Сообщения: 1
Зарегистрирован: 18 май 2026, 05:39

Re: Ментальная модель Git: три состояния, коммит-снимок и граф

Сообщение deno_lord »

Вопрос: если коммит это полный снимок проекта, репозиторий же не пухнет от копий неизменных файлов? Понял что Git ссылается на старую версию, но интересно как именно - жду модуль внутренностей.
👍1 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
Терминал и как спрашивать у Git: выживание в командной строке
Следующая глава →
Первый репозиторий: init, add, commit, status

Все главы курса «Git профессионально: от первого коммита до внутреннего устройства»

Поделиться темой: ✈ Telegram VK
Похожие запросы: git commit: фиксация измененийgit tag: теги и релизыgit blame: кто изменил строкуПодпись коммитов в Git (SSH, GPG)Что такое Git и зачем нужен контроль версийgit cherry-pick: перенос коммитов

Вернуться в «Git профессионально: от первого коммита до внутреннего устройства»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость