Ветки в Git: git branch, switch и удалённые ветки

Git Автор: Среда и версия: git 2.55.0, Linux; всё снято на одноразовых репозиториях, «сервер» — локальный bare-репозиторий
содержание

Про ветки обычно говорят метафорами — «параллельная линия разработки», «отдельная копия проекта». Обе мешают, потому что ветка в Git устроена гораздо проще: это именованная ссылка на один коммит.

В обычном репозитории с SHA-1 такая ссылка может храниться отдельным файлом. В нашем примере:

cat .git/refs/heads/main
9d677caf9b911ca6f87f0e06fcbc85cb81fd7428
stat -c%s .git/refs/heads/main
git rev-parse HEAD
41
9d677caf9b911ca6f87f0e06fcbc85cb81fd7428

Здесь сорок один байт: сорок символов SHA-1 и перевод строки. Это конкретный способ хранения, не свойство любой ветки: ссылки могут быть упакованы в packed-refs или храниться в reftable, а Git поддерживает и SHA-256. Создание ветки дёшево, потому что не копирует проект; коммит обновляет ссылку, а удаление ветки убирает указатель, не сами коммиты.

Git / 01

Ветки — указатели на вершины истории

Ветки — указатели на вершины истории01 / общая история 02 / два указателя main feature/login Ветка указывает на последний коммит. Коммиты хранятся в репозитории.01 / общая история02 / два указателяmainfeature/loginВетка указывает на последний коммит. Коммиты хранятся в репозитории.
Переключение ветки не копирует проект: Git берёт коммит, на который указывает ветка, и приводит рабочий каталог к нему. Время git switch зависит в том числе от объёма изменений рабочего каталога; папок project-v2 заводить не нужно.

Создать, посмотреть, переключиться

git switch -c feature/login
git branch --show-current
feature/login
git branch
* feature/login
  main

Звёздочка отмечает текущую ветку — ту, на которую сейчас показывает HEAD. Флаг -v добавляет к каждой ветке её коммит:

git branch -v
* main 9d677ca первый коммит

Переключение обратно — git switch main. Старый git checkout умеет то же самое, но заодно ещё десяток вещей, поэтому в Git 2.23 работу с ветками вынесли в switch, а с файлами — в restore. В новом коде пиши switch.

Три разных списка, которые все зовут «ветками»

Это место путают чаще всего: git branch показывает не все ветки, которые есть.

git branch          # только локальные
  feature/login
* main
git branch -r       # отслеживаемые копии удалённых
  origin/feature/login
  origin/main
git branch -a       # и те и другие
  feature/login
* main
  remotes/origin/feature/login
  remotes/origin/main
Git / 02

fetch обновляет снимок сервера, а не локальную ветку

fetch обновляет снимок сервера, а не локальную веткугде указатели как увидеть на твоей машине main · feature/login git branch снимок сервера origin/main git branch -r актуальный сервер последний fetch обновляет снимокгдеуказателикак увидетьна твоей машинеmain · feature/logingit branchснимок сервераorigin/maingit branch -rактуальный серверпоследний fetchобновляет снимок
origin/main — локальный снимок состояния удалённой ветки на момент последнего fetch. git fetch обновляет этот снимок, не переключая твою локальную ветку.

Удаление

git branch -d feature/login
error: the branch 'feature/login' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D feature/login'

-d — «удали, если всё уже слито». Git проверяет, слита ли ветка в её upstream, а если upstream не задан — в HEAD. Отказ не доказывает, что этих коммитов нет в других ветках. Если ветка слита, он удаляет её без разговоров:

git branch -d tmp/clean
Deleted branch tmp/clean (was 9d677ca).

Заглавная -D пропускает проверку слияния, но не разрешает удалять ветку, открытую в рабочем дереве:

git branch -D feature/login
Deleted branch feature/login (was b1be398).

