Перейти к содержанию

3. Ветки и слияние

Цель темы: уверенно создавать ветки, переключаться между ними и объединять изменения через merge и rebase; понимать, когда что применять и как разрешать конфликты.


Определения терминов

Ветка (branch)

Ветка — это указатель на один из коммитов. При создании нового коммита указатель текущей ветки сдвигается на этот коммит. Физически ветка — файл в .git/refs/heads/<имя>, в котором записан SHA-1 коммита. История «ветки» — это цепочка коммитов от корня до этого коммита.

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 и конфликтов помогает описать, что именно менялось и как разрешались конфликты.


Дополнительные материалы