Когда вы используете Git ReBase вместо Git Merge?


1549

Когда рекомендуется использовать Git rebase против Git merge?

Нужно ли объединять после успешной перезагрузки?




6
Одна проблема с людьми, которые любят использовать rebase, состоит в том, что это удерживает их от регулярного продвижения их кода. Так что желание чистой истории мешает им делиться своим кодом, что я считаю более важным.
static_rtti

9
@static_rtti: Это просто неправда. Вы используете неверный поток, основанный на ребазе, если он мешает вам регулярно выдвигать изменения.
Джуззлин

5
Очень жаль, что ответ Эндрю Арнотта и ответ Пейса не были опубликованы ранее, поскольку они отвечают на этот вопрос более всесторонне, чем более ранние ответы, которые уже набрали много голосов.
Марк Бут

Ответы:


1136

Укороченная версия

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

Итак, когда вы используете один из них?

Объединить

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

Rebase

  • Второй сценарий: если вы начали заниматься какой-то разработкой, а другой разработчик внес несвязанные изменения. Вы, вероятно, хотите вытащить, а затем перебазировать, чтобы внести изменения из текущей версии из репозитория.

105
@Rob упомянул поддержание временных коммитов при слиянии. Я полагаю, что по умолчанию объединение ветви B (ветвь функций, над которой вы работали) с веткой M (основной веткой) создаст один коммит в M для каждого коммита, который был сделан в B, поскольку эти два разошлись. Но если вы объединяете, используя опцию --squash, все коммиты, сделанные в ветке B, будут «объединены» и объединены как один коммит в ветке M, сохраняя журнал в вашей главной ветке красивым и чистым. Сквош - это, вероятно, то, что вы хотите, если у вас есть множество разработчиков, работающих независимо и возвращающихся в мастер.
spaaarky21

19
Я считаю, что предположение @ spaaarky21 о слиянии неверно. Если вы объедините ветвь B с ведущим M, на M будет только один коммит (даже если B имеет несколько коммитов), независимо от того, используете ли вы простое или --squash слияние. Что будет делать --squash, так это исключить ссылку на B как на родителя. Хорошая визуализация здесь: syntevo.com/smartgithg/howtos.html?page=workflows.merge
jpeskin

14
@jpeskin Это не то, что я вижу. Я только что сделал быстрый тест, чтобы проверить. Создайте каталог с текстовым файлом, initновым репо, addфайлом и commit. Оформить заказ на новую ветвь функции ( checkout -b feature.) Измените текстовый файл, подтвердите и повторите так, чтобы в ветви функции было две новые фиксации. Тогда checkout masterи merge feature. В log, я вижу мой первоначальный коммит на master, а затем два, которые были объединены из feature. Если вы merge --squash feature, функция объединена с мастером, но не зафиксирована, поэтому единственным новым коммитом на мастере будет тот, который вы сделаете сами.
spaaarky21

21
@ spaaarky21 Похоже, мы оба наполовину правы. Когда возможно ускоренное слияние (как в вашем примере), git будет по умолчанию включать все коммиты в ветвь функций B (или, как вы предлагаете, вы можете использовать --squash для объединения в один коммит). Но в случае, когда есть две расходящиеся ветви M и B, которые вы объединяете, git не будет включать в себя все отдельные коммиты из ветви B в случае слияния с M (независимо от того, используете ли вы --squash или нет).
jpeskin

6
Почему это «(вы не заботитесь о сохранении всех временных коммитов)» в стороне еще в этом ответе? Это не имело смысла в '09 и не имеет смысла сейчас. Кроме того , конечно , вы бы только хотите перебазироваться , если другой разработчик сделал изменения , связанные , что вам нужно - если они сделаны несвязанные изменения, ваша функция власть должна легко объединить без конфликтов так или иначе, и ваша история будет maintened.
Марк Бут

372

Это просто. С rebase вы говорите использовать другую ветку в качестве новой базы для вашей работы.

Если у вас есть, например, ветвь master, вы создаете ветку для реализации новой функции и говорите, что называете ее cool-feature, конечно, основная ветвь является основой для вашей новой функции.

Теперь в определенный момент вы хотите добавить новую функцию, которую вы реализовали в masterветке. Вы можете просто переключиться masterи объединить cool-featureветку:

$ git checkout master
$ git merge cool-feature

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

$ git checkout cool-feature
$ git rebase master

