Программирование и IT
Как разрешить конфликт слияния
Конфликт — не поломка, а вопрос. Git сливает изменения автоматически, пока они в разных местах файла; когда две ветки правят одни и те же строки, выбрать правильный вариант может только человек. Задача — прочитать оба варианта, собрать из них рабочий код и сказать git, что вопрос закрыт.
В этой статье
Откуда берётся конфликт#
Слияние сравнивает три состояния: общего предка двух веток и обе их вершины.
Если строку изменила только одна сторона, git берёт её изменение молча. Если
обе стороны изменили одну строку по-разному — оснований предпочесть одно
другому у программы нет, и она останавливается.
Конфликт бывает и без совпадения строк: одна ветка правит файл, другая его
удаляет или переименовывает. Такой случай git описывает отдельно, словами
deleted by them или added by us в выводе состояния.
Важно: конфликт не откатывает работу. Слияние просто остаётся незавершённым,
пока ты его не доделаешь или не отменишь.
Как выглядит остановка#
;
Первая строка говорит, что часть правок слилась сама. Вторая называет файл с
вопросом. Полная картина — в состоянии:
()
()
()
Список только конфликтных файлов, без лишнего, выводится так:
Что написано в файле#
Открыв файл, ты увидишь обе версии, разделённые метками:
<<<<<<<
= 0.20
return
=======
=
return *
>>>>>>> -
Между <<<<<<< и ======= — то, что было у тебя (HEAD, текущая ветка).
Между ======= и >>>>>>> — то, что приходит из сливаемой ветки; её имя
написано в последней метке.
Разрешить конфликт — значит оставить в файле правильный код и удалить все три
метки. Правильным может оказаться первый вариант, второй, их смесь или вообще
третий, написанный заново:
=
return
Полезная настройка — показывать ещё и состояние предка:
Тогда в метках появляется третий кусок, ||||||| — исходный текст до обеих
правок. С ним гораздо понятнее, кто что менял: видно, что одна сторона
поменяла ставку, а другая — округление, и значит нужны оба изменения.
Порядок разрешения#
Команда git add здесь работает не как «добавить файл», а как «этот вопрос
закрыт». Пока хоть один файл не добавлен, git не даст завершить слияние.
Сообщение коммита он предложит сам — обычно его оставляют как есть.
Когда вариант нужен целиком, метки править руками не обязательно:
Здесь спрятана ловушка. При слиянии --ours — это твоя ветка, всё логично.
Но при переносе коммитов стороны меняются местами: --ours становится
основой, на которую переносят, а --theirs — твоими коммитами. Причина в том,
что при переносе git прикладывает твои изменения к чужой вершине. Подробнее
про сам механизм — в статье про git rebase.
Как выйти, если запутались#
Пока слияние не завершено, всё откатывается одной командой:
Ветка и рабочая копия возвращаются в состояние до слияния. Для переноса
коммитов то же делает git rebase --abort, для обратного коммита —
git revert --abort.
Если слияние уже закоммичено и результат плохой, остаётся откат вершины ветки
назад на один коммит — при условии, что коммит слияния ещё не отправлен:
режимы и опасность --hard разобраны
в статье о git reset. Отправленный коммит слияния
отменяют командой git revert -m 1 <хеш>, где единица указывает, какую из
двух линий считать основной.
Частые ошибки#
Метки остались в коде. Классика: файл добавлен и закоммичен вместе со
строками <<<<<<<. Ловится просмотром перед коммитом (git diff --cached)
или поиском по проекту: grep -rn '<<<<<<<' ..
Выбрали свою версию, не читая чужую. Быстро и почти всегда неверно: чужие
правки исчезают без следа, а тесты падают через неделю. Если непонятно, зачем
коллега менял строку, историю этого места покажет git log --merge -p billing/rates.py — в выводе останутся только коммиты, трогавшие конфликтное
место.
Конфликты одни и те же при каждом переносе. Включи запоминание
разрешений — git будет подставлять их сам:
Слияние затеяно на грязной рабочей копии. Git откажется начинать, если
незакоммиченные правки задевают сливаемые файлы. Сначала коммит или полка
(git stash push), потом слияние: как пользоваться полкой, описано
в статье о git stash.
Огромный конфликт после долгой жизни ветки. Лечится не приёмами, а
привычкой: забирать основную ветку к себе почаще. Ветка, отставшая на два
дня, сливается почти всегда без вопросов; отставшая на два месяца — почти
всегда с ними.
План по этапам
- Увидеть список конфликтовgit status или git diff --name-only --diff-filter=U — только нерешённые файлы.
- Прочитать оба вариантаМетки <<<<<<<, =======, >>>>>>>; при zdiff3 виден ещё и общий предок.
- Собрать правильный кодОставить нужное, удалить все метки; при необходимости — checkout --ours или --theirs.
- Отметить как решённоеgit add по каждому файлу; проверить, что не осталось меток (grep по проекту).
- Завершить и проверитьgit commit, затем сборка и тесты: слияние без конфликта не значит рабочий код.
Начать изучать эту тему у себя
План ляжет в твой репозиторий: отмечай этапы, веди конспект — история изменений покажет, как ты продвинулся.
Проверь себя
1.В конфликтном файле часть между <<<<<<< HEAD и ======= — это чей код?
2.Конфликты разрешены в редакторе. Какая команда говорит git, что файл готов?
3.Слияние начато, конфликтов много, нужно вернуть всё как было. Что выполнить?
4.Какая настройка заставляет git показывать в метках ещё и версию общего предка?
Источники
-
Pro Git, русское изданиеГлава «Ветвление в Git» — базовое слияние и конфликты, глава «Инструменты Git» — сложные случаибесплатно
-
Справка git mergeРаздел HOW CONFLICTS ARE PRESENTED — что означают меткибесплатно
-
Справка git rerereЗапоминание разрешённых конфликтов для повторных слиянийбесплатно
Было полезно?