Git ソースコードの鳥瞰図

新しい開発者にとっては Git のソースコードの構成を理解するのは必ずしも 容易ではないでしょう。このセクションでは、どこから開始したら良いかを 示す小さなガイダンスです。

手始めとして良い場所は、初期コミットの内容を見ることです。次のように取得します:

$ git checkout e83c5163

初期コミットには現在の git のほとんど全ての基礎ができていますが、 ざっと読みとおすのには十分な小ささです。

現在ではこのリビジョンの頃から用語が変更されています。例えば、 このリビジョンの README では現在 コミット と呼んでいる ものを "チェンジセット" と呼んでいます。

また、現在は "キャッシュ" という言い方はせず それどころか"索引(index)" と呼んでいますが、 ファイルは今も cache.h です。(注釈:Not much reason to change it now, especially since there is no good single name for it anyway, because it is basically the header file which is included by all of Git's C sources.)

初期コミットを眺めて概要を把握できたら、より新しいバージョンを チェックアウトし、cache.h, object.h, commit.h にざっと目を 通してください。

初期の頃には、Git は UNIX の伝統にならい、非常にシンプルなプログラムの 集まりとなっていて、それらをパイプで組み合わせ、スクリプト化したりして 利用するようなツールでした。新しいことを試すのが容易であったため、 初期の開発でこれは良いことでした。しかし、現在では、これらの大部分は 内部に組み込まれ、いくつかのコア部分は "ライブラリ化" されました。 つまり、パフォーマンスや移植性、コードの重複の回避などの理由で libgit.a に入れられました。

ここまでの確認で、索引が何かを理解し、そして、cashe.h`内の索引のデータ 構造を理解することができたはずです。また、4つのオブジェクトタイプ(blob, tree, commit, tag)が `struct object の共通構造を継承していていることや、 struct object がそれらの最初のメンバであり 例えば (struct object *)commit のようにキャストすることで &commit->object と同じ結果が得られること、同じようにオブジェクト名やフラグも 得られることを確認できたはずです。

ここまでで、オブジェクトデータに関する確認はひと段落です。

次のステップ:オブジェクトの指定方法を理解してください。 コミットの指定方法 に書かれているとおり、 オブジェクトの指定方法は数個です(そして、それらはコミットだけに限ったことではありません!)。 これらの指定部分の実装は sha1_name.c で行われています。 その中の関数 get_sha1() をざっとみてください。 特徴的な部分は `get_sha1_basic() などの関数部分でハンドリングされています。

この部分は、Git の最もライブラリ化された部分、すなわちリビジョンウォーカー に対するよい導入になっています。

基本的には、git log の初期バージョンは次のようなシェルスクリプトでした:

$ git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | \
        LESS=-S ${PAGER:-less}

これは何を意味するでしょうか?

git rev-list はリビジョンウォーカーの元になったバージョンであり、 常に リビジョンの一覧を標準出力に出力するコマンドです。 このコマンドは現在でも役に立っており、新しい Git プログラムを スクリプトとして作成するような場合によく利用されます。

git rev-parse はもはや重要ではありません;何故ならこれは、スクリプトにより コールされる異なる配管コマンドに対して、関連のあるオプションをフィルタリング する為だけに利用されているからです。

git rev-list が行っていることのほとんどは、revision.crevision.h に含まれています。rev_info という名前の構造内のオプションを包み込み、 どのようにどのリビジョンにウォーク(アクセス)するかなどのコントロールをします。

git rev-parse の元の役割は、現在は setup_revisions() 関数に 取って代わられました。setup_revisions() はリビジョンウォーカーの為に リビジョンと共通のコマンドラインの解析をします。解析結果は rev_info に 保管されて利用されます。setup_revisions() を呼び出した後に、自身の コマンドラインオプションを解析することもできます。その後に prepare_revision_walk() をコールして初期化すると、get_revision() 関数にて ここのコミットを取得することができます。

リビジョンウォーカーの処理をより詳しく知りたい場合は、始めに cmd_log() の 実装を参照してください;git show v1.3.0~155^2~4 を実行し、その関数まで スクロールしてください(もはや setup_pager() を直接コールする必要がない 点に注意してください)。

現在では git log はビルトイン化されています(つまり、git コマンド内に含まれています)。 ビルトイン化されているソース部分は

時には1つのソースファイルに複数のビルトインが含まれていることがあります。 例えば、cmd_whatchanged()cmd_log() はコードのかなりを共有しているため、 両方とも builtin-log.c 内に存在します。このような場合、コマンドは .c ファイルの名前で呼ばれず、Makefile 内の BUILT_INS 内に リストされている必要があります。

git log は 元のスクリプトがしていた内容よりも C の実装の方がより 複雑に見えますが、より高い柔軟性とパフォーマンス性を持っています。

ここまでで再び一息つくのにちょうど良い箇所です。

レッスン3はコードの観察です。(基本的な概念を学んだ後に) Git の構成を学ぶには これが一番良い方法です

興味のある内容を考えてください。たとえば "blob のオブジェクト名からどのように blob にアクセスするのだろう?"とか。 まず最初に、それを行う Git コマンドを見つけてください。この例では、 git show または git cat-file です。

明瞭にするため、git cat-file にしましょう。何故なら

builtin-cat-file.c を開き、cmd_cat_file() を探し出して、 何が行われているかを見てください。

        git_config(git_default_config);
        if (argc != 3)
                usage("git cat-file [-t|-s|-e|-p|<type>] <sha1>");
        if (get_sha1(argv[2], sha1))
                die("Not a valid object name %s", argv[2]);

明らかに細かな部分はスキップしましょう;ここで興味深い箇所は get_sha1() を読んでいる箇所です。ここではオブジェクト名である argv[2] を読み込み、もしそれが現在のリポジトリに存在するオブジェクトを参照している場合は その SHA-1 を変数 sha1 に代入します。

ここで興味深いのは次の2点です:

これらの両方をソースを通して確認できます。

さて、ここが核心です:

        case 0:
                buf = read_object_with_reference(sha1, argv[1], &size, NULL);

これが blob (実際のところ、blob だけでなくすべてのオブジェクト)を読む方法です。 この関数 read_object_with_reference() がどのように動くかを知る為には その部分のソースコードを git grep read_object_with | grep ":[a-z]" のように して見つけ出し、ソースを読むことです。

結果がどのように使用されるかを見つけるには、cmd_cat_file() 内を見ます:

        write_or_die(1, buf, size);

時には機能の実装箇所を探し出すことができない場合があるかもしれません。 そのような場合には、git log の出力を探しだし、関連するコミットを git show すると手助けになるかもしれません。

例: git bundle に対するテストケースがあることは知っているが、 それがどこにあるかがわからない場合 (もちろん、git grep bundle t/ することも できますが、それではポイントを示すことになりません!):

$ git log --no-merges t/

ページャ (less) 内で "bundle" を検索し、数行戻ると、 commit 18449ab0… にあるのがわかります。このオブジェクト名をコピーし コマンドラインにペーストします

$ git show 18449ab0

ビンゴ!

次の例:スクリプトをビルトイン化するために必要な作業を調べるには:

$ git log --no-merges --diff-filter=A builtin-*.c

ここまでに見てきたように、Git は Git 自身のソースを調べるときにも 最も役に立つツールなのです!