А затем слить это в master:

$ git checkout master
$ git merge cool-feature

На этот раз, поскольку ветка темы имеет те же коммиты master и коммиты с новой функцией, слияние будет просто ускоренной перемоткой вперед.


31
but this way a new dummy commit is added, if you want to avoid spaghetti-history- как это плохо?
ア レ ッ ク ス

6
Кроме того, флаг слияния --no-ff очень полезен.
Альдо 'xoen' Джамбеллука

3
@ ア レ ッ ク ス, как пользователь Sean Schofieldдобавляет это в комментарии: «Перебазировка также хороша, потому что как только вы в конечном итоге объединяете свои вещи обратно в master (что, как уже было описано, тривиально), вы сидите на вершине истории ваших коммитов. проекты, в которых функции могут быть написаны, но слиты через несколько недель, вы не хотите просто объединять их с мастером, потому что они «втыкаются» в мастер-путь назад в историю. Лично мне нравится иметь возможность делать git log и видеть эта недавняя функция прямо вверху. Обратите внимание, что даты фиксации сохраняются - ребаз не меняет эту информацию.
Adrien Be

4
Я думаю , что стоит повторить здесь - помнить , что все эти термины ( merge, rebase, fast-forwardи т.д.) , имеют в виду конкретных манипуляций направленного ациклического графа. Им становится легче рассуждать с этой мысленной моделью.
Рой Тинкер

10
@ Альдо Нет ничего "чистого" или "аккуратного" в перебазированной истории. Это вообще грязно и ИМХО ужасно, потому что вы понятия не имеете, что на самом деле происходит. Самая «чистая» история Git - это та, которая действительно произошла. :)
Марнен Лайбоу-Козер

269

В дополнение к моему собственному ответу, упомянутому TSamper ,

  • Перед слиянием довольно часто хорошая идея сделать это, потому что идея заключается в том, что вы интегрируете в свою ветвь Yработу ветви B, в которую вы будете сливаться.
    Но, опять же, перед объединением вы разрешаете любой конфликт в вашей ветви (то есть: «rebase», как в «воспроизвести мою работу в моей ветви, начиная с недавней точки из ветви B).
    Если все сделано правильно, последующее объединение из вашей ветви в ветка Bможет быть перемотка вперед.

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


точка слияния после ребазирования?

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

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

Git-дерево по умолчанию, когда мы не слили и не перебазировали

rebase1

мы получаем путем ребазинга:

rebase3

Этот второй сценарий полностью посвящен тому, как вернуть новую функцию в мастер.

Моя точка зрения, описывая первый сценарий перебазирования, состоит в том, чтобы напомнить всем, что перебазировка также может быть использована в качестве предварительного шага к этому (то есть, «получить новую функцию обратно в мастер»).
Вы можете использовать rebase, чтобы сначала ввести master "в" ветке новой функции: rebase будет воспроизводить коммиты новой функции из HEAD master, но все еще в ветке новой функции, эффективно перемещая начальную точку ветки со старой основной фиксации в HEAD-master.
Это позволяет вам разрешать любые конфликты в вашей ветви (то есть изолированно, в то же время позволяя master продолжать развиваться параллельно, если этап разрешения конфликтов занимает слишком много времени).
Затем вы можете переключиться на мастер и объединить new-feature(или перебазировать new-featureна, masterесли вы хотите сохранить коммиты, сделанные в вашемnew-feature филиал).

Так:

  • «перебазироваться против слияния» можно рассматривать как два способа импортировать работу, скажем, master.
  • Но «ребаз, а затем слияние» может быть допустимым рабочим процессом, чтобы сначала разрешить конфликт изолированно, а затем вернуть свою работу.

17
объединение после rebase - это тривиальное ускоренное выполнение без разрешения конфликтов.
obecalp

4
@obelcap: Действительно, это своего рода идея: вы берете на себя все проблемы-конфликты в вашей среде (перебазируйте master в своей ветке новых функций), а затем co master, объединяете new-feature: 1 пикосекунда (fast- вперед), если у мастера не было эволюции
VonC

