Git
目次 ▾ 第2版

7.10 Gitツール - Gitを使ったデバッグ

Gitを使ったデバッグ

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

ファイルの注釈

コードのバグを発見し、いつ、なぜ導入されたのかを知りたい場合、ファイルの注釈は最適なツールの1つです。これは、ファイルの各行を最後に変更したコミットを表示します。そのため、コードのメソッドにバグがあることがわかった場合は、`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のもう1つの優れた点は、ファイルの名前変更を明示的に追跡しないことです。スナップショットを記録し、後で暗黙的に何が名前変更されたかを推測しようとします。これの興味深い機能の1つは、あらゆる種類のコードの移動も調べることができることです。`git blame`に`-C`を渡すと、Gitは注釈を付けているファイルを分析し、コードのスニペットが他の場所からコピーされた場合、その元の場所を特定しようとします。たとえば、`GITServerHandler.m`という名前のファイルを複数のファイル(その1つは`GITPackUpload.m`)にリファクタリングするとします。`-C`オプションを使用して`GITPackUpload.m`を非難することで、コードのセクションが元々どこから来たのかを確認できます。

$ 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`コマンドは、コミット履歴をバイナリサーチして、問題を導入したコミットをできるだけ早く特定するのに役立ちます。

本番環境にコードをリリースしたとしましょう。開発環境では発生していなかったバグの報告を受け、コードがなぜそのような動作をするのか想像もつきません。コードを調べ直すと、問題は再現できるものの、何が間違っているのか分かりません。このような場合、コードをバイセクトして原因を特定できます。まず、git bisect startを実行して開始し、次にgit bisect badを使用して、現在のコミットが壊れていることをシステムに伝えます。次に、git bisect good <good_commit>を使用して、最後に正常だった状態をバイセクトに伝えなければなりません。

$ 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 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は問題が導入された場所を特定するために必要な情報を得ました。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 startコマンドでそれらをリストし、最初に既知の異常なコミット、次に既知の正常なコミットをリストできます。

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

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

scroll-to-top