Коротко: конфликт merge возникает, когда в ветках изменены одни строки; решается вручную в консоли, через PhpStorm или пересборкой ветки.

Конфликт при merge в Git возникает, когда в разных ветках изменены одни и те же строки одного файла и Git не может сам выбрать нужный вариант. Решается это тремя способами: вручную через консоль, через интерфейс PhpStorm или пересборкой ветки с нуля. Разберём каждый на живом примере.

Всем привет, друзья.

Сегодня мы рассмотрим вопрос как решать конфликты git при мерже или слиянии веток. У многих новичков в git появляется страх перед этим событием и когда оно впервые наступает - случается паника, безысходность, тлен и небытие. Человек начинает судорожно пытаться что-то править, применять рандомные команды, что приводит к еще более плачевному результату и переделке всей ветке целиком. Первым что надо делать - это не паниковать и осуществлять действия максимально осознанно. Далее мы рассмотрим какие варианты действий у нас есть.

Как решать конфликты в git

Для решения конфликтов есть несколько вариантов:

-- через консоль

-- через интерфейс PhpStorm

-- пересборка ветки с нуля

Давайте разберем, откуда вообще появляются конфликты в ветках. Конфликты возникают, когда в разных ветках редактируются одни и те же файлы в одних и теж же местах. Сейчас разберем на примере консольной команды симфони, создадим конфликт веток и постараемся его решить разными способами.

Создаем консольную команду TestCommand на ветке master, коммитим и пушим ее в мастер со следующим содержимым:

Код класса TestCommand (Symfony Console) в ветке master, пустой метод execute()

Теперь создаем ветку develop с текущего момента и уже будучи на ветке develop вносим изменения в консольную команду следующим образом, делаем коммит и пушим ветку:

Ветка develop: в execute() добавлены строки print_r с awesome logic (выделены рамкой)

Теперь переключаемся на ветку master и вносим изменения в эти же строки, но другой код например так:

Ветка master: в execute() добавлены строки print_r с логикой и microtime(true)

Теперь делаем коммит на ветке мастер, пушим его и пытаемся сделать следующее:

git checkout develop
git merge master

После выполнения этих команд мы получим конфликт слияния веток и выглядеть это будет следующим образом:

Конфликт merge в TestCommand.php с маркерами <<<<<<< HEAD/=======/>>>>>>> master и вывод git в терминале

На картинке в консоли мы видим надпись "КОНФЛИКТ (содержимое): Конфликт слияния в src/Console/TestCommand.php". А в самом файле мы видим, что у нас туча подчеркнутого кода и какие-то странные надписи "<<<<<<< HEAD", "=======" и ">>>>>>> master".

В таком виде конфликт отображается в коде. Раздел от HEAD до значков равно отображает код, который был реализован на вашей ветке ( develop ( та ветка куда происходит merge ) ). Раздел от значков равно и до именования второй ветки - это тот код, который был выполнен в другой ветке, но на тех же строчках, что и ваш код, в данном случае в мастер ветке.

По итогу git не может определить какой именно код ему нужно оставить и предлагает вам сделать этот выбор.

При этом, если сейчас в консоли выполнить git status - то нам будет отображена информация о том, что мы находимся в состоянии резолва конфликта веток. Выглядеть это будет так:

Терминал git status на ветке develop: не слитые пути, файл TestCommand.php изменён обеими сторонами

Из этого состояния у нас есть два пути - либо выполнить команду

git merge --abort

И в этом случае мы выйдем из режима резолва конфликтов, но при этом и команда git merge master не будет выполнена - это действие будет откачено назад и конфликты по веткам так и останутся не решенными. Либо нужно разрезолвить конфликты и сделать коммит.

Теперь перейдем к вариантам решения конфликтов...

Решение конфликтов git через интерфейсы PhpStorm

PhpStorm имеет довольно неплохой интерфейс резолва конфликтов git. Чтобы его открыть - находясь в состоянии резолва конфликта нужно кликнуть правой кнопкой мыши в любой части кода, выбрать Git и выбрать Resolve Conflicts:

Контекстное меню PhpStorm: Git → Resolve Conflicts для разрешения конфликта слиянияУ вас откроется окно со списком всех файлов где есть конфликты:

Диалог Conflicts в IDE: слияние master в develop с кнопками Accept Yours, Accept Theirs, Merge

В этом окне если кнопки быстрых действий "Accept Yours" и "Accept Theirs" - это кнопки, позволяющие быстро принять изменения с одной из сторон при этом стереть изменения другой стороны. Кнопка Accept Yours оставляет ваши изменения в ветке develop и удаляет изменения из мастера, а Accept Theirs соответственно делает наоборот. Я рекомендую обращаться с этими кнопками максимально аккуратно, потому что часто они приводят к неработоспособности кода, потому что эти кнопки не резолвят конфликт, а просто удаляют куски кода.

Чтобы реить конфликт - нажимаем на конкретный файл в списке двумя кликами и нам откроется такое окно:

Трёхпанельный Merge Revisions в IDE: develop слева, Result по центру, master справа, 1 конфликт

В этом окне мы видим указание веток сверху в левом и правом столбцах. Посередине отображается столбец итогового файла как он будет выглядеть после всех изменений, а так же кнопки быстрых действий с крестиком и стрелочками. Крестик помечает изменения как ненужные, а стрелочки добавляют изменения в средний столбец.

Давайте например будем думать, что нам нужны и те и другие изменения и нажмем стрелочки в обоих столбцах и слева и справа. Сначала нажимаем слева и получаем такую картину:

Трёхпанельный merge: конфликтующие блоки develop и master подсвечены для ручного выбора

