Sat, Jun 10, 2017

git rebaseを図解する

git rebaseを図解する

この記事を読んだ、またはGitのオブジェクトモデルを理解していることを前提に、Gitgit rebase というコマンドについて説明する。

このコマンドは、コミット履歴を改変できるGit特有のコマンドで、分かり辛いGitコマンドの中でも最も分かり辛い部類のものだ。 Gitの最後の関門と言えよう。 けど、それだけに使いこなせばとても便利なものでもある。


git rebaseがもつたった一つの機能

git rebaseにはいろんなオプションがあって、ちょっと調べただけだと、コミットを移動する機能とコミットを修正する機能の二つがあるように見えるかもしれないが、実際は単一の機能しかないシンプルなコマンドだ。

その機能とは、指定した範囲のコミットが含む変更を、別に指定したコミットのコードベースに適用するというもの。

コマンドの基本形は次のようなものだ。

$ git rebase --onto master dev bugfix

このコマンドは、bugfixから辿れるコミット群から、devから辿れるコミット群を除いたコミット群が含む変更を、masterのコードベースに適用する。

と書いても分からないので図解する。



このスライドを見ると、git rebaseに指定した3つのブランチのそれぞれの使われ方が分かるはず。

git rebase --onto master dev bugfixが実行する処理をもっと正確に言うと、

  1. bugfixcheckoutして(i.e. HEADbugfixにして)、
  2. dev..HEADのコミット群が含む変更を、それぞれ仮領域にパッチとして保存して、
  3. git reset --hard masterして、
  4. 仮領域に保存した変更を、HEADが指すコミットのコードベースにひとつひとつ順番に適用する。


上記コマンドでbugfixのところを省略すると、ステップ1のcheckoutが省略される。 言い換えると、上記コマンドは次の二つのコマンドに分解できる。

$ git checkout bugfix
$ git rebase --onto master dev

さらに、--onto masterを省略すると、ステップ3のreset先が変わり、devになる。 このときのコマンドの形は、

$ git rebase dev

という見慣れたものになるが、これが最初に挙げた基本形の省略形だと認識しておくと応用が利く。

以下にgit rebase devの動きを細かめに図解する。


インタラクティブモード

前節のスライドに書いたパッチの適用をカスタマイズできるのがインタラクティブモードで、これは-iオプションで有効にできる。 インタラクティブモードを使うと、パッチをスキップしたり、順番を変えたり、まとめたり、分割したり、編集したりでき、またパッチとパッチの間に任意のコマンドを実行でき、例えばパッチごとにユニットテストを実行できたりする。

インタラクティブモードの使い方についてはググればたくさん出てくるのでここには書かない。 この記事辺りがわかりやすい。

インタラクティブモードのユースケースとしてよく紹介されるのが、git rebase -i HEAD^^で直近の二つのコミットを変更するといったものだが、これを図解すると以下のようになる。



このスライドを見ると、git rebase devgit rebase -i HEAD^^は、パッチの適用がインタラクティブかどうか以外は同じ処理をしていることがわかる。 見た目の違いに惑わされないようにしたい。


git rebaseはブランチを複数指定したりして分かり辛いコマンドであることは確かだけど、上記の基本形を押さえておけばすんなり理解できるはず。