27
Rebase также хорош, потому что, как только вы в конечном итоге объедините свои вещи обратно в master (что, как уже было описано, тривиально), они окажутся на вершине вашей истории коммитов. В более крупных проектах, где функции могут быть написаны, но слиты спустя несколько недель, вы не хотите просто объединять их с мастером, потому что они «втыкаются» в мастер-путь назад в историю. Лично мне нравится, что я могу делать git log и видеть эту недавнюю функцию прямо вверху. Обратите внимание, что даты фиксации сохраняются - rebase не меняет эту информацию.
Шон Шофилд,

3
@Joe: мысленно вы говорите: «Воспроизведите любые мои изменения (сделанные изолированно в моей частной ветке) поверх этой другой ветки, но оставьте меня в моей частной ветке, как только ребаз сделан». Это хорошая возможность очистить локальную историю, избегая «фиксации контрольных точек», разбитых пополам и неверных результатов обвинений. См. «Рабочий процесс Git»: sandofsky.com/blog/git-workflow.html
VonC

4
@scoarescoare ключ, чтобы увидеть , как вы местные изменения совместимы сверху последней входной ветви. Если один из ваших коммитов вызовет конфликт, вы сразу увидите это. Объединение вводит только одну (объединенную) фиксацию, которая может вызвать множество конфликтов без простого способа узнать, какая из ваших локальных фиксаций добавила упомянутый конфликт. Таким образом, в дополнение к более чистой истории, вы получаете более точное представление об изменениях, которые вы вносите, commit by commit (воспроизводится с помощью rebase), в отличие от всех изменений, внесенных вышестоящей веткой (сбрасывается в одно слияние).
VonC

230

TL; DR

Если у вас есть какие-либо сомнения, используйте слияние.

Короткий ответ

Единственные различия между ребазом и слиянием:

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

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

Длинный ответ

Есть несколько факторов, которые вы должны учитывать при выборе операции.

Разделяется ли ветка, от которой вы получаете изменения, с другими разработчиками за пределами вашей команды (например, с открытым исходным кодом, общедоступным)?

Если это так, не перебазируйте. Rebase уничтожает ветку, и эти разработчики будут иметь поврежденные / несовместимые репозитории, если они не используют git pull --rebase. Это хороший способ быстро расстроить других разработчиков.

Насколько опытна ваша команда разработчиков?

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

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

Представляет ли сама ветка полезную информацию

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

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

Можете ли вы отменить слияние по какой-либо причине?

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

Ты работаешь в команде? Если да, то готовы ли вы использовать подход «все или ничего» в этой отрасли?

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

Общие мифы

Слияние уничтожает историю

Предполагая, что у вас есть следующее слияние:

    B -- C
   /      \
  A--------D

Некоторые люди утверждают, что слияние «уничтожает» историю коммитов, потому что если бы вы смотрели журнал только основной ветки (A - D), вы пропустили бы важные сообщения коммитов, содержащиеся в B и C.

Если бы это было правдой, у нас не было бы таких вопросов . По сути, вы увидите B и C, если явно не попросите их не видеть (используя --first-parent). Это очень легко попробовать для себя.

Rebase позволяет более безопасные / простые слияния

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

Rebase круче / сексуальнее / профессиональнее

Если вам нравится использовать псевдоним, rmчтобы rm -rf«сэкономить время», то, возможно, ребаз для вас.

Мои два цента

Я всегда думаю, что когда-нибудь я столкнусь со сценарием, в котором Git rebase является отличным инструментом, который решает проблему. Как и я думаю, я столкнусь со сценарием, в котором Git reflog является отличным инструментом, который решает мою проблему. Я работал с Git более пяти лет. Этого не случилось.

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

Обновление (4/2017)

Я чувствую себя обязанным упомянуть, что лично смягчил использование rebase, хотя мой общий совет остается в силе. Недавно я много общался с проектом Angular 2 Material . Они использовали rebase для сохранения очень чистой истории коммитов. Это позволило мне очень легко увидеть, что фиксация исправила данный дефект и была ли эта фиксация включена в релиз. Это служит хорошим примером правильного использования rebase.


5
Должен быть подтвержденный ответ.
Mik378

Это, безусловно, лучший ответ. Особенно с уточненным замечанием в последнем обновлении. Это помогает сохранять историю git чистым и понятным, но использовать ее безопасно.
zquintana

5
Я в основном люблю этот ответ. Но: Rebase не делает «чистую» историю. Это делает более линейную историю, но это совсем не одно и то же, так как кто знает, сколько сейчас скрывается за «грязью» каждого коммита? Самая чистая и ясная история Git - это та, которая сохраняет ветвь и передает целостность.
Марнен Лайбоу-Козер

