Как стать автором
Обновить

Хватит копировать, пора сливаться. Часть 1. Конфликт слияний

Время на прочтение 4 мин
Количество просмотров 7.6K
Автор оригинала: Raymond Chen

Несмотря на распространённость операции git cherry-pick (копирование коммитов) в Git, обычно это не самое лучшее решение. Иногда это меньшее из двух зол, но я ещё не видел ситуации, где оно было бы целесообразно.


Это первая часть из серии статей, которые начинаются объяснением почему копирование это плохо, продолжаются рассказом почему это ужасно, а затем описывают как получить тот же результат, применяя слияние (merge). Я покажу как применить эту технику в случае, когда вам нужно сделать слияние со старыми коммитами (retroactive merge) и когда вы хотите исправить копирование на слияние пока не случилось чего-нибудь плохого.


В копировании задействованы две ветки: донорская (откуда берётся коммит) и принимающая (куда он копируется). Давайте назовём их соответственно master и feature. Для простоты предположим, что копируемый коммит содержит изменение только в одной строке единственного файла. На данной диаграмме каждый коммит помечен содержанием этой строки, а штрихованная стрелка означает само копирование (операцию git cherry-pick).


Внимание! Все стрелки перевёрнуты! A <- B означает коммит B следует за коммитом А.


Первая диаграмма


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


Есть какой-то общий предок А с коммитом строки "apple". Далее ветки расходятся, коммит F1 лежит в ветке feature, а M1 в master. Эти коммиты не затрагивают рассматриваемую строку, поэтому они всё ещё помечены "apple". Затем мы фиксируем F2 в ветку feature, меняющую содержимое нашей строки на "berry", а затем копируем F2 в ветку master с названием M2.


Пока ничего необычного не происходит.


Время идёт, дерево репозитория обогащается новыми коммитами:


Вторая


Мы зафиксировали М3 в ветку master и F3 в feature. Оба коммита обходят стороной нашу строку, поэтому она всё ещё равна "berry".


Пришло время слить feature в master. Так как строка одна и та же в обоих ветках, конфликтов не происходит, результат слияния всё ещё "berry".


После слияния 1


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


Давайте рассмотрим другой вариант. После копирования F2 мы фиксируем М3 в master и F3 в feature, но на этот раз F3 меняет нашу строку на "cherry". Такое может быть если программист, работающий над веткой feature, нашёл улучшение в коде, или менеджмент резко потребовал перевести весь проект на "cherry". Какова бы ни была причина, теперь дерево репозитория выглядит вот так:


A bomb!


На этот раз при слиянии feature в master происходит конфликт. Основание трехстороннего слияния (three-way merge) содержит "apple", входящая ветка feature содержит "cherry", а текущая — "berry".


<<<<<<<<<< HEAD (master)
berry
||||||||| merged common ancestors
apple
=========
cherry
>>>>>>>>>> feature

Конфликт возникает потому, что скопированные изменения затёрлись новыми, а сама информация о копировании не сохранилась. Вспомним, что штрихованная стрелка только у нас в голове.


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


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


Трёхветочный кошмар


Снова начинаем с коммита А, где наша строка равна "apple". Сразу создаём ветку victim на основе A и фиксируем изменения в V1, не охватывающие нашу строку. Из V1 создаём третью ветку feature с той же историей: коммит F1 не охватывает рассматриваемую строку, поэтому пока она везде равна "apple". Тем временем в ветке master появляется новый коммит М1, который тоже не трогает нашу строку.


Продолжаем веселье. В ветке feature фиксируем изменение нашей строки на "berry" как F2, и копируем его в master под именем M2. Затем снова меняем нашу строку уже на "cherry" в feature и фиксируем это как F3. В master появляется новый коммит М3, который не трогает нашу строку, поэтому в master она пока равна "berry".


Тем временем ветка victim и знать не знает про наши "шуры-муры" с копированием из feature в master. В ней фиксируются два новых изменения V2 и V3, оставляющих нашу строку девственно равной "apple".


Всё хорошее должно когда-то заканчиваться, ветка feature сливается в victim, производя на свет фиксацию V4 с нашей строкой, равной "cherry" благодаря наследию из feature.


Расплачиваться за "непотребство" с копированием придётся ветке victim, когда в неё сливают master. Бум! Впервые возникает конфликт: "благородное" изменение F2 встречает своего клонированного двойника M2. Бедняга программист, разрешающий эту коллизию, не имеет понятия о копировании, к тому же он скорее всего уже устал от других (обоснованных) конфликтов, поэтому вряд ли сможет корректно разрешить и этот.


Вкратце, проблема: при git cherry-pick в дереве появляются две копии одного коммита. Если хотя бы одна из его строк поменяется до слияния её копий, то возникнет невынужденная коллизия. Причём это может произойти и через неделю, и через год. Это значит, что тот, кто будет её разрешать, может попросту не иметь ресурсов для принятия правильного решения (не он копировал, команда полностью поменялась и проч).


Однако, вся эта Санта-Барбара может стать ещё хуже если конфликта не произойдёт!


Почему? Читайте в следующей части.

Теги:
Хабы:
+5
Комментарии 9
Комментарии Комментарии 9

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн