新しい開発者にとっては 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.c
と revision.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
コマンド内に含まれています)。
ビルトイン化されているソース部分は
cmd_<bla>
という名前の関数で、通常は builtin-<bla>.c
内で定義されていて、
宣言は builtin.h
内で行われています。
git.c
内の commands[]
配列のエントリと、
Makefile
内の BUILTIN_OBJECTS
内のエントリとがあります。
時には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
にしましょう。何故なら
cat-file.c
のリビジョンが 20 個、
ビルトイン化されて builtin-cat-file.c
に変更されてからのリビジョンは 10個以下です)。
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点です:
get_cha1()
は 成功 時に 0 を返却します。これは Git のハックを
初めて行う人にとっては驚くべきことですが、エラーの種別毎に異なる負数を返却し、
成功時には 0 を返却するのは UNIX の昔からの伝統です。
get_sha1()
関数の戻り値である変数 sha1
は unsigned char *
ですが、
実際は unsigned char[20]
を指し示すことが想定されています。
この変数は指定されたコミットの 160-bit の SHA-1 を含んでいます。
SHA-1 が unsigned char *
として渡される時はいつでも、
それがバイナリ表現であり、char *
で渡される 16進の ASCII表現とは違う
ことに注意してください。
これらの両方をソースを通して確認できます。
さて、ここが核心です:
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 自身のソースを調べるときにも 最も役に立つツールなのです!