チャプター ▾ 第2版

7.5 Git ツール - 検索

検索

ほぼどのような規模のコードベースでも、関数がどこで呼び出されているか、あるいは定義されているかを探したり、メソッドの履歴を表示したりする必要が頻繁に生じます。Git は、そのデータベースに保存されているコードやコミットを迅速かつ容易に検索するためのいくつかの便利なツールを提供しています。そのうちのいくつかを説明します。

Git Grep

Git には、コミットされたツリー、ワーキングディレクトリ、さらにはインデックスの中から文字列や正規表現を簡単に検索できる grep と呼ばれるコマンドが付属しています。以下の例では、Git 自体のソースコードを検索します。

デフォルトでは、git grep はワーキングディレクトリ内のファイルを検索します。最初のバリエーションとして、-n または --line-number オプションのいずれかを使用して、Git が一致を検出した行番号を出力できます。

$ git grep -n gmtime_r
compat/gmtime.c:3:#undef gmtime_r
compat/gmtime.c:8:      return git_gmtime_r(timep, &result);
compat/gmtime.c:11:struct tm *git_gmtime_r(const time_t *timep, struct tm *result)
compat/gmtime.c:16:     ret = gmtime_r(timep, result);
compat/mingw.c:826:struct tm *gmtime_r(const time_t *timep, struct tm *result)
compat/mingw.h:206:struct tm *gmtime_r(const time_t *timep, struct tm *result);
date.c:482:             if (gmtime_r(&now, &now_tm))
date.c:545:             if (gmtime_r(&time, tm)) {
date.c:758:             /* gmtime_r() in match_digit() may have clobbered it */
git-compat-util.h:1138:struct tm *git_gmtime_r(const time_t *, struct tm *);
git-compat-util.h:1140:#define gmtime_r git_gmtime_r

上記の基本的な検索に加えて、git grep は他にも多数の興味深いオプションをサポートしています。

たとえば、すべての一致を出力する代わりに、-c または --count オプションを使用して、検索文字列を含むファイルと各ファイル内の一致数のみを表示して、git grep に出力を要約させることができます。

$ git grep --count gmtime_r
compat/gmtime.c:4
compat/mingw.c:1
compat/mingw.h:1
date.c:3
git-compat-util.h:2

検索文字列の _コンテキスト_ に興味がある場合は、-p または --show-function オプションのいずれかを使用して、一致する各文字列の囲んでいるメソッドまたは関数を表示できます。

$ git grep -p gmtime_r *.c
date.c=static int match_multi_number(timestamp_t num, char c, const char *date,
date.c:         if (gmtime_r(&now, &now_tm))
date.c=static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
date.c:         if (gmtime_r(&time, tm)) {
date.c=int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset)
date.c:         /* gmtime_r() in match_digit() may have clobbered it */

ご覧のとおり、gmtime_r ルーチンは date.c ファイルの match_multi_number 関数と match_digit 関数の両方から呼び出されています (表示される 3 番目の一致は、コメントに表示される文字列のみを表しています)。

--and フラグを使用すると、複雑な文字列の組み合わせを検索することもできます。これにより、同じテキスト行に複数のマッチが存在することが保証されます。たとえば、Git のコードベースの古いバージョンであるタグ v1.8.0 で、名前に "LINK" または "BUF_MAX" のいずれかのサブストリングを含む定数を定義する行を探してみましょう (出力をより読みやすい形式に分割するのに役立つ --break および --heading オプションも追加します)。

$ git grep --break --heading \
    -n -e '#define' --and \( -e LINK -e BUF_MAX \) v1.8.0
v1.8.0:builtin/index-pack.c
62:#define FLAG_LINK (1u<<20)

v1.8.0:cache.h
73:#define S_IFGITLINK  0160000
74:#define S_ISGITLINK(m)       (((m) & S_IFMT) == S_IFGITLINK)

v1.8.0:environment.c
54:#define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS

v1.8.0:strbuf.c
326:#define STRBUF_MAXLINK (2*PATH_MAX)

v1.8.0:symlinks.c
53:#define FL_SYMLINK  (1 << 2)

v1.8.0:zlib.c
30:/* #define ZLIB_BUF_MAX ((uInt)-1) */
31:#define ZLIB_BUF_MAX ((uInt) 1024 * 1024 * 1024) /* 1GB */

git grep コマンドには、通常の grepack などの検索コマンドよりもいくつかの利点があります。1つ目は、非常に高速であること、2つ目は、ワーキングディレクトリだけでなく、Git 内のどのツリーでも検索できることです。上記の例で見たように、現在チェックアウトされているバージョンではなく、Git ソースコードの古いバージョンで用語を検索しました。

Git Log 検索

もしかしたら、用語が_どこに_存在するのかではなく、_いつ_存在したのか、あるいは導入されたのかを探しているのかもしれません。git log コマンドには、コミットメッセージの内容や、導入された差分の内容によって特定のコミットを見つけるための強力なツールが多数用意されています。

たとえば、ZLIB_BUF_MAX 定数がいつ最初に導入されたかを調べたい場合、-S オプション (通称 Git の「つるはし」オプション) を使用して、その文字列の出現回数を変更したコミットのみを表示するように Git に指示できます。

$ git log -S ZLIB_BUF_MAX --oneline
e01503b zlib: allow feeding more than 4GB in one go
ef49a7a zlib: zlib can only process 4GB at a time

これらのコミットの差分を見ると、ef49a7a で定数が導入され、e01503b で変更されたことがわかります。

より具体的にする必要がある場合は、-G オプションで検索する正規表現を指定できます。

もう一つ非常に便利な高度なログ検索は、行履歴検索です。-L オプションを付けて git log を実行するだけで、コードベース内の関数やコード行の履歴が表示されます。

たとえば、zlib.c ファイル内の関数 git_deflate_bound に加えられたすべての変更を確認したい場合、git log -L :git_deflate_bound:zlib.c を実行できます。これにより、その関数の境界が何であるかを特定しようとし、その後、履歴を調べて、関数が最初に作成された時点に遡る一連のパッチとして、その関数に加えられたすべての変更を表示します。

$ git log -L :git_deflate_bound:zlib.c
commit ef49a7a0126d64359c974b4b3b71d7ad42ee3bca
Author: Junio C Hamano <gitster@pobox.com>
Date:   Fri Jun 10 11:52:15 2011 -0700

    zlib: zlib can only process 4GB at a time

diff --git a/zlib.c b/zlib.c
--- a/zlib.c
+++ b/zlib.c
@@ -85,5 +130,5 @@
-unsigned long git_deflate_bound(z_streamp strm, unsigned long size)
+unsigned long git_deflate_bound(git_zstream *strm, unsigned long size)
 {
-       return deflateBound(strm, size);
+       return deflateBound(&strm->z, size);
 }


commit 225a6f1068f71723a910e8565db4e252b3ca21fa
Author: Junio C Hamano <gitster@pobox.com>
Date:   Fri Jun 10 11:18:17 2011 -0700

    zlib: wrap deflateBound() too

diff --git a/zlib.c b/zlib.c
--- a/zlib.c
+++ b/zlib.c
@@ -81,0 +85,5 @@
+unsigned long git_deflate_bound(z_streamp strm, unsigned long size)
+{
+       return deflateBound(strm, size);
+}
+

Git がプログラミング言語の関数やメソッドを一致させる方法を特定できない場合でも、正規表現 (または _regex_) を提供できます。たとえば、これは上記の例と同じことを行います: git log -L '/unsigned long git_deflate_bound/',/^}/:zlib.c。行の範囲や単一の行番号を指定することもでき、同じ種類の出力が得られます。

scroll-to-top