3
«Общий миф, вы видите коммиты B и C»: не обязательно !! На самом деле вы видите B и C только в том случае, если слияние было ускоренным слиянием, и это возможно только при отсутствии конфликтов. Если есть конфликты, вы получаете один коммит! Однако: вы можете сначала объединить мастер с объектом и разрешить там конфликты, а затем объединить объект с мастером, вы получите коммиты B и C и один коммит X из (первого) слияния с мастером в объект в вашей истории.
Джереми Бенкс

очень хорошее объяснение! За это должно проголосовать больше!
потухает

186

Многие ответы здесь говорят, что слияние превращает все ваши коммиты в один, и поэтому предлагают использовать rebase для сохранения ваших коммитов. Это неверно И плохая идея, если вы уже выдвинули свои коммиты .

Слияние не стирает ваши коммиты. Слияние сохраняет историю! (просто посмотрите на gitk) Rebase переписывает историю, что является плохой вещью после того, как вы ее подтолкнули .

Используйте слияние - не делайте ребаз, когда вы уже нажали.

Вот что сделал Линус (автор Git) (теперь размещенный в моем блоге, найденный Wayback Machine ). Это действительно хорошее чтение.

Или вы можете прочитать мою собственную версию той же идеи ниже.

Перебазирование ветки на мастера:

  • дает неверное представление о том, как были созданы коммиты
  • загрязняет мастера кучей промежуточных коммитов, которые, возможно, не были хорошо протестированы
  • может на самом деле вводить разрывы сборки для этих промежуточных коммитов из-за изменений, которые были внесены в мастерскую между тем, когда исходная ветка темы была создана и когда она была перебазирована.
  • затрудняет поиск хороших мест в мастере для оформления заказа.
  • Заставляет временные метки коммитов не выравниваться с их хронологическим порядком в дереве. Таким образом, вы увидите, что коммит A предшествует коммиту B в master, но коммит B был создан первым. (Какая?!)
  • Создает больше конфликтов, потому что отдельные коммиты в ветке темы могут включать конфликты слияния, которые должны быть разрешены индивидуально (дальнейшая история о том, что происходило в каждом коммите).
  • это переписать историю. Если перебазируемая ветвь была перенесена куда-либо (предоставлена ​​кому-либо, кроме вас), то вы облажались с каждым, у кого есть эта ветка, с тех пор, как вы переписали историю.

Напротив, объединение ветки темы в мастер:

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

3
Кроме того, в git merge есть опция --no-ff (без ускоренной перемотки вперед), которая позволяет вам действительно легко отменить все изменения, внесенные определенным слиянием.
Тиаго

3
Просто сделайте это более ясным: вы ссылаетесь на ситуацию «всякий раз, когда вы уже нажали» - это должно быть смелым. Ссылка на пост Линуса отличная, кстати, проясняет ее.
honzajde

2
но разве не рекомендуется «обновлять» из master в ветку темы, прежде чем объединять ветку темы в master через PR (для разрешения конфликтов в вашей ветке, а не master)? Мы делаем это так, что большинство веток темы имеют в качестве последнего коммита «объединить мастера веток в тему -...», но здесь это указано как «особенность» перебазирования, и никто не упоминает об этом для слияния ...?
Задачи

2
@AndrewArnott «Большинство веток тем должно быть в состоянии объединяться без конфликтов в целевые ветви». Как это должно быть, когда 20 разработчиков работают на 30 ветвях? Пока вы работаете над своим, произойдут слияния - поэтому, конечно, вам нужно обновить ветку темы от цели перед созданием PR ... нет?
Задачи из

3
Не обычно, @Sumit. Git может слить любое направление просто отлично, даже если были внесены изменения в одну или обе ветви. Только когда одни и те же строки кода (или очень близкие) будут изменены в двух ветвях, вы получите конфликты. Если это часто случается в какой-либо команде, команда должна переосмыслить то, как они распределяют работу, поскольку разрешение конфликтов является налогом и замедляет их.
Эндрю Арнотт

76

TLDR: это зависит от того, что является самым важным - аккуратная история или истинное представление последовательности развития

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

Если истинное представление последовательности является наиболее важным, вы бы слились без перебазирования.

