Основы работы с GIT. Merge vs Rebase

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

Друзья, поддержите нас вступлением в наш телеграм канал QaRocks. Там много туториалов, задач по автоматизации и книг по QA.

Обзор концепции

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

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

История форков (forks)

Теперь предположим, что новые коммиты в ветке main имеют отношение к функции, над которой вы работаете. Чтобы включить новые коммиты в свою функциональную ветку feature, у вас есть два варианта: слияние (merge) или ребазинг (rebase).

Опция слияния (merge)

Самый простой вариант – объединить ветку main с веткой feature с помощью следующей команды:

git checkout feature
git merge main

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

git merge feature main

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

Слияние веток feature и main

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

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

Опция ребазинга (rebase)

В качестве альтернативы слиянию вы можете “перебазировать” ветку feature на ветку main, используя следующие команды:

git checkout feature
git rebase main

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

Ребазинг ветки feature поверх ветки main

Основное преимущество ребазинга заключается в том, что вы получаете гораздо более чистую историю проекта. Во-первых, это устраняет ненужные коммиты слияния, получаемые при выполнении git merge. Во-вторых, как видно на диаграмме выше, ребазинг также приводит к идеально линейной истории проекта – вы сможете отследить функционал до самого начала проекта без каких-либо форков. Это облегчает навигацию по проекту с помощью таких команд, как git loggit 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 в конец ветки 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.

Ребазинг в положение HEAD~3

Если вы хотите переписать всю функциональную ветку этим методом, команда 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 из репозитория Джона ваш репозиторий может выглядеть следующим образом:

Совместная работа над одной той же веткой feature

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

Слияние vs. ребазинг с удаленной веткой

Обратите внимание, что этот ребазинг не нарушает Золотое правило ребазинга, поскольку перемещаются только коммиты вашей ветки 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.

Интеграция функциональной ветки в ветку main с и без предварительного ребазинга

Если вам не совсем удобно работать с 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».

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *