つるはしで過去を発掘する

この記事は[Git Advent Calendar / Jun.](http://qiita.com/advent-calendar/git)27日目の記事です！
26日目は[hoshina85@github](http://qiita.com/users/hoshina85@github)さんの[横着で神経質な私とあなたに贈るgit add -p](http://qiita.com/items/1abf08bca4ce51db4775)でした。「Adventってなんなの？」って方は、[Wikipediaの解説](http://ja.wikipedia.org/wiki/アドベント)をご覧下さい。

## 特定の文字列を変更したコミットを探し出す
テンプレの張りつけが終わったところで改めましてこんにちわ、[実用Git](http://www.amazon.co.jp/exec/obidos/ASIN/4873114403/days0aa-22/)の訳者の一人、[hiratara](http://d.hatena.ne.jp/hiratara/)です。

[7/1の江頭2:50さんの降誕](http://www.okw.co.jp/profile/egashira.html)を待ち望むAdventの期間も残り4日間となりました(残り3日間の[担当者募集中](http://atnd.org/events/29378)です！)。今日はGitでつるはしを使って過去の遺物を掘り起こす話を書こうと思います。

つるはしとはpickaxeのことです。pickaxeは`git`のサブコマンドではありませんが、[gitglossary](http://www.kernel.org/pub/software/scm/git/docs/gitglossary.html#def_pickaxe)や[gitdiffcore](http://www.kernel.org/pub/software/scm/git/docs/gitdiffcore.html)のドキュメント中で言及されていたり、[diffcore-pickaxe.c](https://github.com/git/git/blob/master/diffcore-pickaxe.c)というファイルがあったりします。数日前に[rosylilly](http://qiita.com/users/rosylilly)さんが[git grepについて触れられてました](http://qiita.com/items/9648ad2c8aa53465372b)が、これは履歴のある時点について、ファイルに含まれる文字列を検索する機能でした。これに対してpickaxeは、diffに含まれる文字列を検索する機能です。```git grep```が各履歴のコンテンツを水平方向に検索するものだとすれば、pickaxeは履歴を垂直方向に潜って検索していくイメージと言ってもよいでしょう。

それでは、実際につるはしを振るってみましょう。pickaxeを使う代表的なコマンドとして、```git log```があげられます。```git log```の```-S```オプションに文字列を渡すと、指定した文字列を含むログを表示することができます。以下では、[yancha](https://github.com/uzulla/yancha)のリポジトリにおいて、「yairc」という文字列の変更を含むコミットを表示しています。

```diff:
$ git clone https://github.com/uzulla/yancha.git; cd yancha
$ git log -p -Syairc
commit b5519c937e846a2436de3304c3eb6a7feed4dbc3
Author: Junichi Ishida <uzulla@himitsukichi.com>
Date:   Fri Jun 1 19:42:59 2012 +0900

    Update README.mkd

diff --git a/README.mkd b/README.mkd
index 155b850..7f339fe 100644
--- a/README.mkd
+++ b/README.mkd
@@ -1,5 +1,5 @@
-# yancha(仮名) README
-yancha (仮名) はログの保全、リアルタイム性、モバイル端末への対応を重視した、Perlで作成されたチャットシステムです。
+# yancha README
+yancha (旧名yairc) はログの保全、リアルタイム性、モバイル端末への対応を重視した、Perlで作成されたチャットシステムです。
 
 サンプル
 [http://yairc.cfe.jp:3000/](http://yairc.cfe.jp:3000/)
…後略…
```

ただし```-S```を指定しただけだと、該当コミットの中で指定した文字列を含む変更がなされたファイルしか表示されません。コミット中の他のファイルも全て表示したい場合には、```--pickaxe-all```オプションを指定します。以下の実行例では、```--pickaxe-all```の指定前は2ファイル分の変更しか表示されていないことが読み取れます。

```diff:
$ git log --stat -Syairc
commit a931c7a25f22b31e4fa47745a7856ab03da7c5c3
Author: uzulla <uzulla@himitsukichi.com>
Date:   Sat Jun 16 01:05:25 2012 +0900

    [add]PC用に仮テンプレートを適用

 public/chat.html     |  119 +++++++++++++++++++++++++++-----------------------
 public/pc_hints.html |   33 ++++++++++++++
 2 files changed, 97 insertions(+), 55 deletions(-)
…後略…

$ git log --stat -Syairc --pickaxe-all 
commit a931c7a25f22b31e4fa47745a7856ab03da7c5c3
Author: uzulla <uzulla@himitsukichi.com>
Date:   Sat Jun 16 01:05:25 2012 +0900

    [add]PC用に仮テンプレートを適用

 public/chat.html                          |  119 +++++++++++++++-------------
 public/img/chat_btn_hints.gif             |  Bin 0 -> 368 bytes
 public/img/chat_btn_logout.gif            |  Bin 0 -> 507 bytes
 public/img/chat_btn_logsearch.gif         |  Bin 0 -> 604 bytes
…中略…
 public/img/login_text_snsaccountlogin.gif |  Bin 0 -> 1134 bytes
 public/js/connection.js                   |    5 +-
 public/js/main_pc.js                      |   66 +++++++++++++++-
 public/pc_hints.html                      |   33 ++++++++
 public/stylesheets/pc.css                 |   51 ++++++-------
 28 files changed, 185 insertions(+), 89 deletions(-)
…後略…
```

また、```-S```へ指定する文字列に正規表現を使いたい場合には```--pickaxe-regex```オプションを使います。例えば、Perlにおいて```use```したモジュールの変更を追いたい場合には、以下のように指定できます。

```diff:
$ git log -p -S'use +.+' --pickaxe-regex
commit 95c51ca52d4fc8707f4efc40ee42de7ded339591
Author: ytnobody <ytnobody@gmail.com>
Date:   Sat Jun 23 15:08:24 2012 +0900

    add croak() for reminding to set consumer-key/consumer-secret

diff --git a/lib/Yancha/Auth/Twitter.pm b/lib/Yancha/Auth/Twitter.pm
index 55d19b6..8763761 100644
--- a/lib/Yancha/Auth/Twitter.pm
+++ b/lib/Yancha/Auth/Twitter.pm
@@ -8,12 +8,13 @@ use Plack::Builder;
 use Plack::Request;
 use Plack::Response;
 use JSON;
+use Try::Tiny;
+use Carp;
 
 use FindBin;
 use lib ("$FindBin::Bin/lib");
 use Net::Twitter::Lite;
…後略…
```

## -Sと-G
ところで、```git log```には```-S```の他に```-G```というオプションがあり、こちらにも正規表現を指定した検索ができることになっています。```-S --pickaxe-regex```と```-G```の挙動は似ていますが、微妙に異なります。自分でリポジトリを作り、違いを確認してみましょう。

```diff:
$ mkdir testrepo; cd testrepo; git init
$ echo "ABC\nDEF" > plain.txt
$ git add plain.txt; git commit -m "initial commit"
[master (root-commit) 563c2fd] initial commit
 1 files changed, 3 insertions(+), 0 deletions(-)
 create mode 100644 plain.txt
$ echo "DEF123\nGHI" > plain.txt
$ git commit -a -m"second commit"
[master 5b1f6ef] second commit
 1 files changed, 2 insertions(+), 2 deletions(-)
```

最初のコミット"initial commit"でテキストファイルを作り、次のコミット"second commit"でテキストファイルの中身を変更しています。「ABC」という行を削除し、「DEF」という行は「DEF123」に変更、さらに「GHI」という行を追加しました。

この履歴に対してpickaxeを利用して検索をかけると、追加された行と削除された行については、```-S --pickaxe-regex```も```-G```も同じ挙動をします(```A.C```の指定で"initial commit"が引っかかっているのは、最初のコミットでABCという行を追加しているためです)。

```diff:
$ git log --pretty=oneline --abbrev-commit -SA.C --pickaxe-regex
5b1f6ef second commit
6ba731c initial commit
$ git log --pretty=oneline --abbrev-commit -GA.C                
5b1f6ef second commit
6ba731c initial commit
$ git log --pretty=oneline --abbrev-commit -SG.I --pickaxe-regex
5b1f6ef second commit
$ git log --pretty=oneline --abbrev-commit -GG.I                
5b1f6ef second commit
```

ところが、変更された行については```-S --pickaxe-regex```の指定と```-G```の指定で挙動が異なります。

```diff:
$ git log --pretty=oneline --abbrev-commit -SD.F --pickaxe-regex
6ba731c initial commit
$ git log --pretty=oneline --abbrev-commit -GD.F
5b1f6ef second commit
6ba731c initial commit
```

これは、```-S```は単に差分にgrepをかけるだけではなく、指定した文字列(または正規表現)が追加された回数と削除された回数を数え、その回数が異なっているかをチェックしているからです。今回の操作で「DEF」を含む行が"second commit"でどう変化したかをみると、「DEF」という行を削除しているので削除が1回、さらに「DEF123」という行を追加しているので追加が1回と数えられるため、出現回数は変化していません。よって、```-S```による指定ではこのコミットは表示されません。

この```-S```の仕様により、```-S```を使うことによってある単語が追加・削除されたタイミングを追うことができます。例えば、```git log -S'$hoge'```とすれば、```$hoge```という変数を追加、削除したコミットを見つけることができます。$hogeを使っている行を書き換えただけのコミットは-Sでは引っかからないため、ノイズが減って目的のコミットを探しやすくなります。

　```-G```は逆にこのようなコミットを全て拾うもので、Porcelainコマンドから使われることを意図した機能です。```-G```の挙動については、以下のコミットコメントを読むと詳しく出ています。

https://github.com/git/git/commit/f506b8e8b5fb231e63b69668b6b7516f12b32228