What’s an Object Walk?

オブジェクト散歩(object walk)はGitの重要な概念です。これは、オブジェクト転送やfsckなどの操作を支えるプロセスです。特定のコミットから開始して、オブジェクト間のリストは、コミット間の親関係(コミットWに基づくコミットX)とオブジェクト間の包含関係(ツリーYはコミットX内に含まれ、ブロブZはツリーY内にある)をたどることによって検出されます。コミットXの作業ツリーは y/z.txt のようなものです)。

関連する概念はリビジョン散歩(revision walk)です。これは、コミットオブジェクトとその親の関係に焦点を当てており、他のオブジェクトタイプについては詳しく説明していません。リビジョン散歩は、 git log などの操作に使用されます。

  • Documentation/user-manual.txt 「HackingGit」では、リビジョンウォーカーの具現化をいくつか網羅しています。

  • revision.h

  • Git for Computer Scientists Git内のオブジェクトの種類と、オブジェクト散歩が実際に何を記述しているのかについての概要を説明します。

Setting Up

`master`から新しいブランチを作成します。

git checkout -b revwalk origin/master

我々のささやかなモノを新コマンドにします。そう、名前は git walken にするとしましょう。新しいファイル builtin/walken.c を開き、コマンドハンドラーを設定します:

/*
 * "git walken"
 *
 * Part of the "My First Object Walk" tutorial.
 */

#include "builtin.h"

int cmd_walken(int argc, const char **argv, const char *prefix)
{
        trace_printf(_("cmd_walken incoming...\n"));
        return 0;
}
Note
trace_printf() は、実行時にオンまたはオフにできるという点で printf() とは異なります。このチュートリアルでは、「配管」コマンドとして使用することを目的としているかのように「walken」を記述します。つまり人間が対話的に使用する(「磁器」)コマンドではなく、主にスクリプトで使用されるコマンドとして使用します。そのため、代わりにデバッグ出力を trace_printf() に送信します。実行時に、環境変数 GIT_TRACE を設定して、トレース出力を有効にします。

すべてのサブコマンドで一貫して行う必要があるように、使用法テキストと -h 処理を追加します(そうしないと、テストスイートが気づいて文句を言います)。 parse-options.h ヘッダーをインクルードする必要があります。

#include "parse-options.h"

...

int cmd_walken(int argc, const char **argv, const char *prefix)
{
        const char * const walken_usage[] = {
                N_("git walken"),
                NULL,
        };
        struct option options[] = {
                OPT_END()
        };

        argc = parse_options(argc, argv, prefix, options, walken_usage, 0);

        ...
}

また、 builtin.hcmd_whatchanged() の近くに関連する行を追加します:

int cmd_walken(int argc, const char **argv, const char *prefix);

git.ccommands[] にルファベット順を維持しながら、 whatchanged のエントリの近くにコマンドを含めます:

{ "walken", cmd_walken, RUN_SETUP },

Makefilebuiltin/worktree.o の近くに行を追加します:

BUILTIN_OBJS += builtin/walken.o

DEVELOPER フラグが設定されていることを確認することを忘れないようにして、コマンドをビルドしてテストし、 GIT_TRACE を有効にして、デバッグ出力を確認します。

$ echo DEVELOPER=1 >>config.mak
$ make
$ GIT_TRACE=1 ./bin-wrappers/git walken
Note
新しいコマンドプロセスのより完全な概要については、 Documentation/MyFirstContribution.txt をご覧ください。
Note
リファレンス実装は https://github.com/nasamuffin/git/tree/revwalk にあります。

struct rev_cmdline_info

struct rev_cmdline_info の定義は、 revision.h にあります。

この構造体は rev_info 構造体に含まれており、CLIを介してユーザーが提供したパラメーターを反映するために使用されます。

nr は、配列に存在する rev_cmdline_entry の数を表します。

