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