В чем разница между git cherry-pick и git format-patch | мерзавец?
Иногда мне нужно выбрать тег с определенным исправлением в моей ветке, и я делал это через
git cherry-pick tags/myfix
Это работает, но сбор вишни занимает все больше времени, делая "неточное обнаружение переименования".
Я догадывался, что это может быть быстрее с
git format-patch -k -1 --stdout tags/myfix | git am -3 -k
Фактически, это оказалось примененным исправлением немедленно, оставляя мою ветвь точно в том же состоянии, что и сбор вишни.
Теперь мой вопрос: что именно делает сбор вишни по-другому? Я думал, что сбор вишни был в основном реализован именно так, но, должно быть, я ошибся.
2 ответа
cherry-pick
реализован в виде слияния, при этом база слияния является родительским компонентом вводимого вами cmomit. В случаях, когда нет конфликтов слияния, это должно иметь точно такой же эффект, что и создание и применение патча, как у вас (но см. ответ Торека для небольшого предостережения, где am
теоретически может поступить неправильно).
Но сделав слияние, cherry-pick
может попытаться более изящно обрабатывать случаи, когда изменения будут конфликтовать. (На самом деле, -3
вариант, который вы дали am
говорит, что при необходимости он должен делать то же самое, если в патче достаточно контекста, чтобы это можно было сделать. Я вернусь к этому моменту в конце...)
Когда вы применяете патч, по умолчанию, если он изменяет кусок кода, который не совпадает с коммитом, в котором вы его применили, как это было в родительском коммите, из которого он был сгенерирован, то применение завершится неудачно. Но cherry-pick
Подход / слияние будет смотреть на то, что эти различия, и генерировать конфликт слияния из них - так что у вас есть шанс разрешить конфликт и продолжить.
Как часть обнаружения конфликта, cherry-pick
делает переименование обнаружения. Так, например, скажем, у вас есть
o -- x -- x -- A <--(master)
\
B -- C -- D <--(feature)
и ты cherry-pick
совершить C
на master
, Предположим, в o
ты создал file.txt
, И в A
у вас есть изменения в file.txt
, Но совершать B
движется file.txt
в my-old-file.txt
и совершить C
модифицирует my-old-file.txt
,
Изменение в my-old-file.txt
в C
может вступить в конфликт с изменением file.txt
в A
; но чтобы увидеть такую возможность, git должен сделать обнаружение переименования, чтобы он мог выяснить, что file.txt
а также my-old-file.txt
"одно и то же".
Вы можете знать, что у вас нет такой ситуации, но git не знает, пока не попытается обнаружить переименования. Я не уверен, почему это заняло бы много времени в этом случае; по моему опыту это обычно не так, но в репо с большим количеством путей, добавленных и удаленных (между B
и либо C
или же A
в нашем примере) это может быть.
Когда вы генерируете и применяете патч вместо этого, он пытается применить патч в предположении, что конфликта нет. Только если это сталкивается с проблемой (и тогда, только потому, что вы дали -3
вариант) будет ли это обратно к слиянию с обнаружением конфликта. Он может пропустить все это - и любое потенциальное обнаружение переименования - до тех пор, пока его первая попытка применяется чисто.
Обновление - как отмечено в комментариях к вопросу, вы также можете отключить функцию переименования, если это не помогает и работает медленно. Если вы используете это, когда фактически происходит переименование, которое "имеет значение" для слияния, это может вызвать конфликты, при которых обнаружение переименования разрешит их. Хотя я не думаю, что это должно, я не могу исключить, что он может также просто вычислить неверный результат слияния и спокойно применить его - вот почему я редко использую эту опцию.
Для стратегии слияния по умолчанию -X no-renames
опция отключит обнаружение переименования. Вы можете передать эту опцию cherry-pick
,
Согласно комментарию Торека, обнаружение переименования должно быть проблемой am
, Тем не менее, я могу подтвердить, что он может правильно обрабатывать случай, когда объединение работает только с обнаружением переименования. Я вернусь к тому, чтобы попытаться понять все тонкости этого когда-нибудь, когда не в пятницу днем.
Ответ Марка Адельсбергера правильный (и проголосовал, и, вероятно, вы должны его принять). Но здесь есть историческая странность.
На самом деле вишневый пик был когда-то реализован как git format-patch | git am -3
, а также git rebase
до сих пор использует этот конкретный метод копирования коммитов для некоторых видов rebase. 1 Проблема здесь в том, что не удается обнаружить переименованные файлы, а иногда - при (редких) условиях, которые трудно описать, но я постараюсь - неправильно применяет изменения, когда правильное трехстороннее объединение применяет их правильно. Рассмотрим, например, этот случай:
@@ -123,5 ... @@
}
}
- thing();
{
{
где окружающий контекст вокруг удаленной строки - это просто фигурные скобки (или, что еще хуже, пробел), то есть то, что бесполезно совпадает, другими словами. В вашей версии этого же файла из-за какого-то другого события строки с 123 по 127 теперь раньше или позже находятся в том же файле. Скажем, например, что они теперь строки 153-158. Между тем строки 123-127 в вашем файле читаются:
}
}
thing();
{
{
но эти строки верны: призыв к thing()
это должно быть удалено (потому что это неправильно) переместилось вниз, но есть вызов thing()
это не должно быть удалено, в том же месте.
При трехстороннем слиянии база слияния будет сравниваться с вашей версией, и, может быть - возможно, в зависимости от удачи и различий контекста - обнаружится, что вы вставили различные строки, так что ошибочный вызов, который следует удалить, теперь находится на строке 155, а не строка 125. Затем он выполнит правильное удаление, поскольку знает, что линия 125 базы была вашей строкой 155.
Обнаружение переименования является более важным из этих двух различий между форматом-патч-затем-применить против истинного трехстороннего слияния, но в некоторых случаях оба имеют значение. Бег git cherry-pick
делает более тщательную, медленную, чаще правильную вещь.
1 В частности, только неинтерактивные git rebase
когда-либо использует формат-патч, и даже тогда, только если вы не используете -m
опцию, укажите стратегию слияния с -s
или укажите расширенный параметр с помощью -X
, Любая из этих трех заставляет неинтерактивную перебазировку использовать метод "выбора вишни".
Обратите внимание, что документация Git вызывает -X
аргументы "опции стратегии-опции" или "аргументы стратегии-опции", что в любом случае является очень неуклюжей фразой. Мне нравится слово "расширенный" здесь, так как оно объясняет, почему это -X
, т. е. расширенное. Расширенная опция - это просто опция, передаваемая выбранной вами стратегии. -s
: Git не знает, какие дополнительные параметры у каждого -s
понимает, так что все, что вы даете -X
Git дает выбранную стратегию, а сама стратегия затем принимает -X
вариант и что-то делает, или жалуется на это как на неизвестность.