allocALLOC_GROW マクロによって使用されます。 alloc.h を確認してください。この変数は、リストの割り当てられたサイズを追跡するために使用されます。

エントリごとに、以下ことがわかります:

item は、オブジェクト散歩の基礎となるオブジェクトです。 Gitのアイテムは、ブロブまたはツリーまたはコミットまたはタグです。(Documentation/gittutorial-2.txt を参照してください。)

name は、オブジェクトのオブジェクトID(OID)です。これは、Gitを使用してあなたのソースファイルを整理した時におなじみの16進文字列です。OIDの出所については、上記のチュートリアルを上に向かって確認してください。

whence は、指定されたオブジェクトの親をどうするかについての情報を示します。このフラグについては、後ほど詳しく説明します。 Documentation/revisions.txt を見て、 何が whence 値を設定できるかを理解してください。

flags は、リビジョン散歩の開始を示唆するために使用され、 revision.h#include 群の下の最初のブロックです。 rev_cmdline_info に設定される可能性が最も高いのは UNINTERESTINGBOTTOM ですが、これらと同じフラグを散歩中に使用することもできます。

struct rev_info

これはちょっと長くて、多くのフィールドは、構成オプションではなく、 revision.c による散歩中にのみ使用されます。 struct rev_info の構成可能なフラグのほとんどは、 Documentation/rev-list-options.txt にミラーを持っています。じっくり読むことをお勧めします。

Basic Commit Walk

まず、 git log --oneline の出力を複製できるかどうかを見てみましょう。あなた独自のオブジェクト散歩を実行する際の基準を見つけるために、我々はその実装を頻繁に拝みます。

そのためには、まず、現在のコミットに先行するすべてのコミットを順番に検索します。そのそれぞれからコミットの名前と件名を抽出します。

理想的には、私達は現在さまざまなブランチの先端にあるものを見つけることもできます。

Setting Up

オブジェクト散歩の準備には、いくつかの明確な段階があります。

  1. このモード、および呼び出される可能性のある他のモードのデフォルト設定を実行します。

  2. 関連する設定については、構成ファイルを確認してください。

  3. rev_info 構造体を設定します。

  4. 初期化された rev_info を現在の散歩に合うように微調整します。

  5. 散歩のために rev_info を準備します。

  6. オブジェクトを繰り返し(iterate)処理し、各オブジェクトを処理します。

Default Setups

コマンドの動作を変更する可能性のある構成ファイルを調べる前に、コマンドに含まれる可能性のあるスイッチまたはオプションのデフォルト状態を設定してください。コマンドが他のGitコンポーネントを利用している場合は、デフォルトの状態も設定するように依頼してください。たとえば、 git loggrepdiff の機能を利用するため、その init_log_defaults() は独自の状態(decoration_style)を設定し、 grepdiff に、それらの初期化関数をそれぞれを呼び出して初期化するように要求します。

Configuring From .gitconfig

次に、関連する構成設定(つまり git config から読み取り可能で設定可能な設定)を確認する必要があります。 これは git_config() へのコールバックを提供することによって行われます。そのコールバック内で、これらのオプションをインターセプトする必要がある可能性のある他のコンポーネントからメソッドを呼び出すこともできます。コールバックは、(グローバル、ローカル、ワークツリーなど、)Gitが認識している構成値ごとに1回呼び出されます。

デフォルト値と同様に、私達はここではまだ何もする必要はありません。 ただし、他の既存の構成コールバックを呼び出さない場合は、 git_default_config() を呼び出す必要があります。

新しい関数を builtin/walken.c に追加します。 config.h ヘッダーもインクルードする必要があります:

#include "config.h"

...

static int git_walken_config(const char *var, const char *value, void *cb)
{
        /*
         * For now, we don't have any custom configuration, so fall back to
         * the default config.
         */
        return git_default_config(var, value, cb);
}

必ずあなたの cmd_walken()git_config() を呼び出してください:

