list-objects: support for skipping tree traversal

The tree:0 filter does not need to traverse the trees that it has filtered out, so optimize list-objects and list-objects-filter to skip traversing the trees entirely. Before this patch, we iterated over all children of the tree, and did nothing for all of them, which was wasteful. Signed-off-by: Matthew DeVore <matvore@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Matthew DeVore committed Oct 17, 2018 at 17:39 UTC 8b10a206f090e01ce1ac4d9a10ec769e2409e2b0
4 files changed +32 -3
list-objects-filter.c
+9 -2
@@ -102,9 +102,16 @@ static enum list_objects_filter_result filter_trees_none(
102
103 case LOFS_BEGIN_TREE:
104 case LOFS_BLOB:
105 - if (filter_data->omits)
105 + if (filter_data->omits) {
106 oidset_insert(filter_data->omits, &obj->oid);
107 - return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */
107 + /* _MARK_SEEN but not _DO_SHOW (hard omit) */
108 + return LOFR_MARK_SEEN;
109 + } else {
110 + /*
111 + * Not collecting omits so no need to to traverse tree.
112 + */
113 + return LOFR_SKIP_TREE | LOFR_MARK_SEEN;
114 + }
115
116 case LOFS_END_TREE:
117 assert(obj->type == OBJ_TREE);
list-objects-filter.h
+6
@@ -20,6 +20,11 @@
20 * In general, objects should only be shown once, but
21 * this result DOES NOT imply that we mark it SEEN.
22 *
23 + * _SKIP_TREE : Used in LOFS_BEGIN_TREE situation - indicates that
24 + * the tree's children should not be iterated over. This
25 + * is used as an optimization when all children will
26 + * definitely be ignored.
27 + *
28 * Most of the time, you want the combination (_MARK_SEEN | _DO_SHOW)
29 * but they can be used independently, such as when sparse-checkout
30 * pattern matching is being applied.
@@ -41,6 +46,7 @@ enum list_objects_filter_result {
46 LOFR_ZERO = 0,
47 LOFR_MARK_SEEN = 1<<0,
48 LOFR_DO_SHOW = 1<<1,
49 + LOFR_SKIP_TREE = 1<<2,
50 };
51
52 enum list_objects_filter_situation {
list-objects.c
+4 -1
@@ -11,6 +11,7 @@
11 #include "list-objects-filter-options.h"
12 #include "packfile.h"
13 #include "object-store.h"
14 +#include "trace.h"
15
16 struct traversal_context {
17 struct rev_info *revs;
@@ -184,7 +185,9 @@ static void process_tree(struct traversal_context *ctx,
185 if (base->len)
186 strbuf_addch(base, '/');
187
187 - if (!failed_parse)
188 + if (r & LOFR_SKIP_TREE)
189 + trace_printf("Skipping contents of tree %s...\n", base->buf);
190 + else if (!failed_parse)
191 process_tree_contents(ctx, tree, base);
192
193 if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn) {
t/t6112-rev-list-filters-objects.sh
+13
@@ -245,6 +245,19 @@ test_expect_success 'verify tree:0 includes trees in "filtered" output' '
245 test_cmp expected filtered_types
246 '
247
248 +# Make sure tree:0 does not iterate through any trees.
249 +
250 +test_expect_success 'filter a GIANT tree through tree:0' '
251 + GIT_TRACE=1 git -C r3 rev-list \
252 + --objects --filter=tree:0 HEAD 2>filter_trace &&
253 + grep "Skipping contents of tree [.][.][.]" filter_trace >actual &&
254 + # One line for each commit traversed.
255 + test_line_count = 2 actual &&
256 +
257 + # Make sure no other trees were considered besides the root.
258 + ! grep "Skipping contents of tree [^.]" filter_trace
259 +'
260 +
261 # Delete some loose objects and use rev-list, but WITHOUT any filtering.
262 # This models previously omitted objects that we did not receive.
263