Разные сценарии ios для git конфликтов слияния - PullRequest
2 голосов
/ 19 февраля 2020

Я пытаюсь понять, в каких ситуациях может произойти конфликт git после слияния git и как их можно избежать.

Я создал хранилище git и добавил текстовый файл к нему.

  • Я добавил «1» в текстовый файл и передал его мастеру.
  • Я создал новую ветку от мастера (branch2) и добавил новую строку в текстовый файл с содержимым «2».
  • Я создал новую ветку из master (branch3) и добавил новую строку в текстовый файл с содержимым «3».

После этого я сделал следующее:

  • Я слил branch2 в master. Нет конфликтов, что правильно, и я ожидал.
  • Я слил master в branch3. У меня были конфликты, потому что вторая строка текстового файла имеет другое содержание. Я исправил конфликты, сохранив «3» вместо «2».
  • Я хочу объединить branch3 в master. Теперь мои вопросы: 1. Есть ли какие-либо возможности конфликтовать, когда я делаю это слияние? Если да, то почему? Если нет, то почему? 2. Если не должно быть конфликтов, но у меня все еще есть конфликты, в чем могут быть причины?

Ответы [ 2 ]

10 голосов
/ 19 февраля 2020

Конфликты слияния происходят во время слияний, а не после их.

Конфликтная часть действительно очень проста: Конфликт возникает в файле F когда:

  • «наши» изменения - отличие от базового коммита слияния с нашим HEAD коммитом - имеет изменение в файле F , и
  • «их» изменения - отличие от того же самого базового коммита слияния на их коммитный коммит - также имеют изменения в файле F , а также
  • наших изменений и их изменения частично совпадают, но не идентичны.

Чтобы понять это, вам необходимо:

  1. понять вывод git diff; и
  2. понять, что такое база слияния .

Вывод git diff довольно прост, на самом деле, но для этого нужно помнить, что каждый коммит содержит снимок из всех ваших файлов. Это означает, что мы должны дать git diff два снимка: старый и новый. Это две "картинки" того, на что был похож файл в два момента времени. Git затем играет в игру Найди отличия : он говорит вам, что до go от снимка с левой стороны до снимка с правой стороны, вы должны внести некоторые изменения в некоторый набор файлы. Эти изменения могут включать переименование некоторых файлов; они могут включать добавление новых файлов; они могут включать удаление файлов; и они могут включать удаление определенных строк из некоторых файлов и добавление некоторых строк в некоторые файлы, в определенных местах.

Вывод git diff не обязательно является чем-то, что person делал , Это просто набор изменений, которые, если применить их к левому снимку, дают вам правый снимок. «Левая сторона» здесь - это левый аргумент git diff, а «правая сторона» - это правый аргумент, когда вы используете:

git diff <hash1> <hash2>

, где два хэша являются идентификаторами ha sh коммитов. (Это то, что git merge делает, хотя и делает все это внутренне.) Механизм сравнения предназначен для создания наименьшего набора изменений, которые дают правильный эффект. Это, как выясняется, обычно то, что кто-то на самом деле делал делает ... но не всегда; следовательно, обычно верно, но не всегда.

Последней, но, вероятно, самой сложной частью понимания git merge является концепция базы слияния . Технически база слияния - это (единичный) коммит, который получается из алгоритма, который находит наименьшего общего предка (LCA) узлов, выбранных из Направленного ациклического графика c (DAG). Не все пары (или наборы) узлов DAG имеют LCA: у некоторых нет ни одного, а у некоторых более одного. Тем не менее, для графика фиксации Git достаточно иметь здесь один LCA, а git merge имеет несколько методов для работы с несколькими LCA. (Когда существует нет LCA, современный git merge отказывается запускаться по умолчанию, сообщая вам, что две ветви имеют несвязанные истории. Старый Git все равно выполнил слияние, и вы можете сделать современный Git в любом случае выполните слияние, в этом случае Git использует синтетический c коммит без файлов в качестве базы слияния.)

Важной частью здесь является концептуальное "чувство" для база слияния. Для некоторых графиков это легко. Рассмотрим, например, случай Git графа коммитов, где ваши две ветви просто разветвляются от коммитов общего предка, чей идентификатор ha sh равен H:

          I--J   <-- branch1 (HEAD)
         /
...--G--H
         \
          K--L   <-- branch2

Здесь, при объединении branch1 и branch2 - что означает фиксацию J и L - общая отправная точка - это фиксация H. Таким образом, git merge будет выполнять две команды git diff, а база слияния в каждая будет H:

git diff --find-renames <hash-of-H> <hash-of-J>    # what we changed on branch1
git diff --find-renames <hash-of-H> <hash-of-L>    # what they changed on branch2

Git теперь будет объединять набор изменений, произведенных этими двумя git diff командами. Там, где они перекрываются, но не вносите одно и то же изменение , вы получаете конфликты слияния.

Git применит объединенные изменения к снимку в H. Применение вашего изменения к этому снимку приводит к фиксации J; применение их изменений приводит к коммиту L; применение комбинированных изменений приводит, в общем, к комбинации.

Если нет конфликтов, Git сможет объединить изменения самостоятельно. Применив объединенные изменения, Git зафиксирует результат самостоятельно, как новый коммит слияния M:

          I--J
         /    \
...--G--H      M   <-- branch1 (HEAD)
         \    /
          K--L   <-- branch2

, и это будет ваш результат слияния.

Если объединение не удается, Git останавливается в середине слияния. Теперь ваша задача - fini sh объединить (объединить изменения самостоятельно), затем сказать Git, что вы сделали это, и выполнить коммит слияния. Если это слишком большой беспорядок, вы можете сказать Git: прервать слияние целиком , и он откажется от всех своих попыток объединить вещи и оставит вас на коммите J, как будто вы даже никогда не запустим git merge.

Последний хитрый момент заключается в следующем: когда вы делаете завершаете sh слияние - автоматически через Git или вручную - результирующие записи фиксации слияния два родителя. То есть, если вы посмотрите на слияние M выше, вы увидите, что оно подключается обратно к обоим коммитам J и L. Во многих слияниях мы рисовали бы это немного по-другому:

                o--o   <-- small-feature
               /    \
...--o--B--o--D--o---o--o   <-- mainline
         \
          o--o--o--o--o--o   <-- big-feature

Здесь небольшая функция была объединена с основной линией, а большая функция все еще выполняется. Основой слияния небольшой функции был коммит D. Основой слияния большой функции будет коммит B. (Остальные коммиты не очень интересны.) Однако в некоторых случаях мы получаем более запутанный граф:

                  o--o---o   <-- offshoot-feature
                 /      / \
                o--o---o---o--o   <-- medium-feature
               /    \ /
...--o--o--o--o--o---o----o   <-- mainline

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

Git найдет слияние основы. Вы можете найти базы слияния самостоятельно, используя git merge-base --all. Вы можете нарисовать график или использовать Git, используя git log --graph, и попытаться найти базы слияния с помощью глазного яблока. Найдя базы слияния, как бы вы ни делали, вы можете запустить две команды git diff, которые будет запускать git merge. Это скажет вам, где ваши конфликты будут . Но обычно нет никакого смысла: просто запустите git merge и найдите конфликты.

0 голосов
/ 19 февраля 2020

Не должно быть никаких конфликтов при слиянии branch3 в master, вы бы это сделали, если бы вы еще не слились master в branch3. Причина в том, что вы добавили коммит к branch3, который разрешает конфликт между ним и master. Так что теперь слияние с master может быть ускорено.

...