Слияние означает: создать один новый коммит, который объединит мои изменения в место назначения. Примечание. У этого нового коммита будет два родителя - последний коммит из вашей строки коммитов и последний коммит другой ветви, которую вы объединяете.

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

Учитывая это, почему бы вам сделать ребаз? Просто чтобы история развития была понятной. Допустим, вы работаете над функцией X, и когда вы закончите, вы объедините свои изменения. У места назначения теперь будет один коммит, который будет что-то говорить в духе «Добавленной функции X». Теперь, вместо слияния, если вы перебазировали, а затем слили, целевая история разработки будет содержать все отдельные коммиты в одной логической последовательности. Это значительно упрощает последующее рассмотрение изменений. Представьте, как трудно было бы просмотреть историю разработки, если бы 50 разработчиков постоянно объединяли различные функции.

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

В другой раз вы, возможно, захотите сделать ребазинг, когда вы хотите избавиться от коммитов из вашей ветки, прежде чем переходить вверх по течению. Например: коммиты, которые вводят некоторый код отладки на ранних этапах, а другие коммиты в дальнейшем очищают этот код. Единственный способ сделать это - выполнить интерактивную ребазирование:git rebase -i <branch/commit/tag>

ОБНОВЛЕНИЕ: Вы также хотите использовать rebase, когда используете Git для взаимодействия с системой контроля версий, которая не поддерживает нелинейную историю ( например, Subversion ). При использовании моста git-svn очень важно, чтобы изменения, которые вы объединяете обратно в Subversion, представляли собой последовательный список изменений поверх самых последних изменений в транке. Есть только два способа сделать это: (1) заново создать изменения вручную и (2) использовать команду rebase, что намного быстрее.

ОБНОВЛЕНИЕ 2: Еще один способ думать о перебазировании заключается в том, что он обеспечивает своего рода сопоставление вашего стиля разработки со стилем, принятым в репозитории, к которому вы привязываетесь. Допустим, вам нравится совершать небольшие, маленькие кусочки. У вас есть один коммит, чтобы исправить опечатку, один коммит, чтобы избавиться от неиспользуемого кода и так далее. К тому времени, как вы закончите то, что вам нужно сделать, у вас будет длинный ряд коммитов. Теперь предположим, что репозиторий, который вы делаете, поощряет большие коммиты, поэтому для работы, которую вы делаете, можно ожидать один или, может быть, два коммита. Как вы берете свою строку коммитов и сжимаете их до ожидаемого? Вы бы использовали интерактивную перебазировку и разделили свои крошечные коммиты на меньшее количество больших кусков. То же самое верно, если нужно было обратное - если ваш стиль был несколькими большими коммитами, но хранилище требовало длинных цепочек небольших коммитов. Вы должны использовать ребаз для этого. Если вы вместо этого слились, то теперь вы перенесли свой стиль коммита в основной репозиторий. Если разработчиков много, вы можете себе представить, как трудно было бы проследить историю с несколькими различными стилями фиксации через некоторое время.

ОБНОВЛЕНИЕ 3: Does one still need to merge after a successful rebase?Да, вы делаете. Причина в том, что ребаз по сути включает в себя «сдвиг» коммитов. Как я уже говорил выше, эти коммиты рассчитываются, но если у вас было 14 коммитов с точки ветвления, то при условии, что с вашей перебазировкой все в порядке, вы будете на 14 коммитов вперед (от точки, на которую вы перебазируете) после ребаз сделан. У вас была ветка перед ребазой. У вас будет ветвь такой же длины после. Вам все еще нужно объединиться, прежде чем публиковать свои изменения. Другими словами, сделайте ребаз столько раз, сколько хотите (опять же, только если вы не выдвинули свои изменения вверх по течению). Объединять только после того, как вы сделаете ребаз.


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

1
@mbx git mergeподдерживает --no-ffопцию, которая заставляет его сделать коммит слияния.
Гэвин С. Янси

63

Хотя слияние, безусловно, самый простой и распространенный способ интеграции изменений, он не единственный: Rebase является альтернативным средством интеграции.

Понимание, слить лучше

Когда Git выполняет слияние, он ищет три коммита:

  • (1) Общий предок коммит. Если вы проследите историю двух ветвей в проекте, у них всегда будет хотя бы одна общая фиксация: на данный момент обе ветви имели одинаковое содержание, а затем развивались по-разному.
  • (2) + (3) Конечные точки каждой ветви. Целью интеграции является объединение текущих состояний двух ветвей. Поэтому их соответствующие последние изменения представляют особый интерес. Объединение этих трех коммитов приведет к интеграции, к которой мы стремимся.

