В чем разница между 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 вариант и что-то делает, или жалуется на это как на неизвестность.

Другие вопросы по тегам