path-walk: allow consumer to specify object types

We add the ability to filter the object types in the path-walk API so the callback function is called fewer times. This adds the ability to ask for the commits in a list, as well. We re-use the empty string for this set of objects because these are passed directly to the callback function instead of being part of the 'path_stack'. Future changes will add the ability to visit annotated tags. Signed-off-by: Derrick Stolee <stolee@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Dec 20, 2024 at 16:21 UTC c8dba310d734962c0bcadd8cad1ebf7cfe734c8c
5 files changed +170 -50
Documentation/technical/api-path-walk.txt
+9
@@ -39,6 +39,15 @@ It is also important that you do not specify the `--objects` flag for the
39 the objects will be walked in a separate way based on those starting
40 commits.
41
42 +`commits`, `blobs`, `trees`::
43 + By default, these members are enabled and signal that the path-walk
44 + API should call the `path_fn` on objects of these types. Specialized
45 + applications could disable some options to make it simpler to walk
46 + the objects or to have fewer calls to `path_fn`.
47 ++
48 +While it is possible to walk only commits in this way, consumers would be
49 +better off using the revision walk API instead.
50 +
51 Examples
52 --------
53
path-walk.c
+29 -4
@@ -98,6 +98,10 @@ static int add_tree_entries(struct path_walk_context *ctx,
98 if (S_ISGITLINK(entry.mode))
99 continue;
100
101 + /* If the caller doesn't want blobs, then don't bother. */
102 + if (!ctx->info->blobs && type == OBJ_BLOB)
103 + continue;
104 +
105 if (type == OBJ_TREE) {
106 struct tree *child = lookup_tree(ctx->repo, &entry.oid);
107 o = child ? &child->object : NULL;
@@ -159,9 +163,11 @@ static int walk_path(struct path_walk_context *ctx,
163 if (!list->oids.nr)
164 return 0;
165
162 - /* Evaluate function pointer on this data. */
163 - ret = ctx->info->path_fn(path, &list->oids, list->type,
164 - ctx->info->path_fn_data);
166 + /* Evaluate function pointer on this data, if requested. */
167 + if ((list->type == OBJ_TREE && ctx->info->trees) ||
168 + (list->type == OBJ_BLOB && ctx->info->blobs))
169 + ret = ctx->info->path_fn(path, &list->oids, list->type,
170 + ctx->info->path_fn_data);
171
172 /* Expand data for children. */
173 if (list->type == OBJ_TREE) {
@@ -203,6 +209,7 @@ int walk_objects_by_path(struct path_walk_info *info)
209 size_t commits_nr = 0, paths_nr = 0;
210 struct commit *c;
211 struct type_and_oid_list *root_tree_list;
212 + struct type_and_oid_list *commit_list;
213 struct path_walk_context ctx = {
214 .repo = info->revs->repo,
215 .revs = info->revs,
@@ -214,6 +221,9 @@ int walk_objects_by_path(struct path_walk_info *info)
221
222 trace2_region_enter("path-walk", "commit-walk", info->revs->repo);
223
224 + CALLOC_ARRAY(commit_list, 1);
225 + commit_list->type = OBJ_COMMIT;
226 +
227 /* Insert a single list for the root tree into the paths. */
228 CALLOC_ARRAY(root_tree_list, 1);
229 root_tree_list->type = OBJ_TREE;
@@ -224,10 +234,18 @@ int walk_objects_by_path(struct path_walk_info *info)
234 die(_("failed to setup revision walk"));
235
236 while ((c = get_revision(info->revs))) {
227 - struct object_id *oid = get_commit_tree_oid(c);
237 + struct object_id *oid;
238 struct tree *t;
239 commits_nr++;
240
241 + if (info->commits)
242 + oid_array_append(&commit_list->oids,
243 + &c->object.oid);
244 +
245 + /* If we only care about commits, then skip trees. */
246 + if (!info->trees && !info->blobs)
247 + continue;
248 +
249 oid = get_commit_tree_oid(c);
250 t = lookup_tree(info->revs->repo, oid);
251
@@ -245,6 +263,13 @@ int walk_objects_by_path(struct path_walk_info *info)
263 trace2_data_intmax("path-walk", ctx.repo, "commits", commits_nr);
264 trace2_region_leave("path-walk", "commit-walk", info->revs->repo);
265
266 + /* Track all commits. */
267 + if (info->commits && commit_list->oids.nr)
268 + ret = info->path_fn("", &commit_list->oids, OBJ_COMMIT,
269 + info->path_fn_data);
270 + oid_array_clear(&commit_list->oids);
271 + free(commit_list);
272 +
273 trace2_region_enter("path-walk", "path-walk", info->revs->repo);
274 while (!ret && ctx.path_stack.nr) {
275 char *path = ctx.path_stack.items[ctx.path_stack.nr - 1].string;
path-walk.h
+13 -1
@@ -31,9 +31,21 @@ struct path_walk_info {
31 */
32 path_fn path_fn;
33 void *path_fn_data;
34 +
35 + /**
36 + * Initialize which object types the path_fn should be called on. This
37 + * could also limit the walk to skip blobs if not set.
38 + */
39 + int commits;
40 + int trees;
41 + int blobs;
42 };
43
36 -#define PATH_WALK_INFO_INIT { 0 }
44 +#define PATH_WALK_INFO_INIT { \
45 + .blobs = 1, \
46 + .trees = 1, \
47 + .commits = 1, \
48 +}
49
50 void path_walk_info_init(struct path_walk_info *info);
51 void path_walk_info_clear(struct path_walk_info *info);
t/helper/test-path-walk.c
+13 -2
@@ -19,6 +19,8 @@ static const char * const path_walk_usage[] = {
19
20 struct path_walk_test_data {
21 uintmax_t batch_nr;
22 +
23 + uintmax_t commit_nr;
24 uintmax_t tree_nr;
25 uintmax_t blob_nr;
26 };
@@ -33,6 +35,8 @@ static int emit_block(const char *path, struct oid_array *oids,
35 tdata->tree_nr += oids->nr;
36 else if (type == OBJ_BLOB)
37 tdata->blob_nr += oids->nr;
38 + else if (type == OBJ_COMMIT)
39 + tdata->commit_nr += oids->nr;
40 else
41 BUG("we do not understand this type");
42
@@ -54,6 +58,12 @@ int cmd__path_walk(int argc, const char **argv)
58 struct path_walk_info info = PATH_WALK_INFO_INIT;
59 struct path_walk_test_data data = { 0 };
60 struct option options[] = {
61 + OPT_BOOL(0, "blobs", &info.blobs,
62 + N_("toggle inclusion of blob objects")),
63 + OPT_BOOL(0, "commits", &info.commits,
64 + N_("toggle inclusion of commit objects")),
65 + OPT_BOOL(0, "trees", &info.trees,
66 + N_("toggle inclusion of tree objects")),
67 OPT_END(),
68 };
69
@@ -75,9 +85,10 @@ int cmd__path_walk(int argc, const char **argv)
85
86 res = walk_objects_by_path(&info);
87
78 - printf("trees:%" PRIuMAX "\n"
88 + printf("commits:%" PRIuMAX "\n"
89 + "trees:%" PRIuMAX "\n"
90 "blobs:%" PRIuMAX "\n",
80 - data.tree_nr, data.blob_nr);
91 + data.commit_nr, data.tree_nr, data.blob_nr);
92
93 release_revisions(&revs);
94 return res;
t/t6601-path-walk.sh
+106 -43
@@ -33,22 +33,27 @@ test_expect_success 'all' '
33 test-tool path-walk -- --all >out &&
34
35 cat >expect <<-EOF &&
36 - 0:tree::$(git rev-parse topic^{tree})
37 - 0:tree::$(git rev-parse base^{tree})
38 - 0:tree::$(git rev-parse base~1^{tree})
39 - 0:tree::$(git rev-parse base~2^{tree})
40 - 1:tree:right/:$(git rev-parse topic:right)
41 - 1:tree:right/:$(git rev-parse base~1:right)
42 - 1:tree:right/:$(git rev-parse base~2:right)
43 - 2:blob:right/d:$(git rev-parse base~1:right/d)
44 - 3:blob:right/c:$(git rev-parse base~2:right/c)
45 - 3:blob:right/c:$(git rev-parse topic:right/c)
46 - 4:tree:left/:$(git rev-parse base:left)
47 - 4:tree:left/:$(git rev-parse base~2:left)
48 - 5:blob:left/b:$(git rev-parse base~2:left/b)
49 - 5:blob:left/b:$(git rev-parse base:left/b)
50 - 6:blob:a:$(git rev-parse base~2:a)
36 + 0:commit::$(git rev-parse topic)
37 + 0:commit::$(git rev-parse base)
38 + 0:commit::$(git rev-parse base~1)
39 + 0:commit::$(git rev-parse base~2)
40 + 1:tree::$(git rev-parse topic^{tree})
41 + 1:tree::$(git rev-parse base^{tree})
42 + 1:tree::$(git rev-parse base~1^{tree})
43 + 1:tree::$(git rev-parse base~2^{tree})
44 + 2:tree:right/:$(git rev-parse topic:right)
45 + 2:tree:right/:$(git rev-parse base~1:right)
46 + 2:tree:right/:$(git rev-parse base~2:right)
47 + 3:blob:right/d:$(git rev-parse base~1:right/d)
48 + 4:blob:right/c:$(git rev-parse base~2:right/c)
49 + 4:blob:right/c:$(git rev-parse topic:right/c)
50 + 5:tree:left/:$(git rev-parse base:left)
51 + 5:tree:left/:$(git rev-parse base~2:left)
52 + 6:blob:left/b:$(git rev-parse base~2:left/b)
53 + 6:blob:left/b:$(git rev-parse base:left/b)
54 + 7:blob:a:$(git rev-parse base~2:a)
55 blobs:6
56 + commits:4
57 trees:9
58 EOF
59
@@ -59,19 +64,23 @@ test_expect_success 'topic only' '
64 test-tool path-walk -- topic >out &&
65
66 cat >expect <<-EOF &&
62 - 0:tree::$(git rev-parse topic^{tree})
63 - 0:tree::$(git rev-parse base~1^{tree})
64 - 0:tree::$(git rev-parse base~2^{tree})
65 - 1:tree:right/:$(git rev-parse topic:right)
66 - 1:tree:right/:$(git rev-parse base~1:right)
67 - 1:tree:right/:$(git rev-parse base~2:right)
68 - 2:blob:right/d:$(git rev-parse base~1:right/d)
69 - 3:blob:right/c:$(git rev-parse base~2:right/c)
70 - 3:blob:right/c:$(git rev-parse topic:right/c)
71 - 4:tree:left/:$(git rev-parse base~2:left)
72 - 5:blob:left/b:$(git rev-parse base~2:left/b)
73 - 6:blob:a:$(git rev-parse base~2:a)
67 + 0:commit::$(git rev-parse topic)
68 + 0:commit::$(git rev-parse base~1)
69 + 0:commit::$(git rev-parse base~2)
70 + 1:tree::$(git rev-parse topic^{tree})
71 + 1:tree::$(git rev-parse base~1^{tree})
72 + 1:tree::$(git rev-parse base~2^{tree})
73 + 2:tree:right/:$(git rev-parse topic:right)
74 + 2:tree:right/:$(git rev-parse base~1:right)
75 + 2:tree:right/:$(git rev-parse base~2:right)
76 + 3:blob:right/d:$(git rev-parse base~1:right/d)
77 + 4:blob:right/c:$(git rev-parse base~2:right/c)
78 + 4:blob:right/c:$(git rev-parse topic:right/c)
79 + 5:tree:left/:$(git rev-parse base~2:left)
80 + 6:blob:left/b:$(git rev-parse base~2:left/b)
81 + 7:blob:a:$(git rev-parse base~2:a)
82 blobs:5
83 + commits:3
84 trees:7
85 EOF
86
@@ -82,15 +91,66 @@ test_expect_success 'topic, not base' '
91 test-tool path-walk -- topic --not base >out &&
92
93 cat >expect <<-EOF &&
94 + 0:commit::$(git rev-parse topic)
95 + 1:tree::$(git rev-parse topic^{tree})
96 + 2:tree:right/:$(git rev-parse topic:right)
97 + 3:blob:right/d:$(git rev-parse topic:right/d)
98 + 4:blob:right/c:$(git rev-parse topic:right/c)
99 + 5:tree:left/:$(git rev-parse topic:left)
100 + 6:blob:left/b:$(git rev-parse topic:left/b)
101 + 7:blob:a:$(git rev-parse topic:a)
102 + blobs:4
103 + commits:1
104 + trees:3
105 + EOF
106 +
107 + test_cmp_sorted expect out
108 +'
109 +
110 +test_expect_success 'topic, not base, only blobs' '
111 + test-tool path-walk --no-trees --no-commits \
112 + -- topic --not base >out &&
113 +
114 + cat >expect <<-EOF &&
115 + commits:0
116 + trees:0
117 + 0:blob:right/d:$(git rev-parse topic:right/d)
118 + 1:blob:right/c:$(git rev-parse topic:right/c)
119 + 2:blob:left/b:$(git rev-parse topic:left/b)
120 + 3:blob:a:$(git rev-parse topic:a)
121 + blobs:4
122 + EOF
123 +
124 + test_cmp_sorted expect out
125 +'
126 +
127 +# No, this doesn't make a lot of sense for the path-walk API,
128 +# but it is possible to do.
129 +test_expect_success 'topic, not base, only commits' '
130 + test-tool path-walk --no-blobs --no-trees \
131 + -- topic --not base >out &&
132 +
133 + cat >expect <<-EOF &&
134 + 0:commit::$(git rev-parse topic)
135 + commits:1
136 + trees:0
137 + blobs:0
138 + EOF
139 +
140 + test_cmp_sorted expect out
141 +'
142 +
143 +test_expect_success 'topic, not base, only trees' '
144 + test-tool path-walk --no-blobs --no-commits \
145 + -- topic --not base >out &&
146 +
147 + cat >expect <<-EOF &&
148 + commits:0
149 0:tree::$(git rev-parse topic^{tree})
150 1:tree:right/:$(git rev-parse topic:right)
87 - 2:blob:right/d:$(git rev-parse topic:right/d)
88 - 3:blob:right/c:$(git rev-parse topic:right/c)
89 - 4:tree:left/:$(git rev-parse topic:left)
90 - 5:blob:left/b:$(git rev-parse topic:left/b)
91 - 6:blob:a:$(git rev-parse topic:a)
92 - blobs:4
151 + 2:tree:left/:$(git rev-parse topic:left)
152 trees:3
153 + blobs:0
154 EOF
155
156 test_cmp_sorted expect out
@@ -100,17 +160,20 @@ test_expect_success 'topic, not base, boundary' '
160 test-tool path-walk -- --boundary topic --not base >out &&
161
162 cat >expect <<-EOF &&
103 - 0:tree::$(git rev-parse topic^{tree})
104 - 0:tree::$(git rev-parse base~1^{tree})
105 - 1:tree:right/:$(git rev-parse topic:right)
106 - 1:tree:right/:$(git rev-parse base~1:right)
107 - 2:blob:right/d:$(git rev-parse base~1:right/d)
108 - 3:blob:right/c:$(git rev-parse base~1:right/c)
109 - 3:blob:right/c:$(git rev-parse topic:right/c)
110 - 4:tree:left/:$(git rev-parse base~1:left)
111 - 5:blob:left/b:$(git rev-parse base~1:left/b)
112 - 6:blob:a:$(git rev-parse base~1:a)
163 + 0:commit::$(git rev-parse topic)
164 + 0:commit::$(git rev-parse base~1)
165 + 1:tree::$(git rev-parse topic^{tree})
166 + 1:tree::$(git rev-parse base~1^{tree})
167 + 2:tree:right/:$(git rev-parse topic:right)
168 + 2:tree:right/:$(git rev-parse base~1:right)
169 + 3:blob:right/d:$(git rev-parse base~1:right/d)
170 + 4:blob:right/c:$(git rev-parse base~1:right/c)
171 + 4:blob:right/c:$(git rev-parse topic:right/c)
172 + 5:tree:left/:$(git rev-parse base~1:left)
173 + 6:blob:left/b:$(git rev-parse base~1:left/b)
174 + 7:blob:a:$(git rev-parse base~1:a)
175 blobs:5
176 + commits:2
177 trees:5
178 EOF
179