it-roy-ru.com

Git рабочий процесс и ребаз против вопросов слияния

Я использую Git уже пару месяцев в проекте с одним другим разработчиком. У меня есть несколько лет опыта работы с SVN , поэтому я думаю, что я привнес много багажа в отношения.

Я слышал, что Git отлично подходит для ветвления и слияния, и пока я просто не вижу этого. Конечно, ветвление просто невероятно, но когда я пытаюсь слиться, все идет к черту. Теперь я привык к этому из SVN, но мне кажется, что я просто обменял одну систему контроля версий на другую.

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

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature
git checkout master
git merge my_new_feature

По сути, создайте ветвь объектов, ВСЕГДА перебирайте с мастера на ветку и сливайте с ветки обратно на ветку. Важно отметить, что филиал всегда остается локальным.

Вот рабочий процесс, с которого я начал

clone remote repository
create my_new_feature branch on remote repository
git checkout -b --track my_new_feature Origin/my_new_feature
..work, commit, Push to Origin/my_new_feature
git merge master (to get some changes that my partner added)
..work, commit, Push to Origin/my_new_feature
git merge master
..finish my_new_feature, Push to Origin/my_new_feature
git checkout master
git merge my_new_feature
delete remote branch
delete local branch

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

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

Мое рассуждение о слиянии вместо rebase заключается в том, что слияние кажется стандартным, а rebase - расширенной функцией. У меня такое ощущение, что я пытаюсь не продвинутая настройка, поэтому ребаз должен быть ненужным. Я даже просмотрел новую книгу "Прагматическое программирование" по Git, и в ней подробно рассматриваются слияния и едва упоминается ребазинг.

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

Каков "правильный" рабочий процесс для чего-то подобного? Git должен сделать ветвление и слияние очень легким, и я просто не вижу этого.

Обновление 2011-04-15

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

Оказывается, исходный рабочий процесс корректен, по крайней мере, в нашем случае. Другими словами, это то, что мы делаем, и это работает:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge my_new_feature

На самом деле, наш рабочий процесс немного отличается, так как мы склонны делать сквош-слияния вместо сырых слияний. ( Примечание. Это противоречиво, см. Ниже. ) Это позволяет нам превратить всю ветвь функции в один коммит на master. Затем мы удаляем нашу ветку функций. Это позволяет нам логически структурировать наши коммиты на master, даже если они немного запутаны в наших ветках. Итак, вот что мы делаем:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge --squash my_new_feature
git commit -m "added my_new_feature"
git branch -D my_new_feature

Противоречие слиянием сквоша - Как указали несколько комментаторов, слияние сквоша отбросит всю историю в вашей ветви функций. Как следует из названия, он сводит все коммиты в один. Для небольших функций это имеет смысл, поскольку сводит их в один пакет. Для более крупных функций это, вероятно, не очень хорошая идея, особенно если ваши отдельные коммиты уже атомарны. Это действительно сводится к личным предпочтениям.

Github и Bitbucket (другие?) Pull Requests - Если вам интересно, как слияние/ребазирование связано с Pull Requests, я рекомендую выполнить все вышеуказанные шаги до тех пор, пока вы не будете готовы вернуться к мастеру. Вместо ручного слияния с git, вы просто принимаете пиар. Обратите внимание, что это не будет выполнять слияние с помощью сквоша (по крайней мере, по умолчанию), но без сквоша, без ускоренной перемотки является принятым соглашением о слиянии в сообществе запросов на извлечение (насколько я знаю). В частности, это работает так:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git Push # May need to force Push
...submit PR, wait for a review, make any changes requested for the PR
git rebase master
git Push # Will probably need to force Push (-f), due to previous rebases from master
...accept the PR, most likely also deleting the feature branch in the process
git checkout master
git branch -d my_new_feature
git remote Prune Origin

Я полюбил Git и больше не хочу возвращаться в SVN. Если вы боретесь, просто придерживайтесь этого, и в конце концов вы увидите свет в конце туннеля.

