Имеет ли значение порядок слияния Git?

avatar
Tom Ellis
8 апреля 2018 в 07:13
5227
5
26

Допустим, у меня есть две ветви: A и B. Выполняются ли следующие свойства?

  • Слияние A с B конфликтует тогда и только тогда, когда слияние B с конфликтами A.
  • Содержимое моих файлов после объединения A в B совпадает с содержимым моих файлов после объединения B в A.
Источник
Maroun
8 апреля 2018 в 07:23
0

Предполагая, что конфликтов нет, содержимое слияния одинаково.

user4815162342
8 апреля 2018 в 08:13
1

@Maroun Это тоже моя догадка, но действительно ли это где-то задокументировано? Или это логически следует из известных свойств алгоритмов слияния?

Ответы (5)

avatar
torek
8 апреля 2018 в 18:10
18

ответ cmaster правильный, но с оговорками. Начнем с того, что отметим эти пункты/допущения:

.
  • Всегда существует одна базовая фиксация слияния. Давайте назовем эту фиксацию B, для базы.
  • Два других входа также являются одиночными фиксациями. Назовем их L для левого/местного (--ours) и R для правого/удаленного (--theirs).

Первое предположение не обязательно верно. Если есть несколько кандидатов на слияние базы, то <92781113509569>стратегия слияния должна что-то с этим сделать. Две стандартные стратегии двухголового слияния: recursive и resolve. Стратегия resolve просто выбирает один случайным образом. Стратегия recursive объединяет базы слияния по две за раз, а затем использует полученную фиксацию в качестве базы слияния. Тот, который выбран resolve , может <92781113509572> зависеть от порядка аргументов до git merge-base и, следовательно, до git merge, так что здесь есть одно предостережение. Поскольку рекурсивная стратегия может выполнить более одного слияния, здесь есть второе предостережение, которое пока трудно описать, но оно применимо, только если существует более двух баз слияния.

Второе предположение гораздо более верно, но обратите внимание, что код слияния может<92781113509580> работать на частично модифицированном рабочем дереве. В этом случае все ставки сняты, поскольку рабочее дерево не соответствует ни L, ни R. Стандартный git merge сообщит вам, что сначала вы должны зафиксировать, так что обычно это не проблема.

Стратегии слияния имеют значение

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

Слияние Octopus может работать с несколькими головками. Это также изменяет базовое вычисление слияния, но в целом слияние осьминога не будет работать со случаями со сложными проблемами слияния и просто откажется выполняться там, где порядок может иметь значение. Я бы не стал сильно настаивать на этом; это еще один случай, когда правило симметрии может не сработать.

Стратегия слияния -s ours полностью игнорирует все остальные коммиты, поэтому порядок слияния здесь, очевидно, имеет решающее значение: результат всегда L. (Я совершенно уверен, что -s ours даже не удосужится вычислить базу слияния B.)

Вы можете написать свою собственную стратегию и делать все, что захотите. Здесь вы можете сделать порядок важным, как в случае с -s ours.

Слияние высокого уровня (с одной базой слияния): изменение имени файла

Git теперь фактически вычисляет два набора изменений из этих трех моментальных снимков:

  • L - B или git diff --find-renames B L
  • R - B или git diff --find-renames B R

Детекторы переименования здесь независимы — я имею в виду, что ни один из них не влияет на другой; оба используют одни и те же правила. Основная проблема здесь заключается в том, что один и тот же файл в B может быть обнаружен как переименованный в оба набора изменений, и в этом случае мы получаем то, что я называю высоким. конфликт уровня, в частности конфликт переименования/переименования. (Мы также можем получить конфликты высокого уровня с переименованием/удалением и некоторыми другими случаями.) Для конфликта переименования/переименования имя final, которое выбирает Git, является именем в L, а не имя в R. Так что здесь порядок имеет значение с точки зрения конечного имени файла. Это не влияет на объединенное содержимое рабочего дерева.

Слияние низкого уровня

На этом этапе мы должны совершить небольшую экскурсию по внутренностям Git. Теперь у нас есть паре файлы в B -vs- l А в b -vs- R , то есть мы знаем, какие файлы «одинаковые» файлы в каждом из трех коммитов. Однако то, как Git хранит файлы и фиксирует их, интересно. С логической точки зрения Git не имеет дельт: каждый коммит представляет собой полный снимок всех файлов. Однако каждый файл представляет собой всего лишь пару сущностей: имя пути P и хэш-идентификатор H.

