Команда git rebase имеет репутацию магического фокуса, от которого новичкам следует держаться подальше, но на самом деле она может значительно облегчить жизнь команде разработчиков, если использовать ее с осторожностью. В этой статье мы сравним git rebase с родственной командой git merge и определим все потенциальные возможности для включения git rebase в типичный рабочий процесс с Git.
Друзья, поддержите нас вступлением в наш телеграм канал QaRocks. Там много туториалов, задач по автоматизации и книг по QA.
Обзор концепции
Первое, что нужно понять о команде git rebase это то, что она решает ту же проблему, что и git merge. Обе эти команды предназначены для интеграции изменений из одной ветки в другую — просто они делают это совершенно разными способами.
Подумайте, что происходит, когда вы начинаете работать над новой функцией в отдельной ветке, а другой участник команды добавляет новые коммиты в главную ветку main. В результате возникает история форков (forks), которая должна быть знакома всем, кто использовал Git в качестве инструмента для совместной работы.

Теперь предположим, что новые коммиты в ветке main имеют отношение к функции, над которой вы работаете. Чтобы включить новые коммиты в свою функциональную ветку feature, у вас есть два варианта: слияние (merge) или ребазинг (rebase).
Опция слияния (merge)
Самый простой вариант — объединить ветку main с веткой feature с помощью следующей команды:
git checkout feature git merge main
При желании этот код можно записать в одну строку:
git merge feature main
Это создаст в ветке feature новый «коммит слияния», который свяжет воедино истории обеих веток, что даст вам новую структуру ветки, которая выглядит следующим образом:

Слияние удобно тем, что при нем существующие ветки никак не изменяются. Это позволяет избежать всех потенциальных проблем, связанных с ребазингом (о них речь пойдет ниже).
С другой стороны, это означает, что каждый раз, когда вам будет необходимо включить вышестоящие изменения, в функциональную ветку feature будет попадать внешний коммит слияния. Если работа в главной ветке main ведется активно, история вашей функциональной ветки быстро засорится. Хотя эту проблему можно устранить, используя продвинутые варианты команды git log, другим разработчикам будет тяжело разобраться в истории проекта.
Опция ребазинга (rebase)
В качестве альтернативы слиянию вы можете «перебазировать» ветку feature на ветку main, используя следующие команды:
git checkout feature git rebase main
Это перемещает всю ветку feature поверх ветки main, включая в себя все новые коммиты в main. Однако, в отличие от команды слияния, ребазинг переписывает историю проекта, создавая совершенно новые коммиты для каждого коммита в исходной ветке.

Основное преимущество ребазинга заключается в том, что вы получаете гораздо более чистую историю проекта. Во-первых, это устраняет ненужные коммиты слияния, получаемые при выполнении git merge. Во-вторых, как видно на диаграмме выше, ребазинг также приводит к идеально линейной истории проекта — вы сможете отследить функционал до самого начала проекта без каких-либо форков. Это облегчает навигацию по проекту с помощью таких команд, как git log, git bisect и gitk.
Но у такого способа есть два недостатка: безопасность и отслеживаемость. Если вы не следуете золотому правилу ребазинга, то переписывание истории проекта может стать катастрофой для вашего рабочего процесса. Кроме того, при ребазинге теряется контекст, обеспечиваемый коммитом слияния — вы не сможете увидеть, когда изменения, внесенные выше, были включены в функциональность.
Интерактивный ребазинг
Интерактивный ребазинг дает вам возможность изменять коммиты по мере их перемещения в новую ветку. Этот вариант предоставляет еще больше возможностей, чем автоматическое выполнение rebase, поскольку дает полный контроль над историей коммитов ветки. Обычно он используется для очистки истории перед слиянием функциональной ветки с главной веткой main.
Чтобы начать интерактивную сессию ребазинга, передайте параметр i в команду git rebase:
git checkout feature git rebase -i main
Это откроет текстовый редактор со списком всех коммитов, которые будут перемещены:
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 выполнит rebase в соответствии с вашими инструкциями, в результате чего история проекта будет выглядеть следующим образом:

