The git rebase command has a reputation for being magical Git voodoo that beginners should stay away from, but it can actually make life much easier for a development team when used with care. In this article, we’ll compare git rebase with the related git merge command and identify all of the potential opportunities to incorporate rebasing into the typical Git workflow.

Conceptual Overview

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

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

A forked commit history

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

The Merge Option

The easiest option is to merge the master branch into the feature branch using something like the following:

git checkout feature
git merge master

Or, you can condense this to a one-liner:

git merge feature master

This creates a new “merge commit” in the feature branch that ties together the histories of both branches, giving you a branch structure that looks like this:

Merging master into the feature branch

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

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

The Rebase Option

As an alternative to merging, you can rebase the feature branch onto master branch using the following commands:

git checkout feature
git rebase master

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

Rebasing the feature branch onto master

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

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

Interactive Rebasing

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

To begin an interactive rebasing session, pass the i option to the git rebase command:

git checkout feature
git rebase -i master

This will open a text editor listing all of the commits that are about to be moved:

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

This listing defines exactly what the branch will look like after the rebase is performed. By changing the pick command and/or re-ordering the entries, you can make the branch’s history look like whatever you want. For example, if the 2nd commit fixes a small problem in the 1st commit, you can condense them into a single commit with the fixup command:

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

When you save and close the file, Git will perform the rebase according to your instructions, resulting in project history that looks like the following:

Squashing a commit with an interactive rebase

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

The Golden Rule of Rebasing

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

К примеру, представьте, что произойдет, если вы выполните rebase главной ветки в свою функциональную ветку.

Rebasing the master branch

The rebase moves all of the commits in master onto the tip of feature. The problem is that this only happened in your repository. All of the other developers are still working with the original master. Since rebasing results in brand new commits, Git will think that your master branch’s history has diverged from everybody else’s.

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

So, before you run git rebase, always ask yourself, “Is anyone else looking at this branch?” If the answer is yes, take your hands off the keyboard and start thinking about a non-destructive way to make your changes (e.g., the git revert command). Otherwise, you’re safe to re-write history as much as you like.

Force-Pushing

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

# Be very careful with this command!
git push --force

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

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

Workflow Walkthrough

Rebasing can be incorporated into your existing Git workflow as much or as little as your team is comfortable with. In this section, we’ll take a look at the benefits that rebasing can offer at the various stages of a feature’s development.

The first step in any workflow that leverages git rebase is to create a dedicated branch for each feature. This gives you the necessary branch structure to safely utilize rebasing:

Developing a feature in a dedicated branch

Local Cleanup

One of the best ways to incorporate rebasing into your workflow is to clean up local, in-progress features. By periodically performing an interactive rebase, you can make sure each commit in your feature is focused and meaningful. This lets you write your code without worrying about breaking it up into isolated commits—you can fix it up after the fact.

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

git checkout feature
git rebase -i HEAD~3

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

Rebasing onto Head~3

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

git merge-base feature master

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

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

Варианта очистки локальных коммитов с интерактивным использованием rebase с помощью команды git merge не существует.

Incorporating Upstream Changes Into a Feature

In the Conceptual Overview section, we saw how a feature branch can incorporate upstream changes from master using either git merge or git rebase. Merging is a safe option that preserves the entire history of your repository, while rebasing creates a linear history by moving your feature branch onto the tip of master.

This use of git rebase is similar to a local cleanup (and can be performed simultaneously), but in the process it incorporates those upstream commits from master.

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

For example, if you and another developer named John added commits to the feature branch, your repository might look like the following after fetching the remote feature branch from John’s repository:

Collaborating on the same feature branch

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

Merging vs. rebasing onto a remote branch

Note that this rebase doesn’t violate the Golden Rule of Rebasing because only your local feature commits are being moved—everything before that is untouched. This is like saying, “add my changes to what John has already done.” In most circumstances, this is more intuitive than synchronizing with the remote branch via a merge commit.

By default, the git pull command performs a merge, but you can force it to integrate the remote branch with a rebase by passing it the --rebase option.

Reviewing a Feature With a Pull Request

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

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

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

Integrating an Approved Feature

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

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

Integrating a feature into master with and without a rebase

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

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

Summary

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

On the other hand, if you want to preserve the complete history of your project and avoid the risk of re-writing public commits, you can stick with git merge. Either option is perfectly valid, but at least now you have the option of leveraging the benefits of git rebase.