けんごのお屋敷

2013-05-16

gitのコミットの歴史を改変する(git rebase) 1 / 2

git には rebase というとても便利なコマンドがあります。その中でも特に便利なのが -i または --interactive オプションです。便利なのですがよく忘れるのでまとめもかねてこの記事で詳しく紹介します。

前提

この記事では説明のために以下のようなコミット状態である前提で話を始めます。よくあるコミットの流れです。

git rebase -i

-i--interactive とあるように、対話的に rebase が実行できるコマンドです。これでなにが出来るかというと

  • コミットメッセージを編集する
  • コミットをまとめる
  • コミットを分割する
  • コミットの順番を移動させる
  • コミットを削除する

など、いろんなことが出来ます。基本的な構文は

[kengo@tkengo-mac] $ git rebase -i <commit>

これだけ。 <commit> には特定のコミットを指定します。たとえば

[kengo@tkengo-mac] $ git rebase -i HEAD~3

とすると、最後から3つまでのコミットについて、上に書いた何かしらの操作(コミットメッセージ編集だったり順番を入れ替えたり)を出来ます。このコマンドを実行するとテキストエディタが開いてコミットの一覧が表示されて

pick cce19c9 通信用のクラスの実装とテストの追加
pick 70b3379 メソッド名のタイポ修正
pick aebf22c テストが落ちてたので修正

# Rebase cce19c9..aebf22c onto b8e5722
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

上の方にコミットログが表示されてるのがわかります。下の方は全部コメントになっていて、これの使い方を説明してくれています。各コミットの行の先頭になにやら pick とありますが、この部分を edit やら reword やら書き換えて処理を実行してきます。 上から順に行の先頭に指定されたコマンドに従って、コミットを1つずつ適用していく ことになります。コメント部分にもあるように、指定できるコマンドは pick reword edit squash fixup exec の6つです。

1つずつ詳しく見ていきます。

pick(何もしない)

pick が指定されているコミットは特になにもされません。先の例でいうと

[kengo@tkengo-mac] $ git rebase -i HEAD~3

として

pick cce19c9 通信用のクラスの実装とテストの追加
pick 70b3379 メソッド名のタイポ修正
pick aebf22c テストが落ちてたので修正

# コメント部分は省略...

と出ますが、このままテキストエディタを終了(vimの場合は :q)させると特になにもせずに処理が終了します。ログを確認してみても特になにも変わっていません。

reword(コミットメッセージを修正する)

reword が指定されているコミットは内容自体には手を加えずにコメントのみ修正されます。

たとえばこのコミットで言うと 18a48c3バグ修正 というコメントをもっと具体的に説明しておいたほうがいいかもしれません。そんな時は

[kengo@tkengo-mac] $ git rebase -i 18a48c3~1

として

reword 18a48c3 バグ修正
pick cce19c9 通信用のクラスの実装とテストの追加
pick 70b3379 メソッド名のタイポ修正
pick aebf22c テストが落ちてたので修正

# コメント部分は省略...

として、テキストエディタを終了させると(vimの場合は :wq)、次に修正するコメントを入力する画面に切り替わります。

本番環境の判定方法が間違っていたので修正

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD^1 <file>..." to unstage)
#
# modified:   environment.php
#

ここでコメントを入力してテキストエディタを終了させるとコミットメッセージのみ書き換わります。この時、コメントを修正した以降のコミットのハッシュが変わります。以前のコミットについては無くなるわけではなく、裏でこっそり生きているので、元に戻せます。

edit(rebaseを一旦止める)

edit が指定されていれば rebase が一旦そのコミットで止まります。これはコミットを分割したい時などに便利です。

たとえばこのコミットで言うと cce19c9通信用のクラスの実装とテストの追加 で、クラスのコミットとテストのコミットに分割したいこともあるかもしれません。そんな時は

[kengo@tkengo-mac] $ git rebase -i cce19c9~1

こうして

edit cce19c9 通信用のクラスの実装とテストの追加
pick 70b3379 メソッド名のタイポ修正
pick aebf22c テストが落ちてたので修正

とすれば rebase が始まって cce19c9 のコミットで rebase が止まって、以下のようなメッセージが表示されてシェルに制御が戻ります。

Stopped at cce19c9... 通信用のクラスの実装とテストの追加
You can amend the commit now, with

    git commit --amend

Once you are satisfied with your changes, run

    git rebase --continue

この後はいつもの通り git を使ってコミットしていきます。たとえば以下の様な感じ。

[kengo@tkengo-mac] $ vim HttpRequest.php # クラスの実装を行います
[kengo@tkengo-mac] $ git add HttpRequest.php
[kengo@tkengo-mac] $ git commit -m '通信用クラスの追加'
[kengo@tkengo-mac] $ vim HttpRequestTest.php # クラスのテストを追加します
[kengo@tkengo-mac] $ git add HttpRequestTest.php
[kengo@tkengo-mac] $ git commit -m '通信用クラスのテストの追加'

ここまでやったら後は指示にしたがって rebase --continue をします。

[kengo@tkengo-mac] $ git rebase --continue

これでコミットの分割ができました。 reword の時と同じように修正した部分以降のコミットのハッシュが書き換わります。また、同じように前のコミットは裏でこっそり生きているので後で参照することができます。

squash(コミットをまとめる)

squash が指定されているコミットはその1つ前のコミットとまとめられます。

たとえばこのコミットの 70b3379メソッド名のタイポ修正 。ちょっと恥ずかしいので、何事もなかったかのようにしたいと思うことがあるかもしれません。そういう時は cce19c9 とまとめてしまいます。いつものように

[kengo@tkengo-mac] $ git rebase -i cce19c9~1

こうして

pick cce19c9 通信用のクラスの実装とテストの追加
squash 70b3379 メソッド名のタイポ修正
pick aebf22c テストが落ちてたので修正

とすると、次はコミットメッセージを入力する画面に切り替わります。

# This is a combination of 2 commits.
# The first commit's message is:

通信用のクラスの実装とテストの追加

# This is the 2nd commit message:

メソッド名のタイポ修正

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   HttpRequest.php
#

ここで メソッド名のタイポ修正 というコメントを消して

通信用のクラスの実装とテストの追加

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   HttpRequest.php
#

こんな感じにして終了させれば cce19c970b3379 がまとまって新たなコミットが出来上がります。これでタイポの歴史はなかったことになりました。同じようにコミットのハッシュは変わりますし、前のコミットは 後で参照できます。

残りは

ちょっと記事が長くなってきたので、残りはパート2で説明したいと思います。

  • fixed について
  • exec について
  • コミットの順番の入れ替えについて
  • コミットの削除について

パート2の記事はこちら

  • このエントリーをはてなブックマークに追加