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.

考え方の概要

git rebase で最初に理解すべきことは、git merge と同じ問題を解決するということです。これらのコマンドは両方とも、変更を1つのブランチから別のブランチに統合することを目的に設計されています。ただし、実現する方法は非常に異なっています。

専用ブランチの新しいフィーチャーで作業を開始し、その後で別のチームメンバーが master ブランチを新しいコミットで更新すると何が生じるか考えてみましょう。結果として、フォーク済み履歴が作成されます。これは、コラボレーションツールとして Git を使用したことがある人なら誰てもよく知っています。

フォークされたコミット履歴

ここで、master の新しいコミットが作業中のフィーチャーに関連しているとします。新しいコミットを自分の フィーチャーブランチに取り込むには、マージまたはリベースの2つのオプションがあります。

マージオプション

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

git checkout feature
git merge master

または、これを1本の直線に凝縮することができます。

git merge master feature

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:

マスターをフィーチャーブランチにマージする

マージは非破壊的な操作であるため安心です。既存のブランチは決して変更されません。これにより、リベースの潜在的な落とし穴がすべて回避されます (後述)。

逆に言えば、これは上流の変更を取り込む必要が生じるたびに feature ブランチに無関係なマージコミットが作成されることも意味します。master が非常に活発な場合、フィーチャーブランチの履歴がかなり乱雑になる可能性があります。高度な git log オプションでこの問題を軽減することはできますが、他の開発者にはプロジェクトの履歴が理解しづらくなる可能性があります。

リベースオプション

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

git checkout feature
git rebase master

これにより、フィーチャーブランチ全体がマスターブランチの先端から開始され、新しいコミットのすべてを効率的にマスターに組み込むことができます。しかし、リベースは、マージコミットを使用する代わりに、元のブランチでコミットごとにまったく新しくコミットを作成することによってプロジェクト履歴を再書き込みします。

Rebasing the feature branch onto master

リベースの主な利点は、プロジェクト履歴が非常にすっきりすることです。第一に、git merge が必要とする不要なマージコミットが除去されます。第二に、上記の図からわかるように、リベースによって、履歴は完全に直線的になります。フィーチャーの先端からプロジェクトの開始までずっとフォークなしにたどっていくことができます。このため、git loggit bisect、および gitk のようなコマンドでプロジェクトをナビゲートすることが容易になります。

しかし、この初期のコミット履歴には、安全性とトレーサビリティという2つのトレードオフがあります。リベースの黄金律 に従わない場合、プロジェクト履歴を書き換えるとコラボレーションワークフローに大打撃を与える恐れがあります。さらに、重要度は低くなりますが、リベースはマージコミットによって提供されるコンテキストを失います。このため、ユーザーは上流の変更がいつフィーチャーに組み込まれたのかわからなくなります。

対話式リベース

対話式リベースでは、コミットが新しいブランチに移動するときにコミットを変更する機会があります。これは、ブランチのコミット履歴を完全に制御できるという点で、自動化されたリベースよりもはるかに強力です。一般的に、これはフィーチャーブランチを master にマージする前に乱雑な履歴をクリーンアップするために使用されます。

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

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

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

ファイルを保存して閉じると、Git はユーザーの指示に従ってリベースを実行します。その結果、プロジェクト履歴は次のようになります。

対話式リベースを使用してコミットを1つにまとめる

このような重要でないコミットを削除すると、フィーチャーの履歴がはるかに理解しやすくなります。これは git merge ではまったくできないことです。

リベースの黄金律

リベースの特徴を理解できたら、次に最も重要なことは、実行してはいけないときを知ることです。git rebase の黄金律は、リベースを public ブランチでは決して使用しないことです。

たとえば、master を自分の フィーチャーブランチにリベースした場合に生じることを考えてみましょう。

マスターブランチのリベース

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.

2つの master ブランチを同期する唯一の方法は、マージして1つに戻すことですが、その結果、マージコミットが追加され、さらに同じ変更を含むコミットが 2 セット生じます (元のブランチの変更と、リベースしたブランチの変更)。言うまでもなく、これは非常に紛らわしい状況です。

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.

プッシュの強制

リベースした master ブランチをリモートリポジトリにプッシュバックしようとしても、Git はその実行を許可しません。これはリベース済みのブランチがリモート側の master ブランチと競合するためです。しかし、次のように --force フラグを渡すことによって、プッシュの実行を強制することが可能です。

# このコマンドの使用には細心の注意が必要です!
git push --force

これは、ご使用のリポジトリからリベースしたブランチと一致するようにリモート側のマスターブランチを上書きし、チームの他のメンバーに多大な混乱を招くことになります。したがって、自分が何をしようとしているか正確にわかっている場合にのみ、細心の注意を払ってこのコマンドを使用してください。

