Почему развивается git pull origin - rebase вызывает конфликт, когда развивается git pull origin? - PullRequest
0 голосов
/ 05 февраля 2019

Теперь обычно я использую

git pull origin develop

, чтобы получать последние обновления из ветви разработки.Недавно моя команда перешла на использование rebase вместо слияния, поэтому я немного запутался в некоторых вещах.До моего рабочего процесса довольно прямо вперед.Сначала я заглянул в ветку разработки и использовал

git checkout -b feature/foo

. Затем я бы внес свои изменения, зафиксировал и затем протолкнул их.Обычно в развивающейся ветке вносились некоторые изменения, поэтому я использовал бы

 git pull origin develop

, чтобы получить последние изменения и иметь конфликты, только если другие люди изменили тот же файл.Однако, когда я использую

git pull origin develop --rebase

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

1 Ответ

0 голосов
/ 06 февраля 2019

Во-первых, отметим, что git pull в основном состоит из двух команд Git.Это означает, что это удобная операция, позволяющая набирать git pull вместо git fetch, вводить git ......Первая команда всегда git fetch, а вторая по вашему выбору: по умолчанию git merge, но вы можете выбрать git rebase.Чтобы выполнить одну команду, нужно набрать почти столько же, сколько две, когда вы хотите выполнить перебазирование, так что в конце концов это не очень удобно, и я предлагаю использовать отдельные git fetch и вторую команду, по крайней мере, пока вы не оченьзнаком с Git. 1

Итак, ваш вопрос действительно разрешается проще: Почему в rebase иногда возникают конфликты, которых нет в результате слияния? И есть ответк тому, что на самом деле довольно просто: Rebase - это в основном просто повторный сбор вишни, а сбор вишни - это форма слияния .Поэтому, когда вы объединяетесь, у вас есть одно место, где вы можете получить конфликты.Если вы перебазируете десять коммитов, у вас есть десять мест, где вы можете получить конфликты.Сами конфликты также могут быть разными, но основным фактором здесь является просто масштаб возможностей.


1 В репозиториях с подмодулями git pull может возвращаться в подмодули, в этом случае это более двух команд, и его удобство становится значительным.Вы также можете настроить git pull на запуск git rebase по умолчанию, что сделает его снова доступным даже без субмодулей.Тем не менее, я по-прежнему призываю новых пользователей использовать две отдельные команды - синтаксис git pull немного странный и немного отличается от почти всех других Git-компонентов, и его слишком легко запутать.Слишком много магии назначено, чтобы вытащить, когда на самом деле все магия от второй команды - и вам нужно изучить слияние, чтобы понять rebase.


Слияние

Хотя реализация полна хитрых мелких поворотов, идея , стоящая за слиянием, проста.Когда мы просим Git объединиться, у нас есть «наша работа» и «их работа».Git должен выяснить, что мы изменили, что они изменили, и объединить эти изменения.

Чтобы сделать это, Git должен найти общую отправную точку,Фиксация - это не набор изменений вообще: это на самом деле снимок.Git может показывать один из этих снимков как отличия от своего непосредственного предшественника, то есть извлекать оба снимка и видеть, что отличается.Поэтому, если мы начали с какого-то коммита с некоторым хеш-идентификатором B, и они также начали с этого того же коммита:

          C--D   <-- our-branch (HEAD)
         /
...--A--B
         \
          E--F   <-- their-branch

, тогда Git может сравнитьснимок от B до наших последних D и до их последних F.Что бы ни отличалось в B -vs- D, это вещи , мы изменились.Что бы ни отличалось в B -vs- F - это вещи , они изменились.Затем Git объединяет изменения, применяет объединенные изменения к снимку из базы слияния B и фиксирует результат, связывая его не с одним, а с двумя предшественниками:

          C--D
         /    \
...--A--B      G   <-- our-branch (HEAD)
         \    /
          E--F   <-- their-branch

Чтобы попасть туда, Git должен запустить:

  • git diff --find-renames <em>hash-of-B</em> <em>hash-of-D</em> (что мы изменили)
  • git diff --find-renames <em>hash-of-B</em> <em>hash-of-F</em> (что они изменили)

