Различные сценарии конфликтов слияния git

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

Я создал репозиторий git и добавил в него текстовый файл.

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

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

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

person Sam Stewart    schedule 19.02.2020    source источник


Ответы (2)


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

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

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

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

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

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

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

git diff <hash1> <hash2>

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

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

Важной частью здесь является концептуальное «чувство» базы слияния. Для некоторых графиков это легко сделать. Рассмотрим, например, случай графа коммитов Git, где две ваши ветки просто разветвляются от коммита общего предка, хэш-идентификатор которого равен 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 останавливается в середине слияния. Теперь ваша задача состоит в том, чтобы завершить слияние (самостоятельно объединить изменения), затем сообщить Git, что вы это сделали, и выполнить коммит слияния. Если это слишком большой беспорядок, вы можете сказать Git: полностью прервать слияние, и он отменит все свои попытки объединить вещи и вернет вас к коммиту J, как если бы вы никогда не даже запустить git merge вообще.

Последний сложный момент заключается в следующем: когда вы выполняете завершение слияния — автоматически через 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 и найти конфликты.

person torek    schedule 19.02.2020

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

person Adam Nierzad    schedule 19.02.2020
comment
Спасибо за ответ, а как насчет второго вопроса? - person Sam Stewart; 19.02.2020
comment
Я не вижу причин, по которым в описанном вами сценарии могут быть конфликты. Вы можете включить сообщение о конфликте? Это может помочь определить причину возникновения проблемы. - person Adam Nierzad; 19.02.2020