チャプター ▾ 第2版

7.10 Git Tools - Gitを使ったデバッグ

Gitを使ったデバッグ

Gitは主にバージョン管理のために使われますが、ソースコードのデバッグに役立つコマンドもいくつか提供しています。Gitはほぼあらゆる種類のコンテンツを扱えるように設計されているため、これらのツールはかなり汎用的ですが、問題が発生したときにバグや原因を突き止めるのに役立つことがよくあります。

ファイルの注釈付け

コードのバグを突き止めて、いつ、なぜ導入されたのかを知りたい場合、ファイルの注釈付けがしばしば最も良いツールとなります。これにより、各行を最後に変更したコミットがわかります。したがって、コードのメソッドにバグがあることがわかった場合、git blameでファイルに注釈を付けることで、その行の導入に責任があるコミットを特定できます。

次の例では、git blameを使って、トップレベルのLinuxカーネルのMakefileの行に責任があるコミットとコミッターを特定し、さらに-Lオプションを使って、そのファイルの69行目から82行目までの注釈の出力を制限しています

$ git blame -L 69,82 Makefile
b8b0618cf6fab (Cheng Renquan  2009-05-26 16:03:07 +0800 69) ifeq ("$(origin V)", "command line")
b8b0618cf6fab (Cheng Renquan  2009-05-26 16:03:07 +0800 70)   KBUILD_VERBOSE = $(V)
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 71) endif
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 72) ifndef KBUILD_VERBOSE
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 73)   KBUILD_VERBOSE = 0
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 74) endif
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 75)
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 76) ifeq ($(KBUILD_VERBOSE),1)
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 77)   quiet =
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 78)   Q =
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 79) else
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 80)   quiet=quiet_
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 81)   Q = @
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 82) endif

最初のフィールドは、その行を最後に変更したコミットのSHA-1の一部であることに注意してください。次の2つのフィールドは、そのコミットから抽出された値です。つまり、そのコミットの作者名と作成日です。これにより、誰がいつその行を変更したのかを簡単に確認できます。その後に、行番号とファイルの内容が続きます。また、^1da177e4c3f4のコミット行にも注目してください。^プレフィックスは、リポジトリの最初のコミットで導入されて以来、変更されていない行を示します。これは少し紛らわしいです。なぜなら、Gitが^を使ってコミットのSHA-1を変更する少なくとも3つの異なる方法を見てきたからです。しかし、ここではそれが意味するものです。

Gitのもう一つのクールな点は、ファイルの改名を明示的に追跡しないことです。スナップショットを記録し、後で暗黙的に何が改名されたかを推測しようとします。この興味深い機能の一つは、あらゆる種類のコードの移動も推測できることです。git blame-Cを渡すと、Gitは注釈を付けているファイルを分析し、コードの断片が別の場所からコピーされた場合、それらが元々どこから来たのかを推測しようとします。例えば、GITServerHandler.mというファイルを複数のファイルにリファクタリングし、そのうちの一つがGITPackUpload.mであるとします。GITPackUpload.m-Cオプションでblameすることで、コードのセクションが元々どこから来たのかを確認できます