Когда Git объединяет эти два различия, могут быть места, где мы и они изменили одинаковые строки того же файла .Если мы не сделаем то же самое изменение для этих строк, Git объявит конфликт и остановит слияние в середине, пока не сделает коммит G , и заставит насочистить беспорядок и завершить объединение, чтобы создать G.

Cherry-picking

Идея вишневого пика состоит в том, чтобы copy commit.Чтобы скопировать коммит, мы можем заставить Git превратить его в набор изменений:

  • git diff --find-renames <em>hash-of-parent</em> <em>hash-of-commit</em>

Затем мы можем взять эти изменения и применить их где-нибудь вручнуюиначе, т. е. для какого-то другого коммита.Например, если у нас есть:

          C--D   <-- our-branch (HEAD)
         /
...--A--B
         \
          E--F   <-- their-branch

и нам нравится то, что они делали в F, но пока не хотим самого E, мы можем посмотреть E против F, чтобы увидеть, что они сделали.Мы можем использовать это, чтобы попытаться внести такое же изменение в наш снимок в D.Затем мы делаем новый коммит - назовем его F', что означает копия F:

          C--D--F'  <-- our-branch (HEAD)
         /
...--A--B
         \
          E--F   <-- their-branch

Но если мы внесли значительные изменения в C, или онивнесены значительные изменения в E, может быть трудно получить изменения, которые они внесли с E -to- F, в соответствие с тем, что есть в нашем снимке в D.Чтобы Git помог нам и сделал это копирование автоматически , Git хотел бы знать: что отличается между E и D? То есть Git хочет запустить:

  • git diff --find-renames <em>hash-of-E</em> <em>hash-of-D</em> (что мы имеем в C, против E)
  • git diff --find-renames <em>hash-of-E</em> <em>hash-of-F</em> (что они изменили в F)

Но подождите, мы только что видели эту же модель выше, во время git merge!И на самом деле, это именно то, что Git делает здесь: он использует тот же код , что и git merge, он просто заставляет базу слияния - которая будет B для обычного слияния - быть коммитом E, родитель коммита F, который мы собираем.Git теперь объединяет наши изменения с их изменениями, применяя объединенный набор изменений к моментальному снимку в базе - в E - и делая окончательный F' коммит самостоятельно, но на этот раз как обычный коммит.

Новый коммит также повторно использует коммит сообщение от самого коммита F, так что новый коммит F' (который имеет новый идентификатор хеша, отличный от F)очень напоминает F: git show, вероятно, показывает одинаковый или очень похожий список diff для каждого и, конечно, одно и то же сообщение журнала фиксации.

Как и в случае git merge, этот процесс слияния -то, что я люблю называть сливаться как глагол - может пойти не так.Если что-то идет не так, Git жалуется на конфликт слияния, останавливается с незаконченным слиянием и заставляет вас убрать беспорядок и зафиксировать.Когда вы делаете коммит, Git знает, что вы заканчиваете git cherry-pick, и в этот момент копирует для вас сообщение о коммите, чтобы сделать F'.

Повторная выборка повторяется по-черри

