Как исправить несколько ошибочных слияний?

У нас довольно сложная ситуация в нашем хранилище Git. У нас есть две команды, работающие над одним и тем же программным обеспечением. Одна команда выполняет техническое обслуживание версии 3.0, в то время как другая команда делает долгосрочный проект, что приведет к выпуску версии 3.1 в ближайшее время.

Команда 3.1 начала работать в своем собственном филиале, и они решили объединить все изменения с 3.0-веткой, каждый раз, когда была выпущена новая версия, выпущенная из ветки 3.0, каждый раз, как правило, каждую неделю. Поэтому, когда команда 3.0 представила свою первую версию, был сделан фиксатор R1, который был объединен в 3.1, в результате чего M1.

3.1---o---o---M1 / / 3.0---o---o---R1 

Две недели спустя, 3.0-команда выпустила свою вторую версию на R2, и команда 3.1 объединилась, что привело к M2.

  3.1---o---o---M1---o---o---M2 / / / 3.0---o---o---R1---o---o---R2 

Это продолжалось некоторое время, но в итоге команда 3.0 сделала рефакторинг и выпустила это в commit R3. В этом рефакторинге 3.0-команда разделила большой файл Constants.java на два небольших файла: Constants.java и MoreConstants.java . (Это не фактические имена файлов.) Файл Constants.java был ранее изменен обеими командами и успешно сложен. Однако этот рефакторинг вызвал конфликт для команды 3.1, когда они попытались объединить R3 в свою ветку. Команда 3.1 решила, что они не хотят решать этот конфликт, и они думали, что могут «отложить» решение, игнорируя рефакторинг, придерживаясь своей собственной версии. Это означало не только то, что они не добавили MoreConstants.java в свой репозиторий, но также и не объединили изменения в файлах, которые теперь импортировали MoreConstants.java вместо Constants.java . Что они сделали:

 $ git merge 3.0 $ git status 

И затем, для каждого из перечисленных конфликтов, они решили, хотят ли они решить это или нет. Если они не хотят разрешать конфликт, они

 $ git checkout --ours <file they wanted to ignore> $ git add <file they wanted to ignore> 

Это привело к фиксации C3 в 3.1-ветке:

  3.1---o---o---M1---o---o---M2---o---o---C3 / / / / 3.0---o---o---R1---o---o---R2---o---o---R3 

С этого момента они все время делали « --ours », как и конфликты, которые они хотели игнорировать каждый раз. Также важно заметить, что команда 3.1 постоянно вносила изменения в файл Constants.java . Поэтому со временем ситуация стала примерно такой:

  3.1---o---o---M1---o---o---M2---o---o---C3---o---o---C4---o---o---C5 / / / / / / 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 

Теперь обе ветви имели разные версии Constants.java , с одним общим предком, который является фиксацией где-то до R2. На этом этапе мы можем видеть, что это за предок,

 $ git merge-base --all C5 R5 

Теперь идет сложная часть. Команда 3.1 почти закончила создание версии 3.1 продукта. Команда 3.0 будет захвачена. Сразу же после выхода 3.1, 3.0-команда выпустит 3.2. Итак, из 3,0-ветви была создана новая 3.2-ветвь:

  3.1---o---o---M1---o---o---M2---o---o---C3---o---o---C4---o---o---C5 / / / / / / 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ 3.2 A5 1  3.1---o---o---M1---o---o---M2---o---o---C3---o---o---C4---o---o---C5 / / / / / / 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ 3.2 A5 

Конечно, версия 3.2 должна иметь все изменения как 3.0, так и 3.1. Итак, мы сделали слияние:

 $ git checkout 3.2 $ git merge 3.1 

Руководители:

  3.1---o---o---M1---o---o---M2---o---x3--C3---o---o---C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ 3.2 A5---A6 1  3.1---o---o---M1---o---o---M2---o---x3--C3---o---o---C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ 3.2 A5---A6 

