Почему компенсация Mercurial в одной ветке влияет на другие отрасли?

Это сложная ситуация, чтобы объяснить, так что несите меня. У меня есть репозиторий Mercurial с двумя основными ветвями, по умолчанию и dev.

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

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

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

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

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

Если все вышеперечисленное сложно выполнить, вот скриншот от TortoiseHg, который показывает основную проблему. "branch1" и "branch2" являются ветвями функций, "release" и "release2" являются ветвями выпуска:

enter image description here

Ответ 1

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

Так как новая ветвь релиза содержит все панели изменений ветки Feature (ничего не было отменено), почему ветка по умолчанию также не получает все эти изменения?

Когда вы объединяете две ветки, неправильно думать об этом, применяя все изменения из одной ветки на другую ветку. Таким образом, ветвь default не получает "никаких" наборов изменений из release2. Я знаю, что мы обычно думаем о слияниях, но это неточно.

Что действительно происходит при объединении двух наборов изменений:

  • Mercurial находит общего предка для двух наборов изменений.

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

В вашем случае вы объединяете версии 11 и 12. Наименее распространенным предком является ревизия 8. Это означает, что Mercurial будет запускать трехстороннее слияние между файлами из них:

  • Редакция 8: нет отступлений

  • Отредактирована ветвь функций версии 11:

  • Редакция 12: нет резервных копий

В трехстороннем слиянии изменение всегда не превзойдет никаких изменений. Mercurial видит, что файлы были изменены между 8 и 11, и он не видит изменений между 8 и 12. Таким образом, он использует измененную версию из версии 11 в слиянии. Это применимо для любого трехмерного алгоритма слияния. Полная таблица слияния выглядит так: old, new,... - содержимое совпадающих узлов в трех файлах:

ancestor  local  other -> merge
old       old    old      old (nobody changed the hunk)
old       old    new      new (they changed the hunk)
old       new    old      new (you changed the hunk)
old       new    new      new (hunk was cherry picked onto both branches)
old       foo    bar      <!> (conflict, both changed hunk but differently)

Я боюсь, что merge changeset не должен быть полностью отменен из-за этого удивительного поведения слияния. Mercurial 2.0 и более поздние версии будут прерваны и будут жаловаться, если вы попытаетесь отменить слияние.

В общем, можно сказать, что трехсторонний алгоритм слияния предполагает, что все изменения хороши. Поэтому, если вы объедините branch1 в dev, а затем отмените слияние с резервным копированием, тогда алгоритм слияния будет считать, что состояние "лучше", чем раньше. Это означает, что вы не можете просто повторно объединить branch1 в dev в более позднюю точку, чтобы вернуть обратно резервные изменения.

Что вы можете сделать, так это использовать "фиктивное слияние" при слиянии с default. Вы просто объединяетесь и всегда сохраняете изменения в ветки релиза, которые вы объединяете в default:

$ hg update default
$ hg merge release2 --tool internal:other -y
$ hg revert --all --rev release2
$ hg commit -m "Release 2 is the new default"

Это приведет к тому, что проблема и сила default будут такими же, как release2. Это предполагает, что на default не происходит никаких изменений без объединения в ветвь release.

Если вы должны иметь возможность выпускать выпуски с пропущенными функциями, тогда "правильный" способ - не объединять эти функции вообще. Слияние - это сильная приверженность: вы говорите Mercurial, что у сменного набора теперь есть все хорошие вещи от обоих его предков. Пока Mercurial не позволит вам выбрать собственную версию базы при слиянии, трехсторонний алгоритм слияния не позволит вам передумать об отступлении.

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

release: ... o --- o --- m1 --- m2
                        /      /
feature-A:   ... o --- o      /
                             /
feature-B:  ... o --- o --- o 

Теперь вы решили, что функция A была плохая, и вы отменили слияние:

release: ... o --- o --- m1 --- m2 --- b1
                        /      /
feature-A:   ... o --- o      /
                             /
feature-B:  ... o --- o --- o 

Затем вы объединяете еще одну функцию в свою ветвь релиза:

release: ... o --- o --- m1 --- m2 --- b1 --- m3
                        /      /             /
feature-A:   ... o --- o      /             /
                             /             /
feature-B:  ... o --- o --- o             /
                                         /
feature-C:  ... o --- o --- o --- o --- o 

Если вы хотите снова ввести функцию A, вы можете сделать резервную копию b1:

release: ... o --- o --- m1 --- m2 --- b1 --- m3 --- b2
                        /      /             /
feature-A:   ... o --- o      /             /
                             /             /
feature-B:  ... o --- o --- o             /
                                         /
feature-C:  ... o --- o --- o --- o --- o 

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

                     +A     +B     -A     +C     --A
release: ... o --- o --- m1 --- m2 --- b1 --- m3 --- b2

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

release: ... o --- o --- m1 --- m2 --- b1 --- m3 --- b2
                        /      /             /
feature-A:   ... o -- a1 - a2 /             /
                             /             /
feature-B:  ... o --- o --- o             /
                                         /
feature-C:  ... o --- o --- o --- o --- o 

и вы объедините a2 и b2. Общим предком будет a1. Это означает, что единственными изменениями, которые вам нужно учитывать при трехходовой слиянии, являются те, которые находятся между a1 и a2 и a1 и b2. Здесь b2 уже есть основная часть изменений "в" a2, поэтому слияние будет небольшим.

Ответ 2

Ответ Мартина, как обычно, на деньги, но я просто хотел добавить свой 2p.

Еще один способ задуматься о том, что откат ничего не удаляет, он добавляет обратное изменение.

Итак, когда вы сливаетесь, вы не делаете:

Branch after changes <-> Branch before changes => Result with changes

вы делаете:

Branch after changes <-> Branch after changes with removal of changes => Result with changes removed.

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