Удаление таких незначительных коммитов помогает быстрее разобраться в истории функциональной ветки. Это то, что git merge просто не может сделать.
Золотое правило ребазинга
Как только вы поймете, что такое ребазинг, самое важное, что нужно усвоить, — это когда не стоит его делать. Золотое правило команды git rebase заключается в том, чтобы никогда не использовать её в публичных ветках.
Например, подумайте, что произойдет, если выполнить ребазинг главной ветки main на вашу функциональную ветку feature:

Ребазинг перемещает все коммиты из ветки main в конец ветки feature. Проблема в том, что это происходит только в вашем репозитории. Все остальные разработчики по-прежнему работают с исходной веткой main. Поскольку ребазинг приводит к появлению совершенно новых коммитов, Git будет считать, что история вашей ветки main разошлась со всеми остальными.
Единственный способ синхронизировать две ветки main — это слить их обратно вместе, в результате чего появится дополнительный коммит слияния и два набора коммитов, которые содержат одни и те же изменения (исходные изменения и изменения из вашей ветки после выполнения команды rebase). Нет нужды говорить, что это очень запутанная ситуация.
Поэтому, прежде чем выполнить git rebase, всегда спрашивайте себя: «А кто-нибудь еще просматривает эту ветку?». Если ответ положительный, уберите руки с клавиатуры и подумайте об ином способе внесения изменений (например, о команде git revert).
Принудительный пуш (Force-pushing)
Если вы попытаетесь поместить переделанную ветку main обратно в удаленный репозиторий, Git не даст вам этого сделать, поскольку она конфликтует с удаленной веткой main. Но вы можете принудительно выполнить команду push, передав параметр --force следующим образом:
# Be very careful with this command! git push --force
Это действие перезапишет удалённую ветку main, чтобы она соответствовала переделанной ветке из вашего репозитория, что, в свою очередь, может внести путаницу в работу остальных членов вашей команды. Поэтому будьте очень осторожны и используйте эту команду только тогда, когда вы точно знаете, что делаете.
Одна из немногих ситуаций, требующих выполнения принудительного пуша, — это локальная очистка после помещения частной функциональной ветки в удаленный репозиторий (например, для создания резервной копии). Это все равно что сказать: «Упс, я не очень-то хотел отправлять исходную версию этой функциональной ветки. Возьмите вместо нее текущую». Опять же, важно, чтобы никто не работал с коммитами из исходной версии функциональной ветки.
Описание рабочего процесса
В этом разделе мы рассмотрим преимущества, которые может дать ребазинг на различных этапах разработки функций продукта.
Первым шагом в любом рабочем процессе, использующем git rebase является создание отдельной ветки для каждой функции. Это даст вам необходимую структуру веток для безопасного использования команды rebase:

Локальная очистка
Один из лучших способов включить ребазинг в рабочий процесс — это очистка локальных функциональных веток, в которых еще ведется работа. Периодически выполняя интерактивный ребазинг, вы можете убедиться, что ни один коммит в вашей ветке не потеряет смысла. Вы сможете быть уверены, что ваш код не распадется на изолированные коммиты. Если это случится, ситуацию всегда можно будет исправить.
Когда вы вызываете git rebase, у вас есть два варианта для нового положения ветки: родительская ветка для функциональной ветки (например, main) или более ранний коммит в функциональной ветке. Пример первого варианта мы рассмотрели в разделе Интерактивный ребазинг. Последний вариант удобен, когда вам нужно исправить только несколько последних коммитов. Например, описанная ниже команда запускает интерактивный ребазинг только последних 3 коммитов:
git checkout feature git rebase -i HEAD~3
Указав HEAD~3 в качестве нового положения, вы фактически не перемещаете ветку, а лишь интерактивно переписываете 3 коммита, которые следуют за ней. Обратите внимание, что это не включит вышестоящие изменения в ветку feature.