int cmd_walken(int argc, const char **argv, const char *prefix)
{
        ...

        git_config(git_walken_config, NULL);

        ...
}

Setting Up rev_info

私達は外部構成とオプションを収集したので、散歩の実行に使用する rev_info オブジェクトを初期化します。これは通常、 ターゲットとするリポジトリと、 cmd_walkenprefix 引数と、あなたの rev_info 構造体を使用して repo_init_revisions() を呼び出す事によって行います。

struct rev_inforepo_init_revisions() 関数呼び出しを追加します。 revision.h ヘッダーもインクルードする必要があります:

#include "revision.h"

...

int cmd_walken(int argc, const char **argv, const char *prefix)
{
        /* This can go wherever you like in your declarations.*/
        struct rev_info rev;
        ...

        /* This should go after the git_config() call. */
        repo_init_revisions(the_repository, &rev, prefix);

        ...
}

Tweaking rev_info For the Walk

私達は目的に近づいていますが、まだ準備が整っていません。 rev が初期化されたので、ニーズに合わせて変更できます。これは通常、わかりやすくするためにヘルパー内で行われるため、以下のように追加します:

static void final_rev_info_setup(struct rev_info *rev)
{
        /*
         * We want to mimic the appearance of `git log --oneline`, so let's
         * force oneline format.
         */
        get_commit_format("oneline", rev);

        /* Start our object walk at HEAD. */
        add_head_to_pending(rev);
}
Note

短縮形の add_head_to_pending() を使用する代わりに、以下のようにすることができます:

        struct setup_revision_opt opt;

        memset(&opt, 0, sizeof(opt));
        opt.def = "HEAD";
        opt.revarg_opt = REVARG_COMMITTISH;
        setup_revisions(argc, argv, rev, &opt);

setup_revision_opt を使用すると、散歩の開始点をより細かく制御できます。

Then let’s invoke final_rev_info_setup() after the call to repo_init_revisions():

int cmd_walken(int argc, const char **argv, const char *prefix)
{
        ...

        final_rev_info_setup(&rev);

        ...
}

後で final_rev_info_setup() にさらに引数を追加したい場合があるかもしれません。しかし今のところ、これが私たちに必要なすべてです。

Preparing rev_info For the Walk

rev がすべて初期化および構成されました。しかし始める前にもう1つのセットアップ手順があります。これはヘルパーで行うことができます。ヘルパーは、散歩用の rev_info を準備し、散歩自体を実行します。 prepare_revision_walk() の呼び出しでヘルパーを開始しましょう。これにより、自動的に停止せずにエラーを返すことができます:

static void walken_commit_walk(struct rev_info *rev)
{
        if (prepare_revision_walk(rev))
                die(_("revision walk setup failed"));
}
Note
die() は標準エラー出力に出力し、プログラムを終了します。標準エラーに出力されると、人間に見られる可能性が高いので、国際化(localize)します。

Performing the Walk!

ついに散歩を始める準備ができました! これで、 rev_info がイテレータとしても使用できることがわかります。 get_revision() を繰り返し使用して、散歩の次のアイテムに移動します。リストされた変数宣言を上部に追加し、散歩ループを walken_commit_walk() 内の prepare_revision_walk() 呼び出しの下に追加します:

static void walken_commit_walk(struct rev_info *rev)
{
        struct commit *commit;
        struct strbuf prettybuf = STRBUF_INIT;

        ...

        while ((commit = get_revision(rev))) {
                strbuf_reset(&prettybuf);
                pp_commit_easy(CMIT_FMT_ONELINE, commit, &prettybuf);
                puts(prettybuf.buf);
        }
        strbuf_release(&prettybuf);
}
Note
puts()char*stdout に出力します。これは、マシンでパースされることが期待されるコマンドの一部であるため、私達はそれを stdout に直接送信します。

では、試してみましょう。

