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 (другие?) Запросы на извлечение - Если вам интересно, как слияние / перебазирование связано с запросами на извлечение, я рекомендую выполнять все вышеописанные шаги до тех пор, пока вы не будете готовы к слиянию с мастером. Вместо ручного слияния с 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. Если вы боретесь, просто придерживайтесь этого, и в конце концов вы увидите свет в конце туннеля.

10 ответов

Решение

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

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

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

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

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


На эту тему (перебазирование, а затем слияние рабочего процесса) Capi Etheriel упоминает в комментариях два интересных поста, оба из http://www.randyfay.com/:

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

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

TL;DR

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


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

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

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

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

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

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

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

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

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

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

Ты, должно быть, шутишь, если это твой любимый рабочий процесс. Все, что требуется, - это исправление пробелов, которое конфликтует с одним изменением, внесенным в 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 к конфликтующему файлу.

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

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

Напомним, что слияние фактически создает узел, у которого есть два родителя. Теперь рассмотрим следующую ситуацию: у меня есть две независимые ветви функций 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

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

НЕ ИСПОЛЬЗУЙТЕ git push origin --mirror ПОД ПОЧТИ ЛЮБОЙ ОБСТОЯТЕЛЬСТВОМ

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

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

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

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

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

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

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

git checkout master
git pull origin
git checkout my_new_feature

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Я использую только рабочий процесс rebase, потому что он нагляднее (не только в GitKraken, но и в Intellij, и в gitk, но я больше всего рекомендую первую): у вас есть ветка, она исходит от мастера и возвращается к мастеру. Когда диаграмма станет чистой и красивой, вы поймете, что ничто никогда не пойдет к черту.

Мой рабочий процесс почти не отличается от вашего, но с одним небольшим отличием: я squash передается в один из моих локальных веток rebase моя ветка о последних изменениях на master, так как:

rebaseработает на основе каждого коммита

это означает, что если у вас есть 15 коммитов, меняющих ту же строку, что и master неужели, вы должны проверить 15 раз, если вы не сквош, но важен конечный результат, верно?

Итак, весь рабочий процесс:

  1. Оформить заказ на master и потяните, чтобы убедиться, что у вас последняя версия

  2. Оттуда создайте новую ветку

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

  4. Если кто-то скажет вам: "Эй, мой PR/MR одобрен, теперь он объединен с мастером", вы можете получить их / вытащить их. Вы можете сделать это в любое время или на шаге 6.

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

  6. Оформить заказ на master, pull еще раз, чтобы убедиться, что у вас есть последняя masterв местном. Ваша диаграмма должна быть похожей на эту:

Как видите, вы находитесь в своем локальном филиале, который имеет устаревший статус на master, пока master(как локальный, так и удаленный) продвинулся вперед со сменой вашего коллеги.

  1. Вернитесь в свою ветку и переустановите ее на master. Теперь у вас будет только одна фиксация, поэтому вы разрешаете конфликты только один раз (а в GitKraken вам нужно только перетащить свою ветку наmasterи выбираем "Rebase"; еще одна причина, почему мне это нравится.) После этого вы будете как:

  1. Итак, теперь у вас есть все изменения в последних master, в сочетании с изменениями в вашей ветке. Теперь вы можете нажать на свой пульт, и, если вы нажимали раньше, вам придется принудительно нажать; Git скажет вам, что вы не можете просто перемотать вперед. Это нормально, из-за перебазирования вы изменили начальную точку своей ветки. Но не стоит бояться: используйте силу с умом. В конце концов, пульт также является вашей веткой, поэтому вы не влияете наmaster даже если ты что-то делаешь не так.

  2. Создайте PR/MR и дождитесь его утверждения, поэтому masterбудет ваш вклад. Поздравляю! Итак, теперь вы можете оформить заказ наmaster, потяните свои изменения и удалите локальную ветвь, чтобы очистить диаграмму. Удаленную ветку тоже следует удалить, если этого не сделать, когда вы объедините ее с мастером.

Итоговая диаграмма снова чистая и ясная:

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