Чтобы сделать git rebase <em>target</em>, в Git:

  • перечислены коммиты, которые у вас есть в вашей ветке и которые не достижимы (технический термин: см. Think Like (a) Git из target ;
  • обрезает этот список, если необходимо - см. Ниже;
  • проверяет коммит target как «отсоединенный HEAD»";
  • несколько раз, один коммит за раз, использует git cherry-pick для копирования каждого коммита, который есть в списке. 2

После того, как всекоммиты be-copied были успешно скопированы, Git перемещает имя ветви в конец скопированного списка.

Предположим, мы начнем с настройки, аналогичной предыдущей, хотя я перечислюеще несколько коммитов здесь:

          C--D--E--F   <-- our-branch (HEAD)
         /
...--A--B
         \
          G--H   <-- their-branch

Мы запускаем git rebase their-branch, поэтому Git перечисляет коммиты для копирования: C-D-E-F, в этом порядке. Затем Git проверяет коммит H как «отделенная ГОЛОВА»:

          C--D--E--F   <-- our-branch
         /
...--A--B
         \
          G--H   <-- their-branch, HEAD

Теперь Git выберет C, чтобы скопировать его.Если все идет хорошо:

          C--D--E--F   <-- our-branch
         /
...--A--B
         \
          G--H   <-- their-branch
              \
               C'  <-- HEAD

Git повторяется для D, E и F.Как только это будет сделано D и E, мы находимся в этом состоянии:

          C--D--E--F   <-- our-branch
         /
...--A--B
         \
          G--H   <-- their-branch
              \
               C'-D'-E'  <-- HEAD

После того, как Git завершит копирование F в F', последний шаг перебазирования - вернуть имя our-branch чтобы указать на последний скопированный коммит и повторно присоединить HEAD к нему:

          C--D--E--F   [abandoned]
         /
...--A--B
         \
          G--H   <-- their-branch
              \
               C'-D'-E'-F'  <-- our-branch (HEAD)

Каждый вишневый кир выполняет одно трехстороннее слияние, причем основа слияния операции является родительскойиз копируемого коммита, а "наш" коммит - это тот, что на отдельном HEAD - отметим, что изначально это их коммит H, и по мере нашего продвижения он становится "их коммитом Hплюс наша работа "со временем.Каждый раз «их» коммит - это наш собственный коммит.Каждый вишневый кир может иметь все обычные конфликты слияния, хотя в большинстве случаев у большинства их нет.

Есть дваo случаи особенно плохие.Один из них, возможно, самый распространенный, - это когда ваши собственные коммиты, например, в списке C-D-E-F, сами выбирают что-то, что было в цепочке G-H (которая часто бывает длиннее, чем двакоммиты) - или наоборот, например, возможно H по существу D'.

Если бы вы или они смогли сделать этот вишневый отбор легко, без конфликтов, ваша копия, вероятно, выглядит почтиточно так же, или даже на 100% точно, как одна из цепей G-H.Если это так, Git может распознать, что это является такой копией, и удалить ее из списка «для копирования».В нашем примере, если H действительно D', и Git может это видеть, Git удалит D из списка, подлежащего копированию, и скопирует только C-E-F.Но если нет - если, например, им пришлось изменить свою копию D связкой, чтобы сделать H - тогда Git будет попытаться скопировать D и этиизменения почти наверняка будут конфликтовать с их измененными H.

Если вы объединяете, а не копируете, вы будете сравнивать B против H (их) и B против F (твой) и вероятность конфликтов, возможно, уменьшена.Даже если есть конфликты, они, вероятно, более очевидны и их легче разрешить.Если конфликты происходят из-за ненужной копии, по моему опыту они выглядят более хитрыми.

Другая распространенная проблема - это когда в вашей цепочке C-D-E-F последние несколько коммитов были чем-то, что вы делалиспециально для облегчения слияния.То есть кто-то мог сказать что-то вроде: мы изменили подсистему foo, теперь вам нужен третий параметр , и вы добавили третий параметр в F после того, как вишня выбрала изменение в E.Вы получите конфликты при копировании C и D.Вы можете пропустить копирование E, потому что оно является выбором вишни, и затем копирование F не требуется после устранения конфликтов в D и E, но это две копии, которыетребуется исправление, которое автоматически отбрасывается, а другое - собственное, ручное отбрасывание.

Итак, в итоге git merge выполняет одно слияние, но git rebase выполняет много вишневых пиков, каждый из которыхкоторый - внутренне - слияние, и каждый из которых может привести к конфликтам слияния.Неудивительно, что перебазирование вызывает больше конфликтов!


2 Технически, простой (неинтерактивный) git rebase часто не использует git cherry-pick.Вместо этого он, по сути, использует git format-patch ... | git am ....Использование git rebase -i всегда использует git cherry-pick, а git rebase -m заставляет неинтерактивный git rebase использовать git cherry-pick.Тот факт, что простая перебазировка позволяет избежать этого, в основном является пережитком древнего (до 2008 года или около того, вероятно) Git, до того, как вишневый пикник научился выполнять правильное трехстороннее слияние.

The git am step использует -3, поэтому в случае неудачи патча Git «отступит» к трехстороннему слиянию.Результат обычно тот же, но метод format-patch-pipe-to-am никогда не находит переименованные файлы.Это делает стиль форматирования патча быстрее , но не так хорошо.

...