Коммиты не удаляются сразу и становятся недостижимыми только тогда, когда на них больше не ведёт другая ссылка. Вместе с веткой удаляется её reflog; запись может остаться в reflog HEAD, но это не гарантировано. Восстановление по известному хешу возможно, пока объект не удалён сборщиком мусора; подробности — в разборе git reset.

На сервере она осталась

Локальное удаление сервера не касается:

git branch -r
  origin/feature/login
  origin/main

Удалять там нужно отдельно:

git push origin --delete feature/login
 - [deleted]         feature/login

Ветка исчезла у коллеги, а у меня всё ещё в списке

Обратная ситуация: ветку удалили на сервере, но git branch -r продолжает её показывать. Это тот самый кэш из среднего столбца — он не протухает сам:

git fetch --prune
 - [deleted]         (none)     -> origin/feature/old
git branch -r
  origin/HEAD -> origin/main
  origin/main

При этом локальная копия ветки остаётся жить своей жизнью:

git branch
  feature/old
* main

Это не сбой: локальная ветка твоя, и Git не удаляет твои указатели по чужому решению. Удалять — руками, через git branch -d.

Переименование

git branch -m main master
git branch
* master

Флаг -m переименовывает текущую ветку; чтобы переименовать другую, укажи два имени: git branch -m старое новое. Если ветка уже отправлена, переименование локального указателя серверу ничего не скажет — там придётся создать новую и удалить старую.

Что стоит помнить

  1. Ветка не хранит коммитыКоммиты принадлежат репозиторию, ветка — лишь указатель на последний. Поэтому один и тот же коммит может быть в трёх ветках сразу, не занимая места трижды.
  2. origin/main — не ветка, а снимокЭто копия того, каким сервер был на момент последнего fetch. Напрямую её не двигают и в неё не коммитят.
  3. -d защищает, -D нетНачинай с маленькой: её отказ — не препятствие, а сообщение о том, что ты теряешь неслитую работу. Прочитай, потом решай.
  4. Локальное и удалённое удаление независимыgit branch -d убирает у себя, git push origin --delete — на сервере, git fetch --prune подчищает устаревшие отслеживаемые. Три разные операции.
  5. Имя ветки — это путьСлеш в feature/login не создаёт вложенности, но задаёт группировку в выводе и в интерфейсах. Заводить одновременно feature и feature/login нельзя: первое станет каталогом.

Частые вопросы

Как узнать текущую ветку

git branch --show-current печатает только имя, что удобно в скриптах. В интерактивной работе хватает git branch — текущая помечена звёздочкой, и она же видна в первой строке git status.

Чем git switch отличается от git checkout

switch работает только с ветками и потому предсказуем. checkout совмещает переключение веток и восстановление файлов, из-за чего опечатка в имени может привести к неожиданному результату. Разделение появилось в Git 2.23; старая команда работает, но новую писать безопаснее.

Почему я не вижу ветку, которую создал коллега

Потому что список удалённых веток у тебя — это снимок последнего fetch. Выполни git fetch, и она появится в git branch -r. Отдельно скачивать ничего не нужно: git switch имя-ветки создаст локальную копию автоматически, если такая есть у origin.

Что делать, если удалил ветку по ошибке

Попробовать найти последний коммит в git reflog или взять его хеш из вывода удаления. Если объект ещё доступен, создать ветку заново: git switch -c имя <хеш>. Reflog самой удалённой ветки исчезает, поэтому наличие записи в reflog HEAD и срок восстановления не гарантированы.

Сколько веток можно держать

Веток можно держать много: они не создают копию проекта, хотя ссылки и их reflog занимают место. Практически — чем дольше ветка живёт в стороне, тем дороже её вливать, поэтому длинные ветки конфликтуют, а однодневные почти никогда. Подробнее об этом в разборе конфликтов слияния.

Где потренироваться

Заведи одноразовый репозиторий, сделай пару коммитов и посмотри cat .git/refs/heads/main своими глазами — после этого метафоры про «параллельные линии» больше не понадобятся. Команды, из которых складывается ежедневная работа, разобраны в основных командах Git.

Источники