gitで1つのコミットを複数のコミットに分割する


2010年 12月 10日

gitでは様々な方法でコミットログを書き換えることができます。
その一例として1つのコミットを複数のコミットに分割する方法を紹介します。

問題

先日紹介したgitで複数のコミットを1つにまとめる例ですが、そこでは以下のコミットログを:

$ git log -n 4 --oneline --reverse
0000001 Add a neat feature X into the library
0000002 Update to use X
0000003 Fix a typo in X
0000004 Fix a bug in X

以下のような形になるよう書き換えました:

$ git rebase -i HEAD~4
[detached HEAD b000001] Add a neat feature X into the library
 8 files changed, 94 insertions(+), 9 deletions(-)
Successfully rebased and updated refs/heads/topic-x.

$ git log -n 2 --oneline --reverse
b000001 Add a neat feature X into the library
b000002 Update to use X

ライブラリ側の変更点(0000001と0000003と0000004)を1つにまとめて
順序を入れ替えた形になります。

当初のコミットログから比較するとかなり美しくなりました。
しかし改めて git log -p などとしてコミットログを確認していると新たな問題が発覚しました。
b000002は新機能Xを使うようアプリケーション側を変更するコミットだったのですが、
よくよく見返してみると「新機能Xを使うよう更新する」変更とは関係のない
using等の宣言の羅列をソートする」
といったリファクタリング的な変更がいっしょくたになっていました。
これは美しくありません。

この場合、b000002を「新機能Xを使うよう更新する」と「using等の宣言の羅列をソートする」の2つに分割すれば
よりコミットログが美しくなります。
しかし具体的にはどうすればよいでしょうか。

解決方法

gitではこのような1つのコミットを複数のコミットに分割することができます。
方法は色々とありますが、ここではgit resetgit add -pを使う方法を紹介します。
git rebase -i でも可能ですが、やることは一緒です。
まずは次のコマンドを実行しましょう:

$ git reset HEAD~1

これを実行すると、作業ディレクトリの内容はそのままで、
現在checkoutしているブランチとindexの内容が1つ前のコミット(= b000001)に差し替えられます。
結果としてb000002の変更をコミットする直前の状態に巻き戻すことになります。

さらに次のコマンドを実行しましょう:

$ git add -p

すると以下のようなメッセージとプロンプトが表示されます:

diff --git a/library.source b/library.source
index 0f00000..0f00001 100644
--- a/library.source
+++ b/library.source
@@ -1,10 +1,10 @@
+using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Web;
+using System.Web.UI;
 using System.Web.UI.HtmlControls;
 using System.Web.UI.WebControls;
-using System.Web.UI;
-using System.Web;
-using System;

 namespace Library
 {
Stage this hunk [y,n,q,a,d,/,e,?]?

git add -p では作業ディレクトリで行われた変更点から特定の部分を選んでindexに記録することができます。
この例では「using等の宣言の羅列をソートする」変更点をどう扱うか問われています。
まずはこれだけ先にindexに記録しましょう。これには y を入力します。

同様に別の変更点についても差分とどう扱うかのプロンプトが表示されます。
今回の場合は先程の変更点を先にコミットしておきたいので、
q を入力して残りの変更点を全て無視することにします。

これでindexには「using等の宣言の羅列をソートする」変更点のみが入っている状態になったので、
先にこれだけコミットします:

$ git commit -m 'Refactor - Sort using statements'

さらに分割してコミットしたい変更点があれば git add -pgit commit を繰り返せばOKです。
今回の場合はこれ以上分割するものはないので残りの変更点をまとめてコミットします:

$ git commit -am 'Update to use X'

これでコミットの分割が完了しました。結果を確認してみましょう:

$ git log -n 3 --oneline --reverse
b000001 Add a neat feature X into the library
c000012 Refactor - Sort using statements
c000022 Update to use X

各コミットが論理的な単位で分割されました。やりましたね。

(続く)