$ make
$ ./bin-wrappers/git walken

ツリーの履歴にあるすべてのコミットのすべての件名が順番に表示され、最初のコミット「"Initial revision of "git", the information manager from hell」で終わります。おめでとう! あなたは最初のリビジョン散歩を書きました。興味があれば、各コミットからいくつかの追加フィールドを出力して遊ぶことができます。 commit.h で利用可能な関数をご覧ください。

Adding a Filter

次に、作者に基づいて、表示されるコミットをフィルタリングしてみましょう。 これは、 git log --author=<pattern> を実行するのと同じです。 struct grep_opt である rev_info.grep_filter を変更することで、フィルターを追加できます。

最初にいくつかのセットアップを行います。 grep_config()git_walken_config() に追加:

static int git_walken_config(const char *var, const char *value, void *cb)
{
        grep_config(var, value, cb);
        return git_default_config(var, value, cb);
}

次に、 grep_filter を変更できます。 これは、 grep.h にある便利な関数を使用して行います。戯れに、私達は gmail.com メールアドレスを使用している人々からのコミットのみにフィルタリングしています(これは、誰が趣味としてGitに取り組んでいるかについての情報とするには、あまり正確ではありませんが)。ヘッダーの特定の行である作者をチェックしているので、 append_header_grep_pattern() ヘルパーを使用します。 enum grep_header_field を使用して、コミットヘッダーのどの部分を検索するかを指定できます。

final_rev_info_setup() に、あなたのフィルター行を追加:

static void final_rev_info_setup(int argc, const char **argv,
                const char *prefix, struct rev_info *rev)
{
        ...

        append_header_grep_pattern(&rev->grep_filter, GREP_HEADER_AUTHOR,
                "gmail");
        compile_grep_patterns(&rev->grep_filter);

        ...
}

append_header_grep_pattern() は、あなたの新しい "gmail" パターンを rev_info に追加しますが、それは compile_grep_patterns() でコンパイルしない限り機能しません。

Note
setup_revisions() を使用している場合(たとえば add_head_to_pending() を使用する代わりに setup_revision_opt を渡す場合)、 compile_grep_patterns() を呼び出す必要はありません。なぜなら setup_revisions()compile_grep_patterns() を呼び出すからです。
Note
append_header_grep_pattern()enum grep_contextenum grep_pat_token を追加します。あなたは必要に応じて append_grep_pattern() ヘルパーを介して全く同じフィルターを追加することもできます。

Changing the Order

リビジョン散歩中にコミットの順序を変更する方法はいくつかあります。 まず、 enum rev_sort_order を使用して、いくつかの一般的な順序から選択できます。

topo_ordergit log --topo-order と同じです。すべての子が表示される前に親を表示することを避け、異なる履歴行にあるコミットを混在させることを避けます。 (--topo-order に関する git help log のセクションには、これを説明するための非常に優れた図があります。)

REV_SORT_BY_AUTHOR_DATE ではなく REV_SORT_BY_COMMIT_DATE で実行するとどうなるか見てみましょう。以下を追加します:

static void final_rev_info_setup(int argc, const char **argv,
                const char *prefix, struct rev_info *rev)
{
        ...

        rev->topo_order = 1;
        rev->sort_order = REV_SORT_BY_COMMIT_DATE;

        ...
}

これをファイルに出力して、作者の日付でソートされた散歩と簡単にdiffできるようにします。

$ make
$ ./bin-wrappers/git walken > commit-date.txt

次に、作成者の日付で並べ替えて、もう一度実行してみましょう。

static void final_rev_info_setup(int argc, const char **argv,
                const char *prefix, struct rev_info *rev)
{
        ...

        rev->topo_order = 1;
        rev->sort_order = REV_SORT_BY_AUTHOR_DATE;

        ...
}
$ make
$ ./bin-wrappers/git walken > author-date.txt