強制的にプッシュする必要があるのは、(バックアップ目的などで) プライベートフィーチャーブランチをリモートリポジトリにプッシュした、ローカルクリーンアップを実行した場合です。これは、「しまった、フィーチャーブランチの元のバージョンではなく、こっちの現在のブランチをプッシュしたかったのに」と言っているようなものです。ここで重要なのは、フィーチャーブランチの元のバージョンのコミットから作業している人は誰もいないことです。

ワークフローの手引き

リベースは、チームが快適に感じる程度に応じて、既存の Git ワークフローに組み込むことができます。このセクションでは、機能の開発の様々な段階でリベースがもたらすメリットを見ていきます。

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:

専用ブランチでの機能の開発

ローカルクリーンアップ

ワークフローにリベースを組み込む最良の方法の1つは、ローカルの進行中のフィーチャーをクリーンアップすることです。対話式リベースを定期的に実行することで、フィーチャーブランチ内の各コミットに焦点を当て、意義があることを確認できます。これにより、コードを分割して個別にコミットすることを心配せずにコードを記述できます。コードは後で修正できます。

git rebase を呼び出す場合、新規ベース用に2つのオプションがあります。フィーチャの親ブランチ (master など)、またはフィーチャー内の以前のコミットです。最初のオプションの例は、対話式リベースセクションで見ました。後者のオプションは、最新のいくつかのコミットの修正のみ行う場合に便利です。たとえば、次のコマンドは最新の3つのコミットに対してのみ対話式リベースを開始します。

git checkout feature
git rebase -i HEAD~3

新規ベースとして HEAD~3 を指定することによって、実際にはブランチを移動しておらず、単にそのブランチの後に続く3つのコミットを対話式に再書き込みしているだけです。これは、上流の変更をフィーチャーブランチに取り込まないので注意してください。

Rebasing onto Head~3

この方法でフィーチャー全体を書き換える場合、git merge-base コマンドを使用して feature ブランチの元のベースを見つけると便利です。次のコマンドは、元のベースのコミット ID を返します。これを git rebase に渡すことができます。

git merge-base feature master

このような対話式リベースの利用は、ローカルブランチにのみ影響を与えるため、git rebase をワークフローに導入するには優れた方法です。他の開発者に表示されるのは、完了した製品のみであり、必然的にフィーチャーブランチ履歴はすっきりとして、追跡しやすくなります。

しかし、これもプライベートフィーチャーブランチに関してのみ有効です。同じフィーチャーブランチを介して他の開発者とコラボレーションしている場合、そのブランチはパブリックであり、その履歴を書き換えることはできません。

git merge には、対話式リベースによるローカルコミットのクリーンアップに代わる方法はありません。

上流の変更をフィーチャーに取り込む

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.

マスターの代わりにリモートブランチにリベースすることは、まったく理にかなっていることを覚えておいてください。同じフィーチャーで別の開発者とコラボレーションしていて、その変更を自分のリポジトリに取り込む必要がある場合にこのようなことが生じる可能性があります。

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:

同じフィーチャーブランチ上でのコラボレーション

このフォークは、master の上流の変更を統合する場合とまったく同じ方法で解決できます。つまり、ローカルのフィーチャーJohn のフィーチャーをマージするか、ローカルのフィーチャーJohn のフィーチャーの先端にリベースします。

リモートブランチへのマージとリベース

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.

プルリクエストを使用してフィーチャーをレビューする

コードレビュープロセスの一環としてプルリクエストを使用する場合は、プルリクエストを作成した後に git rebase を使用しないでください。プルリクエストを行うとすぐに、他の開発者があなたのコミットを見ることになります。つまり、それは パブリック ブランチです。履歴を書き換えると、Git とチームメイトはフィーチャーに追加された後続のコミットを追跡できなくなります。

他の開発者の変更は git merge で取り込む必要があり、 git rebase ではありません。

このため、プルリクエストを送信する前に、対話式リベースを使用してコードをクリーンアップすることが一般的に勧められます。

承認済みフィーチャーの統合

フィーチャーがチームに承認された後、そのフィーチャーを master ブランチの先端にリベースしてから git merge を使用してフィーチャーをメインのコードベースに統合するというオプションがあります。

これは上流の変更をフィーチャーブランチに組み込むのと似た状況ですが、master ブランチではコミットを書き換えることができないため、最終的に git merge を使用してフィーチャーを統合する必要があります。しかし、マージの前にリベースを実行するとマージが早送りされ、完全に線形の履歴を確実に得ることができます。また、これにより、プルリクエスト中に追加された後続のコミットをまとめて1つのコミットにする機会も得られます。

リベースを使用した場合と使用しない場合のマスターへのフィーチャーの統合

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

Summary

ブランチのリベースを開始するために実際に知っておく必要があるのはこれだけです。不要なマージコミットのないすっきりした、直線的な履歴にしたいのであれば、別のブランチから変更を統合する際に、git merge ではなく、git rebase を使用するようにします。

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.