odb: remove mutual recursion when parsing alternates

When adding an alternative object database source we not only have to consider the added source itself, but we also have to add _its_ sources to our database. We implement this via mutual recursion: 1. We first call `link_alt_odb_entries()`. 2. `link_alt_odb_entries()` calls `parse_alternates()`. 3. We then add each alternate via `odb_add_alternate_recursively()`. 4. `odb_add_alternate_recursively()` calls `link_alt_odb_entries()` again. This flow is somewhat hard to follow, but more importantly it means that parsing of alternates is somewhat tied to the recursive behaviour. Refactor the function to remove the mutual recursion between adding sources and parsing alternates. The parsing step thus becomes completely oblivious to the fact that there is recursive behaviour going on at all. The recursion is handled by `odb_add_alternate_recursively()` instead, which now recurses with itself. This refactoring allows us to move parsing of alternates into object database sources in a subsequent step. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Dec 11, 2025 at 10:30 UTC 430e0e0f2e75673206321f6f4942c0bc7856c8b7
1 file changed +27 -33
odb.c
+27 -33
@@ -147,9 +147,8 @@ out:
147 * of the object ID, an extra slash for the first level indirection, and
148 * the terminating NUL.
149 */
150 -static void read_info_alternates(struct object_database *odb,
151 - const char *relative_base,
152 - int depth);
150 +static void read_info_alternates(const char *relative_base,
151 + struct strvec *out);
152
153 static struct odb_source *odb_source_new(struct object_database *odb,
154 const char *path,
@@ -171,6 +170,7 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
170 int depth)
171 {
172 struct odb_source *alternate = NULL;
173 + struct strvec sources = STRVEC_INIT;
174 khiter_t pos;
175 int ret;
176
@@ -189,9 +189,17 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
189 kh_value(odb->source_by_path, pos) = alternate;
190
191 /* recursively add alternates */
192 - read_info_alternates(odb, alternate->path, depth + 1);
192 + read_info_alternates(alternate->path, &sources);
193 + if (sources.nr && depth + 1 > 5) {
194 + error(_("%s: ignoring alternate object stores, nesting too deep"),
195 + source);
196 + } else {
197 + for (size_t i = 0; i < sources.nr; i++)
198 + odb_add_alternate_recursively(odb, sources.v[i], depth + 1);
199 + }
200
201 error:
202 + strvec_clear(&sources);
203 return alternate;
204 }
205
@@ -203,6 +211,9 @@ static void parse_alternates(const char *string,
211 struct strbuf pathbuf = STRBUF_INIT;
212 struct strbuf buf = STRBUF_INIT;
213
214 + if (!string || !*string)
215 + return;
216 +
217 while (*string) {
218 const char *end;
219
@@ -259,34 +270,11 @@ static void parse_alternates(const char *string,
270 strbuf_release(&buf);
271 }
272
262 -static void link_alt_odb_entries(struct object_database *odb, const char *alt,
263 - int sep, const char *relative_base, int depth)
273 +static void read_info_alternates(const char *relative_base,
274 + struct strvec *out)
275 {
265 - struct strvec alternates = STRVEC_INIT;
266 -
267 - if (!alt || !*alt)
268 - return;
269 -
270 - if (depth > 5) {
271 - error(_("%s: ignoring alternate object stores, nesting too deep"),
272 - relative_base);
273 - return;
274 - }
275 -
276 - parse_alternates(alt, sep, relative_base, &alternates);
277 -
278 - for (size_t i = 0; i < alternates.nr; i++)
279 - odb_add_alternate_recursively(odb, alternates.v[i], depth);
280 -
281 - strvec_clear(&alternates);
282 -}
283 -
284 -static void read_info_alternates(struct object_database *odb,
285 - const char *relative_base,
286 - int depth)
287 -{
288 - char *path;
276 struct strbuf buf = STRBUF_INIT;
277 + char *path;
278
279 path = xstrfmt("%s/info/alternates", relative_base);
280 if (strbuf_read_file(&buf, path, 1024) < 0) {
@@ -294,8 +282,8 @@ static void read_info_alternates(struct object_database *odb,
282 free(path);
283 return;
284 }
285 + parse_alternates(buf.buf, '\n', relative_base, out);
286
298 - link_alt_odb_entries(odb, buf.buf, '\n', relative_base, depth);
287 strbuf_release(&buf);
288 free(path);
289 }
@@ -622,13 +610,19 @@ int odb_for_each_alternate(struct object_database *odb,
610
611 void odb_prepare_alternates(struct object_database *odb)
612 {
613 + struct strvec sources = STRVEC_INIT;
614 +
615 if (odb->loaded_alternates)
616 return;
617
628 - link_alt_odb_entries(odb, odb->alternate_db, PATH_SEP, NULL, 0);
618 + parse_alternates(odb->alternate_db, PATH_SEP, NULL, &sources);
619 + read_info_alternates(odb->sources->path, &sources);
620 + for (size_t i = 0; i < sources.nr; i++)
621 + odb_add_alternate_recursively(odb, sources.v[i], 0);
622
630 - read_info_alternates(odb, odb->sources->path, 0);
623 odb->loaded_alternates = 1;
624 +
625 + strvec_clear(&sources);
626 }
627
628 int odb_has_alternates(struct object_database *odb)