最後に、2つを比較します。 これは、オブジェクト名や日付がないとあまり役に立ちませんが、うまくいけば、私たちはアイデアを得ることができます。

$ diff -u commit-date.txt author-date.txt

この表示は、コミットが書き込まれた後、たとえば git rebase を使用してコミットを並べ替えることができることを示しています。

コミットの並べ替えをもう一度試してみましょう。 rev_inforeverse フラグを公開します。そのフラグを final_rev_info_setup() 内のどこかに設定します:

static void final_rev_info_setup(int argc, const char **argv, const char *prefix,
                struct rev_info *rev)
{
        ...

        rev->reverse = 1;

        ...
}

もう一度あなたの散歩を実行して、順番の違いを注視してください。(もしあなたがgrepパターンを削除すると、この呼び出しによって現在のHEADとして与えられる最後のコミットが表示されます。)

Basic Object Walk

これまでのところ、私達はコミットのみを歩いてきました。しかし、Gitにはもっと多くの種類のオブジェクトがあります! 全てのオブジェクトを散歩することができるかどうかを確かめて、それぞれについていくつかの情報を見つけるとしましょう。

私達の例に基づいて作業を行うとすると、 git pack-objects は、ビットマップまたはパックファイルにパックするためのあらゆる種類のオブジェクトを準備します。私たちが興味を持っている作業は、 builtins/pack-objects.c:get_object_list() にあります。その関数を調べると、すべてのオブジェクトの散歩が traverse_commit_list() または traverse_commit_list_filtered() によって実行されていることがわかります。これらの2つの関数は list-objects.c にあります。ソースを調べると、名前にもかかわらず、これらの関数はすべての種類のオブジェクトをトラバースすることがわかります。それでは、 traverse_commit_list_filtered() の引数を見てみましょう。

  • struct rev_info *revs: これは、ウォークに使用される rev_info です。 その filter メンバーが NULL でない場合、filter にはオブジェクト・リストをフィルタリングする方法に関する情報が含まれます。

  • show_commit_fn show_commit: 個々のコミットオブジェクトを処理するために使用されるコールバック。

  • show_object_fn show_object: 各非コミットオブジェクト(つまり、 各blobまたはtreeまたはtag)を処理するために使用されるコールバック。

  • void *show_data: show_commit と`show_object` に順番に渡されるコンテキストバッファ。

さらに、 traverse_commit_list_filtered() には追加のパラメーターがあります:

  • struct oidset *omitted: 提供されたフィルターによって省略されたオブジェクトIDのリンクリスト。

これらのメソッドは、自分で繰り返し呼び出す必要はなく、我々が提供するコールバックを使用するようです。素敵!では最初にコールバックを追加しましょう。

このチュートリアルのために、検出した各種類のオブジェクトの数を追跡するだけですけどね。 builtin/walken.c のファイルスコープで、以下の追跡変数を追加します:

static int commit_count;
static int tag_count;
static int blob_count;
static int tree_count;

コミットは、他のオブジェクトとは異なるコールバックによって処理されます。まずそれをやってみましょう:

static void walken_show_commit(struct commit *cmt, void *buf)
{
        commit_count++;
}

cmt 引数は割と明です。 ただし、 buf 引数は、実際にはトラバーサル呼び出しに提供できるコンテキストバッファーであることに注意してください。これは、先ほど説明した show_data です。

struct commit オブジェクトがあるので、以前のcommit-only散歩で見たのと同じ部分をすべて見ることができます。ただし、このチュートリアルのために、コミットカウンターをインクリメントして次に進みます。

非コミットのコールバックは、処理しているオブジェクトの種類を確認する必要があるため、少し異なります:

static void walken_show_object(struct object *obj, const char *str, void *buf)
{
        switch (obj->type) {
        case OBJ_TREE:
                tree_count++;
                break;
        case OBJ_BLOB:
                blob_count++;
                break;
        case OBJ_TAG:
                tag_count++;
                break;
        case OBJ_COMMIT:
                BUG("unexpected commit object in walken_show_object\n");
        default:
                BUG("unexpected object type %s in walken_show_object\n",
                        type_name(obj->type));
        }
}

