Зачем нужен Git и как он устроен

Зачем нужен Git и как он устроен

Любой проект, в котором работает больше одного человека (а часто и один), рано или поздно сталкивается с тремя вопросами: «что изменилось?», «когда это сломалось?» и «кто это сделал?». Система контроля версий отвечает на все три. Git - самая распространённая из таких систем, и именно с ней ты будешь работать каждый день как бэкенд-разработчик.

Без Git невозможны code review, CI/CD, параллельная командная работа, откат неудачного релиза и десятки других вещей, которые считаются стандартом индустрии. Понимание того, как Git устроен внутри, помогает не бояться его команд и быстро разбираться в нетривиальных ситуациях.

Централизованные и распределённые VCS

До Git были системы вроде SVN (Subversion) и CVS. Они работали по модели «один центральный сервер»: вся история хранилась на сервере, а разработчик получал только текущую версию файлов. Если сервер падал - история была недоступна.

Git - распределённая система. Каждый разработчик хранит у себя полную копию репозитория со всей историей. Это даёт несколько преимуществ:

  • работа оффлайн - коммиты, просмотр истории, создание веток не требуют сети;
  • скорость - почти все операции локальные;
  • надёжность - потеря сервера не означает потерю истории, потому что у каждого участника есть полная копия;
  • гибкий workflow - ветки стоят дёшево, merge и rebase делаются локально.

Централизованная VCS против распределённой: SVN с одним сервером и Git с полными копиями у каждого

В централизованной модели разработчики - тонкие клиенты, а сервер - единственный держатель истории. В Git каждый узел равноправен: твой ноутбук, ноутбук коллеги и сервер GitHub - это три одинаково полноценные копии репозитория. Remote (GitHub/GitLab) - не «главный сервер», а просто общая точка синхронизации.

Объектная модель Git

Git хранит данные не как «список изменений», а как граф объектов. Есть четыре типа объектов:

blob - содержимое одного файла. Git не хранит имя файла в blob, только содержимое. Два файла с одинаковым содержимым - один blob.

tree - аналог директории. Содержит ссылки на blob-ы (файлы) и другие tree (поддиректории), плюс имена и права доступа.

commit - снимок проекта. Содержит ссылку на корневой tree, имя автора, дату, сообщение коммита и ссылку на родительский коммит (или несколько при merge).

tag - именованная ссылка на коммит (обычно для версий: v1.0.0, v2.3.1).

Объектная модель Git: commit ссылается на корневой tree и parent commit, tree содержит blob-файлы и вложенные tree

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

Когда ты понимаешь, что коммит - это снимок (tree), а не diff, становится ясно, почему Git так быстро переключает ветки: он просто подменяет рабочую директорию содержимым нужного tree.

Три зоны: working directory, staging area, repository

Git разделяет работу на три зоны, и это ключевая ментальная модель:

Три зоны Git: working directory, staging area и repository со стрелками git add, git commit и обратной git checkout

Working directory - файлы, которые ты видишь в файловом менеджере. Здесь ты пишешь код.

Staging area (index) - промежуточная зона. Ты выбираешь, какие именно изменения попадут в следующий коммит. Это позволяет коммитить не всё подряд, а логически связанные изменения.

Repository - папка .git/, где хранятся все объекты, ветки, конфиг. Коммит фиксирует то, что было в staging area.

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

Когда ты делаешь git init, в проекте появляется скрытая папка .git/. Вот что внутри:

Структура папки .git: HEAD, config, objects, refs/heads, refs/remotes, hooks, index, logs и их назначение

Папка `.git/` - это весь репозиторий. Удалишь её - потеряешь всю историю. Файлы на диске останутся, но Git перестанет их отслеживать.

DAG: история как граф

История в Git - это направленный ациклический граф (DAG). Каждый коммит указывает на своего родителя (или родителей при merge). Ветки - это просто указатели (ссылки) на определённые коммиты.

DAG истории Git: ветка main A-B-C-D с веткой feature/login E-F, сходящейся в merge-коммит D

В этом примере коммит D - merge-коммит с двумя родителями: C и F. Ветка main указывает на D, ветка feature/login указывает на F. Стрелки в графе всегда смотрят от потомка к родителю - именно так Git ходит по истории при git log: от текущего коммита назад во времени.

Такая модель даёт Git возможность эффективно работать с ветками: создание ветки - это запись 41 байта (SHA-1 хеш + перенос строки), а не копирование файлов.

Зачем VCS бэкенд-разработчику

На первый взгляд, контроль версий - это про «сохранение файлов». Но в реальной работе Git решает задачи, которые без него были бы мучительными:

Откат неудачного деплоя. Релиз ушёл в прод и сломал авторизацию. Без Git - паника, попытки вспомнить, что изменилось. С Git - git revert <hash> или деплой предыдущего тега, и через 2 минуты прод снова работает.

Параллельная работа. Три разработчика одновременно пилят три фичи в одном микросервисе. Каждый в своей ветке, код не мешает друг другу, merge request собирает изменения вместе.

Code review. Merge Request в GitLab/GitHub показывает ровно те строки, которые изменились. Ревьюер читает diff, оставляет комментарии, автор исправляет - и всё это отслеживается.

CI/CD. Pipeline запускается автоматически при push: линтеры, тесты, сборка Docker-образа, деплой. Без Git нет push, без push нет CI.

Аудит. git blame показывает, кто написал каждую строку. git log --follow показывает историю файла, даже если его переименовали. git bisect находит коммит, который сломал тесты, бинарным поиском.

Git ≠ GitHub

Git - инструмент, работающий локально на твоём компьютере. GitHub, GitLab, Bitbucket - это хостинги для удалённых репозиториев, которые добавляют веб-интерфейс, pull/merge requests, CI/CD, issue tracker и другие фичи поверх Git. Можно использовать Git без GitHub, но не GitHub без Git.

В реальной работе бэкенд-разработчика Git участвует в каждом этапе:

  • разработка - ветки, коммиты, локальное тестирование;
  • code review - merge/pull request на GitLab/GitHub;
  • CI/CD - pipeline запускается при push, собирает и деплоит;
  • откат - если релиз сломал прод, git revert или деплой предыдущего тега;
  • аудит - git blame и git log показывают, кто, когда и зачем менял код.

Каждый из этих пунктов разберём отдельно дальше по треку. Начинаем с настройки Git: имя, email и SSH-ключ, без которых первый же push упрётся в ошибку.

Мини-задание

  • Установи Git и проверь версию: git --version
  • Создай пустой репозиторий: git init test-repo && cd test-repo
  • Загляни внутрь папки .git/ и найди файл HEAD - посмотри, на что он указывает
  • Сделай первый коммит и посмотри, как изменилось содержимое .git/objects/
  • Выполни git cat-file -t <hash> и git cat-file -p <hash> для любого объекта, чтобы увидеть его тип и содержимое

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.