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

Обзор основных моментов

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

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

Разветвленная история коммитов

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

Способ слияния (merge)

Проще всего слияние ветки master с функциональной выполняется с помощью такого кода:

git checkout feature
git merge master

При желании этот код можно записать в одну строку:

git merge feature master

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

Слияние основной ветки с функциональной

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

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

Способ перебазирования (rebase)

Вместо слияния можно выполнить перебазирование функциональной ветки (feature) на основную (master) с помощью следующих команд:

git checkout feature
git rebase master

В результате вся ветка feature окажется в конце master и объединит все новые коммиты в ветке master. Однако команда rebase не создает коммит слияния — она перезаписывает историю проекта, создавая новые коммиты для каждого коммита в исходной ветке.

Перебазирование функциональной ветки на основную

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

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

Интерактивное перебазирование

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

Чтобы запустить интерактивное перебазирование, назначьте опцию 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 merge.

Золотое правило перебазирования

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

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

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

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

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

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

Принудительная отправка изменений

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

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

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

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

Пошаговый разбор процесса

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

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

Разработка функции в отдельной ветке

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

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

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

git checkout feature
git rebase -i HEAD~3

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

Перебазирование на Head~3

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

git merge-base feature master

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

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

Очистку локальных коммитов путем интерактивного перебазирования можно выполнить только с помощью команды git merge.

Включение восходящих изменений в функциональную ветку

В разделе «Обзор основных моментов» рассматривалось включение восходящих изменений из ветки master в функциональную с помощью команды git merge или git rebase. Операцию слияния можно выполнять безопасно, поскольку при этом сохраняется вся история репозитория. При выполнении перебазирования создается линейная структура: функциональная ветка перемещается в конец master.

В этом случае команда git rebase используется так же, как и для локальной очистки (которую можно осуществить одновременно), но при ее выполнении включаются восходящие изменения из ветки master.

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

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

Совместная работа в одной функциональной ветке

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

Сравнение слияния с удаленной веткой и перебазирования на такую ветку

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

По умолчанию команда git pull выполняет слияние. Однако если назначить ей опцию --rebase, будет выполнено перебазирование для удаленной ветки.

Проверка функции с помощью запроса pull

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

Все изменения от других разработчиков нужно добавлять командой git merge вместо git rebase.

Поэтому нередко очистку кода путем интерактивного перебазирования рекомендуется проводить перед созданием запроса pull.

Внедрение утвержденной функции

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

Это похоже на включение восходящих изменений в функциональную ветку. Однако перезаписывать коммиты в ветке master запрещено, поэтому для включения функциональной ветки вам придется использовать команду git merge. Если выполнить команду rebase перед merge, произойдет ускорение слияния и вы получите абсолютно линейную историю. Этот подход также позволяет склеивать любые последующие коммиты, добавленные до закрытия запроса pull.

Внедрение функциональной ветки в основную с помощью команды rebase и без нее

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

git checkout feature
git checkout -b temporary-branch
git rebase -i master
# [Очистка истории]
git checkout master
git merge temporary-branch

Резюме

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

Однако если вам нужно сохранить полную историю проекта и избежать перезаписи публичных коммитов, воспользуйтесь командой git merge. Теперь вы можете не только применить слияние, но и оценить преимущества команды git rebase — оба варианта эффективны в работе.