3. Ветки и слияние
Цель темы: уверенно создавать ветки, переключаться между ними и объединять изменения через merge и rebase; понимать, когда что применять и как разрешать конфликты.
Определения терминов
Ветка (branch)
Ветка — это указатель на один из коммитов. При создании нового коммита указатель текущей ветки сдвигается на этот коммит. Физически ветка — файл в .git/refs/heads/<имя>, в котором записан SHA-1 коммита. История «ветки» — это цепочка коммитов от корня до этого коммита.
HEAD
HEAD — указатель на текущую позицию в репозитории. Обычно HEAD указывает на ветку (например, refs/heads/main), а ветка — на последний коммит. При переключении на конкретный коммит (detached HEAD) HEAD указывает напрямую на коммит, а не на ветку.
Merge (слияние)
Merge — объединение двух линий истории. Команда git merge <ветка> создаёт коммит слияния (merge commit), у которого два родителя: текущий коммит и последний коммит вливаемой ветки. История остаётся ветвящейся; ничего не переписывается.
Rebase (перебазирование)
Rebase — перенос коммитов одной ветки «поверх» другой. Git последовательно применяет коммиты текущей ветки поверх указанного коммита (например, актуального main). История становится линейной; хеши коммитов меняются (история переписывается). Нельзя применять к коммитам, уже отправленным в общую ветку, без согласованной политики.
Fast-forward merge
Fast-forward — частный случай merge, когда целевая ветка не «уходила вперёд»: в неё просто перемещается указатель на конец вливаемой ветки. Merge-коммит не создаётся. Получается линейная история без лишнего коммита.
Конфликт слияния
Конфликт возникает, когда в двух ветках изменили одни и те же строки одного и того же файла, и Git не может автоматически выбрать вариант. В файле появляются маркеры <<<<<<<, =======, >>>>>>>; нужно вручную отредактировать файл, оставить нужный код, удалить маркеры и завершить merge/rebase.
Создание и переключение веток
Создать ветку
git branch feature/login # создать ветку (не переключаться)
git switch -c feature/login # создать и переключиться (современный синтаксис)
git checkout -b feature/login # создать и переключиться (классический синтаксис)
Переключиться на существующую ветку
git switch main
git checkout main
Список веток
git branch # локальные ветки, текущая отмечена *
git branch -a # локальные + удалённые (remote-tracking)
git branch -v # с последним коммитом
Практика
Используйте осмысленные имена: feature/краткое-описание, fix/баг-123, hotfix/security-patch. Так проще ориентироваться в истории и в CI.
Удаление веток
Локально
git branch -d feature/old # удалить, если ветка уже влита (без потери коммитов)
git branch -D feature/old # принудительно удалить (включая неслитые изменения)
На удалённом репозитории
git push origin --delete feature/old
После удаления на remote локальные ссылки origin/feature/old можно почистить: git fetch --prune (или git remote prune origin).
Merge: слияние веток
Обычный merge
Переключитесь на ветку, в которую вливаете (например, main), затем выполните merge:
git switch main
git merge feature/login
Если конфликтов нет, Git создаст merge-коммит (или выполнит fast-forward). Если есть конфликты — Git сообщит, в каких файлах их разрешать.
Fast-forward и запрет fast-forward
Если история целевой ветки — прямая «линия» до точки расхождения, Git по умолчанию делает fast-forward (без отдельного merge-коммита):
git merge feature/login # может быть fast-forward
Чтобы всегда создавать merge-коммит (удобно для видимости «ветки фичи» в истории):
git merge --no-ff feature/login -m "Merge branch 'feature/login'"
Пример
На main один коммит A. Создали ветку feature, сделали коммиты B и C. На main новых коммитов не было. Тогда git switch main && git merge feature просто передвинет main на C (fast-forward). Если бы на main успели сделать коммит D, merge создал бы merge-коммит с двумя родителями (C и D).
Rebase: перебазирование
Rebase «перекладывает» ваши коммиты поверх другой ветки. История становится линейной, но коммиты получают новые хеши.
git switch feature/login
git rebase main
Сначала обновить main, потом перебазировать поверх него:
git fetch origin
git rebase origin/main
Внимание
Не делайте rebase коммитов, которые уже запушены в общую ветку (например, в main или в общую feature-ветку). Это переписывает историю и ломает работу коллег. Rebase уместен для локальных или только ваших веток до push.
Merge vs Rebase: когда что использовать
| Критерий | Merge | Rebase |
|---|---|---|
| История | Ветвящаяся, видны ветки | Линейная |
| Переписывание | Нет | Да (новые хеши коммитов) |
| Безопасность для общих веток | Да | Нет |
| Типичное использование | Вливание feature в main, сохранение контекста | Обновление своей ветки от main перед merge |
Паттерн: на общих ветках — только merge (или merge с fast-forward). Свою feature-ветку перед слиянием в main можно обновить через git rebase main, пока вы один в ней работаете и не пушите её.
Разрешение конфликтов слияния
Как выглядят конфликты
В файле появляются маркеры:
<<<<<<< HEAD
код в текущей ветке (например, main)
=======
код из вливаемой ветки (например, feature/login)
>>>>>>> feature/login
Нужно оставить нужный вариант (или объединить оба), удалить строки с <<<<<<<, =======, >>>>>>> и сохранить файл.
После разрешения конфликта
При merge:
git add resolved_file.txt
git commit # или git merge --continue
При rebase:
git add resolved_file.txt
git rebase --continue
Отмена merge или rebase
git merge --abort # отменить merge в процессе
git rebase --abort # отменить rebase в процессе
Паттерны использования
| Паттерн | Описание |
|---|---|
| Короткоживущие ветки | Ветка под одну задачу, влили в main — удалили. Меньше конфликтов и путаницы. |
| Именование | feature/, fix/, hotfix/, номера тикетов — чтобы по имени было понятно назначение. |
| Обновлять ветку от main | Перед финальным merge подтянуть main (merge или rebase в свою ветку), проверить сборку и тесты. |
| Merge с --no-ff для фич | В main вливать через --no-ff, чтобы в истории был явный merge-коммит и видна граница фичи. |
Антипаттерны
| Антипаттерн | Почему плохо | Что делать |
|---|---|---|
| Rebase общих веток | Переписывание истории ломает клоны и пайплайны у других. | Rebase только своих локальных или личных веток. |
| Долгоживущие ветки без обновления от main | Огромные конфликты при слиянии. | Регулярно подтягивать main (merge или rebase). |
| Хаотичный порядок merge | Сложно понять, что когда вливалось. | Чёткая модель ветвления (Git Flow, trunk-based и т.д.). |
| Много мелких конфликтующих правок | Постоянные конфликты в одних и тех же местах. | Дробить задачи, чаще синхронизироваться с main. |
Примеры из production
Защита main
В GitLab/GitHub на main включают: запрет push в обход merge request, обязательный код-ревью, прохождение CI. Вливание только через merge (или squash merge по политике). Rebase уже запушенных коммитов в main отключён или запрещён правилами.
Стратегии ветвления
- Git Flow: долгоживущие
developиmain, фичи отdevelop, релизы через merge вmain. - Trunk-based: короткие ветки от
main, быстрый merge обратно; минимум веток, частые мелкие коммиты.
Выбор зависит от релизного цикла и команды; главное — зафиксировать правила и не смешивать rebase общих веток с merge.
Код-ревью и merge
Фича вливается через merge request/pull request. Ревьюер смотрит diff между веткой и main. Понимание merge и конфликтов помогает описать, что именно менялось и как разрешались конфликты.
Дополнительные материалы
- Pro Git — глава 3 «Ветвление»
- Pro Git — глава 3.6 «Rebase»
- git merge — документация
- git rebase — документация
- Learn Git Branching — интерактивный тренажёр по веткам и слиянию