繰り返しになりますが、 obj は割と自明であり、 bufwalken_show_commit() が受け取るコンテキストポインタと同じであると推測できます。 traverse_commit_list()traverse_commit_list_filtered() への show_data 引数です。最後に、 str にはオブジェクトの名前が含まれ、最終的には foo.txt (blob) または bar/baz (tree) または v1.2.3 (tag) のようになります。

コミットを二重にカウントしていないことを確認するために、コミットオブジェクトが非コミットコールバックを介してルーティングされているかどうかについての一言を含めます。無効なオブジェクトタイプが表示された場合も一言います。これらの2つのケースは到達不能であり、Gitコードベースにセマンティックが変更された場合にのみ変更されるため、 BUG() を使用して一言います。これは、開発者が行った変更が意図しない結果を引き起こしたことを示すシグナルです。その変更を理解するには、コードベースの残りの部分を更新する必要があります。 BUG() は一般に公開されることを意図していないため、ローカライズされていません。

メインのオブジェクト散歩の実装は、コミット散歩の実装とは大幅に異なるため、オブジェク散歩を実行するための新しい関数を作成しましょう。ここでもすべてのオブジェクトに適用できるセットアップを実行して、コミットのみの散歩に適用できるセットアップとは別にすることができます。

まず、 structrev_info ですべてのタイプのオブジェクトを有効にします。 また、 tree_blobs_in_commit_order をオンにします。これは、コミット履歴が検出された後に終了を待ってすべてのツリーをウォークスルーするのではなく、コミットのツリーとそれが指すすべてのものを、各コミットを見つけた直後に散歩することを意味します。適切な設定が構成されたら、 prepare_revision_walk() を呼び出す準備ができています。

static void walken_object_walk(struct rev_info *rev)
{
        rev->tree_objects = 1;
        rev->blob_objects = 1;
        rev->tag_objects = 1;
        rev->tree_blobs_in_commit_order = 1;

        if (prepare_revision_walk(rev))
                die(_("revision walk setup failed"));

        commit_count = 0;
        tag_count = 0;
        blob_count = 0;
        tree_count = 0;

フィルタリングされていない散歩だけを呼び出して、カウントを報告することから始めましょう。あなたの walken_object_walk() の実装を完了します。 list-objects.h ヘッダーもインクルードする必要があります:

#include "list-objects.h"

...

        traverse_commit_list(rev, walken_show_commit, walken_show_object, NULL);

        printf("commits %d\nblobs %d\ntags %d\ntrees %d\n", commit_count,
                blob_count, tag_count, tree_count);
}
Note
この出力は、マシンでのパースを目的としています。 したがって、これを trace_printf() に送信したり、ローカライズしたりすることはありません。ここに示されているフォーマットとおりに正確にカウントできるスクリプトが必要です。この出力を人間が読み取ることを意図している場合は、 _() を使用してローカライズする必要があります。

最後に、代わりにオブジェクト散歩を使用するように cmd_walken() に要求します。コマンドラインオプションの説明はこのチュートリアルの範囲外であるため、今回はコンパイル時に変更できるブランチをハードコーディングするだけです。 final_rev_info_setup()walken_commit_walk() を呼び出す場合は、代わりに以下のように条件分岐します:

        if (1) {
                add_head_to_pending(&rev);
                walken_object_walk(&rev);
        } else {
                final_rev_info_setup(argc, argv, prefix, &rev);
                walken_commit_walk(&rev);
        }
Note
簡単にするために、 final_rev_info_setup() で適用したすべてのフィルターと並べ替えを避け、保留中のキューに HEAD を追加しただけです。必要に応じて、 final_rev_info_setup() を条件から外し、 add_head_to_pending() の呼び出しを削除することで、前に追加したフィルターを確実に使用できます。

