Merging или Rebasing?

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

Концептуальный обзор

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

Рассмотрим, что случится, когда вы начинаете работать над новой фичей в выделенной ветке, а затем другой член команды обновляет ветку master новыми коммитами. Это результат в истории ответвлений, который должен быть знаком любому, кто использовал Git для совместной работы.

Предположим, что новые коммиты в мастере релевантны фиче над которой вы сейчас работаете. Для внедрения новых коммитов в ветку feature у вас есть две возможности: слияние (merging) и перемещение (rebasing).

Вариант со слиянием (merge)

Самый легкий вариант: это слияние ветки master с веткой feature используя нечто подобное:

git checkout feature
git merge master

Или вы можете объединить это в однострочник:

git merge master feature

Эти команды создают мерж-коммит в ветке feature, который связывает вместе истории обоих веток, создавая структуру веток подобную следующему:

Объединение — это хорошо, т.к. оно не является разрушительной операцией и существующие ветки не изменяются. Это устраняет все потенциальные подводные камни выполнения ребейзинга (как обсуждается ниже).

С другой стороны, это также означает, что ветка feature будет иметь лишний мерж-коммит каждый раз при внедрении изменений. Если работа в мастере ведётся очень активно, то это может загрязнить историю ветки очень быстро. Несмотря на то, что имеется возможность уменьшить эту проблему с расширенной опцией git log, это может затруднить работу с историей проекта другим разработчикам.

Вариант перемещения (rebasing)

Вместо слияния вы можете переместить ветку feature в ветку master следующими командами:

git checkout feature
git rebase master

Это перемещает всю ветку feature на конец ветки master, эффективно объединяя все новые коммиты в master. Но вместо использования мерж-коммита, перемещение переписывает историю проекта создавая новые коммиты для каждого коммита в оригинальном бранче.

Основной выигрыш от перемещения заключается в том, что вы получаете значительно более чистую историю проекта. Во-первых, это избавляет от ненужного мерж-коммита требуемого командой git merge. Во-вторых, как вы могли видеть на вышеприведённой диаграмме, перемещение приводит к идеально ровной истории проекта – вы можете устанавливать конец ветки feature в начало проекта, без каких-либо ветвлений. Это делает лёгкой навигацию по вашему проекту с командами подобными git log, git bisect и gitk.

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

Интерактивное перемещение

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

Для начала сессии интерактивного перемещения передайте опцию i команде git rebase.

git checkout feature
git rebase -i master

Это откроет текстовый редактор, отображающий список всех коммитов, которые будут перемещены:

pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3

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

pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3

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

Золотое правило перемещения

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

Для примера, подумайте о том, что может случиться, если вы переместите в master вашу ветку feature:

Команда rebase переместит все коммиты в мастере на конец feature. Проблема в том, что это случится только в вашем репозитории. Все другие разработчики продолжают работать с оригинальным мастером. Т.к. результатом перемещения являются абсолютно новые коммиты, то git будет думать, что история вашей ветки мастер разошлась с кем-то ещё.

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

Force-Pushing

Если вы попытаетесь отправить перемещённую ветку обратно в удалённый репозиторий, то Git предотвратит выполнение этого, поскольку ветка будет конфликтовать с удаленной веткой master. Но вы можете форсировать отправку используя флаг --force, например, так:

# Будьте осторожны с этой командой!
git push --force

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

Единственный раз, когда вы должны использовать эту команду, это тогда, когда вы навели порядок в вашей приватной локальной ветке, которую перед этим вы уже отправили на удалённый репозиторий (например, для целей резервного копирования). Это выглядит подобно тому, что вы говорите: «Упс, в действительности я не хотел отправить ту оригинальную версию ветки фичи. На самом деле нужна текущая.» И вновь, важно, чтобы никто не работал с коммитами из оригинальной версии ветки.

Использование в рабочем процессе

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

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

Локальная вычистка

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

При вызове git rebase у вас есть два варианта формирования новой истории ветки: или родительская ветка, от которой произошло ответвление (например, master) или ранний коммит в вашей ветке. Мы видели пример первого варианта ранее в разделе Интерактивного перемещения. Второй вариант хорош, когда вам необходимо исправить несколько последних коммитов. Для примера, следующая команда выполнит интерактивное перемещение последних трёх коммитов.

git checkout feature
git rebase -i HEAD~3

Определяя HEAD~3 как новую базу (для истории ветки), вы в действительности не перемещаете бранч, вы всего лишь интерактивно перезаписываете три коммита, которые следуют за ним. Обратите внимание, что эта команда не интегрирует изменения из апстрима (ветка master) в ветку feature.

Если вы хотите перезаписать всю ветку фичи, то команда git merge-base может быть полезна для поиска оригинальной базы ветки. Следующая команда возвращает идентификатор коммита оригинальной базы, который мы затем может передать в git rebase:

git merge-base feature master

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

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

Не существует альтернативы git merge для вычистки локальных коммитов с интерактивным перемещением.

Интеграция изменений из апстрима в ветвь фичи

В разделе концептуального обзора мы видели как в ветвь фичи могут быть внедрены изменения из мастера используя либо git merge либо git rebase. Слияние – безопасный вариант, который сохраняет всю историю вашего репозитория, в то время как перемещение создаёт линейную историю перемещая ветвь фичи на конец ветви мастера.

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

Для примера, если вы и другой разработчик Джон добавили коммиты в ветку feature, то ваш репозиторий может выглядеть подобно следующему, после извлечения (fetching) удалённой ветки из репозитория Джона.

Вы можете разрешить эту ветвь точно таким же образом как вы интегрировали изменения из master: либо слив (merge) ваши локальную ветку feature с john/feature, или выполнив перемещение (rebase) ваших локальных изменений на кончик john/feature.

Обратите внимание, что это перемещение не нарушает золотого правила перемещения, т.к. только лишь ваши локальные коммиты перемещаются – все до этого, остается нетронутым. Это тоже самое, если сказать «добавь мои изменения к тому, что Джон уже выполнил». В большинстве обстоятельств это более интуитивно, чем синхронизация с удалённым бранчем через мерж коммит.

Ревью фичи с пул реквестом.

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

Интегрирование одобренной фичи

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

Эта ситуация подобна интеграции изменений в апстриме в ветку feature, но т.к. вам не позволено перезаписывать коммиты в ветке master, то вы должны в итоге использовать git merge для интеграции фичи в master. Однако, выполняя перемещение перед слиянием, вы уверены в том, что слияние будет выполнено режиме быстрой перемотке (fast-forwarded), с результатом идеальной ровной истории. Это также даёт вам шанс для плющения (squash) любых пришедших коммитов во время выполнения пул-реквеста.

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

git checkout feature
git checkout -b temporary-branch
git rebase -i master
# [Clean up the history]
git checkout master
git merge temporary-branch

Заключение

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

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

Оригинал статьи: https://www.atlassian.com/git/tutorials/merging-vs-rebasing
Перевод выполнил A.Saushkin специально для Цифровой Лаборатории