Git рабочий процесс и rebase vs merge questions
Я использую 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
По существу, создайте ветвь признаков, ВСЕГДА переустановите от мастера к ветке и слейте из ветки обратно в master. Важно отметить, что ветвь всегда остается локальной.
Вот рабочий процесс, который я начал с
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, и они охватывают много слияния и едва упоминают о rebase.
Во всяком случае, я следил за своим рабочим процессом в недавнем филиале, и когда я попытался объединить его с хозяином, все это попало в ад. Было много конфликтов с вещами, которые не должны были иметь значения. Конфликты просто не имели для меня никакого смысла. Мне потребовался день, чтобы разобраться во всем, и в итоге завершился вынужденным толчком к удалённому мастеру, поскольку у моего локального хозяина все конфликты разрешены, но удаленный все еще не был счастлив.
Каков "правильный" рабочий процесс для чего-то подобного? 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
Фактически, наш рабочий процесс немного отличается, так как мы склонны делать сквош-слияния вместо сырых слияний. (Примечание. Это противоречиво, см. ниже.). Это позволяет нам превратить всю нашу ветку функций в единую фиксацию на сервере. Затем мы удалим нашу ветвь функции. Это позволяет нам логически структурировать наши фиксации на мастере, даже если они немного грязны в наших ветких. Итак, это то, что мы делаем:
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 вы просто принимаете PR. В частности, он работает следующим образом:
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. Если вы боретесь, просто придерживайтесь его, и в конце концов вы увидите свет в конце туннеля.
Ответы
Ответ 1
"Конфликты" означают "параллельные эволюции одного и того же контента". Поэтому, если во время слияния он "попадает в ад", это означает, что у вас есть значительные эволюции в одном и том же наборе файлов.
Причина, по которой перебаза лучше, чем слияние, такова:
- вы переписываете свою локальную историю фиксации с помощью одного из мастеров (а затем повторно применяете свою работу, разрешая любой конфликт).
- окончательное слияние, безусловно, будет "быстрым", потому что у него будет вся история фиксации мастера, плюс только ваши изменения для повторного применения.
Я подтверждаю, что правильный рабочий процесс в этом случае (эволюция в общем наборе файлов) сначала сворачивается, а затем объединяется.
Однако это означает, что если вы нажмете свою локальную ветвь (для причины резервного копирования), эту ветвь не следует вытаскивать (или, по крайней мере, использовать) кого-либо еще (так как история фиксации будет переписана последовательной перезагрузкой).
В этой теме (rebase then merge workflow) barraponto упоминает в комментариях два интересных сообщения, как из randyfay.com:
Используя эту технику, ваша работа всегда идет поверх публичной ветки, как исправление, актуальное с текущим HEAD
.
(аналогичный метод существует для базара)
Ответ 2
TL; DR
A git рабочий процесс переадресации не защищает вас от людей, которые плохо подходят для разрешения конфликтов, или людей, которые привыкли к рабочему процессу SVN, например, предложено в Избежать git Катастрофы: история Гори. Это только делает конфликтное разрешение более утомительным для них и затрудняет восстановление после плохого разрешения конфликтов. Вместо этого используйте diff3, чтобы это было не так сложно в первую очередь.
Рабочий процесс Rebase не лучше для разрешения конфликтов!
Я очень про-rebase для очистки истории. Однако, если я когда-либо сталкивался с конфликтом, я немедленно отменю rebase и делаю слияние вместо этого!. Меня действительно убивает то, что люди рекомендуют рабочий процесс rebase как лучшую альтернативу рабочему процессу слияния для разрешения конфликтов (что это именно то, о чем был этот вопрос).
Если во время слияния он будет "все в ад", он будет "все в ад" во время перебазирования и, возможно, намного больше ада! Вот почему:
Причина № 1: разрешать конфликты один раз, а не один раз для каждой фиксации
Когда вы пересобираете вместо слияния, вам придется выполнять разрешение конфликтов столько раз, сколько вы обязуетесь переустанавливать, для одного и того же конфликта!
Реальный сценарий
Отключение мастера для реорганизации сложного метода в ветке. Моя работа по рефакторингу состоит из 15 коммитов, поскольку я работаю над его рефакторингом и получаю обзоры кода. Часть моего рефакторинга включает в себя исправление смешанных вкладок и пробелов, которые присутствовали в главном ранее. Это необходимо, но, к сожалению, оно будет противоречить любым изменениям, внесенным впоследствии в этот метод в мастер. Разумеется, пока я работаю над этим методом, кто-то делает простое, законное изменение того же метода в главной ветке, которая должна быть объединена с моими изменениями.
Когда пришло время объединить мою ветку с мастером, у меня есть два варианта:
git merge:
У меня конфликт. Я вижу изменение, которое они сделали для освоения, и объединить его с (конечным продуктом) моей ветки. Готово.
git rebase:
Я получаю конфликт с моей первой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с моей второй фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с моей третьей фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с моей четвертой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с моей пятой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с моей шестой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с моей седьмой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с моей восьмой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с девятой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с моей десятой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с моей одиннадцатой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с моей двенадцатой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с моей тринадцатой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с четырнадцатой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Я получаю конфликт с моей пятнадцатой фиксацией. Я разрешаю конфликт и продолжаю rebase.
Вы должны шутить, если это ваш предпочтительный рабочий процесс. Все, что требуется, это пробельное исправление, которое конфликтует с одним изменением, сделанным на master, и каждая фиксация будет конфликтовать и должна быть разрешена. И это простой сценарий с конфликтом без пробелов. Небеса запрещают вам иметь реальный конфликт с основными изменениями кода в файлах и должны разрешать это несколько раз.
Со всем дополнительным разрешением конфликта, которое вам нужно сделать, это просто увеличивает вероятность того, что вы совершите ошибку. Но ошибки в git прекрасны, так как вы можете отменить, верно? Кроме конечно...
Причина № 2: с rebase нет отмены!
Я думаю, мы все можем согласиться с тем, что разрешение конфликтов может быть затруднено, а также что некоторые люди очень плохо относятся к этому. Это может быть очень склонно к ошибкам, и почему это так здорово, что git позволяет легко отменить!
При объединенииветвь, git создает слияние, которое может быть отброшено или изменено, если разрешение конфликта ухудшится. Даже если вы уже подтолкнули фиксацию плохого слияния к публичному/авторитарному репо, вы можете использовать git revert
, чтобы отменить изменения, введенные слиянием, и правильно выполнить слияние в новом компиляторе слияния.
Когда вы переформатируете ветку, в вероятном случае, если разрешение конфликта сделано неправильно, вы ввернуты. Каждая фиксация теперь содержит плохое слияние, и вы не можете просто переустановить rebase *. В лучшем случае вам нужно вернуться и внести поправки в каждый из затронутых коммитов. Не весело.
После переустановки невозможно определить, что изначально было частью коммитов и что было введено в результате плохого разрешения конфликтов.
* Может быть возможно отменить rebase, если вы можете выкопать старые refs из внутренних журналов git или создать третью ветку, которая указывает на последнюю фиксацию перед перезагрузкой.
Устранитесь от разрешения конфликта: используйте diff3
Возьмите этот конфликт, например:
<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch
Глядя на конфликт, невозможно сказать, что изменило каждая отрасль или каково его намерение. Это самая большая причина, по моему мнению, в том, почему разрешение конфликтов затруднительно и сложно.
diff3 для спасения!
git config --global merge.conflictstyle diff3
Когда вы используете diff3, каждый новый конфликт будет иметь третий раздел, объединенный общий предок.
<<<<<<< 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, передавая те же параметры. Вы также можете увидеть, что цель feature-branch - передать false вместо true для опции: include_timestamp. Чтобы объединить эти изменения, объедините цель обоих:
TextMessage.send(:include_timestamp => false)
В общем:
- Сравните общий предк с каждой ветвью и определите, какая ветка имеет самое простое изменение.
- Примените это простое изменение к другой версии ветки кода, так что оно содержит как более простые, так и более сложные изменения.
- Удалите все разделы кода конфликта, отличные от того, с которым вы только что объединили изменения, в
Альтернативный вариант: разрешите вручную, применяя изменения ветки
Наконец, некоторые конфликты ужасны для понимания даже при использовании diff3. Это происходит особенно, когда diff находит общие строки, которые не являются семантически распространенными (например, в обеих ветвях есть пустая строка в одном месте!). Например, одна ветвь меняет отступы тела класса или переупорядочивает аналогичные методы. В этих случаях лучшей стратегией разрешения может быть проверка изменений с обеих сторон слияния и ручное применение diff к другому файлу.
Посмотрим, как мы можем разрешить конфликт в сценарии, где слияние origin/feature1
где lib/message.rb
конфликты.
-
Определите, является ли наше текущее выделенное ветвление (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
-
Проверьте более сложную версию файла. Это позволит удалить все маркеры конфликтов и использовать выбранную вами сторону.
git checkout --ours -- lib/message.rb # if our branch change is more complicated
git checkout --theirs -- lib/message.rb # if origin/feature1 change is more complicated
-
С усложненным изменением выведите разницу между более простым изменением (см. шаг 1). Примените каждое изменение этого diff к конфликтующему файлу.
Ответ 3
В моем рабочем процессе я переустанавливаю как можно больше (и я стараюсь делать это часто. Не позволяя аккумулировать несоответствия, резко уменьшает количество и тяжесть столкновений между ветвями).
Однако даже в основном рабочем процессе, основанном на базах данных, есть место для слияния.
Напомним, что слияние действительно создает node, в котором есть два родителя. Теперь рассмотрим следующую ситуацию: у меня есть две независимые функциональные возможности A и B, и теперь вы хотите развить материал на ветке функций C, которая зависит как от A, так и от B, в то время как A и B. просматриваются.
То, что я делаю тогда, следующее:
- Создайте (и checkout) ветку C поверх A.
- Объединить его с B
Теперь ветвь C включает изменения от A и B, и я могу продолжить разработку на ней. Если я сделаю какое-либо изменение в A, тогда я реконструирую график ветвей следующим образом:
- создать ветку T на новой вершине A
- слияние T с B
- rebase C на T
- удалить ветвь T
Таким образом, я могу фактически поддерживать произвольные графы ветвей, но выполнение чего-то более сложного, чем описанная выше ситуация, уже слишком сложна, учитывая, что нет автоматического инструмента для восстановления при изменении родительского элемента.
Ответ 4
НЕ ИСПОЛЬЗУЙТЕ git push origin --mirror ПОСЛЕДУЮЩЕЕ КАКОЙ-ЛИБО ОБСТОЯТЕЛЬ.
Он не спрашивает, уверены ли вы, что вы хотите это сделать, и вам лучше быть уверенным, потому что он удалит все ваши удаленные ветки, которые не находятся в вашем локальном поле.
http://twitter.com/dysinger/status/1273652486
Ответ 5
У меня есть один вопрос после прочтения вашего объяснения: может быть, вы никогда не делали
git checkout master
git pull origin
git checkout my_new_feature
перед тем, как выполнить 'git rebase/merge master' в вашей ветке свойств?
Поскольку ваш главный ветвь не будет автоматически обновляться из вашего репозитория друзей. Вы должны сделать это с помощью git pull origin
. То есть может быть, вы всегда откажетесь от постоянно меняющейся локальной ветки мастера? И затем нажимайте время, вы нажимаете репозиторий, который имеет (локальный) фиксатор, который вы никогда не видели, и, таким образом, нажатие не работает.
Ответ 6
В вашей ситуации я думаю, что ваш партнер прав. Что приятное в том, что побалованность заключается в том, что посторонний ваши изменения выглядят так, как будто все они были в чистой последовательности все сами по себе. Это означает
- Ваши изменения очень просты в просмотре
- вы можете продолжать делать приятные мелкие коммиты, и все же вы можете сделать множество этих коммитов общедоступными (путем слияния с мастером) все сразу
- когда вы смотрите на ветку public master, вы увидите разные серии коммитов для разных функций разных разработчиков, но они не будут перемешаны.
Вы по-прежнему можете перенаправить вашу частную ветвь развития в удаленный репозиторий ради резервного копирования, но другие не должны рассматривать это как "общедоступную" ветвь, так как вы будете перезагружаться. BTW, простая команда для этого - git push --mirror origin
.
В статье Программное обеспечение для упаковки с использованием Git выполняет довольно приятную работу, объясняя компромиссы в слиянии и переустановке. Это немного другой контекст, но принципы одинаковы - в основном это сводится к тому, являются ли ваши ветки общедоступными или частными, и как вы планируете интегрировать их в магистраль.
Ответ 7
Во всяком случае, я следил за своим рабочим процессом в недавнем филиале, и когда я попытался объединить его с хозяином, все это попало в ад. Было много конфликтов с вещами, которые не должны были иметь значения. Конфликты просто не имели для меня никакого смысла. Мне потребовался день, чтобы разобраться во всем, и в итоге завершился вынужденным толчком к удалённому мастеру, поскольку у моего локального хозяина все конфликты разрешены, но удаленный все еще не был счастлив.
Ни ваш партнер, ни предлагаемые вами рабочие процессы не должны встречаться с конфликтами, которые не имеют смысла. Даже если бы вы были, если вы следовали предложенным рабочим процессам, то после разрешения "принудительный" толчок не требуется. Это предполагает, что вы на самом деле не объединили ветку, на которую вы нажимали, но пришлось надавить ветку, которая не была потомком удаленного наконечника.
Думаю, вам нужно внимательно посмотреть на то, что произошло. Может ли кто-то еще (намеренно или нет) перематывать удаленную мастер-ветку между созданием локальной ветки и точкой, в которой вы пытались объединить ее обратно в локальную ветвь?
По сравнению со многими другими системами управления версиями я обнаружил, что использование Git подразумевает меньшую борьбу с инструментом и позволяет вам работать над проблемами, которые являются фундаментальными для ваших исходных потоков. Git не выполняет магии, поэтому конфликтующие изменения вызывают конфликты, но это должно облегчить выполнение записи с помощью отслеживания ее фиксации.
Ответ 8
Из того, что я наблюдал, git merge имеет тенденцию удерживать ветки отдельно даже после слияния, тогда как rebase then merge объединяет его в одну ветвь.
Последнее выходит намного чище, тогда как в первом было бы легче узнать, какие коммиты принадлежат к какой ветке даже после слияния.
Ответ 9
"Даже если у вас один разработчик с несколькими ветвями, его стоит привыкнуть к правильной перестановке и слиянию. Основной шаблон работы будет выглядеть следующим образом:
-
Создать новую ветвь B из существующей ветки A
-
Добавить/зафиксировать изменения на ветке B
-
Обновите обновления из ветки A
-
Слияние изменений из ветки B на ветку A "
https://www.atlassian.com/git/tutorials/merging-vs-rebasing/
Ответ 10
С Git не существует "правильного" рабочего процесса. Используйте все, что плавает на вашей лодке. Однако, если вы постоянно сталкиваетесь с конфликтами при слиянии веток, возможно, вам лучше скоординировать свои усилия с другими разработчиками (разработчиками)? Похоже, что вы продолжаете редактировать одни и те же файлы. Кроме того, обратите внимание на пробелы и ключевые слова subversion (т.е. "$ Id $" и другие).