Ветки в Git: git branch, создание, переключение и удаление
Ветка в Git — это лёгкий указатель на коммит, а HEAD показывает, на какой ветке вы сейчас находитесь. git branch выводит, создаёт, переименовывает и удаляет ветки, а git switch переключает между ними; git switch -c имя создаёт ветку и сразу переходит на неё.
Что такое ветка в Git
Ветка в Git — это не копия проекта, а маленький файл с хешем одного коммита. Создание ветки занимает доли секунды и почти не тратит места, потому что Git ничего не дублирует: все ветки ссылаются на одни и те же объекты в базе репозитория. Когда вы делаете коммит, указатель текущей ветки просто передвигается на новый коммит.
Это меняет отношение к веткам. Их не нужно бояться и экономить: отдельная ветка под каждую задачу, исправление или эксперимент — нормальная практика. Если эксперимент не удался, ветку удаляют, и основная история остаётся чистой.
Увидеть, что ветка — это файл, можно своими глазами:
$ cat .git/refs/heads/main
8b7d4e2a9f1c0d3e5b6a7c8d9e0f1a2b3c4d5e6f
(В крупных репозиториях часть ссылок упакована в .git/packed-refs, поэтому файла может не быть — суть от этого не меняется.)
HEAD в Git: где вы сейчас
HEAD — это указатель на то, что у вас сейчас «извлечено» в рабочую папку. Обычно HEAD указывает не на коммит, а на ветку:
$ cat .git/HEAD
ref: refs/heads/main
Цепочка выглядит так: HEAD → main → коммит. Новый коммит получит родителем коммит, на который смотрит HEAD, после чего Git сдвинет ветку, на которую указывает HEAD. Именно поэтому коммиты «попадают» в текущую ветку.
Если переключиться прямо на коммит или тег (git switch --detach v1.0 или git checkout 3f2a1c9), HEAD будет указывать на коммит напрямую. Это состояние называется detached HEAD. Смотреть код в нём безопасно, но новые коммиты ни к какой ветке не привязаны, и после переключения их легко потерять. Если вы начали что-то менять, создайте ветку прямо там: git switch -c fix-from-old-version.
В выражениях HEAD удобно использовать как точку отсчёта: HEAD~1 — предыдущий коммит, HEAD~3 — три коммита назад, HEAD^2 — второй родитель коммита слияния.
Как посмотреть ветки: git branch
git branch # локальные ветки, текущая отмечена *
git branch -v # плюс последний коммит в каждой
git branch -vv # плюс upstream и отставание/опережение
git branch -r # удалённые ветки (origin/...)
git branch -a # все ветки
git branch --show-current # только имя текущей ветки
Пример вывода git branch -vv:
feature/login 4c1e2d7 [origin/feature/login: ahead 2] Форма входа
* main 8b7d4e2 [origin/main] Обновить README
old-spike 1a9b3c0 Эксперимент с кэшем
Здесь видно, что в feature/login два неотправленных коммита, main совпадает с сервером, а у old-spike вообще нет пары на сервере.
Отфильтровать ветки помогают git branch --merged (уже влиты в текущую — кандидаты на удаление), git branch --no-merged (ещё не влиты) и git branch --list 'feature/*' (по шаблону).
Как создать ветку
Есть два пути. git branch создаёт ветку, но оставляет вас на текущей:
git branch feature/login # от текущего коммита
git branch hotfix v1.2.0 # от тега
git branch review 3f2a1c9 # от конкретного коммита
Чаще нужно создать ветку и сразу на неё перейти. Для этого есть git switch -c (появился в Git 2.23) или старый эквивалент git checkout -b:
git switch -c feature/login
git switch -c hotfix origin/main # новая ветка от свежего состояния сервера
Хорошая привычка — перед созданием ветки под задачу обновить основу: git fetch, затем git switch -c feature/x origin/main. Так ветка начнётся с актуального кода, а не с вашей давно не обновлявшейся локальной main.
Как называть ветки
Устойчивые соглашения в командах: префикс по типу работы и короткое описание через дефис — feature/user-avatar, fix/login-timeout, chore/update-deps, иногда с номером задачи: feature/SHOP-142-cart. Нельзя использовать пробелы, ~ ^ : ? * [, две точки подряд и окончание на .lock.
Как переключиться на ветку: git switch
git switch main # перейти на существующую ветку
git switch - # вернуться на предыдущую ветку
git switch feature/login # если локальной нет, но есть origin/feature/login,
# Git создаст локальную и свяжет её с удалённой
При переключении Git меняет файлы в рабочей папке на версию из целевой ветки. Незакоммиченные изменения переносятся с вами, если они не конфликтуют с целевой веткой. Если конфликтуют, Git откажет:
error: Your local changes to the following files would be overwritten by checkout:
app.js
Please commit your changes or stash them before you switch branches.
Решения: закоммитить, спрятать через git stash (вернуть потом git stash pop) или осознанно перенести изменения с попыткой слияния: git switch -m main. Флаг --discard-changes выбросит правки — используйте его, только если они точно не нужны.
Почему git switch, а не checkout? Checkout умеет и переключать ветки, и перезаписывать файлы, и из-за этого легко ошибиться. Switch делает только одно — работает с ветками, поэтому безопаснее. Подробное сравнение checkout, switch и restore — в отдельной статье.
Как переименовать ветку
git branch -m new-name # переименовать текущую
git branch -m old-name new-name # переименовать любую
git branch -M main # то же с перезаписью существующей ветки
Если ветка уже была на сервере, переименование локальное. Отправьте новую и удалите старую:
git push -u origin new-name
git push origin --delete old-name
Как удалить ветку
git branch -d feature/login # удалит, только если ветка влита
git branch -D old-spike # удалит принудительно
git push origin --delete feature/login # удалить на сервере
-d защищает от потери работы: если в ветке есть коммиты, которых нет в текущей ветке (или в её upstream), Git откажет с сообщением «The branch 'x' is not fully merged». -D удаляет без вопросов. Текущую ветку удалить нельзя — сначала переключитесь на другую.
Удалили лишнее? Коммиты не исчезают сразу. Найдите хеш последнего коммита ветки в git reflog и восстановите её: git branch old-spike 1a9b3c0.
Устаревшие закладки удалённых веток, которых уже нет на сервере, чистит git fetch --prune.
Связь локальной ветки с удалённой
У локальной ветки может быть upstream — удалённая ветка, с которой она сравнивается и синхронизируется. Он нужен, чтобы git push и git pull работали без аргументов, а git status показывал «ahead/behind».
git push -u origin feature/login # при первой отправке
git branch --set-upstream-to=origin/main main # для существующей ветки
git branch --unset-upstream # убрать связь
Слияние веток: коротко
Ветки создают, чтобы потом объединить. Когда работа готова, переключитесь на целевую ветку и влейте в неё свою:
git switch main
git merge feature/login
Если main не ушла вперёд с момента создания ветки, Git сделает fast-forward: просто передвинет указатель main на последний коммит ветки, без коммита слияния. Если обе ветки получили новые коммиты, произойдёт трёхстороннее слияние: Git найдёт общего предка, сравнит обе стороны и создаст коммит слияния с двумя родителями. В командах слияние обычно делают через pull request на сервере, а локально лишь подтягивают свежий main.
Шпаргалка по веткам
| Задача | Команда |
|---|---|
| Список веток | git branch -vv |
| Создать ветку | git branch имя |
| Создать и перейти | git switch -c имя |
| Переключиться | git switch имя |
| Вернуться назад | git switch - |
| Переименовать | git branch -m старое новое |
| Удалить влитую | git branch -d имя |
| Удалить принудительно | git branch -D имя |
| Удалить на сервере | git push origin --delete имя |
| Ветки, которые можно удалить | git branch --merged |
| Восстановить удалённую | git reflog, затем git branch имя хеш |
Типичные ошибки новичков
- Работа прямо в main. Если коммиты уже сделаны, но не отправлены, создайте ветку на текущем месте (
git branch feature/x), затем вернитеmainк серверу:git reset --hard origin/main(проверьте, что всё закоммичено). - Ветка от устаревшей main. Сначала
git fetch, ветку создавайте отorigin/main. - Коммиты в detached HEAD. Перед переключением создайте ветку; если уже переключились — найдите коммиты в
git reflog. - Долгоживущие ветки. Чем дольше ветка живёт отдельно, тем больнее слияние. Регулярно подтягивайте
mainи вливайте работу небольшими порциями.
Ветки в командной работе
Процессы в командах отличаются, но почти везде встречается одна из двух схем. В GitHub Flow есть одна долгоживущая ветка main, которая всегда готова к выкладке, и короткие feature-ветки, которые вливаются через pull request после ревью и проверок CI. В Git Flow добавляются develop, ветки релизов и hotfix-ветки; схема тяжелее и подходит продуктам с редкими версионированными релизами.
Независимо от схемы работают общие правила: одна ветка — одна задача, ветка живёт дни, а не месяцы, перед pull request ветка обновлена свежим main, после слияния ветку удаляют и локально, и на сервере. Основную ветку защищают настройками репозитория: прямой push и force push в неё запрещены, слияние только через ревью.
Как посмотреть историю веток
Понять, как ветки соотносятся друг с другом, проще всего по графу: git log --oneline --graph --all рисует все ветки и точки их расхождения прямо в терминале. Чтобы увидеть коммиты, которые есть в ветке, но ещё не попали в main, используйте git log main..feature/login, а разницу в файлах — git diff main...feature/login (три точки сравнивают с общим предком, то есть показывают только изменения самой ветки).
Итог
Ветка в Git — дешёвый указатель на коммит, HEAD — указатель на текущую ветку. git branch управляет ветками (список, создание, переименование, удаление), git switch переключает между ними, а git switch -c делает оба действия сразу. Ветки, HEAD, fast-forward и трёхстороннее слияние разобраны на живых примерах в курсе по Git.
Частые вопросы
Сколько веток можно создать в Git?
Практически сколько угодно: каждая ветка — это ссылка размером в несколько десятков байт, поэтому ограничений по объёму нет.
Удаляются ли коммиты вместе с веткой?
Не сразу. Коммиты, на которые больше ничто не ссылается, хранятся ещё некоторое время и доступны через git reflog, пока сборщик мусора их не удалит (по умолчанию не раньше чем через 30 дней для недостижимых записей reflog).
Как узнать, от какой ветки создана текущая?
Git этого не хранит: ветка помнит только свой коммит. Общего предка с main можно найти командой git merge-base HEAD main.
Чем ветка отличается от тега?
Ветка двигается вперёд с каждым новым коммитом, а тег навсегда закреплён за одним коммитом и используется для отметки релизов.
Почему main, а не master?
Это лишь имя ветки по умолчанию. Многие сервисы перешли на main; задать имя для новых репозиториев можно настройкой git config --global init.defaultBranch main.
Эта тема подробно разобрана с практикой в курсе «Git с нуля до уверенного разработчика».
Перейти к курсу