「git をもう一歩使いこなす(前回の記事)」でgitの基本のほんの少し先の機能を紹介しました。(ここでいう基本とは addしてcommitしてpushすることはできるというレベルを想定しています。)
前回の記事
この記事ではさらにそれよりも難しい(けれどとても便利な)gitの機能を紹介したいと思います。覚えなくてもなんとかなると言われればそれまでですが、覚えれば自分の生産性をぐっと高めることができるようになります。
そうやって作り出した時間で「賢くサボる」のが強いエンジニアだと思います。ここで紹介する機能は少し複雑ですがとても強力な機能です。
わからないからと最初から諦めてしまったらとても勿体ないのでこの機会に覚えてしまいましょう。
「git add -p」で行ごとに変更を add する
コミットを分ける意義
ファイルをコミットする場合コミットしたいファイルを「git add」します。しかし1ファイル内の変更を複数のコミットにわけたい場合もあります。
コミット履歴は後で何かあった時にその部分に戻ってくることになるので非常に重要な履歴です。それを「一旦コミット」とか「修正」などといったなんの役にも立たないコミットメッセージとともに大量の変更をコミットされてしまうのはそのリポジトリの負債といってもいいでしょう。そのため1コミットには可能な限り1つの意味を持たせてコミットするべきです。
しかし、開発中はそんなことを気にしてられないというのが実際のところだと思います。最初から完璧なプログラムを書くことは不可能ですので。
だからといって書き上げたプログラムをまとめて1つのコミットにするのは他人にも未来の自分にも不親切です。
そこで役に立つのが「git add -p」を使ったコミットする部分の選択機能です。
使い方
下記はファイルを2回に分割してコミットする例です。「git diff」をすると2行分の変更履歴が見れます。
// 元ファイル
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccc
dddddddddddddddddddd
eeeeeeeeeeeeeeeeeeee
// 変更後ファイル
aaaaaaaaaaaaaaaaaaaa1
bbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccc
dddddddddddddddddddd3
eeeeeeeeeeeeeeeeeeee
// git diff
-aaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaa1
bbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccc
-dddddddddddddddddddd
+dddddddddddddddddddd3
eeeeeeeeeeeeeeeeeeee
「git add -p」でファイルを指定すると「Stage this hunk [y,n,q,a,d,/,s,e,?]?」と聞かれます。最初の1回は普通に「git add」した場合と変わらない差分が表示されます。
分割して「git add」したい場合ここで「s」を入力します。
git add -p test.txt
diff --git a/test3.txt b/test3.txt
index 738ec26..2cec427 100644
--- a/test3.txt
+++ b/test3.txt
@@ -1,5 +1,5 @@
-aaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaa1
bbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccc
-dddddddddddddddddddd
+dddddddddddddddddddd3
eeeeeeeeeeeeeeeeeeee
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? s
すると今度は行ごとに「Stage this hunk [y,n,q,a,d,/,s,e,?]?」と聞かれます。これでコミットしたいところのみ「y」を入力し、コミットしたくない部分は「n」を入力します。
これでコミットしたい部分だけをステージングに乗せることができました。あとは普通に「git commit」コマンドでコミットすれば1ファイルの1部分のみをコミットできます。
// コミットしたい部分
Split into 2 hunks.
@@ -1,3 +1,3 @@
-aaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaa1
bbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccc
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? y
// コミットしたくない部分
@@ -2,4 +2,4 @@
bbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccc
-dddddddddddddddddddd
+dddddddddddddddddddd3
eeeeeeeeeeeeeeeeeeee
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? n
これ以外にも「git add -p」を利用した時に聞かれるオプションが複数あります。
「Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?」と聞かれた時に「a」を押すと「s」で行ごとに「git add」している状態を無視して、そのファイルの残りの変更部分を全て「git add」してくれます。
「d」はその逆で、そのファイルの残りの変更部分を全て「git add」しないという動作をします。「j,k」は「git add」するかどうかを決めずに次の差分を見に行くという挙動をしてくれます。
「e」を使うとその場で編集モードになり今の変更差分を自分で編集することができます。
このようにただ「git add」している時ではできなかった痒いところに手がとどく様々ことが「git add -p」でできるようになります。
「git rebase」でコミットをまとめる
コミットする前の変更は「git add -p」を使ってまとめたい情報ごとにコミットすることができます。しかしすでにコミットしてしまった情報をまとめたい場合もあります。
そんな時に使用するのが「git rebase -i」です。「git rebase」自体は他にも利用の機会がある便利なコマンドですが、ここでは”複数のコミットをまとめる”という場合に限定して紹介します。
下記のようなコミットがあるとします。「ファイル追加」と「ファイル追加2」という2つのコミットをした後で1つのコミットにまとめたくなったとします。
commit b967252e15273900df426b30035c4701ceaf3490
Author: author
Date: Mon Jan 9 23:32:51 2017 +0900
ファイル追加2
commit 32fe808e5835dcd92526a476a13164d18e2b2a8e
Author: author
Date: Mon Jan 9 21:05:10 2017 +0900
ファイル追加
commit 92958e487a95f7c427613e4df70112055372597f
Author: author
Date: Mon Jan 9 15:58:54 2017 +0900
second commit
commit 96632032e41a360b040db710665770c8e02356d2
Author: author
Date: Mon Jan 9 15:58:20 2017 +0900
first commit
その場合1つ前のコミットハッシュを指定します。
git rebase -i 92958e487a95f7c427613e4df70112055372597f
すると指定したコミットより新しい順に選択する画面が出てきます。「pick」というのはそのコミットはいじらないということを示しています。
pick 32fe808 ファイル追加
pick b967252 ファイル追加2
# Rebase 92958e4..b967252 onto 92958e4 (2 command(s))
#
# 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
# d, drop = remove commit
#
# 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
今やりたいことは2つのコミットを一つにまとめることなので使用するのは「squash」か「fixup」です。
今回は「squash」を使ってみます。「ファイル追加2」というコミットの右の「pick」を「squash」に書き換えます。
pick 32fe808 ファイル追加
squash b967252 ファイル追加2
「squash」と書いたら保存します(vimモードなので:wqですね)。すると下記のような画面が現れます。「ファイル追加」「ファイル追加2」の2つのコミットを1つにするのでそのコミットメッセージを求められている状態です。何もせずに保存するとそのまま「ファイル追加」「ファイル追加2」というコミットメッセージとなります。
コミットメッセージを書いたらまた保存します。
# This is a combination of 2 commits.
# The first commit's message is:
ファイル追加
# This is the 2nd commit message:
ファイル追加2
するとコミットが1になり下記のようなコミットログに変わります。
commit ed80192de7dc2706c5d93809993078360904ecf6
Author: author
Date: Mon Jan 9 16:05:01 2017 +0900
ファイル追加
ファイル追加2
commit 92958e487a95f7c427613e4df70112055372597f
Author: author
Date: Mon Jan 9 15:58:54 2017 +0900
second commit
commit 96632032e41a360b040db710665770c8e02356d2
Author: author
Date: Mon Jan 9 15:58:20 2017 +0900
first commit
「fixup」を使った場合はほとんど「squash」と同じですが、上記のようなコミットメッセージ編集画面が出ずに「pick」した側のコミットメッセージが使用されて1つのコミットにまとまります。
特に編集する必要がない場合、こちらを利用すると良いでしょう。
このようにコミットでも後から編集できる方法を知っておけば「まだ完全に整ってないけど現在の状態を保存しておきたい」という場合にガンガンコミットをすることができます。
コミットしておけば必ず履歴に残るので何かあった時に非常に重宝します。
まとめ
本記事ではわかりやすくコミットを分ける2つの方法を紹介しました。個人で使う分にはあまり気にしないかもしれませんが、プロジェクトで開発する時にはほぼ必須のスキルとなると思います。
わかりやすいコミットはプロジェクトのメンバーと未来の自分を救うことになります。是非この機会に覚えておきましょう。