Почему отказы Mercurial в одной ветви влияют на другие ветви?
Это сложная ситуация, чтобы объяснить, так что терпите меня. У меня есть репозиторий Mercurial с 2 основными ветками, по умолчанию и dev.
Работа обычно выполняется в именованной ветви от dev (функциональная ветка). В любой момент может быть много ветвей функций. Когда работа в этой ветке завершена, она снова объединяется с dev.
Когда приходит время для подготовки релиза, из dev создается ветка с именем (ветвь релиза). Иногда необходимо исключить все функции из релиза. Если это так, набор изменений слияния, из которого ветвь функции была объединена с dev, возвращается из новой ветви выпуска.
Когда ветвь релиза готова к выпуску, она объединяется со значением по умолчанию (поэтому по умолчанию всегда отображается состояние кода в производстве). Работа в ветке dev и Feature продолжается как обычно.
Проблема возникает, когда приходит время сделать еще один выпуск, включая функцию, которая была отменена в предыдущем выпуске. Новая ветка релиза создается как обычно (off of dev). Эта новая ветка релиза теперь содержит функцию, которая была исключена из предыдущей ветки релиза (поскольку откат был выполнен в ветке релиза, а набор изменений слияния остается в ветке dev).
На этот раз, когда ветвь выпуска готова к выпуску и объединена с настройками по умолчанию, любые изменения, которые были отменены в результате возврата слияния в предыдущей ветви выпуска, не объединяются с настройками по умолчанию. Почему это так? Поскольку новая ветвь выпуска содержит все наборы изменений ветви функций (ничего не отменено), почему ветвь по умолчанию также не получает все эти наборы изменений?
Если всем вышеперечисленным сложно следовать, вот скриншот из TortoiseHg, который показывает основную проблему. "branch1" и "branch2" - это ветви функций, "release" и "release2" - ветви релизов:
2 ответа
Я считаю, что проблема в том, что слияния работают не так, как вы думаете. Ты пишешь
Поскольку новая ветвь выпуска содержит все наборы изменений ветви функций (ничего не отменено), почему ветвь по умолчанию также не получает все эти наборы изменений?
Когда вы объединяете две ветви, неправильно думать, что они применяют все изменения из одной ветви в другую. Итак 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)
Я боюсь, что слияние изменений не должно быть вообще отменено из-за этого удивительного поведения слияния. 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
без объединения в ветку релиза.
Если вы должны иметь возможность выпускать выпуски с пропущенными функциями, то "правильный" способ - вообще не объединять эти функции. Слияние - это серьезное обязательство: вы говорите 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
Если вы хотите снова ввести функцию А, вы можете отказаться 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р.
Еще один способ думать об этом - то, что backout ничего не удаляет, а добавляет обратное изменение.
Итак, когда вы сливаетесь, вы не делаете:
Branch after changes <-> Branch before changes => Result with changes
ты делаешь:
Branch after changes <-> Branch after changes with removal of changes => Result with changes removed.
В основном первый релиз был сделан плохо. Было бы лучше, чтобы вишня выбрала функции в релизе, чем включала бы все и функции вишни. Прививка может помочь вам здесь, но я еще не пытался использовать его в гневе, чтобы узнать все подводные камни.