git: объединить две ветки: в каком направлении?

У нас такая ситуация:

             A --- B --- C --- ... --- iphone
           /
  ... --- last-working --- ... --- master

Между последним работающим и iPhone было совершено 32 фиксации. Между последним работающим и основным было сделано много коммитов.

Сейчас я хочу новую ветку, в которой iphone и текущий мастер объединены. И через некоторое время это должно быть объединено с мастером.

Сначала я планировал сделать:

git checkout iphone -b iphone31
git merge master

Но потом подумал, а не лучше ли сделать:

git checkout master -b iphone31
git merge iphone

Теперь мне интересно. В чем будет разница в результате? Будет ли слияние вести себя иначе?

Я уже пробовал оба и, как я ожидал, у меня много конфликтов, потому что iphone действительно старый по сравнению с master. Теперь я задаюсь вопросом, как проще всего их объединить.

Может быть, даже начать с мастера и объединить в него каждый коммит iphone? Как это сделать:

git checkout master -b iphone31
git merge A
git merge B
git merge C
...
git merge iphone

В самом конце, когда это слияние будет выполнено (т.е. все конфликты разрешены и оно работает), я хочу сделать следующее:

git checkout master
git merge iphone31

person Albert    schedule 28.02.2010    source источник
comment
связанные (по крайней мере, для меня): http://stackoverflow.com/questions/1241720/git-cherry-pick-vs-merge-workflow   -  person cregox    schedule 21.10.2011
comment
git checkout -b iphone31 эквивалентно git checkout iphone -b iphone31?   -  person Gerald    schedule 21.05.2015
comment
git log --first-parent будет вести себя иначе   -  person Chuck Lu    schedule 14.05.2021


Ответы (5)


Что касается альтернатив

git checkout iphone -b iphone31
git merge master

и

git checkout master -b iphone31
git merge iphone

они будут иметь одинаковую легкость или трудность, это все равно что спорить, наполовину полон стакан или наполовину пуст.

Восприятие дерева версий

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

    A----------+
    |          |
    |          |
   \_/        \_/
    B          X
    |          |
    |          |
   \_/        \_/
    C          Y
    |
    |
   \_/
    D
    |
    |
   \_/
    E

И предположим, что мы хотим, чтобы новая версия Z была извлечена из Y на основе изменений с C на E, но не включая изменения, сделанные с A на C.

«О, это будет сложно, потому что нет общей отправной точки». Ну не совсем. Если мы просто разместим объекты в графическом макете немного иначе, как это

      /
    C+---------+
    | \        |
    |          |
   \_/         |
    D          B
    |         / \
    |          |
   \_/         |
    E          A
               |
               |
              \_/
               X
               |
               |
              \_/
               Y

теперь все становится многообещающим. Обратите внимание, что здесь я не менял никаких отношений, все стрелки указывают так же, как на предыдущем рисунке, и версия A по-прежнему является общей базой. Изменена только раскладка.

Но теперь тривиально представить себе другое дерево.

    C'---------+
    |          |
    |          |
   \_/        \_/
    D          B'
    |          |
    |          |
   \_/        \_/
    E          A
               |
               |
              \_/
               X
               |
               |
              \_/
               Y

где задача состояла бы в обычном слиянии версии E.

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

Это может быть непросто с некоторыми системами / инструментами контроля версий, но если все остальное не помогает, ничто не мешает вам сделать это вручную, проверяя версию C и сохраняя файл как file1, проверяя версию E и сохраняя файл как file2 , проверяя версию Y, сохраните файл как file3 и запустите kdiff3 -o merge_result file1 file2 file3.

Отвечать

Теперь для вашей конкретной ситуации трудно точно сказать, какая стратегия вызовет наименьшее количество проблем, но если есть много изменений, которые создают какой-то конфликт, вероятно, легче разделить и объединить более мелкие части.

Мое предложение заключалось в том, что, поскольку между последним работающим и iphone есть 32 коммита, вы могли бы, например, начать с разветвления мастера, а затем объединить первые 16 коммитов. Если это окажется слишком большой проблемой, вернитесь и попробуйте объединить 8 первых коммитов. И так далее. В худшем случае вы закончите слияние каждого из 32 коммитов один за другим, но это, вероятно, будет проще, чем обрабатывать все накопленные конфликты за одну единственную операцию слияния (и в этом случае вы работаете с действительно < / em> расходящаяся база кода).

Советы:

Нарисуйте на бумаге дерево версий и отметьте стрелками, что вы хотите объединить. Вычеркивайте вещи по мере их выполнения, если вы разделите процесс на несколько этапов. Это даст вам более четкое представление о том, чего вы хотите достичь, что вы уже сделали и что осталось.

Я действительно могу порекомендовать KDiff3, это отличный инструмент для сравнения / слияния.

