В чем разница между git cherry-pick и git format-patch | мерзавец? - PullRequest
0 голосов
/ 31 августа 2018

Иногда мне нужно выбрать тег с определенным исправлением в моей ветке, и обычно это делается через

git cherry-pick tags/myfix

Это работает, но сбор вишни занимает все более длительное время при "неточном обнаружении переименования".

Я догадывался, что это может быть быстрее с

git format-patch -k -1 --stdout tags/myfix | git am -3 -k

На самом деле, оказалось, что это исправление было применено мгновенно, и моя ветвь осталась в том же состоянии, что и вишня.

Теперь мой вопрос: что именно делает сбор вишни по-другому? Я думал, что сбор вишни был в основном реализован именно так, но, должно быть, я ошибся.

Ответы [ 2 ]

0 голосов
/ 31 августа 2018

Марк Адельсбергер ответил правильно (и проголосовал, и, вероятно, вы должны его принять). Но здесь стоит отметить историческую странность.

Фактически, Cherry-pick когда-то был реализован как git format-patch | git am -3, и git 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 и что-то делает, либо жалуется на нее как на неизвестную.

0 голосов
/ 31 августа 2018

cherry-pick реализовано в виде слияния, при этом база слияния является родительским элементом в вводимом cmomit. В случаях, когда нет конфликтов слияния, это должно иметь точно такой же эффект, что и создание и применение патча как у вас (но см. ответ Торека для некоторого предостережения, где am теоретически может сделать неправильную вещь).

Но, осуществляя слияние, cherry-pick может попытаться более изящно обрабатывать случаи, когда изменения будут конфликтовать. (На самом деле, опция -3, которую вы дали am, говорит о том, что при необходимости она должна делать то же самое, если в патче достаточно контекста, чтобы можно было это сделать. Я вернусь к эта точка в конце ...)

Когда вы применяете патч, по умолчанию, если он изменяет кусок кода, который не совпадает с коммитом, в котором вы его применяете, как это было в родительском коммите, из которого он был сгенерирован, то применение завершится неудачно. Но подход cherry-pick / merge рассмотрит, каковы эти различия, и создаст из них конфликт слияния - так что у вас есть шанс разрешить конфликт и продолжить.

Как часть обнаружения конфликта, 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. Тем не менее, я могу подтвердить, что он может правильно обрабатывать случай, когда объединение работает только с обнаружением переименования. Я вернусь к тому, чтобы попытаться понять все тонкости этого когда-нибудь, когда не пятничный полдень.

...