Другими словами, на данном этапе нет необходимости просматривать все коммиты, ведущие от B до L или R<9296160350. Мы знаем, что у нас есть некоторый файл F, идентифицируемый тремя отдельными путями (и, как отмечалось выше, Git будет использовать путь L в большинстве случаев, но использовать R, если есть только одно переименование на стороне B-vs-R слияния). Полное содержимое всех трех файлов доступно путем прямого поиска: HB представляет базовое содержимое файла, -боковой файл, а HR представляет правый файл.

Два файла точно совпадают тогда и только тогда, когда совпадают их хэши.1 Итак, на этом этапе Git просто сравнивает идентификаторы хэшей. Если все три совпадают, объединенный файл такой же, как левый, правый и базовый файлы: работы нет. Если L и R совпадают, объединенный файл является содержимым L или R; база не имеет значения, поскольку обе стороны внесли одно и то же изменение. Если B соответствует либо L, либо R, но не другому, объединенный файл является несовпадающим хэшем. Git должен выполнять низкоуровневое слияние только в том случае, если существует потенциал для низкоуровневого конфликта слияния.

Итак, теперь, Git извлекает три содержимого и выполняет слияние. Это работает построчно (строки группируются вместе при изменении нескольких соседних строк):

  • Если обе стороны касаются только разных исходных строк, Git примет оба изменения. Это явно симметрично.

  • Если слева и справа коснулись одных и тех же строк исходного кода, Git проверит, является ли само изменение таким же. Если это так, Git возьмет одну копию изменения. Это тоже явно симметрично.

  • Если левый и правый коснулись одних и тех же строк, но внесли разные изменения, Git объявит конфликт слияния. Содержимое рабочего дерева будет зависеть от порядка внесения изменений, поскольку содержимое рабочего дерева имеет маркеры <<<<<<< HEAD ... ||||||| base ... ======= ... other >>>>>>> (раздел base является необязательным и отображается, если вы выберете стиль diff3).

Определение тех же строк немного сложно. Это зависит от алгоритма сравнения (который вы можете выбрать), так как некоторые разделы некоторых файлов могут повторяться. Однако Git всегда использует один и тот же алгоритм для вычисления L и R, поэтому порядок здесь не имеет значения.


1Иными словами, если вам удастся создать файл-двойник, который имеет другое содержимое, но тот же хеш существующего файла Git просто отказывается помещать этот файл в репозиторий. Shattered.it PDF не является таким файлом, потому что Git добавляет к данным файла префикс blob и размер файла, но принцип применим. Обратите внимание, что помещение такого файла в SVN ломает SVN — ну, вроде.


-X варианты явно асимметричны

Вы можете переопределить жалобы на конфликт слияния, используя -X ours или -X theirs. Они предписывают Git разрешать конфликты в пользу изменения L или R соответственно.

Слияние создает фиксацию слияния, которая влияет на вычисление базы слияния

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

git merge one    (and fix conflicts and commit if needed)
git merge two    (fix conflicts and commit if needed)

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

git merge two
git merge one

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

Это особенно важно, если у вас есть конфликты, которые вы должны исправить до завершения слияния, которое идет первым, так как это также влияет на ввод L для второй команды git merge. Он будет использовать снимок первого слияния как L, а новую (возможно, другую) базу слияния как B для двух из трех входов.

.

Вот почему я упомянул, что -s recursive ​​имеет потенциальные различия в порядке при работе с несколькими базами слияния. Предположим, что есть три базы слияния. Git объединит первые два (в любом порядке, в котором они появляются из базового вычисления слияния), зафиксирует результат (даже если есть конфликты слияния — он просто фиксирует конфликты в этом case), а затем объединить эту фиксацию с третьей фиксацией и зафиксировать результат. Последним коммитом здесь является ввод B. Только если все части этого процесса симметричны, окончательный результат B будет нечувствительным к порядку. Большинство слияний являются симметричными, но мы видели все предостережения выше.

cmaster - reinstate monica
8 апреля 2018 в 19:37
2

Спасибо, что дополнили мой очень высокоуровневый, неточный ответ этим низкоуровневым, точным, технически подробным ответом :-)

avatar
Nɪsʜᴀɴᴛʜ ॐ
8 апреля 2018 в 09:36
0

Считайте A основной ветвью, а B — подветвью.

  1. Теперь создайте файл readme.txt и добавьте некоторый контент, т.е. "измените 1" и зафиксируйте его.
  2. Теперь создайте еще одну ветку B, внесите некоторые изменения в readme.txt, например, добавьте «изменение 2» в readme.txt и зафиксируйте его.
  3. Вернитесь к главной ветке A, теперь вы не увидите никаких изменений, сделанных вложенной ветвью B. Чтобы отразить те же изменения, сделанные веткой B, выполните слияние из основной ветки, т. е. из B в A
  4. После того, как вы окажетесь в главной ветке A, добавьте «change 3» в файл readme.txt и зафиксируйте его.
  5. Теперь перейдите в подветвь B, добавьте "change 4" в файл readme.txt и зафиксируйте внесенные изменения
  6. Однажды, когда вы были в подветви B, слияние главной ветки A с подветвью B вызвало конфликты слияния