$ git blame -C -L 141,153 GITPackUpload.m
f344f58d GITServerHandler.m (Scott 2009-01-04 141)
f344f58d GITServerHandler.m (Scott 2009-01-04 142) - (void) gatherObjectShasFromC
f344f58d GITServerHandler.m (Scott 2009-01-04 143) {
70befddd GITServerHandler.m (Scott 2009-03-22 144)         //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 145)
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 146)         NSString *parentSha;
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 147)         GITCommit *commit = [g
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 148)
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 149)         //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 150)
56ef2caf GITServerHandler.m (Scott 2009-01-05 151)         if(commit) {
56ef2caf GITServerHandler.m (Scott 2009-01-05 152)                 [refDict setOb
56ef2caf GITServerHandler.m (Scott 2009-01-05 153)

これは本当に便利です。通常、コードをコピーしたコミットが元のコミットとして取得されます。なぜなら、それがこのファイルでそれらの行を初めて触ったときだからです。Gitは、たとえ別のファイルであっても、それらの行を最初に書いた元のコミットを教えてくれます。

問題がどこにあるか分かっている場合は、ファイルに注釈を付けるのが役立ちます。何が壊れているのか分からず、コードが機能していた最後の状態から何十、何百ものコミットがあった場合は、git bisectに助けを求めることになるでしょう。bisectコマンドは、コミット履歴を二分探索して、どのコミットが問題を導入したかを可能な限り迅速に特定するのに役立ちます。

コードのリリースを本番環境にプッシュしたばかりで、開発環境では発生しなかったバグ報告を受け取っており、なぜコードがそのような動作をするのか想像できないとします。コードに戻ると、問題を再現できることが判明しましたが、何が問題なのか分かりません。コードをbisectして見つけることができます。まずgit bisect startを実行して開始し、次にgit bisect badを使って、現在いるコミットが壊れていることをシステムに伝えます。次に、git bisect good <good_commit>を使って、最後に既知の良い状態がいつだったかをbisectに伝えなければなりません

$ git bisect start
$ git bisect bad
$ git bisect good v1.0
Bisecting: 6 revisions left to test after this
[ecb6e1bc347ccecc5f9350d878ce677feb13d3b2] Error handling on repo

Gitは、最後に良いコミットとしてマークしたコミット(v1.0)と現在の悪いバージョンとの間に約12のコミットがあったことを把握し、その中間をチェックアウトしてくれました。この時点で、テストを実行して、このコミットで問題が存在するかどうかを確認できます。もし存在すれば、この中間コミットより前にどこかで導入されたことになります。存在しなければ、問題はこの中間コミットより後にどこかで導入されたことになります。ここでは問題がないことが判明し、git bisect goodと入力してGitに伝え、旅を続けます

$ git bisect good
Bisecting: 3 revisions left to test after this
[b047b02ea83310a70fd603dc8cd7a6cd13d15c04] Secure this thing

今度は別のコミットにいます。先ほどテストしたコミットと悪いコミットの中間です。もう一度テストを実行すると、このコミットが壊れていることが判明したので、git bisect badと入力してGitに伝えます

$ git bisect bad
Bisecting: 1 revisions left to test after this
[f71ce38690acf49c1f3c9bea38e09d82a5ce6014] Drop exceptions table

このコミットは問題なく、Gitは問題がどこで導入されたかを判断するために必要なすべての情報を持っています。最初の悪いコミットのSHA-1を伝え、コミット情報の一部と、そのコミットでどのファイルが変更されたかを表示するので、このバグを導入した可能性のある事柄を把握できます

$ git bisect good
b047b02ea83310a70fd603dc8cd7a6cd13d15c04 is first bad commit
commit b047b02ea83310a70fd603dc8cd7a6cd13d15c04
Author: PJ Hyett <pjhyett@example.com>
Date:   Tue Jan 27 14:48:32 2009 -0800

    Secure this thing

:040000 040000 40ee3e7821b895e52c1695092db9bdc4c61d1730
f24d3c6ebcfc639b1a3814550e62d60b8e68a8e4 M  config

終了したら、git bisect resetを実行して、開始前のHEADをリセットする必要があります。そうしないと、奇妙な状態になってしまいます

$ git bisect reset

これは、数分で何百ものコミットから導入されたバグをチェックするのに役立つ強力なツールです。実際、プロジェクトが良い場合は0を、悪い場合は0以外の値を終了するスクリプトがあれば、git bisectを完全に自動化できます。まず、既知の悪いコミットと良いコミットを提供することで、bisectの範囲を再び伝えます。これは、bisect startコマンドでリストアップすることで可能です。既知の悪いコミットを最初に、既知の良いコミットを次にリストアップします

$ git bisect start HEAD v1.0
$ git bisect run test-error.sh

これにより、Gitが最初の壊れたコミットを見つけるまで、チェックアウトされた各コミットでtest-error.shが自動的に実行されます。makemake testsなど、自動テストを実行するものを実行することもできます。

scroll-to-top