oidtree: extend iteration to allow for arbitrary return codes

The interface `cb_each()` iterates through a crit-bit tree and calls a specific callback function for each of the contained items. The callback function is expected to return either: - `CB_CONTINUE` in case iteration shall continue. - `CB_BREAK` to abort iteration. This is needlessly restrictive though, as callers may want to return arbitrary values and have them be bubbled up to the `cb_each()` call site. In fact, this is a rather common pattern we have: whenever such a callback function returns a non-zero error code, we abort iteration and bubble up the code as-is. Refactor both the crit-bit tree and oidtree subsystems to behave accordingly. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Mar 20, 2026 at 08:07 UTC fe446b01aeaab307adcbfb39d4aaa72c37afbcda
6 files changed +43 -33
cbtree.c
+12 -9
@@ -96,26 +96,28 @@ struct cb_node *cb_lookup(struct cb_tree *t, const uint8_t *k, size_t klen)
96 return p && !memcmp(p->k, k, klen) ? p : NULL;
97 }
98
99 -static enum cb_next cb_descend(struct cb_node *p, cb_iter fn, void *arg)
99 +static int cb_descend(struct cb_node *p, cb_iter fn, void *arg)
100 {
101 if (1 & (uintptr_t)p) {
102 struct cb_node *q = cb_node_of(p);
103 - enum cb_next n = cb_descend(q->child[0], fn, arg);
104 -
105 - return n == CB_BREAK ? n : cb_descend(q->child[1], fn, arg);
103 + int ret = cb_descend(q->child[0], fn, arg);
104 + if (ret)
105 + return ret;
106 + return cb_descend(q->child[1], fn, arg);
107 } else {
108 return fn(p, arg);
109 }
110 }
111
111 -void cb_each(struct cb_tree *t, const uint8_t *kpfx, size_t klen,
112 - cb_iter fn, void *arg)
112 +int cb_each(struct cb_tree *t, const uint8_t *kpfx, size_t klen,
113 + cb_iter fn, void *arg)
114 {
115 struct cb_node *p = t->root;
116 struct cb_node *top = p;
117 size_t i = 0;
118
118 - if (!p) return; /* empty tree */
119 + if (!p)
120 + return 0; /* empty tree */
121
122 /* Walk tree, maintaining top pointer */
123 while (1 & (uintptr_t)p) {
@@ -130,7 +132,8 @@ void cb_each(struct cb_tree *t, const uint8_t *kpfx, size_t klen,
132
133 for (i = 0; i < klen; i++) {
134 if (p->k[i] != kpfx[i])
133 - return; /* "best" match failed */
135 + return 0; /* "best" match failed */
136 }
135 - cb_descend(top, fn, arg);
137 +
138 + return cb_descend(top, fn, arg);
139 }
cbtree.h
+9 -8
@@ -30,11 +30,6 @@ struct cb_tree {
30 struct cb_node *root;
31 };
32
33 -enum cb_next {
34 - CB_CONTINUE = 0,
35 - CB_BREAK = 1
36 -};
37 -
33 #define CBTREE_INIT { 0 }
34
35 static inline void cb_init(struct cb_tree *t)
@@ -46,9 +41,15 @@ static inline void cb_init(struct cb_tree *t)
41 struct cb_node *cb_lookup(struct cb_tree *, const uint8_t *k, size_t klen);
42 struct cb_node *cb_insert(struct cb_tree *, struct cb_node *, size_t klen);
43
49 -typedef enum cb_next (*cb_iter)(struct cb_node *, void *arg);
44 +/*
45 + * Callback invoked by `cb_each()` for each node in the critbit tree. A return
46 + * value of 0 will cause the iteration to continue, a non-zero return code will
47 + * cause iteration to abort. The error code will be relayed back from
48 + * `cb_each()` in that case.
49 + */
50 +typedef int (*cb_iter)(struct cb_node *, void *arg);
51
51 -void cb_each(struct cb_tree *, const uint8_t *kpfx, size_t klen,
52 - cb_iter, void *arg);
52 +int cb_each(struct cb_tree *, const uint8_t *kpfx, size_t klen,
53 + cb_iter, void *arg);
54
55 #endif /* CBTREE_H */
object-name.c
+2 -2
@@ -103,12 +103,12 @@ static void update_candidates(struct disambiguate_state *ds, const struct object
103
104 static int match_hash(unsigned, const unsigned char *, const unsigned char *);
105
106 -static enum cb_next match_prefix(const struct object_id *oid, void *arg)
106 +static int match_prefix(const struct object_id *oid, void *arg)
107 {
108 struct disambiguate_state *ds = arg;
109 /* no need to call match_hash, oidtree_each did prefix match */
110 update_candidates(ds, oid);
111 - return ds->ambiguous ? CB_BREAK : CB_CONTINUE;
111 + return ds->ambiguous;
112 }
113
114 static void find_short_object_filename(struct disambiguate_state *ds)
oidtree.c
+6 -6
@@ -71,7 +71,7 @@ struct oidtree_each_data {
71 uint8_t last_byte;
72 };
73
74 -static enum cb_next iter(struct cb_node *n, void *cb_data)
74 +static int iter(struct cb_node *n, void *cb_data)
75 {
76 struct oidtree_each_data *data = cb_data;
77 struct object_id k;
@@ -80,18 +80,18 @@ static enum cb_next iter(struct cb_node *n, void *cb_data)
80 memcpy(&k, n->k, sizeof(k));
81
82 if (data->algo != GIT_HASH_UNKNOWN && data->algo != k.algo)
83 - return CB_CONTINUE;
83 + return 0;
84
85 if (data->last_nibble_at) {
86 if ((k.hash[*data->last_nibble_at] ^ data->last_byte) & 0xf0)
87 - return CB_CONTINUE;
87 + return 0;
88 }
89
90 return data->cb(&k, data->cb_data);
91 }
92
93 -void oidtree_each(struct oidtree *ot, const struct object_id *prefix,
94 - size_t prefix_hex_len, oidtree_each_cb cb, void *cb_data)
93 +int oidtree_each(struct oidtree *ot, const struct object_id *prefix,
94 + size_t prefix_hex_len, oidtree_each_cb cb, void *cb_data)
95 {
96 struct oidtree_each_data data = {
97 .cb = cb,
@@ -106,5 +106,5 @@ void oidtree_each(struct oidtree *ot, const struct object_id *prefix,
106 data.last_nibble_at = &klen;
107 }
108
109 - cb_each(&ot->tree, prefix->hash, klen, iter, &data);
109 + return cb_each(&ot->tree, prefix->hash, klen, iter, &data);
110 }
oidtree.h
+12 -6
@@ -35,16 +35,22 @@ void oidtree_insert(struct oidtree *ot, const struct object_id *oid);
35 /* Check whether the tree contains the given object ID. */
36 bool oidtree_contains(struct oidtree *ot, const struct object_id *oid);
37
38 -/* Callback function used for `oidtree_each()`. */
39 -typedef enum cb_next (*oidtree_each_cb)(const struct object_id *oid,
40 - void *cb_data);
38 +/*
39 + * Callback function used for `oidtree_each()`. Returning a non-zero exit code
40 + * will cause iteration to stop. The exit code will be propagated to the caller
41 + * of `oidtree_each()`.
42 + */
43 +typedef int (*oidtree_each_cb)(const struct object_id *oid,
44 + void *cb_data);
45
46 /*
47 * Iterate through all object IDs in the tree whose prefix matches the given
48 * object ID prefix and invoke the callback function on each of them.
49 + *
50 + * Returns any non-zero exit code from the provided callback function.
51 */
46 -void oidtree_each(struct oidtree *ot,
47 - const struct object_id *prefix, size_t prefix_hex_len,
48 - oidtree_each_cb cb, void *cb_data);
52 +int oidtree_each(struct oidtree *ot,
53 + const struct object_id *prefix, size_t prefix_hex_len,
54 + oidtree_each_cb cb, void *cb_data);
55
56 #endif /* OIDTREE_H */
t/unit-tests/u-oidtree.c
+2 -2
@@ -38,7 +38,7 @@ struct expected_hex_iter {
38 const char *query;
39 };
40
41 -static enum cb_next check_each_cb(const struct object_id *oid, void *data)
41 +static int check_each_cb(const struct object_id *oid, void *data)
42 {
43 struct expected_hex_iter *hex_iter = data;
44 struct object_id expected;
@@ -49,7 +49,7 @@ static enum cb_next check_each_cb(const struct object_id *oid, void *data)
49 &expected);
50 cl_assert_equal_s(oid_to_hex(oid), oid_to_hex(&expected));
51 hex_iter->i += 1;
52 - return CB_CONTINUE;
52 + return 0;
53 }
54
55 LAST_ARG_MUST_BE_NULL