person hlovdal    schedule 28.02.2010
comment
И предположим, что мы хотим сделать новую версию Z извлеченной из Y на основе изменений с C на E, но не включая изменения, сделанные с A на C. Я не хочу исключать какие-либо коммиты. Я действительно не понимаю, как это связано с тем, что я хочу. Я просто хочу слить мастер с iphone. - person Albert; 28.02.2010
comment
Мое предложение заключалось в том, что, поскольку между последним работающим и iphone есть 32 коммита, вы могли бы, например, начать с разветвления мастера, а затем объединить первые 16 коммитов. Было бы легче? Разве мне не пришлось бы снова и снова разрешать одни и те же конфликты для каждой фиксации? Как будто я добавил одну строку в одну фиксацию и другую строку сразу после нее в другой фиксации. Теперь справиться с ними обоими вместе было бы проще, чем делать это дважды, не так ли? - person Albert; 28.02.2010
comment
у них будет одинаковая легкость или сложность. Это был мой вопрос. Итак, оба способа, которые я предложил вначале, дадут точно одинаковые результаты? Значит, слияние Git на 100% симметрично? - person Albert; 28.02.2010
comment
Процитирую еще раз мой главный вопрос: теперь мне интересно. Какая будет разница в результате? Будет ли слияние вести себя иначе? - person Albert; 28.02.2010
comment
Я не хочу исключать никаких коммитов. Да, я понимаю это, и эта часть моего ответа была более общей, чем это было строго необходимо, извините за то, что не уточнил это. Я хочу сказать, что вы можете объединить все, что захотите, просто укажите начальную / конечную точку. - person hlovdal; 28.02.2010
comment
Но есть ли разница в результате слияния в зависимости от того, каким способом я выполняю слияние (см. Мои 3 предложенных способа в вопросе)? - person Albert; 02.03.2010
comment
Kdiff может быть отличным, но от веб-сайта меня тошнит - person Connor; 02.08.2018

Есть разница.


Всегда проверяйте целевую ветвь во время слияния (т. е. при слиянии A с B, проверяйте B).


Люди часто говорят, что направление слияния не имеет значения, но это неверно. Хотя результирующий контент будет одинаковым независимо от направления слияния, есть несколько различных аспектов:

  1. Различия, перечисленные в итоговом коммите слияния, будут разными в зависимости от направления.
  2. Большинство визуализаторов ветвей решают, какая ветвь является «основной», используя направление слияния.

Чтобы уточнить, представьте себе этот преувеличенный пример:

  • Вы отделились от MASTER через 1000 коммитов и назвали его DEVELOP (или, в сценарии с отслеживанием веток, вы не выполняли выборку в течение некоторого времени).
  • Вы добавляете одну фиксацию в DEVELOP. Вы знаете, что это изменение не вызывает конфликтов.
  • Вы хотите отправить свои изменения в MASTER.
  • Вы неправильно объединяете MASTER в DEVELOP (т.е. DEVELOP извлекается во время слияния). Затем вы нажимаете РАЗРАБОТКУ в качестве нового МАСТЕРА.
  • Различия в результирующем слиянии-коммите покажут все 1000 коммитов, которые произошли в MASTER, потому что DEVELOP является точкой отсчета.

Эти данные не только бесполезны, но и трудно понять, что происходит. Большинство визуализаторов будут выглядеть так, как будто ваша строка DEVELOP была основной все время, с 1000 внесенными в нее коммитами.

Мое предложение таково: Всегда проверяйте целевую ветку во время слияния (т. Е. При слиянии A с B, проверяйте B).

  • Если вы работаете над параллельной ветвью и хотите периодически вносить изменения из основной ветки, проверьте свою параллельную ветвь. Различия будут иметь смысл - вы увидите изменения, сделанные в основной ветке относительно вашей ветки.
  • Когда вы закончите работу в параллельном режиме и захотите объединить свои изменения в основную ветвь, проверьте основную ветвь и слейте ее с вашей параллельной ветвью. Разница снова будет иметь смысл - она ​​покажет, какие параллельные изменения внесены в основную ветку.

Читаемость журнала, на мой взгляд, имеет значение.

person Ryuu    schedule 31.08.2017
comment
То, о чем вы говорите, - это концепция --first-parent в git, которая будет вести себя по-другому, когда вы покажете график журнала с первым родителем. Я также сделал для него образец репозитория github.com/ChuckGitMerge/FirstParentTest - person Chuck Lu; 14.05.2021

Наилучший подход действительно зависит от того, есть ли у других людей удаленные копии вашего кода. Если ветка master находится только на вашем локальном компьютере, вы можете использовать команду rebase для интерактивного применения коммитов из ветки функции в master:

git checkout master -b iphone-merge-branch
git rebase -i iphone

Обратите внимание, что это изменяет историю фиксации вашей новой ветки iphone-merge-branch, что может вызвать проблемы у всех, кто позже попытается перенести ваши изменения в свою кассу. Напротив, команда слияния применяет изменения как новую фиксацию, что безопаснее при совместной работе, поскольку не влияет на историю веток. См. эту статью для получения некоторых полезных советов по использованию перебазировать.

Если вам нужно синхронизировать историю коммитов, лучше выполнить слияние. Вы можете использовать git mergetool для интерактивного устранения конфликтов один за другим с помощью инструмента визуального сравнения (учебник по этому поводу может быть найдено здесь):

git checkout master -b iphone-merge-branch
git merge iphone
git mergetool -t kdiff3