И теперь для неприятного сюрприза: в команде 3.0 было много исправлений в фиксации A6. Оказывается, все, что не хватало, могло как-то быть связано с Constants.java рефакторингом. Конечно, новый MoreConstants.java отсутствует в 3.2, но также много изменений в файлах, которые импортировали новый MoreConstants.java отсутствовали в 3.2-ветке.

После прочтения Как вернуть слияние, которое использовало strategy = ours? и как вернуть неправильное слияние (особенно ADDENDUM), я думаю, что понимаю, почему это так: команда 3.1 решила множество конфликтов, выбрав «наш». Таким образом, на самом деле они сказали Git, что противоречивые изменения с 3.0 были «неправильными». Таким образом, это фактически некоторая форма возврата слияния, о чем говорится в упомянутых документах. Будь то, что не все слияние вернулось, а лишь часть его.

Оба документа содержат много подсказок о том, как это исправить. Тем не менее, оба они сосредоточены на устранении ошибочного слияния. В этом упрощенном примере мы говорим о трех ошибочных слияниях. И на самом деле у нас есть, по-моему, 15-20 ошибочных слияний.

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

1. Много выбора вишни

Мы могли бы вишней выбрать каждую фиксацию на 3.1-строчной линии до 3.2-строки, за исключением коммитов, которые были слияниями. Звучит просто, но поскольку у нас около 20 ошибочных слияний в реальности, и есть гораздо больше компромиссов между слияниями, чем отображаемыми в упрощенных графиках, мы говорим о сотнях коммитов к выбору вишни. Выполнение этого вручную утомительно и подвержено ошибкам. Я думаю, что это решение является жизнеспособным только в том случае, если есть способ создать список всех коммитов для вишни и выбрать этот список для команды (серии) git автоматически.

2. Множество переустановок

Серия последовательных rebase s. Учитывая количество ошибочных слияний, это все еще подвержено ошибкам и утомительным, но, возможно, немного меньше, чем предыдущее предложение. На стороне профессионала это, вероятно, ведет к более чистой истории. Я думаю, что этот процесс будет следующим:

 $ git checkout x3 $ git rebase --no-ff 3.0 3.1---o---o---M1---o---o---M2---o---x3--C3---o---o---C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ 3.2 A5---A6 \ 3.1'--o--o--M1'--o--o--M2'---o---x3' $ git merge R3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ 3.2 A5---A6 \ \ 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3 $ git checkout x4 $ git rebase --no-ff R3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ \ C3'--o--x4' 3.2 A5---A6 \ \ 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3 $ git merge D3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---x5--C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ \ C3'--o--x4'--D4 3.2 A5---A6 \ \ / 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3----------o 1 $ git checkout x3 $ git rebase --no-ff 3.0 3.1---o---o---M1---o---o---M2---o---x3--C3---o---o---C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ 3.2 A5---A6 \ 3.1'--o--o--M1'--o--o--M2'---o---x3' $ git merge R3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ 3.2 A5---A6 \ \ 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3 $ git checkout x4 $ git rebase --no-ff R3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ \ C3'--o--x4' 3.2 A5---A6 \ \ 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3 $ git merge D3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---x5--C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ \ C3'--o--x4'--D4 3.2 A5---A6 \ \ / 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3----------o 1 $ git checkout x3 $ git rebase --no-ff 3.0 3.1---o---o---M1---o---o---M2---o---x3--C3---o---o---C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ 3.2 A5---A6 \ 3.1'--o--o--M1'--o--o--M2'---o---x3' $ git merge R3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ 3.2 A5---A6 \ \ 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3 $ git checkout x4 $ git rebase --no-ff R3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ \ C3'--o--x4' 3.2 A5---A6 \ \ 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3 $ git merge D3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---x5--C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ \ C3'--o--x4'--D4 3.2 A5---A6 \ \ / 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3----------o 1 $ git checkout x3 $ git rebase --no-ff 3.0 3.1---o---o---M1---o---o---M2---o---x3--C3---o---o---C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ 3.2 A5---A6 \ 3.1'--o--o--M1'--o--o--M2'---o---x3' $ git merge R3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ 3.2 A5---A6 \ \ 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3 $ git checkout x4 $ git rebase --no-ff R3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ \ C3'--o--x4' 3.2 A5---A6 \ \ 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3 $ git merge D3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---x5--C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ \ C3'--o--x4'--D4 3.2 A5---A6 \ \ / 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3----------o 1 $ git checkout x3 $ git rebase --no-ff 3.0 3.1---o---o---M1---o---o---M2---o---x3--C3---o---o---C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ 3.2 A5---A6 \ 3.1'--o--o--M1'--o--o--M2'---o---x3' $ git merge R3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ 3.2 A5---A6 \ \ 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3 $ git checkout x4 $ git rebase --no-ff R3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---o---C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ \ C3'--o--x4' 3.2 A5---A6 \ \ 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3 $ git merge D3 3.1---o---o---M1---o---o---M2---o---x3--C3---o---x4--C4---o---x5--C5 / / / / / / \ 3.0---o---o---R1---o---o---R2---o---o---R3---o---o---R4---o---o---R5 \ \ \ \ \ \ \ \ C3'--o--x4'--D4 3.2 A5---A6 \ \ / 3.1'--o--o--M1'--o--o--M2'---o---x3'---D3----------o 