Если вы хотите переписать всю функциональную ветку этим методом, команда git merge-base поможет вам найти начальное положение этой ветки. Следующая команда возвращает возвращает ID коммита начального положения, который затем можно передать в команду git rebase:
git merge-base feature main
Такое использование интерактивного ребазинга — отличный способ внедрить git rebase в ваш рабочий процесс, поскольку он затрагивает только локальные ветки. Единственное, что увидят другие разработчики, — это ваш конечный продукт, который должен представлять собой чистую, легко читаемую историю ветки feature.
Но, опять же, это работает только для частных функциональных веток. Если вы сотрудничаете с другими разработчиками через одну и ту же ветку, эта ветка является публичной, и вам не разрешается переписывать ее историю.
Варианта очистки локальных коммитов с интерактивным использованием rebase с помощью команды git merge не существует.
Внесение изменений из репозитория другого разработчика
В разделе Обзор концепции мы рассмотрели, как включить вышестоящие изменения из ветки main в функциональную ветку с помощью команд git merge или git rebase. Слияние — это безопасный вариант, который сохраняет всю историю вашего репозитория, в то время как rebase создает линейную историю, перемещая вашу функциональную ветку в конец ветки main.
Это использование git rebase похоже на локальную очистку, но при ее выполнении включаются вышестоящие изменения из главной ветки main.
Помните, что вы можете делать ребазинг в удаленную ветку, отличную от ветки main. Это может произойти, когда вы работаете над одной и той же функцией с другим разработчиком и вам нужно включить его изменения в свой репозиторий.
Например, если вы и другой разработчик по имени Джон добавили коммиты в ветку feature, то после получения удаленной ветки feature из репозитория Джона ваш репозиторий может выглядеть следующим образом:

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

Обратите внимание, что этот ребазинг не нарушает Золотое правило ребазинга, поскольку перемещаются только коммиты вашей ветки feature, тогда как предшествующие элементы остаются нетронутыми. Это все равно что сказать: «Добавьте мои изменения к тому, что уже сделал Джон». В большинстве случаев это более интуитивно понятно, чем синхронизация с удаленной веткой с помощью коммита слияния.
По умолчанию команда git pull выполняет слияние. Однако если передать ей параметр --rebase, будет выполнен ребазинг удаленной ветки.
Пул-реквест (pull request)
Если вы используете пул-реквесты как часть процесса проверки кода, вам следует избегать использования git rebase после создания пул-реквеста. Как только вы создадите pull request, другие разработчики смогут видеть ваши коммиты, то есть ваша ветка станет публичной. В случае перезаписи ее истории Git и ваши коллеги не смогут отслеживать последующие коммиты в функциональную ветку.
Любые изменения от других разработчиков должны быть включены с помощью команды git merge, а не git rebase.
По этой причине, как правило, рекомендуется очистить код с помощью интерактивного ребазинга до подачи запроса на pull request.
Интеграция утвержденной функции
После того как функция была одобрена вашей командой, у вас есть возможность перенести её на конец ветки main, прежде чем использовать git merge для интеграции функции в основную кодовую базу.
Эта ситуация похожа на включение вышестоящих изменений в функциональную ветку, но поскольку вам не разрешено переписывать коммиты в ветке main, вам придется в конечном итоге использовать git merge для интеграции фич. Однако, выполнив rebase перед слиянием, вы обеспечите ускоренное слияние и идеальную линейную историю проекта. Это также даст вам возможность совместить любые последующие коммиты, добавленные до закрытия запроса pull.

Если вам не совсем удобно работать с git rebase, вы всегда можете выполнить rebase во временную ветку. Таким образом, если вы случайно испортите историю своей функциональной ветки, вы всегда сможете переключиться в исходную ветку и попробовать снова. Например:
git checkout feature git checkout -b temporary-branch git rebase -i main # [Clean up the history] git checkout main git merge temporary-branch
Резюме
И это всё, что вам нужно знать, чтобы приступить к ребазингу ваших веток. Если вы предпочитаете чистую, линейную историю, свободную от ненужных коммитов слияния, вам следует использовать git rebase вместо git merge при интеграции изменений из другой ветки.
С другой стороны, если вы хотите сохранить полную историю своего проекта и избежать перезаписи публичных коммитов, воспользуйтесь командой git merge. Любой из этих вариантов вполне приемлем, но теперь у вас, по крайней мере, есть возможность использовать преимущества git rebase.
Перевод статьи «Merging vs. rebasing».

Пингбэк: Команды Git для QA автоматизации