Конфликт — это признание Git в том, что он не может решить за тебя. Он умеет складывать непересекающиеся правки и честно останавливается там, где две ветки утверждают разное про одно и то же место.
Отсюда первое, что стоит понять и что снимает половину страха: правки одного файла ещё не означают конфликт: Git часто объединяет их автоматически. Типичный текстовый конфликт — несовместимые изменения пересекающихся участков. Бывают и другие: изменение удалённого другой веткой файла, несовместимые переименования или правки бинарных файлов.
Конфликт возникает в пересекающихся изменениях
Проверяется за минуту. Файл из четырёх строк, две ветки правят разные:
git merge feature/price
Auto-merging page.txt
Merge made by the 'ort' strategy.
page.txt | 2 +-
заголовок
цена: 200
валюта: руб
подвал 2026
Обе правки на месте, вопросов Git не задал. Теперь то же самое, но обе ветки меняют строку с ценой:
Auto-merging page.txt
CONFLICT (content): Merge conflict in page.txt
Automatic merge failed; fix conflicts and then commit the result.
Что теперь в файле
заголовок
<<<<<<< HEAD
цена: 150
=======
цена: 200
>>>>>>> feature/price
подвал
Маркеры разделяют две версии одного участка
Состояние видно и в 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: page.txt
В коротком виде это UU page.txt — две U от unmerged, в той же нотации двух столбцов, что и MM из разбора основных команд.
Два выхода
Отменить и подумать
git merge --abort
В этом примере до слияния рабочий каталог был чистым, и отмена возвращает его к исходному состоянию. Но git merge --abort не гарантирует восстановление правок, которые были не закоммичены до слияния, особенно если их меняли и после начала merge. Перед слиянием сохрани работу коммитом или через stash; нужные правки разрешения конфликта перед отменой скопируй отдельно.
Разрешить
Разрешение — это просто правка файла. Открыть, решить, какой вариант верен (или собрать третий), удалить все три строки маркеров и сказать Git, что готово:
git add page.txt
git status -s
M page.txt
Буква U сменилась на M — файл больше не в конфликте. Остаётся закоммитить:
git commit --no-edit
git log --oneline --graph
* e732486 Merge branch 'feature/price'
|\
| * 6123cb3 цена 200
* | 52bcefa цена 150
|/
* 1bcb57f исходная страница
Коммит слияния — единственный вид коммита с двумя родителями, и на графе это видно: две линии сходятся в одну.
Ловушка rebase: «мой» и «чужой» меняются местами
При merge всё интуитивно: стоишь на main, вливаешь feature/price, и под HEAD оказывается твой код.
<<<<<<< HEAD
цена: 150 ← main, ты здесь
=======
цена: 200 ← feature/price, входящая
>>>>>>> feature/price
При rebase те же самые две ветки дают перевёрнутую картину. Стоя на feature/price и выполняя git rebase main:
<<<<<<< HEAD
цена: 150 ← main! а ты стоишь на feature/price
=======
цена: 200 ← твой собственный коммит
>>>>>>> 6a4ce0c (цена 200)
Причина в том, что rebase не вливает чужое в твоё, а переносит твои коммиты по одному на чужую вершину. В момент конфликта HEAD указывает на уже построенную часть новой истории: исходную базу плюс успешно перенесённые коммиты, а «входящим» оказывается твой же коммит — он и подписан не именем ветки, а хешем и сообщением.
Практический вывод: --ours и --theirs при rebase означают противоположное тому, что подсказывает интуиция. Не полагайся на них по памяти, читай нижнюю строку блока: если там хеш с сообщением коммита, ты в rebase.
git status во время rebase тоже выглядит иначе и подсказывает свою команду продолжения:
interactive rebase in progress; onto dd18184
You are currently rebasing branch 'feature/price' on 'dd18184'.
(fix conflicts and then run "git rebase --continue")
Отменяется он своей командой — git rebase --abort, а не merge --abort.
Как конфликтовать реже
- Забирай чужие изменения частоВетка, отставшая на два дня, конфликтует по паре строк; отставшая на две недели — по всему файлу.
git pullв свою ветку дешевле, чем героическое слияние в конце. - Держи ветки короткимиЗадача на день почти никогда не конфликтует. Ветка, живущая месяц, конфликтует всегда — это свойство календаря, а не Git.
- Не переформатируй файл заодноАвтоформатирование всего файла вместе с правкой двух строк превращает любую параллельную работу в конфликт по всему файлу. Форматирование — отдельным коммитом.
- Разрешай конфликт, а не выбирай сторонуКнопка «принять моё» в редакторе тихо выбрасывает чужую работу. Перед выбором посмотри
git log --merge— он покажет коммиты обеих сторон, которые трогали этот файл. - Включи diff3, если варианты непонятны
git config merge.conflictStyle diff3добавляет в блок третью секцию — исходную версию до обеих правок. Часто именно она объясняет, что каждая сторона пыталась сделать.
Частые вопросы
Что значит both modified
Что файл изменён в обеих ветках и Git не смог соединить правки. В коротком git status -s это UU. Пока такие файлы есть, коммит слияния сделать нельзя.
Как отменить слияние и вернуть всё как было
Сначала прочитай git status: при merge нужна git merge --abort, при rebase — git rebase --abort. git pull может использовать оба режима в зависимости от настроек и флагов. Если до операции были несохранённые правки, полного восстановления через abort гарантировать нельзя.
Что выбрать: merge или rebase
merge сохраняет историю как было и добавляет коммит слияния; rebase переписывает свои коммиты поверх чужих и оставляет линейную историю. Практическое правило: rebase — только для своих неотправленных веток, merge — для всего, что уже видели другие.
Почему при rebase конфликт повторяется
Потому что rebase переносит коммиты по одному, и каждый может конфликтовать со своим предшественником. После разрешения — git rebase --continue, и следующий коммит начнёт переноситься. Если это надоело, помогает git rerere, запоминающий разрешения.
Можно ли просто удалить маркеры и закоммитить
Маркеры удалить нужно обязательно, но одного этого мало: оставив обе версии подряд, ты закоммитишь код, который вряд ли работает. Git проверять содержимое не будет — он лишь следит, чтобы файл был помечен через git add.
Где потренироваться
Конфликт воспроизводится за минуту в одноразовом репозитории: git init, коммит с файлом, две ветки, правящие одну строку, и git merge. Сломать нечего, а руки запомнят и маркеры, и --abort. Команды, из которых всё это состоит, разобраны в основных командах Git, а откат неудачных попыток — в разборе git reset.