929
Micah

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

Причина, по которой ребазинг тогда лучше, чем слияние, заключается в том, что:

  • вы переписываете свою локальную историю коммитов с одной из основных (а затем повторно применяете свою работу, разрешая тогда любой конфликт)
  • окончательное слияние, безусловно, будет "ускоренной перемоткой вперед", потому что оно будет иметь всю историю коммитов мастера, плюс только ваши изменения для повторного применения.

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

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


По этой теме (перебазировать, а затем объединить рабочий процесс) barraponto упоминает в комментариях два интересных сообщения, оба из randyfay.com :

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

(похожая техника существует для базара )

360
VonC

TL; DR

Рабочий процесс git rebase не защищает вас от людей, плохо разбирающихся в разрешении конфликтов, или от людей, привыкших к рабочему процессу SVN, как это было предложено в Избежание Git Disasters: История Гори . Это только делает разрешение конфликтов более утомительным для них и затрудняет восстановление после плохого разрешения конфликтов. Вместо этого используйте diff3, чтобы это не было так сложно в первую очередь.


Перебазировать рабочий процесс не лучше для разрешения конфликтов!

Я очень сторонник перебазирования за очистку истории. Однако, если я когда-либо сталкиваюсь с конфликтом, я немедленно прекращаю ребазирование и вместо этого выполняю слияние! Меня действительно убивает, что люди рекомендуют рабочий процесс ребаз в качестве лучшая альтернатива рабочему процессу слияния для разрешения конфликтов (именно об этом и был этот вопрос).

Если во время слияния он пойдет "все в ад", то во время перебазировки он пойдет "все в ад", и, возможно, намного больше в ад! Вот почему:

Причина № 1: Разрешать конфликты один раз, а не один раз для каждого коммита

Когда вы перебазируете вместо слияния, вам придется выполнять разрешение конфликтов столько раз, сколько вы выполняете перебазирование, для одного и того же конфликта!

Реальный сценарий

Я разветвляюсь от мастера, чтобы провести рефакторинг сложного метода в ветке. Моя работа по рефакторингу состоит из 15 коммитов, так как я работаю над ее рефакторингом и получаю обзоры кода. Часть моего рефакторинга включает исправление смешанных вкладок и пробелов, которые присутствовали в master раньше. Это необходимо, но, к сожалению, оно будет конфликтовать с любыми изменениями, внесенными впоследствии в этот метод в master. Конечно, пока я работаю над этим методом, кто-то вносит простое, законное изменение в тот же метод в основной ветке, который должен быть объединен с моими изменениями.

Когда пришло время объединить мою ветку с master, у меня есть два варианта:

git merge: Я получил конфликт. Я вижу изменения, которые они сделали, чтобы освоить и объединить их с (конечным продуктом) моей ветки. Готово.

git rebase: Я получаю конфликт с моим первым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получаю конфликт с моим вторым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получаю конфликт с моим третьим коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получил конфликт с моим четвертым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получил конфликт с моим пятым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получил конфликт с моим шестым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получил конфликт с моим седьмым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получаю конфликт с моим восьмым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получил конфликт с моим девятым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получаю конфликт с моим десятым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получаю конфликт с моим одиннадцатым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получаю конфликт с моим двенадцатым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получил конфликт с моим тринадцатым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получаю конфликт с моим четырнадцатым коммитом. Я разрешаю конфликт и продолжаю перебазирование. Я получаю конфликт с моим пятнадцатым коммитом. Я разрешаю конфликт и продолжаю перебазирование.

Вы, должно быть, шутите, если this - ваш предпочтительный рабочий процесс. Все, что требуется, - это исправление пробелов, которое конфликтует с одним изменением, внесенным в master, и каждый коммит будет конфликтовать и должен быть разрешен. И это простой сценарий с конфликтом только пробелов. Слава Богу, у вас есть реальный конфликт, связанный с серьезными изменениями кода в файлах, и вы должны разрешить это несколько раз.

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