Etcetera …

3. Создание патчей

Третьим решением может быть создание «патчей», содержащих только изменения, сделанные между некоторыми коммитами. Поскольку мы визуализируем дерево еще раз:

  3.1---o---o---M1---o---o---M2--y2--o--x3--C3--y3--o--x4--C4--y4--o--x5--C5 / / / / / / 3.0---o---o---R1---o---o---R2--o---o--o---R3--o---o---o--R4--o---o---o--R5 \ 3.2 A5 1  3.1---o---o---M1---o---o---M2--y2--o--x3--C3--y3--o--x4--C4--y4--o--x5--C5 / / / / / / 3.0---o---o---R1---o---o---R2--o---o--o---R3--o---o---o--R4--o---o---o--R5 \ 3.2 A5 ,  3.1---o---o---M1---o---o---M2--y2--o--x3--C3--y3--o--x4--C4--y4--o--x5--C5 / / / / / / 3.0---o---o---R1---o---o---R2--o---o--o---R3--o---o---o--R4--o---o---o--R5 \ 3.2 A5 

Это означало бы создание патчей, содержащих все изменения, сделанные в следующих «диапазонах»:

  • [y2 - x3] ,
  • [y3 - x4] и
  • [y4 - x5] .

И затем, вместо того, чтобы делать слияние, мы могли бы применить эти исправления поверх A5.

Вопрос

Итак, это мои вопросы:

  1. Какое лучшее решение этой проблемы. Это может быть (вариант) одного из моих предложенных решений, чего-то совершенно другого.
  2. Есть ли способ сделать решение 1 в более или менее автоматическом режиме, чтобы предотвратить ошибки и ускорить работу?
  3. Будет ли план, набросанный в решении 2, работать? Или я чего-то не хватает?
  4. Как насчет решения 3 ? Будет ли способ автоматизировать этот процесс?

Мы могли бы вишней выбрать каждую фиксацию на 3.1-строчной линии до 3.2-строки, за исключением коммитов, которые были слияниями. … Я думаю, что это решение является жизнеспособным только в том случае, если есть способ создать список всех коммитов для вишни и выбрать этот список для (серии) git-команд (-ов) автоматически.

Вы можете передать целый ряд коммитов на cherry-pick , например, git cherry-pick 16234..351be , и вы также можете указать -m 1 чтобы следовать только за первыми родителями, что позволяет вам правильно передать зачеты слияния, поэтому вы не не нужно пропускать их. Когда вы решаете конфликты слияния на каждом шаге, они должны быть решены в будущем, поэтому, надеюсь, вы просто столкнетесь с постоянным конфликтом слияния на каждом конфликтующем шаге, и все будет намного более автоматизированным и менее подверженным ошибкам.