Третий вариант, если вы хотите полностью контролировать процесс, - это использовать git cherry-pick. Вы можете использовать gitk (или ваш любимый просмотрщик истории), чтобы просмотреть хэши коммитов в ветке iphone, записать их и выбрать их по отдельности в ветке слияния, устраняя конфликты по ходу дела. Объяснение этого процесса можно найти здесь . Этот процесс будет самым медленным, но может быть лучшим вариантом, если другие методы не сработают:

gitk iphone
<note down the 35 commit SHA hashes in this branch>
git checkout master -b iphone-merge-branch
git cherry-pick b50788b
git cherry-pick g614590
...
person seanhodges    schedule 28.02.2010
comment
Какое преимущество будет у этого перебазирования перед слиянием? Я не совсем понимаю. Почему git rebase будет более умным по сравнению с git merge? Но в любом случае перебазирование здесь не вариант. - person Albert; 28.02.2010
comment
Rebasing применяет коммиты индивидуально к вашей ветке в хронологическом порядке, это значительно уменьшает конфликты, вставляя коммиты в ту точку истории, которая подходит им. Учитывая, что перебазирование для вас не вариант, я предлагаю вам изучить подход git-mergetool. - person seanhodges; 28.02.2010
comment
Но я мог бы сделать то же самое, объединяя каждый коммит по отдельности. В чем тогда будет разница в перебазировании? - person Albert; 28.02.2010
comment
При слиянии вы применяете каждый коммит поверх HEAD, который с тех пор отделился от другой ветки, вызывая конфликты. Когда вы перебазируете, вы эффективно перематываете время и применяете (не слияние) коммиты в той позиции, в которой они были сделаны. Например, фиксация в iphone, сделанная 12 января 2010 года, будет применена до фиксации, сделанной 14 февраля 2010 года в основной ветке. - person seanhodges; 28.02.2010
comment
При выполнении git checkout iphone && git rebase master, в чем разница между git checkout master && git merge A && ...? Афаик, это должно привести к тому же самому. - person Albert; 28.02.2010

Ты говоришь:

ветка iphone очень старая по сравнению с мастером

Вы действительно хотите объединить их обоих в новую ветку?

Назначение веток master и iphone теперь было бы совсем другим (потому что iphone очень старый). Было бы лучше создать новую ветку, объединяющую iphone с предком мастера? Подумай об этом.

Я настоятельно рекомендую вам прочитать Веселье со слиянием и назначением ветвей.

После прочтения этой статьи, если вы все еще чувствуете, что хотите объединить iphone и master, @seanhodges объяснит, как действительно хорошо справляться с конфликтами.

person ardsrk    schedule 28.02.2010
comment
Что ты имеешь в виду под целью? На самом деле, я хочу, чтобы изменения, которые я сделал в iphone, были главными. Когда я начал эту ветку iphone, она должна была добавить поддержку iPhone. Это закончено и работает нормально, поэтому я хочу, чтобы он был в ветке master. Мой вопрос заключался в том, есть ли какая-либо разница в двух предложенных мною подходах. Или есть более простые способы получить то, что я хочу. - person Albert; 28.02.2010
comment
Эта ссылка начинается проницательно: при объединении веток в git все ветки считаются равными. Это часто проблема, не так ли? Хотя посмотрите, где ссылка предлагает сделать противоположное тому, что вы предлагаете: If you started working on... 'frotz' back when the 'master' release was 1.0, and later the codebase of 'master' has substantially changed and [branch 'add-frotz' doesn't] cleanly merge anymore into 'master'... you could choose to change the purpose of your 'add-frotz'... to a more specific "add frotz feature in a way that it merges cleanly to the 2.0 codebase" [& merge with master first]. - person ruffin; 16.10.2012
comment
Это не ответ на вопросы OP о том, есть ли разница. Этот ответ больше похож на то, почему бы не сделать это вместо этого ?. И да, иногда мы действительно хотим объединить то, что очень далеко позади. Например, слияние изменений файла gitignore. - person Ryuu; 31.08.2017

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

Сделайте резервную копию своей работы, чтобы не потерять ее во время перебазирования, если вы хотите сохранить ее для справки.

git checkout iphone -b iphone_backup

Создать новую ветку от мастера

git checkout master -b iphone31

затем переустановите поверх него.

git rebase -i --onto iphone31 iphone

Вышеупомянутый Шон прав в отношении повторного базирования, применяя по одному коммиту за раз. Но не беспокойтесь о существующих коммитах в ветке, на которую вы переустанавливаете - они не будут изменены. Rebase помещает поверх них новые (адаптированные) коммиты. Выполняя одну фиксацию за раз, вы лучше контролируете конфликты. Однако что важно, вы должны перебазировать свою работу поверх мастера, а не наоборот, чтобы история мастера не изменилась.

В качестве альтернативы вы можете сделать только

git rebase -i master iphone

если вы не заботитесь о резервном копировании ветки iphone. Подробности и альтернативы см. В документации по перебазированию. http://git-scm.com/docs/git-rebase.

person Thinkeye    schedule 09.05.2014