Fast-Forward или Merge Commit

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

Введите описание изображения здесь

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

Введите описание изображения здесь

Однако во многих случаях обе ветви двигались вперед по отдельности.

Введите описание изображения здесь

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

Введите описание изображения здесь

Human Commits & Merge Commits

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

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

Интеграция с Rebase

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

Введите описание изображения здесь

Давайте шаг за шагом пройдемся по операции rebase. Сценарий такой же, как в предыдущих примерах: мы хотим интегрировать изменения из ветви B в ветку A, но теперь с помощью rebase.

Введите описание изображения здесь

Мы сделаем это в три этапа

  1. git rebase branch-A // Synchronises the history with branch-A
  2. git checkout branch-A // Change the current branch to branch-A
  3. git merge branch-B // Merge/take the changes from branch-B to branch-A

Во-первых, Git «отменит» все коммиты на ветви-A, которые произошли после того, как линии начали ветвиться (после коммитов общего предка). Однако, конечно, он не откажется от них: вместо этого вы можете думать об этих коммитах как о «временно спасенных».

Введите описание изображения здесь

Затем он применяет коммиты из Branch-B, которые мы хотим интегрировать. На данный момент обе ветви выглядят одинаково.

Введите описание изображения здесь

На последнем шаге новые коммиты в ответвлении A теперь повторно применяются - но на новой позиции, поверх интегрированных коммитов из ответвления B (они переоснованы).

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

Введите описание изображения здесь

Наконец, вы получаете чистую ветвь A без нежелательных и автоматически генерируемых коммитов.

Примечание: Взято из удивительном поста по git-tower. В минусы от rebaseтакже хорошо прочитать в той же должности.


+1 за очень крутые диаграммы. Я всегда хотел иметь возможность проиллюстрировать пример потока мерзавцев подобным образом без удачи.
Микаил Абдуллаев

60

Перед слиянием / перебазированием:

A <- B <- C    [master]
^
 \
  D <- E       [branch]

После git merge master:

A <- B <- C
^         ^
 \         \
  D <- E <- F

После git rebase master:

A <- B <- C <- D' <- E'

(A, B, C, D, E и F являются коммитами)

Этот пример и гораздо более наглядная информация о Git можно найти в Git The Basics Tutorial .


30

Это предложение получает это:

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

Источник: 3.6 Git Branching - Rebase, Rebase vs. Merge


25

Этот ответ широко ориентирован на Git Flow . Таблицы были получены с хорошей ASCII Таблица генератор , и история деревьев с этой замечательной командой ( псевдонимом , как git lg):

git log --graph --abbrev-commit --decorate --date=format:'%Y-%m-%d %H:%M:%S' --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%ad%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'

Таблицы расположены в обратном хронологическом порядке, чтобы больше соответствовать деревьям истории. Смотрите также разницу между первым git mergeи git merge --no-ffпервым (вы обычно хотите использовать git merge --no-ffэто, так как ваша история выглядит ближе к реальности):

git merge

Команды:

Time          Branch "develop"             Branch "features/foo"
------- ------------------------------ -------------------------------
15:04   git merge features/foo
15:03                                  git commit -m "Third commit"
15:02                                  git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Результат:

* 142a74a - YYYY-MM-DD 15:03:00 (XX minutes ago) (HEAD -> develop, features/foo)
|           Third commit - Christophe
* 00d848c - YYYY-MM-DD 15:02:00 (XX minutes ago)
|           Second commit - Christophe
* 298e9c5 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git merge --no-ff

Команды:

Time           Branch "develop"              Branch "features/foo"
------- -------------------------------- -------------------------------
15:04   git merge --no-ff features/foo
15:03                                    git commit -m "Third commit"
15:02                                    git commit -m "Second commit"
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Результат:

*   1140d8c - YYYY-MM-DD 15:04:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/foo' - Christophe
| * 69f4a7a - YYYY-MM-DD 15:03:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 2973183 - YYYY-MM-DD 15:02:00 (XX minutes ago)
|/            Second commit - Christophe
* c173472 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git merge против git rebase

Первый момент: всегда объединяйте функции в разработке, никогда не изменяйте разработку из функций . Это следствие золотого правила перебазирования :