Причина № 2: с rebase, нет отмены!

Я думаю, что мы все можем согласиться с тем, что разрешение конфликтов может быть трудным, а также что некоторые люди очень плохо в этом. Он может быть очень подвержен ошибкам, поэтому он так хорош, что git позволяет легко его отменить!

Когда вы объединяете ветвь, git создает коммит слияния, который можно отменить или изменить, если разрешение конфликта идет плохо. Даже если вы уже отправили неудачный коммит слияния в публичное/авторитетное репо, вы можете использовать git revert, чтобы отменить изменения, внесенные слиянием, и правильно повторить слияние в новом коммите слияния.

Когда вы перебазируете ветку, в вероятном случае, если разрешение конфликта сделано неправильно, вы облажались. Каждый коммит теперь содержит неверное слияние, и вы не можете просто повторить ребаз *. В лучшем случае вы должны вернуться и внести поправки в каждый из затронутых коммитов. Не смешно.

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

* Может быть возможно отменить ребазинг, если вы можете выкопать старые ссылки из внутренних журналов git или если вы создадите третью ветку, которая указывает на последний коммит перед ребазингом.

Избавьтесь от разрешения конфликта: используйте diff3

Возьмите этот конфликт, например:

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

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

на помощь diff3!

git config --global merge.conflictstyle diff3

Когда вы используете diff3, каждый новый конфликт будет иметь 3-й раздел, объединенный общий предок.

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
||||||| merged common ancestor
EmailMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

Сначала изучите объединенного общего предка. Затем сравните каждую сторону, чтобы определить намерение каждой ветви. Вы можете видеть, что HEAD изменил EmailMessage на TextMessage. Его цель - изменить используемый класс на TextMessage, передавая те же параметры. Вы также можете видеть, что намерение ветви функции - передать false вместо true для опции: include_timestamp. Чтобы объединить эти изменения, объедините намерение обоих:

TextMessage.send(:include_timestamp => false)

В общем:

  1. Сравните общего предка с каждой ветвью и определите, какая ветвь имеет самое простое изменение
  2. Примените это простое изменение к версии кода другой ветви, чтобы оно содержало как более простые, так и более сложные изменения.
  3. Удалите все секции кода конфликта, кроме той, в которую вы только что слили изменения вместе

Альтернатива: разрешить вручную, применяя изменения ветви

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

Давайте посмотрим, как мы можем разрешить конфликт в сценарии, где происходит слияние Origin/feature1, где lib/message.rb конфликтует.

  1. Решите, является ли наша ветвь, извлеченная в данный момент (HEAD, или --ours), или веткой, которую мы объединяем (Origin/feature1, или --theirs), более простым изменением, чтобы применить его. Использование diff с тройной точкой (git diff a...b) показывает изменения, которые произошли в b с момента его последнего отклонения от a, или, другими словами, сравните общего предка a и b с b.

    git diff HEAD...Origin/feature1 -- lib/message.rb # show the change in feature1
    git diff Origin/feature1...HEAD -- lib/message.rb # show the change in our branch
    
  2. Проверьте более сложную версию файла. Это удалит все маркеры конфликта и будет использовать ту сторону, которую вы выберете.

    git checkout --ours -- lib/message.rb   # if our branch's change is more complicated
    git checkout --theirs -- lib/message.rb # if Origin/feature1's change is more complicated
    
  3. Сложное изменение проверено, найдите различие более простого изменения (см. Шаг 1). Примените каждое изменение из этого diff к конфликтующему файлу.

373
Edward Anderson

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

Однако даже в основном процессе, основанном на ребазе, есть место для слияний.

Напомним, что слияние фактически создает узел, у которого есть два родителя. Теперь рассмотрим следующую ситуацию: у меня есть две независимые ветви функций A и B, и теперь я хочу разработать материал для ветви функций C, который зависит как от A, так и от B, в то время как A и B рассматриваются.