今や私達はコマンドの実行を試みることができます。コミット散歩よりもかなり長い時間がかかるはずですが、出力を調べると、その理由がわかります。出力はこの例のようになりますが、カウントは異なります:

Object walk completed. Found 55733 commits, 100274 blobs, 0 tags, and 104210 trees.

これの意味は、Gitプロジェクトには変更可能なサブディレクトリがたくさんあり、コミットごとに少なくとも1つのツリーがあるため、コミットよりも多くのツリーがあります。 コミット(HEAD)で開始したため、タグはありません。タグはコミットを指すことができますが、コミットはタグを指すことができません。

Note
これをあなた自身が実行すると、カウントは異なります。オブジェクトの数は、Gitプロジェクトとともに増加します。

Adding a Filter

Documentation/rev-list-options.txt に配置されたオブジェクト散歩に適用できるフィルターがいくつかあります。これらのフィルターは通常、パックファイルの作成や部分的なクローンの実行などの操作に役立ちます。 それらは list-objects-filter-options.h で定義されています。このチュートリアルでは、 "tree:1" フィルターを使用します。これにより、散歩の開始時に、コミットから到達可能なコミットによって直接参照されないすべてのツリーとブロブが散歩から省略されます。 (pending は、散歩中にトラバースする必要のあるオブジェクトのリストです。あなたが理解するのを助けるため、広さ優先のツリートラバースを想像してください。この場合、「pending」リストの「HEAD」のみで散歩を開始するため、「HEAD」または「HEAD」の履歴によって直接参照されていないツリーとブロブを省略します。)

今のところ、省略されたオブジェクトを追跡するつもりはないので、それらのパラメータを NULL に置き換えます。簡単にするために、フィルターを使用するかどうかに関係なく、単純なビルド時ブランチを追加します。 traverse_commit_list() を呼び出す行の「前に」以下の行を追加します。これにより、実行したばかりの散歩の種類がわかります:

        if (0) {
                /* Unfiltered: */
                trace_printf(_("Unfiltered object walk.\n"));
        } else {
                trace_printf(
                        _("Filtered object walk with filterspec 'tree:1'.\n"));
                CALLOC_ARRAY(rev->filter, 1);
                parse_list_objects_filter(rev->filter, "tree:1");
        }
        traverse_commit_list(rev, walken_show_commit,
                             walken_show_object, NULL);

rev->filter メンバーは通常、コマンド・ライン引数から直接構築されるため、モジュールは文字列から構築する簡単な方法を提供します。 現在はユーザー入力を受け取っていませんが、 parse_list_objects_filter() を使用してハードコーディングされた文字列で作成できます。

フィルタ仕様「tree:1」では、コミットごとにルートツリーのみが表示されることを期待しています。したがって、ツリーオブジェクトの数はコミット数以下である必要があります。 (これが当てはまる理由の例: git commit --revert は、祖父母と同じツリーオブジェクトを指します。)

Counting Omitted Objects

私達は、 git log --filter=<spec> --filter-print-omitted のように、フィルターによって省略されたすべてのオブジェクトを列挙する機能も持っています。 traverse_commit_list_filtered()omitted リストにデータを入力するように要求することは、オブジェクト散歩が、フィルタリングされていないオブジェクト散歩より優れたパフォーマンスを発揮し無いことを意味します。リストにデータを入力するために、到達可能なすべてのオブジェクトを散歩します。

まず、反復処理に使用する struct oidset と関連アイテムを追加します:

