git merge: как слить ветки и разрешить конфликты
git merge объединяет историю другой ветки с текущей: если текущая ветка не ушла вперёд, Git просто передвигает указатель (fast-forward), иначе создаёт merge-коммит с двумя родителями. Перейдите в целевую ветку и выполните git merge имя-ветки; при конфликте поправьте файлы, сделайте git add и git commit или отмените всё через git merge --abort.
Что делает git merge
Команда git merge берёт изменения из указанной ветки и встраивает их в ту ветку, в которой вы сейчас находитесь. Важно запомнить направление: сливают «в себя». Чтобы влить feature/login в main, сначала переключаются на main, а потом вызывают слияние.
git switch main
git pull # подтянуть свежий main с сервера
git merge feature/login
Git ничего не копирует построчно «вручную». Он находит общего предка двух веток (merge base), сравнивает с ним обе версии и складывает изменения. Если обе стороны правили разные места, результат получается автоматически. Если одно и то же место — возникает конфликт, и решение принимаете вы.
Два режима: fast-forward и трёхстороннее слияние
Fast-forward — когда коммит слияния не нужен
Предположим, вы ответвили feature от main, сделали три коммита, а в main за это время никто ничего не добавил. Тогда история main — это просто начало истории feature. Git передвигает указатель main вперёд, на последний коммит feature, и новый коммит не создаётся.
$ git merge feature
Updating 3f2a1c4..9b7e0d2
Fast-forward
src/auth.js | 42 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
История остаётся линейной, но факт «здесь была ветка feature» в ней теряется. Иногда это хорошо, иногда нет — об этом ниже, в разделе про --no-ff.
Трёхстороннее слияние и merge-коммит
Если обе ветки ушли вперёд от общего предка, передвинуть указатель уже нельзя. Git выполняет трёхстороннее слияние: база, «наша» сторона (текущая ветка) и «их» сторона (сливаемая ветка). Результат фиксируется отдельным merge-коммитом, у которого два родителя.
$ git merge feature
Merge made by the 'ort' strategy.
src/auth.js | 42 ++++++++++++++++++++
src/router.js | 3 +-
2 files changed, 44 insertions(+), 1 deletion(-)
Git откроет редактор с сообщением вида Merge branch 'feature'. Его можно оставить или дописать, зачем делалось слияние. Посмотреть получившуюся «развилку» удобно так:
git log --oneline --graph --all
Стратегия ort используется по умолчанию начиная с Git 2.34; раньше это была recursive. Для повседневной работы менять стратегию не нужно.
Полезные флаги git merge
| Флаг | Что делает | Когда пригодится |
|---|---|---|
--no-ff | Всегда создаёт merge-коммит, даже если возможен fast-forward | Хотите видеть в истории, какие коммиты пришли из одной фичи |
--ff-only | Сливает только перемоткой, иначе завершается ошибкой | Обновление локальной ветки без случайных merge-коммитов |
--squash | Складывает все изменения ветки в индекс, но не коммитит и не создаёт связь с веткой | Нужно превратить фичу в один аккуратный коммит |
--no-commit | Выполняет слияние, но останавливается перед коммитом | Хотите проверить результат или прогнать тесты до фиксации |
-m "текст" | Задаёт сообщение merge-коммита | Слияние в скриптах и без редактора |
--abort | Отменяет начатое слияние и возвращает состояние до него | Конфликтов слишком много, нужно начать заново |
--continue | Завершает слияние после разрешения конфликтов | Альтернатива git commit на последнем шаге |
-X ours / -X theirs | При конфликтующих фрагментах автоматически берёт нашу или их версию | Заведомо знаете, чья версия правильная (осторожно!) |
--no-ff: сохранить границы фичи
Многие команды сливают фичи только с --no-ff. Тогда в графе каждая задача выглядит как «пузырь», и её можно откатить одним git revert -m 1 <merge-коммит>. При fast-forward такой точки нет, и отменять пришлось бы каждый коммит отдельно.
--squash: один коммит вместо десяти
git switch main
git merge --squash feature/login
git commit -m "Вход по e-mail и паролю"
После --squash Git не считает ветку слитой: git branch -d feature/login откажется удалять её с предупреждением «not fully merged». Удаляйте через git branch -D, когда убедились, что всё на месте. И не продолжайте работу в той же ветке после squash — следующее слияние принесёт уже влитые изменения повторно и почти наверняка даст конфликты.
Конфликт слияния: как его разрешить
Конфликт — нормальная рабочая ситуация, а не авария. Он значит одно: две ветки поменяли одни и те же строки (или одна удалила файл, который другая правила), и Git не берётся угадывать, какая версия верна.
$ git merge feature/login
Auto-merging src/config.js
CONFLICT (content): Merge conflict in src/config.js
Automatic merge failed; fix conflicts and then commit the result.
Шаг 1. Посмотреть, что конфликтует
$ git status
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/config.js
Шаг 2. Отредактировать файл
В конфликтном файле Git расставляет маркеры:
<<<<<<< HEAD
const TIMEOUT = 5000;
=======
const TIMEOUT = 10000;
>>>>>>> feature/login
Верхняя часть — ваша текущая ветка (HEAD), нижняя — сливаемая. Оставьте правильный вариант (или объедините оба), удалите все три строки-маркера и сохраните файл. Частая ошибка — забыть маркер где-то в середине файла; перед коммитом поищите их: git diff --check подсветит оставшиеся маркеры.
Шаг 3. Отметить решение и завершить слияние
git add src/config.js
git commit # или git merge --continue
Если конфликт целиком решается выбором одной стороны, не нужно править файл руками:
git checkout --ours src/config.js # оставить версию текущей ветки
git checkout --theirs src/config.js # взять версию сливаемой ветки
git add src/config.js
Удобнее в редакторе
VS Code, IDE от JetBrains и другие редакторы показывают конфликт в три колонки и дают кнопки «принять текущее / входящее / оба». Можно подключить внешний инструмент через git mergetool. Полезная настройка — git config --global merge.conflictStyle zdiff3: в маркерах появится ещё и исходная версия из общего предка, и становится понятно, кто что поменял.
Передумали — отменить слияние
git merge --abort
Команда вернёт рабочую папку и индекс в состояние до git merge. Поэтому перед слиянием лучше иметь чистое рабочее дерево: закоммитьте или отложите незавершённые правки через git stash, иначе их будет сложно отделить от результатов слияния.
Как отменить уже сделанный merge
Если merge-коммит ещё не отправлен на сервер, проще всего откатить ветку назад:
git reset --hard ORIG_HEAD
ORIG_HEAD Git записывает перед опасными операциями, в том числе перед слиянием, — это положение ветки до merge. Команда --hard выбросит незакоммиченные изменения, так что проверьте git status.
Если слияние уже в общем репозитории, историю не переписывают. Создают обратный коммит:
git revert -m 1 a1b2c3d
Флаг -m 1 говорит, что «основной» считается первый родитель (ветка, в которую сливали). Учтите подвох: после такого revert Git считает коммиты фичи уже влитыми. Если позже вы захотите влить исправленную фичу снова, сначала придётся сделать revert самого revert-коммита.
Merge в командной работе
git pull — это fetch плюс merge
Обычный git pull скачивает изменения и сливает удалённую ветку в локальную. Если вы и коллега коммитили параллельно, появится коммит «Merge branch 'main' of ...». Чтобы таких технических слияний не было, настройте git config --global pull.ff only (pull падает, если перемотка невозможна, и вы решаете сами) или используйте git pull --rebase.
Pull request и кнопка Merge
На GitHub и GitLab слияние обычно происходит через pull/merge request. Кнопка «Create a merge commit» эквивалентна git merge --no-ff, «Squash and merge» — слиянию со сжатием, «Rebase and merge» — перебазированию. Выбор режима — договорённость команды, а сам механизм тот же, что и в консоли.
Влить main в свою ветку
Долгоживущая ветка расходится с main, и конфликты копятся. Регулярно подтягивайте основную ветку к себе:
git switch feature/login
git fetch origin
git merge origin/main
Так конфликты решаются маленькими порциями по мере появления, а финальное слияние в main проходит гладко.
Типичные ошибки
- Слили не в ту сторону. Выполнили
git merge main, находясь вmain, или влили фичу в другую фичу. Проверяйте текущую ветку командойgit branch --show-currentперед слиянием. - Слияние с устаревшей веткой. Слили локальный
main, который неделю не обновлялся. Сначалаgit pull, потом merge. - «Your local changes would be overwritten by merge». В рабочей папке есть незакоммиченные правки в тех же файлах. Закоммитьте их или уберите в stash.
- «refusing to merge unrelated histories». У веток нет общего предка — например, локальный репозиторий создан через
git init, а удалённый уже содержит README. Если объединение действительно нужно, добавьте--allow-unrelated-histories. - Закоммитили маркеры конфликта. Сборка падает на
<<<<<<<. Проверяйтеgit diff --checkи запускайте тесты до коммита.
Итог
git merge — безопасный способ объединить ветки: он не переписывает существующие коммиты, а лишь добавляет новый (или перематывает указатель). Сливайте в текущую ветку, держите рабочее дерево чистым, используйте --no-ff, если важны границы фич, и --squash, если нужен один коммит. Конфликты решаются в три шага: правка файла, git add, git commit, а git merge --abort всегда вернёт вас к исходной точке. Слияние веток и разрешение конфликтов подробно отрабатываются на практике в курсе по Git.
Частые вопросы
Чем git merge отличается от git pull?
git pull сначала скачивает изменения с сервера (git fetch), а затем сливает удалённую ветку в текущую через git merge или, при настройке, через rebase. git merge работает только с тем, что уже есть локально.
Можно ли слить сразу несколько веток?
Да, git merge feature-a feature-b выполнит слияние-осьминог (octopus) с несколькими родителями, но только если нет конфликтов. На практике ветки обычно сливают по одной.
Как посмотреть, какие ветки уже слиты в текущую?
Команда git branch --merged покажет ветки, полностью вошедшие в текущую, а git branch --no-merged — ещё не слитые. Слитые ветки можно спокойно удалять через git branch -d.
Как заранее увидеть, что принесёт слияние?
Выполните git log main..feature, чтобы увидеть коммиты, которых нет в main, и git diff main...feature, чтобы увидеть изменения ветки относительно общего предка.
Что такое ORIG_HEAD после merge?
Это ссылка, которую Git сохраняет перед слиянием: она указывает на положение ветки до merge. Через git reset --hard ORIG_HEAD можно откатить локальное слияние, пока оно не отправлено на сервер.
Эта тема подробно разобрана с практикой в курсе «Git с нуля до уверенного разработчика».
Перейти к курсу