Золотое правило git rebaseзаключается в том, чтобы никогда не использовать его в общественных ветках.

Другими словами :

Никогда не опровергайте ничего, что вы куда-то толкнули.

Я бы лично добавил: если только это не ветка, а вы и ваша команда знаете о последствиях .

Таким образом, вопрос git mergevs git rebaseотносится почти только к ветвям объектов (в следующих примерах --no-ffвсегда использовался при объединении). Обратите внимание, что, поскольку я не уверен, что есть одно лучшее решение ( существует дискуссия ), я лишь расскажу, как ведут себя обе команды. В моем случае я предпочитаю использоватьgit rebase как это дает более красивое дерево истории :)

Между функциональными ветвями

git merge

Команды:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- --------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Результат:

*   c0a3b89 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 37e933e - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   eb5e657 - YYYY-MM-DD 15:07:00 (XX minutes ago)
| |\            Merge branch 'features/foo' into features/bar - Christophe
| * | 2e4086f - YYYY-MM-DD 15:06:00 (XX minutes ago)
| | |           Fifth commit - Christophe
| * | 31e3a60 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| | |           Fourth commit - Christophe
* | |   98b439f - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \ \            Merge branch 'features/foo' - Christophe
| |/ /
|/| /
| |/
| * 6579c9c - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 3f41d96 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 14edc68 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

Команды:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git rebase features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Результат:

*   7a99663 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 708347a - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 949ae73 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 108b4c7 - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   189de99 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
| * 26835a0 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * a61dd08 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* ae6f5fc - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

От developк функциональной ветви

git merge

Команды:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m "Sixth commit"
15:08                                                                    git merge --no-ff develop
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Результат:

*   9e6311a - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 3ce9128 - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| *   d0cd244 - YYYY-MM-DD 15:08:00 (XX minutes ago)
| |\            Merge branch 'develop' into features/bar - Christophe
| |/
|/|
* |   5bd5f70 - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| * | 4ef3853 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| | |           Third commit - Christophe
| * | 3227253 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/ /            Second commit - Christophe
| * b5543a2 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * 5e84b79 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
* 2da6d8d - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git rebase

Команды:

Time           Branch "develop"              Branch "features/foo"           Branch "features/bar"
------- -------------------------------- ------------------------------- -------------------------------
15:10   git merge --no-ff features/bar
15:09                                                                    git commit -m "Sixth commit"
15:08                                                                    git rebase develop
15:07   git merge --no-ff features/foo
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Результат:

*   b0f6752 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 621ad5b - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * 9cb1a16 - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * b8ddd19 - YYYY-MM-DD 15:05:00 (XX minutes ago)
|/            Fourth commit - Christophe
*   856433e - YYYY-MM-DD 15:07:00 (XX minutes ago)
|\            Merge branch 'features/foo' - Christophe
| * 694ac81 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * 5fd94d3 - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* d01d589 - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

Примечания стороны

git cherry-pick

Когда вам нужен только один конкретный коммит, git cherry-pickэто хорошее решение ( -xопция добавляет строку с надписью " (вишня выбрана из коммита ...) » к исходному телу сообщения о коммите, поэтому обычно хорошей идеей является его использование -git log <commit_sha1> чтобы увидеть Это):

Команды:

Time           Branch "develop"              Branch "features/foo"                Branch "features/bar"
------- -------------------------------- ------------------------------- -----------------------------------------
15:10   git merge --no-ff features/bar
15:09   git merge --no-ff features/foo
15:08                                                                    git commit -m "Sixth commit"
15:07                                                                    git cherry-pick -x <second_commit_sha1>
15:06                                                                    git commit -m "Fifth commit"
15:05                                                                    git commit -m "Fourth commit"
15:04                                    git commit -m "Third commit"
15:03                                    git commit -m "Second commit"
15:02   git checkout -b features/bar
15:01   git checkout -b features/foo
15:00   git commit -m "First commit"

Результат:

*   50839cd - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop)
|\            Merge branch 'features/bar' - Christophe
| * 0cda99f - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar)
| |           Sixth commit - Christophe
| * f7d6c47 - YYYY-MM-DD 15:03:00 (XX minutes ago)
| |           Second commit - Christophe
| * dd7d05a - YYYY-MM-DD 15:06:00 (XX minutes ago)
| |           Fifth commit - Christophe
| * d0d759b - YYYY-MM-DD 15:05:00 (XX minutes ago)
| |           Fourth commit - Christophe
* |   1a397c5 - YYYY-MM-DD 15:09:00 (XX minutes ago)
|\ \            Merge branch 'features/foo' - Christophe
| |/
|/|
| * 0600a72 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo)
| |           Third commit - Christophe
| * f4c127a - YYYY-MM-DD 15:03:00 (XX minutes ago)
|/            Second commit - Christophe
* 0cf894c - YYYY-MM-DD 15:00:00 (XX minutes ago)
            First commit - Christophe