static void walken_object_walk(
        ...

        struct oidset omitted;
        struct oidset_iter oit;
        struct object_id *oid = NULL;
        int omitted_count = 0;
        oidset_init(&omitted, 0);

        ...

traverse_commit_list_filtered() の呼び出しを変更して、 あなたの omitted オブジェクトを含めます:

        ...

                traverse_commit_list_filtered(rev,
                        walken_show_commit, walken_show_object, NULL, &omitted);

        ...

あなたのトラバーサルの後、 oidset トラバーサルするのは非常に簡単です。 内部のすべてのオブジェクトをカウントし、printステートメントを変更します:

        /* Count the omitted objects. */
        oidset_iter_init(&omitted, &oit);

        while ((oid = oidset_iter_next(&oit)))
                omitted_count++;

        printf("commits %d\nblobs %d\ntags %d\ntrees %d\nomitted %d\n",
                commit_count, blob_count, tag_count, tree_count, omitted_count);

フィルタを使用して、または使用せずに散歩すると、それぞれの場合の合計オブジェクト数が同じであることがわかります。 また、 omitted が渡される場合とされない場合で、 walken サブコマンドの各呼び出しの時間を計って、省略されたすべてのオブジェクトを追跡することによる実行時の影響を確認することもできます。

Changing the Order

最後に、コミットの散歩だけでなく、すべてのオブジェクトの散歩を並べ替えることもできることを示しましょう。まず、ハンドラーをおしゃべりにします。 walken_show_commit()walken_show_object() を変更して、オブジェクトを出力します:

static void walken_show_commit(struct commit *cmt, void *buf)
{
        trace_printf("commit: %s\n", oid_to_hex(&cmt->object.oid));
        commit_count++;
}

static void walken_show_object(struct object *obj, const char *str, void *buf)
{
        trace_printf("%s: %s\n", type_name(obj->type), oid_to_hex(&obj->oid));

        ...
}
Note
この出力は人間が直接調べるので、ここでは trace_printf() を使用します。 さらに、この変更によりかなりの数のprint行が導入されるため、 trace_printf() を使用しておくと、再コンパイルせずにそれらの行を簡単に黙らせる事ができます。

(カウンターインクリメントロジックはそのままにしておきます。)

その変更だけで、もう一度実行します(ただし、スクロールバックは少し我慢してください):

$ GIT_TRACE=1 ./bin-wrappers/git walken | head -n 10

git show を使用したトップコミットと印刷した場合のオブジェクトを見てください。 git show HEAD の出力と同じである必要があります。

次に、 walken_object_walk() 内の struct rev_info の設定を変更しましょう。 rev->tree_objectsrev->tree_blobs_in_commit_order など、 rev の他の設定を変更する場所を見つけて、下部に reverse 設定を追加します:

        ...

        rev->tree_objects = 1;
        rev->blob_objects = 1;
        rev->tag_objects = 1;
        rev->tree_blobs_in_commit_order = 1;
        rev->reverse = 1;

        ...

さて、もう一度実行しますが、今回は、最初の一握りではなく、最後の一握りのオブジェクトを取得しましょう:

$ make
$ GIT_TRACE=1 ./bin-wrappers git walken | tail -n 10

与えられた最後のコミットオブジェクトは、前に見たものと同じOIDを持つ必要があり、そのOIDで git show <oid> を実行すると、再び git show HEAD と同じ結果が得られます。さらに、最初の10行を再度実行して調べると(「reverse」設定を適用する前に行ったように「tail」ではなく「head」を使用)、最初に出力されるコミットが最初のコミット「e83c5163」であることがわかります。

Wrapping Up

確認してみましょう。 このチュートリアルでは、私達は以下のことを行いました:

  • ゼロからコミット散歩を構築しました

  • そのコミット散歩のgrepフィルターを有効にしました

  • そのフィルタリングされたコミット散歩のソート順を変更しました

  • オブジェクト散歩(タグ、コミット、ツリー、ブロブ)をゼロから構築しました

  • フィルタ仕様をオブジェクト散歩に追加する方法を学びました

  • フィルタリングされたオブジェクト散歩の表示順序を変更しました