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

Моя структура филиала:

0x1---->0x2---->0x3
/\              /\
|               |
master          dev

Общие предки 0x1.

Я тщательно выбираю функцию, которую нужно освоить.

Сцена 1:

Master branch have a.txt file.
0x1 first commit.
a.txt content:
1

Then I create a branch dev, I add "2" to a.txt,
0x2 second commit
0x1 first commit

a.txt content:
1
2

Then I add "3" to a.txt
0x3 third commit
0x2 second commit
0x1 first commit

a.txt content:
3
1
2

I cherry-pick 0x3 to master:
master> git cherry-pick 0x3

В нем нет конфликта.

Сцена 2: Но я изменяю добавленную позицию.

0x3 third commit
0x2 second commit
0x1 first commit

a.txt content:
1
2
3

Я применяю 0x3 к мастеру. Будет конфликт.

В чем разница между Сценой 1 и Сценой 2? Я смущен!


person study_20160808    schedule 22.01.2021    source источник


Ответы (1)


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

Сам конфликт, я думаю, легче объяснить, когда он делается с git merge. Предположим, у нас есть следующая серия коммитов:

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

Мы выбираем некоторую фиксацию, например фиксацию J, в качестве текущей фиксации, выбирая одну имя ветки, например branch1, в качестве текущей ветки. Git извлечет снимок в коммите J в наше рабочее дерево:

git checkout branch1

Мне нравится обозначать эту ситуацию, добавляя специальное имя HEAD к названию выбранной ветки, например:

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

Если мы сейчас запустим git merge branch2, Git активирует механизм слияния, чтобы выполнить процесс слияния. Для этого механизма слияния требуется три коммита:

  • Один из коммитов — наш текущий коммит J.
  • Один из коммитов мы называем git merge: в данном случае branch2 выбирает коммит L.
  • Третий — или, в некотором смысле, первый, потому что Git должен использовать его первым — коммит — это тот, который находит процесс слияния. Команда merge находит наилучшую общую фиксацию: фиксацию, которая находится в обеих ветвях и в каком-то смысле ближе всего к двум фиксациям кончиков ветвей. Здесь этот коммит явно является коммитом H.

Чтобы выполнить фактическую работу по слиянию, Git сейчас:

  • сравнивает снимок в базе (H) со снимком в текущем коммите J; а также
  • сравнивает снимок в базе с другим коммитом L.

Это создает два различия: два рецепта изменения файлов, которые появляются в коммите базы слияния. В первом рецепте говорится, что если вы выполните различные действия, например, добавите и/или удалите определенные строки определенных файлов с файлами в H, вы получите файлы в J. Второй рецепт говорит, что если вы сделаете еще что-нибудь, то получите файлы в L.

Механизм слияния теперь объединяет два различия. Такое объединение может привести к конфликтам слияния.

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

The quick brown fox
jumps over
the lazy dog.

Предположим, что версии J и L гласят:

The quick brown fox
sometimes jumps over
the lazy dog.

а также:

The quick brown fox
has often jumped over
the lazy dog.

Оба дифференциала изменили строку 2 несовместимым образом. Git не знает, какое изменение принять, поэтому он не принимает ни то, ни другое, в зависимости от того, как вы на это смотрите, а затем объявляет конфликт слияния и заставляет вас наводить порядок.

Ваши вишни выбирает

[Примечание: это было изменено, чтобы использовать график фиксации в обновленном вопросе. Теперь мы создаем репозиторий с:

mkdir tcherry && cd tcherry && git init
echo 1 > a.txt && git add a.txt && git commit -m 0x1
git checkout -b dev
echo 2 >> a.txt && git add a.txt && git commit -m 0x2

а затем либо вставьте 3 в начало a.txt, либо добавьте его в конец a.txt, чтобы сгенерировать настройку для двух сценариев:

printf "3\n1\n2\n" > a.txt && git add a.txt && git commit -m 0x3
git checkout master
git cherry-pick dev

or:

echo 3 >> a.txt && git add a.txt && git commit -m 0x3
git checkout master
git cherry-pick dev

Первый работает без конфликтов; второй дает конфликт.]

В вашем случае вы не используете git merge, вы используете git cherry-pick. Но вишневый выбор по-прежнему является слиянием. Вместо:

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

у вас есть:

  B--C   <-- dev
 /
A   <-- master (HEAD)

и вы запускаете git cherry-pick dev. Обратите внимание, что dev выбирает фиксацию C, в то время как ваша текущая ветка — master, а текущая фиксация — A.

Команда cherry-pick вызывает механизм слияния Git, но на этот раз база слияния вынуждена быть родителем коммита, который вы выбираете. Таким образом, база слияния здесь — коммит B. Теперь Git будет:

  • diff B vs A, чтобы получить наши изменения; а также
  • diff B и C, чтобы получить их изменения.