git pull --rebase

Я не уверен, что могу объяснить это лучше, чем Дерек Гурлей ... По сути, используйте git pull --rebaseвместо git pull:) Однако в статье не хватает того, что вы можете включить ее по умолчанию :

git config --global pull.rebase true

git rerere

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


15

Книга Pro Git имеет действительно хорошее объяснение на странице перебазирования .

По сути, слияние займет две фиксации и объединит их.

Ребаз будет идти к общему предку на двоих и постепенно применять изменения друг на друге. Это делает «чище» и более линейной истории.

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

Только по этой причине я почти исключительно сливаюсь. В 99% случаев мои ветви мало чем отличаются, поэтому, если возникают конфликты, это только в одном или двух местах.


1
Слияния не объединяют коммиты - это было бы переписыванием истории. Rebase делает это.
kellyfj

4

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

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

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

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


4

Если вы только один разработчик, вы можете использовать rebase вместо слияния, чтобы иметь чистую историю

введите описание изображения здесь


3

Некоторые практические примеры, в некоторой степени связанные с крупномасштабным развитием, где Геррит используется для обзора и интеграции доставки:

Я объединяюсь, когда поднимаю свою ветвь функций на новый удаленный мастер. Это дает минимальную работу по подъему и легко отслеживать историю развития функции, например, в gitk .

git fetch
git checkout origin/my_feature
git merge origin/master
git commit
git push origin HEAD:refs/for/my_feature

Я объединяюсь, когда готовлю коммит доставки.

git fetch
git checkout origin/master
git merge --squash origin/my_feature
git commit
git push origin HEAD:refs/for/master

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

git fetch
git fetch <gerrit link>
git checkout FETCH_HEAD
git rebase origin/master
git push origin HEAD:refs/for/master

3

Много раз объяснялось, что такое ребаз и что такое слияние, но когда вы должны использовать что?

Когда вы должны использовать rebase?

  • когда вы не нажали ветку / над ней никто больше не работает
  • ты хочешь полную историю
  • Вы хотите избежать всех автоматически сгенерированных сообщений "слияния .."

Как Git rebase меняет историю. Поэтому вы не должны использовать его, когда кто-то еще работает над той же веткой / если вы нажали ее. Но если у вас есть локальная ветвь, вы можете выполнить мастер слияния ребаз до слияния своей ветки с главной, чтобы сохранить более чистую историю. При этом после слияния с основной ветвью не будет видно, что вы использовали ветку в основной ветке - история «чище», поскольку у вас нет автоматически сгенерированной «слитой ..», но все еще есть полная история в вашей основной ветке без автоматического создания коммитов "слил ..".

Тем не менее, убедитесь, что вы используете, git merge feature-branch --ff-onlyчтобы убедиться, что нет конфликтов, создающих единый коммит при объединении вашей функции с основной.Это интересно, если вы используете ветви функций для каждой задачи, над которой работаете, когда вы получаете историю ветви функций, а не коммит "объединенный .."

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

Когда вы должны использовать слияние?

  • когда вы нажали ветку / другие тоже работают над этим
  • вам не нужна полная история
  • достаточно просто слияния

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


-4

Когда я использую git rebase? Почти никогда, потому что переписывает историю. git mergeпочти всегда предпочтительный выбор, потому что он уважает то, что на самом деле произошло в вашем проекте.


1
@benjaminhull Спасибо! - кроме того, я надеюсь, что мой ответ основан на фактах. ИМХО мнение не имеет большого значения в подобных вещах: факт, что потеря вашей реальной истории усложняет жизнь позже.
Марнен Лайбоу-Козер

1
Согласен. Слияние никогда не приведет к поврежденной истории и т. Д. (Когда вы перебазируете свои выдвинутые коммиты)
surfrider
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.