Что я тогда делаю, так это следующее:

  1. Создать (и оформить заказ) ветку C поверх A.
  2. Объединить это с B

Теперь ветвь C включает в себя изменения как от A, так и от B, и я могу продолжать развивать ее. Если я сделаю какие-либо изменения в A, то я восстановлю график ветвей следующим образом:

  1. создать ветку T на новой вершине A
  2. объединить T с B
  3. перебазировать С на Т
  4. удалить ветку T

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

31
Alex Gontmakher

НЕ ИСПОЛЬЗУЙТЕ git Push Origin --mirror В СООТВЕТСТВИИ С ЛЮБОЙ ЛЮБОЙ ОБСТОЯТЕЛЬНОСТЬЮ.

Он не спрашивает, уверены ли вы, что хотите это сделать, и вам лучше быть уверенным, потому что он удалит все ваши удаленные ветви, которые не находятся в вашем локальном ящике.

http://Twitter.com/dysinger/status/1273652486

21
Scott Brown

У меня есть один вопрос после прочтения вашего объяснения: может быть, вы никогда не делали

git checkout master
git pull Origin
git checkout my_new_feature

перед выполнением 'git rebase/merge master' в вашей ветке функций?

Потому что ваша ветка master не будет обновляться автоматически из репозитория вашего друга. Вы должны сделать это с помощью git pull Origin. То есть может быть, вы всегда будете перебазировать из неизменной локальной ветки master? И затем наступает время Push, вы загружаете репозиторий, в котором есть (локальные) коммиты, которые вы никогда не видели, и, следовательно, Push не работает.

14
knweiss

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

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

Вы все еще можете продолжать отправлять свою частную ветку разработки в удаленное хранилище для резервного копирования, но другие не должны воспринимать это как "публичную" ветку, так как вы будете перебазировать. Кстати, простая команда для этого - git Push --mirror Origin.

Статья Упаковка программного обеспечения с использованием Git довольно неплохо объясняет компромиссы между слиянием и перебазированием. Это немного другой контекст, но принципы одинаковы - в основном все зависит от того, являются ли ваши филиалы общедоступными или частными, и как вы планируете интегрировать их в основную ветку.

13
Pat Notz

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

Ни в вашем партнёре, ни в предложенных вами рабочих процессах вы не должны сталкиваться с конфликтами, которые не имеют смысла. Даже если у вас есть, если вы следуете предлагаемым рабочим процессам, после разрешения "принудительный" толчок не требуется. Это говорит о том, что вы на самом деле не слили ветку, в которую вы давили, но вам пришлось нажать ветку, которая не была потомком удаленного наконечника.

Я думаю, вам нужно внимательно посмотреть на то, что произошло. Может ли кто-то другой (намеренно или нет) перемотать удаленную главную ветку между созданием локальной ветки и точкой, в которой вы попытались объединить ее с локальной веткой?

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

12
CB Bailey

Из того, что я наблюдал, git merge имеет тенденцию держать ветви разделенными даже после слияния, тогда как rebase затем объединяет их в одну ветвь. Последнее получается намного чище, тогда как в первом было бы легче выяснить, какие коммиты принадлежат какой ветви, даже после слияния.

7
Pepe

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

  • Создать новую ветку B из существующей ветки A

  • Добавить/зафиксировать изменения в ветке B

  • Перебазировать обновления из ветки А

  • Объединить изменения из ветви B в ветку A "

https://www.atlassian.com/git/tutorials/merging-vs-rebasing/

6
Rakka Rage

В Git нет "правильного" рабочего процесса. Используйте все, что плавает на вашей лодке. Однако, если вы постоянно сталкиваетесь с конфликтами при объединении веток, возможно, вам следует лучше координировать свои усилия с коллегами-разработчиками? Похоже, вы двое продолжаете редактировать одни и те же файлы. Кроме того, следите за пробелами и ключевыми словами Subversion (т. Е. "$ Id $" и другими).

2
Bombe