Поскольку вы не увидите текст "change 3" в файле readme.txt из ветки B, и вы не добавляете текст "change 4" в файл readme.txt. Вместо этого вы перезаписываете файл readme.txt, т. е. объединяете содержимое текста readme.txt с «изменением 3» с текстом «изменение 4»

В приведенном выше примере сохраняются оба свойства.

avatar
cmaster - reinstate monica
8 апреля 2018 в 08:39
13

Я бы даже сказал, что если два ваших свойства не выполняются, то вы нашли ошибку в git merge.

Обоснование: Одновременное слияние во всех направлениях является той самой целью, для которой был создан git. Вот почему git с самого начала использует трехстороннее слияние: это единственный способ обеспечить правильные результаты слияния. Это трехстороннее слияние является симметричным с математической точки зрения, оно в основном вычисляет состояние R = (A - B) + (C - B) + B на основе базовой фиксации B из расходящихся состояний A и C. Единственное различие, связанное с порядком слияния, должно заключаться в порядке родительских элементов коммита слияния.


Редактировать: Если вас интересуют более подробные сведения, ответ Торека — это то, что вы ищете. Он дает вам все технические подробности о различных стратегиях слияния и указывает, где мой ответ неточен из-за того, что он написан на очень высоком уровне абстракции.

user4815162342
8 апреля 2018 в 10:28
1

Вопрос заключается в том, гарантирует ли применение (C - B), за которым следует (A - B) и B, получение тех же результатов, что и в обратном порядке, для произвольно сложных слияний (например, включая обнаружение переименования и аналогичные расширенные функции). Поскольку в git нет формальной теории патчей, заявление о том, что все слияния симметричны и что все остальное является ошибкой, должно быть подтверждено документацией.

torek
8 апреля 2018 в 17:19
1

@ user4815162342: Git не применяет изменения в этом порядке. В частности, GIt начинается с трех хэшей больших двоичных объектов (основной, левый, правый): если B=L или B=R, берется тот, который не равен. Только если все три неравны, мы получаем слияние, а затем мы получаем конфликт слияния для любого случая, когда может быть какая-либо асимметрия. Что касается переименований, то детектор переименований запускается до слияния в дереве результатов двух различий, и на них также не влияет порядок. Однако все это справедливо только для одного слияния.

torek
8 апреля 2018 в 17:26
0

На это есть еще кое-что, поэтому я напишу реальный ответ.

user4815162342
8 апреля 2018 в 17:29
0

@torek Спасибо за уточнение переименований. Только если все три неравны, мы получаем слияние — а затем мы получаем конфликт слияния для любого случая, когда может быть какая-либо асимметрия — это где-то задокументировано? Или вы говорите, что это фундаментальное свойство patch, которое исторически понятно и не нуждается в специальной документации? Мне это искренне любопытно; концепция «применения патча» никогда не казалась точно определенной (в git), и, учитывая, что патчу разрешено интерпретировать свой ввод с некоторой «нечеткостью», не совсем понятно, что это такое.

torek
8 апреля 2018 в 18:23
0

@ user4815162342: Слияние на самом деле не применяет исправления. Смотрите мой ответ (более или менее сделано, я думаю сейчас). Фактический код, объединяющий файлы, см. на github.com/git/git/blob/master/xdiff/xmerge.c (и на самом деле все в xdiff/).

user4815162342
8 апреля 2018 в 19:43
0

@torek Увлекательный ответ, спасибо, что нашли время написать его.

avatar
max630
8 апреля 2018 в 07:46
1

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

Я бы проверил некоторые неоднозначные случаи, например, если раздел файла был дублирован рядом с ним одним человеком и изменен другим. Какие из копий менять? Поскольку единого правильного ответа не существует, это может зависеть от второстепенных причин, таких как порядок родителей.

avatar
Albin
8 апреля 2018 в 07:29
0

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

В общем,

  • Если возникают конфликты при попытке объединить B с A, возникнут конфликты при попытке объединить A с B.

  • Результат слияния A с B и B с A должен быть одинаковым (если нет конфликтов).

Вопрос чисто логический. Поэтому, если кто-то считает, что мой логический ответ неверен или нуждается в улучшении, не стесняйтесь исправлять меня или редактировать это.