Git под капотом: как на самом деле работают ветки, и почему это не копии файлов

Разбираем внутреннее устройство Git: blob, tree, commit, HEAD и почему ветки — это просто файлы с хешами. Понимание механики делает работу с историей увереннее.

Большинство разработчиков используют Git каждый день, но мало кто задумывается, что происходит внутри, когда мы пишем git checkout -b feature или git merge main. С первого взгляда кажется, что ветки — это просто копии папок с кодом. На деле Git устроен гораздо элегантнее, и понимание его внутреннего механизма делает вас увереннее при работе с конфликтами, rebase и историей.

Объекты Git: всё — хеш

В основе Git лежит простая идея: все данные хранятся как объекты, адресуемые по SHA-1 хешу. Когда вы делаете коммит, Git создаёт не один, а три типа объектов:

  • Blob — сжатое содержимое файла. Имя файла не хранится здесь, только его данные.
  • Tree — аналог директории. Содержит список имён файлов, прав доступа и ссылки на blob-объекты.
  • Commit — точка во времени. Содержит автора, дату, сообщение, ссылку на tree и ссылку на родительский коммит (или несколько, в случае merge).

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

Ветка — это просто файл с 40 символами

Вот где начинается магия. Когда вы создаёте ветку feature, Git не копирует ни одного файла проекта. Вместо этого он создаёт маленький текстовый файл в папке .git/refs/heads/feature, содержащий всего лишь SHA-1 хеш последнего коммита.

Переключение ветки (git checkout или git switch) означает: «возьми хеш из файла ветки, найди соответствующий tree-объект и восстанови файлы в рабочей директории». Это происходит почти мгновенно, потому что Git работает не с дельтами, а со снимками (snapshots).

Почему merge быстрый, а rebase — хирургия

Fast-forward merge — это вообще не «слияние» в привычном смысле. Git просто перемещает указатель ветки на новый коммит. Никаких новых объектов не создаётся.

Трёхсторонний merge находит общего предка (base), сравнивает две ветки с ним и объединяет изменения. Если в одном и том же месте файла меняли обе ветки — возникает конфликт.

Rebase же переписывает историю. Git берёт ваши коммиты, вычисляет diff по отношению к base, «откладывает» их во временную область, перематывает ветку на новое основание и по очереди применяет патчи. Каждый применённый патч становится новым коммитом с новым хешем. Именно поэтому rebase опасен для публичных веток — вы меняете историю, с которой уже могли работать коллеги.

HEAD и detached HEAD: что это значит

Файл .git/HEAD обычно содержит что-то вроде ref: refs/heads/main. Это указатель на текущую ветку. Но если вы перейдёте на конкретный коммит по хешу (git checkout a1b2c3d), HEAD будет содержать сам хеш. Такое состояние называется detached HEAD — вы «плаваете» в истории без привязки к ветке. Коммиты, сделанные в этом состоянии, легко потерять при переключении, если не создать ветку.

Практические выводы

  • Ветки дёшевы — создавайте их для каждой задачи без страха.
  • Не бойтесь конфликтов: Git просто просит вас решить, чьи изменения оставить в пересекающихся участках.
  • Используйте rebase для локальных веток, чтобы история была линейной и читаемой.
  • Никогда не делайте rebase веток, которые уже запушены и используются командой.

Понимание внутреннего устройства Git превращает его из «магической чёрной коробки» в предсказуемый и мощный инструмент. А следующий раз, когда кто-то скажет «Git сломался», вы будете знать: Git не ломается — просто нужно понять, на какой коммит указывает HEAD.

А вы когда-нибудь теряли коммиты в detached HEAD? Расскажите в комментариях, как спасали ситуацию.

Добавить комментарий 0

Ваш электронный адрес не будет опубликован. Обязательные поля помечены *