Разница между B и A заключается в том, что вы удалили строку, читающую 2 в конце a.txt. Следующее (в котором используется хитрый тип git rev-parse для поиска правильных коммитов; см. описание :/<text> в документация gitrevisions) показывает это:

$ git diff :/0x2 :/0x1
diff --git a/a.txt b/a.txt
index 1191247..d00491f 100644
--- a/a.txt
+++ b/a.txt
@@ -1,2 +1 @@
 1
-2

Разница между A и C зависит от того, какой из этих двух сценариев вы используете.

Вот разница для неконфликтного случая:

$ git diff :/0x2 :/0x3
diff --git a/a.txt b/a.txt
index 1191247..0571a2e 100644
--- a/a.txt
+++ b/a.txt
@@ -1,2 +1,3 @@
+3
 1
 2

Это говорит о том, что нужно добавить 3 перед строкой, читающей 1, которая была строкой 1 и становится строкой 2. Таким образом, Git должен удалить строку, читающую 2, которая была строкой 2 в базовой версии слияния. Git может безопасно это сделать, потому что известно, где это сделать, и сама строка 1 не затрагивается: затрагиваются только строки со 2 до конца файла. Также предполагается, что Git вставляет строку, читающую 3, перед строку, читающую 1. Это действует от начала файла (строка 0) до строки 1. Затронутые диапазоны строк не перекрываются, а чтение строки 1 находится между ними, поэтому они также не соприкасаются друг с другом.

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

$ git reset --hard HEAD^          # discard the cherry-pick
HEAD is now at 2b4180d 0x1
$ git checkout -q dev
$ git reset --hard HEAD^          # discard commit 0x3
HEAD is now at 1160130 0x2
$ echo 3 >> a.txt && git add a.txt && git commit -m 0x3
[dev c546f74] 0x3
 1 file changed, 1 insertion(+)
$ cat a.txt
1
2
3
$ git checkout master
Switched to branch 'master'

Опять же, сейчас мы на коммите A, и мы дадим Git указание выбрать вишневый коммит C (тема 0x3), чьим родителем является B. Разница между B и A по-прежнему удаляет строку со значением 2 (которая находится в строке 2). Давайте посмотрим на разницу между B и C на этот раз:

git diff :/0x2 :/0x3
diff --git a/a.txt b/a.txt
index 1191247..01e79c3 100644
--- a/a.txt
+++ b/a.txt
@@ -1,2 +1,3 @@
 1
 2
+3

Теперь предполагается, что Git объединит удаление строки с чтением 2 с добавлением строки с чтением 3. Первый касается строки 2 (удалив ее полностью). Второй добавляет строку после строки 2.

Git, конечно, может объединить их, но алгоритму конфликта не нравится тот факт, что оба различия влияют на строку 2. В этих различиях нет строки между двумя изменениями, разделяющей изменения. друг от друга. Получаем конфликт:

$ git cherry-pick dev
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
error: could not apply c546f74... 0x3
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

Осмотрев оставленный a.txt, мы видим:

$ cat a.txt
1
<<<<<<< HEAD
||||||| parent of c546f74... 0x3
2
=======
2
3
>>>>>>> c546f74... 0x3

Обратите внимание, что я установил merge.conflictStyle на diff3, поэтому я получаю строку ||||||| parent of ..., показывающую, о чем конфликт. Сравните это со стилем по умолчанию:

$ git checkout -m --conflict merge a.txt
Recreated 1 merge conflict
$ cat a.txt
1
<<<<<<< ours
=======
2
3
>>>>>>> theirs

что лично я нахожу более загадочным. В случае выбора вишни, подобного этому, тот факт, что разница между B и A идет в обратном направлении, означает, что мы имеем удаление строки 2. Это формирует ours часть конфликта. Это совсем не очевидно из конфликта стилей merge; в стиле diff3 я могу посмотреть на это и увидеть, что в базовой версии слияния есть строка, читающая 2, которую я удалил. Они сохранили строку 2, когда добавили строку 3.

person torek    schedule 22.01.2021
comment
Структура не a-›b(dev), a-›c(master). Я добавляю график структуры ветвей в верхнюю позицию вопроса. a-›b-›c(dev), a(мастер). Не могли бы вы подробно объяснить Диапазоны линий - это полуоткрытые интервалы? - person study_20160808; 24.01.2021
comment
Ах хорошо. Позволю себе пересмотреть ответ, так как возможно диапазоны не полуоткрыты и отсутствие конфликта по какой-то другой причине. - person torek; 24.01.2021
comment
Большое спасибо! Теперь я понимаю, почему это порождает конфликт. Я также знаю, как git cherry-pick наш коммит на целевую ветку. Мне все еще нужно изучить, как git проверяет логику конфликта. - person study_20160808; 25.01.2021