Обратите внимание, что теперь подсветка diff у нас изменилась и в diff нам теперь подсвечаются только тексты, а не целые конструкции print_r(); Теперь нам необходимо как-то вклеить часть, которая находится справа ( так как этот код тоже кто-то писал и он нужен ). Если мы нажмем на стрелочки справа ( которые кстати стали наклонными, что значит, что код добавится ниже ) - то код из правой колонки добавится после только что добавленного кода из develop. По итогу у нас получится в средней колонке код из двух веток собранный один за другим и надпись сверху:

Merge Revisions: все конфликты разрешены, в Result объединены строки обеих веток, кнопка ApplyНадпись All changes have been processed. Save changes and finish merging появляется только в том случае, если все конфликты во всех файлах были зарезолвлены. Если в других файлах еще остались конфликты, то этой надписи не будет. Сейчас мы можем либо нажать на надпись Savechanges and finish merging либо нажать на кнопку Apply и выйти в окно редактирования кода.

Допустим мы нажали apply - теперь выходим в окно редактирования кода, в консоли запускаем команду git status и видим, что конфликт зарезолвлен, мы можем сделать коммит с исправленным кодом например так:

git commit -m 'fix merge conflicts'

И пушим ветку. На этом конфликт можно считать зарезолвленным.

Решение конфликтов git через консоль

Теперь попробуем решить конфликты без использования PhpStorm нтерфейсов и вернемся к тому месту, когда у нас был конфликт в коде:

Конфликт merge в TestCommand.php с маркерами <<<<<<< HEAD/=======/>>>>>>> master и вывод git в терминалеЧтобы зарезолвить конфликт без использования интерфейсов - достаточно просто корректно отредактировать код следующим образом ( удаляем стрелочки HEAD, значки равно и все что не является php кодом ):

Итоговый метод execute() после слияния: строки awesome logic и логика с microtime(true) вместе

После редактирования кода выполняем команды:

git add .
git commit -m 'fix git conflicts'
git push

На этом всё - ручной резолв конфликтов завершён.

Такой способ хорош в том случае, когда конфликтов мало и они простые.

Решение конфликтов git через пересборку ветки

Данный метод обычно подходит в следующем сценарии. Например вы работаете в микросервисной архитектуре, выполнили какую-то задачу и у вас 4 мерж реквеста в разные компоненты по одной задаче. Вы задачу выполнили в августе месяце, а у QA руки до нее дошли только в конце ноября. В итоге все ваши ветки протухли настолько, что конфликты есть по ВСЕМ файлам в 75% строк кода, которые вы писали и изменений заехало столько, что резолвить их попросту не получается.

В этом случае более разумно просто отказаться от идеи резолва конфликтов и выполнить задачу заново. В этот раз вы смотрите в свой старый мерж реквест - стараетесь выполнить все те же самые действия, но уже в новой ветке сделанной от свежих основных веток (develop/master) и таким образом пересобираете новую ветку с нуля.

Это довольно крайний случай, но подобное тоже иногда встречается. Поэтому старайтесь не запускать свои ветки на несколько месяцев, так как в основные ветки других компонентов вливаются ветки других компонентов, а ваша ветка в это время протухает и теряет шансы нормально заехать без конфликтов. Особенно актуально это для тех веток, где много изменений по коду не в одну строчку кода.

Про интерактивный ребейс с одним коммитом

С какого-то момента времени я начал практиковать разработку по всем задачам по правилу 1 ветка = 1 коммит. Для выполнения данного правила я использую команду git rebase -i HEAD~2 ( если надо схлопнуть 2 коммита ). Например задача пришла в работу, я делаю первый коммит с нормальным названием вида 'TTR-1025 some commit message' где TTR-1025 это номер задачи, а все последующие коммиты, которые мне требуется сделать - я сразу сквошу через интерактивный ребейз. Делается это примерно так:

//задача пришла в работу

//переключаюсь на develop
git checkout develop

//скачиваю обновления, чтобы develop был свежий
git pull -r

//создаю новую ветку по номеру задачи
git checkout -b TTR-1025

//добавляю все изменения сделанные в процессе работы
git add .

//создаю первый коммит
git commit -m 'TTR-1025 some feature description'

//пушу ветку впервые
git push -u origin TTR-1025

// еще какая-то разработка

//снова добавляю еще какие-то доработки
git add .

//делаю второй коммит с рандомным комментарием
git commit -m 'fix'

//делаю интерактивный ребейс во время которого для
//второго коммита вместо pick проставляю squash
git rebase -i HEAD~2

//делаю пуш с форсом
git push -f

При вызове интерактивного ребейса в консоли будет открываться редактор. Сначала одно окно, где необходимо заменить все pick на squash кроме первого коммита (самого верхнего) и сохранить - далее откроется второй редактор и предложит отредактировать комментарий и также нужно будет сохранить.

Таким образом у вас будет всегда сохраняться всего 1 коммит.

Но зачем это вообще надо и какие дает преимущества.

Во-первых, 1 коммит намного проще вымержить из любой другой ветки в случае необходимости ( например в случае, когда на релизной ветке нашли баг в какой-либо задаче ).

Во-вторых, когда вы делаете ребейз от основной ветки, в случае, если у вас много коммитов - то конфликты вам придется резолвить для каждого коммита, где они возникают, а не только для последней версии кода. Например если у вас в ветке 40 коммитов и ваша ветка конфликтует с какой-то другой - то вам придется решать конфликты 40 раз ( причем может быть даже одни и те же ) вместо того, чтобы решить все конфликты всего один раз.

Итоги

Используйте интерактивный ребейс, друзья! И старайтесь держать ваши коммиты в чистоте с внятными комментариями - ваш тим-лид вам будет благодарен! ))) Надеюсь статья была вам полезна! )))

Если конфликты в Git регулярно превращаются в боль на проекте — помогу выстроить аккуратный git-flow на консультации.