В последний раз мы видели как изменение кода в скопированном коммите создаёт опасность конфликта, который сидит себе тихо пока обе копии где-нибудь не сольются, что может произойти и в очень отдалённом будущем. Но знаете что хуже, чем конфликт слияний?
Отсутствие любого конфликта.
Давайте вернёмся к прежней ситуации:
Представим что ветка "feature" существует уже давно и периодически сливается в "master". Диаграмма показывает срез дерева сразу после последнего слияния А. Мы работаем над спринтом в "feature", полным нового функционала.
Предположим, что строка "apple" находится в файле настроек, управляющим какой-то фичей. Обе ветки фиксируют коммиты M1 и F1, которые не трогают этот файл.
Теперь представим что мы нашли серьёзную ошибку в старой фиче, которая полностью рушит поведение всей программы. Резко пресекая проблему, мы выключаем фичу, меняя строку на "berry" и фиксируя изменения как F2.
В жизни произойдёт что-то похожее на изменение строки c
#define IS_FEATURE_ENABLED 1
на
#define IS_FEATURE_ENABLED 0
Но раз уж я начал использовать "apple" и "berry", пусть так они и останутся.
Хорошо, мы отключили фичу, проверили что теперь никаких проблем нет, и скопировали отключение в "master". Фух, теперь можно выдохнуть, кровотечение остановлено, у нас есть время подумать что пошло не так и как это чинить.
Можно также отключить фичу напрямую в "master", а затем скопировать коммит в "feature", картина будет той же.
Пока мы исследуем проблему, работа в "master" ветке продолжается. Позже мы чиним баг, включаем обратно фичу и фиксируем изменения как F3 в "feature" ветке. Дерево коммитов теперь выглядит так:
Новый несвязанный с "berry" коммит M3 был добавлен в ветку "master". В "feature" мы вернули обратно "apple" коммитом F3.
Теперь мы хотим слить ветку "feature" в "master" чтобы временное отключение фичи было заменено нормальным фиксом. Но вместо этого после слияния мы получаем такую картину:
Слияние породило коммит M4, но вопреки ожиданиям строка всё ещё содержит "berry". В "master" перекочевало временное отключение фичи! Даже хуже, в "master" теперь находятся оба фикса: как основной, так и временный. Вполне возможно они плохо друг с другом совмещаются, и появятся только новые баги. Получается, фича работает только в ветке "feature", а в "master" вообще непонятно что.
Разберёмся почему это произошло, в следующей статье я опишу как этого избежать.
Вернёмся к дереву коммитов до слияния:
При слиянии Git смотрит на базу слияния (merge base): ближайшего родственника между двумя ветками, в нашем случае это коммит А. Затем Git выполняет трёх-стороннее слияние (three-way merge), используя А как базу, M3 как HEAD, а F3 как вливающееся изменение. Теперь нас интересует только дельта между базой и двумя последними коммитами, так что уберём вся лишнее из диаграммы:
Сравнивая базу с верхушкой "master" ветки мы видим что "apple" поменялась на "berry". Сравнение с "feature" не даёт никаких изменений. Так как строка "apple" осталась прежней в "feature", слияние её в "master" также оставит всё как есть. В результате "master" будет содержать "berry" после слияния.
Но это ещё не всё, если затем слить "master" в "feature", то неверная строка "berry" распространится и в "feature":
Это происходит потому, что теперь меняются местами принимающая и отдающая сторона. В основании (коммит А) строка "apple", в ветке "feature" она остаётся неизменной, но в неё вливают "berry" из "master". И здесь даже не важно, используется быстрая перемотка (fast-forward) или нет.
Многие думают о копировании коммитов как о "частичных слияниях, предвосхищающих будущее полное слияние", когда вам надо слить часть наработок в главную ветвь. Вы ожидаете, что позже, когда вы полностью сольёте исходную ветку в главную, сольются только новые наработки.
Если же вы настолько аккуратны, что не трогаете строк, задействованных при копировании, ожидания оправдаются. Иначе, вместо слияния вы получите наложение изменений друг на друга. Хуже того, если ваши изменения отменяют скопированные, то вы даже не получите конфликт при слиянии, который показал бы что что-то идёт не так. Внутри нашей команды мы называем это АБА проблемой, т.к. сначала строка содержала А, затем её поменяли на Б, Б скопировали, а затем вернули А прямо перед слиянием в "master".
Хорошо, вкратце получается мы хотели сделать частичное слияние из "feature" в "master", но проблема в том, что никакого "частичного" слияния нет.
Или всё же есть?
В следующей статье я покажу как его осуществить.