Почему дополнительные изменения в конфликте git cherry-pick?
За git cherry-pick
приводящий к конфликту, почему Git предлагает больше изменений, чем просто из данного коммита?
Пример:
-bash-4.2$ git init
Initialized empty Git repository in /home/pfusik/cp-so/.git/
-bash-4.2$ echo one >f
-bash-4.2$ git add f
-bash-4.2$ git commit -m "one"
[master (root-commit) d65bcac] one
1 file changed, 1 insertion(+)
create mode 100644 f
-bash-4.2$ git checkout -b foo
Switched to a new branch 'foo'
-bash-4.2$ echo two >>f
-bash-4.2$ git commit -a -m "two"
[foo 346ce5e] two
1 file changed, 1 insertion(+)
-bash-4.2$ echo three >>f
-bash-4.2$ git commit -a -m "three"
[foo 4d4f9b0] three
1 file changed, 1 insertion(+)
-bash-4.2$ echo four >>f
-bash-4.2$ git commit -a -m "four"
[foo ba0da6f] four
1 file changed, 1 insertion(+)
-bash-4.2$ echo five >>f
-bash-4.2$ git commit -a -m "five"
[foo 0326e2e] five
1 file changed, 1 insertion(+)
-bash-4.2$ git checkout master
Switched to branch 'master'
-bash-4.2$ git cherry-pick 0326e2e
error: could not apply 0326e2e... five
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'
-bash-4.2$ cat f
one
<<<<<<< HEAD
=======
two
three
four
five
>>>>>>> 0326e2e... five
Я ожидал только "пять" граней между маркерами конфликта. Могу ли я переключить Git на ожидаемое поведение?
2 ответа
Прежде чем мы продолжим, давайте нарисуем график фиксации:
A <-- master (HEAD)
\
B--C--D--E <-- foo
или, чтобы вы могли сравнить, вот как Git рисует это:
$ git log --all --decorate --oneline --graph
* 7c29363 (foo) five
* a35e919 four
* ee70402 three
* 9a179e6 two
* d443a2a (HEAD -> master) one
(Обратите внимание, что я превратил ваш вопрос в последовательность команд, которую я добавил; мои хэши коммитов, конечно, отличаются от ваших.)
Вишня - своеобразная форма слияния
Причина, по которой вы видите здесь несколько патологическое поведение, состоит в том, что git cherry-pick
фактически выполняет операцию слияния. Самое странное в этом - выбранная база слияния.
Нормальное слияние
Для нормального слияния вы проверяете некоторую фиксацию (проверяя некоторую ветвь, которая проверяет коммит tip этой ветки) и запускаете git merge other
, Git находит коммит, указанный other
затем использует граф фиксации, чтобы найти базу слияния, что часто довольно очевидно из графика. Например, когда график выглядит так:
o--o--L <-- ours (HEAD)
/
...--o--B
\
o--o--R <-- theirs
база слияния просто коммитB
(для базы).
Чтобы сделать слияние, Git затем делает дваgit diff
s, один из базы слияния в наш локальный коммитL
слева, и их совершить R
справа (иногда называемый удаленным коммитом). То есть:
git diff --find-renames B L # find what we did on our branch
git diff --find-renames B R # find what they did on theirs
Git может затем объединить эти изменения, применяя объединенные изменения кB
, чтобы сделать новый коммит слияния, чей первый родительL
а второй родитель R
, Этот последний коммит слияния является коммитом слияния, в котором в качестве прилагательного используется слово "слияние". Мы часто просто называем это слиянием, которое использует слово "слияние" как существительное.
Однако, чтобы получить это слияние как существительное, Git должен был запустить механизм слияния, чтобы объединить два набора различий. Это процессслияния, использующий слово "слияние" в качестве глагола.
Вишневое слияние
Чтобы сделать вишневый отбор, Git запускает механизм слияния -слияние как глагол, как мне нравится это выражать - но выбирает особую базу слияния. Основа слияния вишневого пика - просто родитель коммита, выбранного вишней.
В твоем случае ты чертишь коммитE
, Так что Git сливается (глагол) с коммитомD
в качестве базы слияния, совершить A
как левый / локальный L коммит и коммит E
как правая сторонаR коммит. Git генерирует внутренний эквивалент двух списков различий:
git diff --find-renames D A # what we did
git diff --find-renames D E # what they did
Мыудалили четыре строки: те, которые читают two
,three
, а также four
, Что они сделали, так это добавили одну строку: five
,
С помощьюmerge.conflictStyle
Все становится несколько яснее - ну,может быть, несколько яснее - если мы установим merge.conflictStyle
вdiff3
, Теперь вместо того, чтобы показывать нам наши и их разделы, окруженные <<<<<<<
и т.д., Git также добавляет базовую версию слияния, отмеченную |||||||
:
one
<<<<<<< HEAD
||||||| parent of 7c29363... five
two
three
four
=======
two
three
four
five
>>>>>>> 7c29363... five
Теперь мы видим, что Git утверждает, что мы удалили три строки из базы, в то время как они сохранили эти три строки и добавили четвертую.
Конечно, мы должны понимать, что база слияния здесь была родителем коммита E
что, во всяком случае, "опережает" наш текущий коммит A
, Это не совсем правда, что мы удалили три строки. На самом деле, у нас никогда не было трех строк. Нам просто нужно разобраться с тем, как Git показывает вещи, как если бы мы удалили несколько строк.
Приложение: скрипт для генерации коллизии
#! /bin/sh
set -e
mkdir t
cd t
git init
echo one >f
git add f
git commit -m "one"
git checkout -b foo
echo two >>f
git commit -a -m "two"
echo three >>f
git commit -a -m "three"
echo four >>f
git commit -a -m "four"
echo five >>f
git commit -a -m "five"
git checkout master
git cherry-pick foo
Ну... мне кажется, что это крайний случай, живущий на пересечении "выбора вишни вставкой, которая была сделана в исходной ветви на линии, которая не существует в целевой ветви", и "прогрессией изменения, которые все считаются одним "ломоть", потому что каждый смежный с предыдущим ".
Эти проблемы делают git неуверенным в ваших намерениях, поэтому он спрашивает "что мне делать" самым широким способом, предоставляя вам весь код, который вам может понадобиться для принятия правильного решения.
Если вы инициализируете файл как
a
b
c
d
e
f
g
h
i
а затем создавать изменения как
x -- A <--(master)
\
C -- E -- G <--(branch)
(где коммит обозначен A
в верхнем регистре соответствующая буква в файле и т. д.), это случай, который будет вести себя намного лучше, чем вы ожидаете - потому что все выглядит очень ясно для git, и он просто делает очевидную вещь, когда вы говорите cherry-pick E
в master
, (Он не только не вставляет несвязанные изменения в маркеры конфликта, но и не содержит маркеров конфликта; он просто работает.)
Теперь, чтобы быть ясным - я не говорю, что Cherry-Pick будет делать правильные вещи только в таких ясных случаях, как мой. Но так же, как ваш тестовый сценарий является своего рода "наихудшим" сценарием для черри, мой идеальный случай. Между ними много случаев, и на практике люди обычно получают желаемые результаты из вишни. (В случае, если это звучит как одобрение команды cherry-pick, позвольте мне уточнить: я думаю, что это наиболее рекомендуемая команда в наборе, и я никогда не использую ее. Но она работает в пределах своих определенных границ.)
В основном помните две вещи:
1) Если вы вставите новую строку в исходную ветвь, и соответствующая точка вставки не может быть однозначно определена в целевой ветви, вы получите конфликт, и он, вероятно, будет включать в себя "контекст" из исходной ветви
2) Если два изменения являются смежными - даже если они не пересекаются - они все равно могут рассматриваться как конфликтующие.