Конфликт слияния читается как поломка инструмента: система не справилась и переложила работу на человека. На самом деле произошло обратное — система обнаружила, что двое изменили одно место по-разному, и отказалась выбирать за них. Выбирать здесь действительно нельзя: правильный вариант определяется намерением, а его в файлах нет.
Что именно произошло
Слияние сравнивает три версии: общего предка и две ветки. Для каждого куска файла система смотрит, кто его менял. Изменила одна сторона — берётся её вариант. Изменили обе, но в разных местах — берутся оба. Изменили обе в одном месте — сравнивать нечего, и это конфликт.
Отсюда важное следствие: конфликт означает не «код сломан», а «два намерения пересеклись». Разрешать его — значит понять оба намерения и собрать вариант, который выполняет их сразу, а не выбрать тот, что короче.
Маркеры в файле показывают три части: ваш вариант, чужой вариант и, если попросить, версию предка. Последняя полезнее всего — именно она показывает, от чего каждая сторона отталкивалась.
Показ предка включается настройкой и по умолчанию выключен — это единственная настройка, которую стоит поменять сразу после установки. Без неё вы видите два готовых варианта и вынуждены угадывать, что было общего; с ней видно, кто что добавил, а кто что убрал, и выбор перестаёт быть выбором вслепую.
Тупик: взять свою сторону целиком
Первое, что пробуют, — принять свои изменения и идти дальше. Файл собирается, тесты проходят, задача закрывается.
Это не помогает, а прячет проблему. Чужие изменения при таком разрешении исчезают бесследно: в истории останется слияние, где чей-то коммит формально учтён, а фактически отменён. Обнаружится это через недели, когда кто-то спросит, куда делась его правка, и по истории будет видно, что она есть, — а в коде её нет.
Симметричный вариант — взять чужую сторону, потому что «мне не жалко». Результат тот же, только пострадавший другой.
Признак, что вы в этом тупике: разрешение заняло секунды, и вы не читали чужой код. Конфликт по определению требует понять оба изменения; если понимание не потребовалось, вы не разрешили конфликт, а отменили одну из сторон.
Почему конфликтов много
Частота конфликтов — не свойство инструмента, а свойство того, как устроена работа.
Долго живущие ветки. Чем дольше ветка не видела основную, тем больше в ней накопилось расхождений и тем крупнее будет столкновение. Ветка, живущая неделю, конфликтует не в семь раз чаще дневной, а сильнее: конфликты в ней перекрываются и разбираются хуже.
Крупные правки формата. Переформатирование файла, смена отступов, автоматическая замена по всему проекту трогают каждую строку. Любая параллельная работа в этом файле после такого гарантированно конфликтует, причём во всех строках сразу.
Файлы, куда пишут все. Список зависимостей, файл маршрутов, общий индекс экспортов. Они конфликтуют при каждом втором слиянии, потому что каждая задача что-то в них добавляет.
Сгенерированные файлы в репозитории. Файлы блокировок зависимостей, сборки, снимки. Они меняются целиком, разбирать их построчно бессмысленно, и правильный ответ — перегенерировать, а не разрешать.
Перемещение кода между файлами. Одна сторона перенесла функцию в другой файл, другая правила её на старом месте. Формально конфликта может не быть вовсе: система видит удаление в одном файле и изменение в другом как независимые правки. Слияние пройдёт молча, а правка потеряется — это худший вид расхождения, потому что он не сопровождается предупреждением.
Как разрешать
Порядок, который работает и не требует героизма.
Сначала понять, что менял каждый. Не по маркерам, а по коммитам: посмотреть, какая задача породила каждое изменение. Часто этого достаточно, чтобы стало очевидно, как их совместить.
Затем посмотреть версию предка. Она отвечает на вопрос, что было до, и превращает выбор между двумя вариантами в понимание двух правок от общей точки.
Затем собрать результат руками, а не выбором стороны. В большинстве конфликтов правильный ответ содержит части обоих изменений, и ни один из готовых вариантов ему не равен.
И проверить результат целиком, а не только собираемость. Слияние — единственное место, где корректный код каждой стороны даёт некорректный результат: два изменения по отдельности верны, вместе противоречат друг другу. Тесты после разрешения обязательны, даже если ни одна из сторон их не трогала.
Для сгенерированных файлов порядок другой: не разрешать, а взять любую сторону и перегенерировать. Ручное слияние такого файла — потерянное время и источник ошибок, которые никто не заметит.
Если разрешение пошло не туда, отменить его дешевле, чем доводить. Прерванное слияние возвращает рабочий каталог в состояние до начала, и второй заход с пониманием обычно занимает меньше, чем попытка спасти первый.
Как их уменьшить
Короткие ветки — самое действенное. Ветка, которая живёт день, почти не конфликтует, и это дешевле любых инструментов разрешения.
Регулярное подтягивание основной ветки к себе. Тогда столкновения происходят маленькими порциями и на своей территории, а не разом при попытке влить готовую работу.
Переформатирование отдельным изменением, ничего больше не содержащим, и договорённость влить его первым. Смешанное с содержательной правкой, оно делает её нечитаемой и конфликтной одновременно.
Разделение общих файлов там, где это возможно: список, собираемый из нескольких файлов, конфликтует реже, чем один общий.
Договорённость о порядке слияния для больших изменений. Если две задачи заведомо трогают одно место, дешевле условиться, кто вливается первым, и второму подтянуть основную ветку к себе, чем разбирать столкновение потом на двоих.
Наконец, стоит различать конфликт и потерю. Конфликт виден и требует внимания; потеря происходит тихо и не требует ничего. Из двух зол первое лучше, поэтому инструменты, которые «умно» разрешают конфликты сами, стоит применять с осторожностью: они уменьшают число видимых столкновений, увеличивая число невидимых.
Позиция редакции
Мы считаем, что конфликт — это сигнал о пересечении намерений, и разрешать его надо чтением обеих правок, а не выбором стороны. Практический вывод: если разрешение заняло меньше времени, чем чтение чужого изменения, вы что-то потеряли и узнаете об этом позже.
Мы неправы, если конфликт в сгенерированном файле: там читать нечего, и правильное действие — перегенерировать, потратив на это секунды. Разбирать такой файл построчно — работа, которая не даёт ничего.