From 1660496fc400b3956b4abe7bfc40351c9eddc168 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 11 Dec 2025 10:30:10 +0100 Subject: [PATCH 01/16] odb: refactor parsing of alternates to be self-contained Parsing of the alternates file and environment variable is currently split up across multiple different functions and is entangled with `link_alt_odb_entries()`, which is responsible for linking the parsed object database sources. This results in two downsides: - We have mutual recursion between parsing alternates and linking them into the object database. This is because we also parse alternates that the newly added sources may have. - We mix up the actual logic to parse the data and to link them into place. Refactor the logic so that parsing of the alternates file is entirely self-contained. Note that this doesn't yet fix the above two issues, but it is a necessary step to get there. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 70 ++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/odb.c b/odb.c index dc8f292f3d9645..9785f62cb6be5e 100644 --- a/odb.c +++ b/odb.c @@ -216,39 +216,50 @@ static struct odb_source *link_alt_odb_entry(struct object_database *odb, return alternate; } -static const char *parse_alt_odb_entry(const char *string, - int sep, - struct strbuf *out) +static void parse_alternates(const char *string, + int sep, + struct strvec *out) { - const char *end; + struct strbuf buf = STRBUF_INIT; - strbuf_reset(out); + while (*string) { + const char *end; + + strbuf_reset(&buf); + + if (*string == '#') { + /* comment; consume up to next separator */ + end = strchrnul(string, sep); + } else if (*string == '"' && !unquote_c_style(&buf, string, &end)) { + /* + * quoted path; unquote_c_style has copied the + * data for us and set "end". Broken quoting (e.g., + * an entry that doesn't end with a quote) falls + * back to the unquoted case below. + */ + } else { + /* normal, unquoted path */ + end = strchrnul(string, sep); + strbuf_add(&buf, string, end - string); + } - if (*string == '#') { - /* comment; consume up to next separator */ - end = strchrnul(string, sep); - } else if (*string == '"' && !unquote_c_style(out, string, &end)) { - /* - * quoted path; unquote_c_style has copied the - * data for us and set "end". Broken quoting (e.g., - * an entry that doesn't end with a quote) falls - * back to the unquoted case below. - */ - } else { - /* normal, unquoted path */ - end = strchrnul(string, sep); - strbuf_add(out, string, end - string); + if (*end) + end++; + string = end; + + if (!buf.len) + continue; + + strvec_push(out, buf.buf); } - if (*end) - end++; - return end; + strbuf_release(&buf); } static void link_alt_odb_entries(struct object_database *odb, const char *alt, int sep, const char *relative_base, int depth) { - struct strbuf dir = STRBUF_INIT; + struct strvec alternates = STRVEC_INIT; if (!alt || !*alt) return; @@ -259,13 +270,12 @@ static void link_alt_odb_entries(struct object_database *odb, const char *alt, return; } - while (*alt) { - alt = parse_alt_odb_entry(alt, sep, &dir); - if (!dir.len) - continue; - link_alt_odb_entry(odb, dir.buf, relative_base, depth); - } - strbuf_release(&dir); + parse_alternates(alt, sep, &alternates); + + for (size_t i = 0; i < alternates.nr; i++) + link_alt_odb_entry(odb, alternates.v[i], relative_base, depth); + + strvec_clear(&alternates); } static void read_info_alternates(struct object_database *odb, From 84cec5276e70bdabd651a3d0a250d006434d639f Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 11 Dec 2025 10:30:11 +0100 Subject: [PATCH 02/16] odb: resolve relative alternative paths when parsing Parsing alternates and resolving potential relative paths is currently handled in two separate steps. This has the effect that the logic to retrieve alternates is not entirely self-contained. We want it to be just that though so that we can eventually move the logic to list alternates into the `struct odb_source`. Move the logic to resolve relative alternative paths into `parse_alternates()`. Besides bringing us a step closer towards the above goal, it also neatly separates concerns of generating the list of alternatives and linking them into the object database. Note that we ignore any errors when the relative path cannot be resolved. This isn't really a change in behaviour though: if the path cannot be resolved to a directory then `alt_odb_usable()` still knows to bail out. While at it, rename the function to `odb_add_alternate_recursively()` to more clearly indicate what its intent is and to align it with modern terminology. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 64 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/odb.c b/odb.c index 9785f62cb6be5e..699bdbffd1e7a3 100644 --- a/odb.c +++ b/odb.c @@ -159,44 +159,21 @@ static struct odb_source *odb_source_new(struct object_database *odb, return source; } -static struct odb_source *link_alt_odb_entry(struct object_database *odb, - const char *dir, - const char *relative_base, - int depth) +static struct odb_source *odb_add_alternate_recursively(struct object_database *odb, + const char *source, + int depth) { struct odb_source *alternate = NULL; - struct strbuf pathbuf = STRBUF_INIT; struct strbuf tmp = STRBUF_INIT; khiter_t pos; int ret; - if (!is_absolute_path(dir) && relative_base) { - strbuf_realpath(&pathbuf, relative_base, 1); - strbuf_addch(&pathbuf, '/'); - } - strbuf_addstr(&pathbuf, dir); - - if (!strbuf_realpath(&tmp, pathbuf.buf, 0)) { - error(_("unable to normalize alternate object path: %s"), - pathbuf.buf); - goto error; - } - strbuf_swap(&pathbuf, &tmp); - - /* - * The trailing slash after the directory name is given by - * this function at the end. Remove duplicates. - */ - while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/') - strbuf_setlen(&pathbuf, pathbuf.len - 1); - - strbuf_reset(&tmp); strbuf_realpath(&tmp, odb->sources->path, 1); - if (!alt_odb_usable(odb, pathbuf.buf, tmp.buf)) + if (!alt_odb_usable(odb, source, tmp.buf)) goto error; - alternate = odb_source_new(odb, pathbuf.buf, false); + alternate = odb_source_new(odb, source, false); /* add the alternate entry */ *odb->sources_tail = alternate; @@ -212,20 +189,22 @@ static struct odb_source *link_alt_odb_entry(struct object_database *odb, error: strbuf_release(&tmp); - strbuf_release(&pathbuf); return alternate; } static void parse_alternates(const char *string, int sep, + const char *relative_base, struct strvec *out) { + struct strbuf pathbuf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT; while (*string) { const char *end; strbuf_reset(&buf); + strbuf_reset(&pathbuf); if (*string == '#') { /* comment; consume up to next separator */ @@ -250,9 +229,30 @@ static void parse_alternates(const char *string, if (!buf.len) continue; + if (!is_absolute_path(buf.buf) && relative_base) { + strbuf_realpath(&pathbuf, relative_base, 1); + strbuf_addch(&pathbuf, '/'); + } + strbuf_addbuf(&pathbuf, &buf); + + strbuf_reset(&buf); + if (!strbuf_realpath(&buf, pathbuf.buf, 0)) { + error(_("unable to normalize alternate object path: %s"), + pathbuf.buf); + continue; + } + + /* + * The trailing slash after the directory name is given by + * this function at the end. Remove duplicates. + */ + while (buf.len && buf.buf[buf.len - 1] == '/') + strbuf_setlen(&buf, buf.len - 1); + strvec_push(out, buf.buf); } + strbuf_release(&pathbuf); strbuf_release(&buf); } @@ -270,10 +270,10 @@ static void link_alt_odb_entries(struct object_database *odb, const char *alt, return; } - parse_alternates(alt, sep, &alternates); + parse_alternates(alt, sep, relative_base, &alternates); for (size_t i = 0; i < alternates.nr; i++) - link_alt_odb_entry(odb, alternates.v[i], relative_base, depth); + odb_add_alternate_recursively(odb, alternates.v[i], depth); strvec_clear(&alternates); } @@ -348,7 +348,7 @@ struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, * overwritten when they are. */ odb_prepare_alternates(odb); - return link_alt_odb_entry(odb, dir, NULL, 0); + return odb_add_alternate_recursively(odb, dir, 0); } struct odb_source *odb_set_temporary_primary_source(struct object_database *odb, From d17673ef4285d3d5f70909136f1ffe2745bcb71c Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 11 Dec 2025 10:30:12 +0100 Subject: [PATCH 03/16] odb: move computation of normalized objdir into `alt_odb_usable()` The function `alt_odb_usable()` receives as input the object database, the path it's supposed to determine usability for as well as the normalized path of the main object directory of the repository. The last part is derived by the function's caller from the object database. As we already pass the object database to `alt_odb_usable()` it is redundant information. Drop the extra parameter and compute the normalized object directory in the function itself. While at it, rename the function to `odb_is_source_usable()` to align it with modern terminology. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/odb.c b/odb.c index 699bdbffd1e7a3..e314f86c3b843d 100644 --- a/odb.c +++ b/odb.c @@ -89,17 +89,20 @@ int odb_mkstemp(struct object_database *odb, /* * Return non-zero iff the path is usable as an alternate object database. */ -static int alt_odb_usable(struct object_database *o, const char *path, - const char *normalized_objdir) +static bool odb_is_source_usable(struct object_database *o, const char *path) { int r; + struct strbuf normalized_objdir = STRBUF_INIT; + bool usable = false; + + strbuf_realpath(&normalized_objdir, o->sources->path, 1); /* Detect cases where alternate disappeared */ if (!is_directory(path)) { error(_("object directory %s does not exist; " "check .git/objects/info/alternates"), path); - return 0; + goto out; } /* @@ -116,13 +119,17 @@ static int alt_odb_usable(struct object_database *o, const char *path, kh_value(o->source_by_path, p) = o->sources; } - if (fspatheq(path, normalized_objdir)) - return 0; + if (fspatheq(path, normalized_objdir.buf)) + goto out; if (kh_get_odb_path_map(o->source_by_path, path) < kh_end(o->source_by_path)) - return 0; + goto out; + + usable = true; - return 1; +out: + strbuf_release(&normalized_objdir); + return usable; } /* @@ -164,13 +171,10 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database * int depth) { struct odb_source *alternate = NULL; - struct strbuf tmp = STRBUF_INIT; khiter_t pos; int ret; - strbuf_realpath(&tmp, odb->sources->path, 1); - - if (!alt_odb_usable(odb, source, tmp.buf)) + if (!odb_is_source_usable(odb, source)) goto error; alternate = odb_source_new(odb, source, false); @@ -188,7 +192,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database * read_info_alternates(odb, alternate->path, depth + 1); error: - strbuf_release(&tmp); return alternate; } From dccfb39cdb68e47a4c7103b3c465cde91c5f9f56 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 11 Dec 2025 10:30:13 +0100 Subject: [PATCH 04/16] odb: stop splitting alternate in `odb_add_to_alternates_file()` When calling `odb_add_to_alternates_file()` we know to add the newly added source to the object database in case we have already loaded alternates. This is done so that we can make its objects accessible immediately without having to fully reload all alternates. The way we do this though is to call `link_alt_odb_entries()`, which adds _multiple_ sources to the object database source in case we have newline-separated entries. This behaviour is not documented in the function documentation of `odb_add_to_alternates_file()`, and all callers only ever pass a single directory to it. It's thus entirely surprising and a conceptual mismatch. Fix this issue by directly calling `odb_add_alternate_recursively()` instead. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/odb.c b/odb.c index e314f86c3b843d..3112eab5d03ed1 100644 --- a/odb.c +++ b/odb.c @@ -338,7 +338,7 @@ void odb_add_to_alternates_file(struct object_database *odb, if (commit_lock_file(&lock)) die_errno(_("unable to move new alternates file into place")); if (odb->loaded_alternates) - link_alt_odb_entries(odb, dir, '\n', NULL, 0); + odb_add_alternate_recursively(odb, dir, 0); } free(alts); } From 430e0e0f2e75673206321f6f4942c0bc7856c8b7 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 11 Dec 2025 10:30:14 +0100 Subject: [PATCH 05/16] 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 Signed-off-by: Junio C Hamano --- odb.c | 60 +++++++++++++++++++++++++++-------------------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/odb.c b/odb.c index 3112eab5d03ed1..59944d46496e9c 100644 --- a/odb.c +++ b/odb.c @@ -147,9 +147,8 @@ static bool odb_is_source_usable(struct object_database *o, const char *path) * of the object ID, an extra slash for the first level indirection, and * the terminating NUL. */ -static void read_info_alternates(struct object_database *odb, - const char *relative_base, - int depth); +static void read_info_alternates(const char *relative_base, + struct strvec *out); static struct odb_source *odb_source_new(struct object_database *odb, const char *path, @@ -171,6 +170,7 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database * int depth) { struct odb_source *alternate = NULL; + struct strvec sources = STRVEC_INIT; khiter_t pos; int ret; @@ -189,9 +189,17 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database * kh_value(odb->source_by_path, pos) = alternate; /* recursively add alternates */ - read_info_alternates(odb, alternate->path, depth + 1); + read_info_alternates(alternate->path, &sources); + if (sources.nr && depth + 1 > 5) { + error(_("%s: ignoring alternate object stores, nesting too deep"), + source); + } else { + for (size_t i = 0; i < sources.nr; i++) + odb_add_alternate_recursively(odb, sources.v[i], depth + 1); + } error: + strvec_clear(&sources); return alternate; } @@ -203,6 +211,9 @@ static void parse_alternates(const char *string, struct strbuf pathbuf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT; + if (!string || !*string) + return; + while (*string) { const char *end; @@ -259,34 +270,11 @@ static void parse_alternates(const char *string, strbuf_release(&buf); } -static void link_alt_odb_entries(struct object_database *odb, const char *alt, - int sep, const char *relative_base, int depth) +static void read_info_alternates(const char *relative_base, + struct strvec *out) { - struct strvec alternates = STRVEC_INIT; - - if (!alt || !*alt) - return; - - if (depth > 5) { - error(_("%s: ignoring alternate object stores, nesting too deep"), - relative_base); - return; - } - - parse_alternates(alt, sep, relative_base, &alternates); - - for (size_t i = 0; i < alternates.nr; i++) - odb_add_alternate_recursively(odb, alternates.v[i], depth); - - strvec_clear(&alternates); -} - -static void read_info_alternates(struct object_database *odb, - const char *relative_base, - int depth) -{ - char *path; struct strbuf buf = STRBUF_INIT; + char *path; path = xstrfmt("%s/info/alternates", relative_base); if (strbuf_read_file(&buf, path, 1024) < 0) { @@ -294,8 +282,8 @@ static void read_info_alternates(struct object_database *odb, free(path); return; } + parse_alternates(buf.buf, '\n', relative_base, out); - link_alt_odb_entries(odb, buf.buf, '\n', relative_base, depth); strbuf_release(&buf); free(path); } @@ -622,13 +610,19 @@ int odb_for_each_alternate(struct object_database *odb, void odb_prepare_alternates(struct object_database *odb) { + struct strvec sources = STRVEC_INIT; + if (odb->loaded_alternates) return; - link_alt_odb_entries(odb, odb->alternate_db, PATH_SEP, NULL, 0); + parse_alternates(odb->alternate_db, PATH_SEP, NULL, &sources); + read_info_alternates(odb->sources->path, &sources); + for (size_t i = 0; i < sources.nr; i++) + odb_add_alternate_recursively(odb, sources.v[i], 0); - read_info_alternates(odb, odb->sources->path, 0); odb->loaded_alternates = 1; + + strvec_clear(&sources); } int odb_has_alternates(struct object_database *odb) From 3f42555322f86f17a2dac4f585edab1d84f3df57 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 11 Dec 2025 10:30:15 +0100 Subject: [PATCH 06/16] odb: drop forward declaration of `read_info_alternates()` Now that we have removed the mutual recursion in the preceding commit it is not necessary anymore to have a forward declaration of the `read_info_alternates()` function. Move the function and its dependencies further up so that we can remove it. Note that this commit also removes the function documentation of `read_info_alternates()`. It's unclear what it's documenting, but it for sure isn't documenting the modern behaviour of the function anymore. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 125 +++++++++++++++++++++++++--------------------------------- 1 file changed, 54 insertions(+), 71 deletions(-) diff --git a/odb.c b/odb.c index 59944d46496e9c..dcf4a62cd2eaf2 100644 --- a/odb.c +++ b/odb.c @@ -132,77 +132,6 @@ static bool odb_is_source_usable(struct object_database *o, const char *path) return usable; } -/* - * Prepare alternate object database registry. - * - * The variable alt_odb_list points at the list of struct - * odb_source. The elements on this list come from - * non-empty elements from colon separated ALTERNATE_DB_ENVIRONMENT - * environment variable, and $GIT_OBJECT_DIRECTORY/info/alternates, - * whose contents is similar to that environment variable but can be - * LF separated. Its base points at a statically allocated buffer that - * contains "/the/directory/corresponding/to/.git/objects/...", while - * its name points just after the slash at the end of ".git/objects/" - * in the example above, and has enough space to hold all hex characters - * of the object ID, an extra slash for the first level indirection, and - * the terminating NUL. - */ -static void read_info_alternates(const char *relative_base, - struct strvec *out); - -static struct odb_source *odb_source_new(struct object_database *odb, - const char *path, - bool local) -{ - struct odb_source *source; - - CALLOC_ARRAY(source, 1); - source->odb = odb; - source->local = local; - source->path = xstrdup(path); - source->loose = odb_source_loose_new(source); - - return source; -} - -static struct odb_source *odb_add_alternate_recursively(struct object_database *odb, - const char *source, - int depth) -{ - struct odb_source *alternate = NULL; - struct strvec sources = STRVEC_INIT; - khiter_t pos; - int ret; - - if (!odb_is_source_usable(odb, source)) - goto error; - - alternate = odb_source_new(odb, source, false); - - /* add the alternate entry */ - *odb->sources_tail = alternate; - odb->sources_tail = &(alternate->next); - - pos = kh_put_odb_path_map(odb->source_by_path, alternate->path, &ret); - if (!ret) - BUG("source must not yet exist"); - kh_value(odb->source_by_path, pos) = alternate; - - /* recursively add alternates */ - read_info_alternates(alternate->path, &sources); - if (sources.nr && depth + 1 > 5) { - error(_("%s: ignoring alternate object stores, nesting too deep"), - source); - } else { - for (size_t i = 0; i < sources.nr; i++) - odb_add_alternate_recursively(odb, sources.v[i], depth + 1); - } - - error: - strvec_clear(&sources); - return alternate; -} - static void parse_alternates(const char *string, int sep, const char *relative_base, @@ -288,6 +217,60 @@ static void read_info_alternates(const char *relative_base, free(path); } + +static struct odb_source *odb_source_new(struct object_database *odb, + const char *path, + bool local) +{ + struct odb_source *source; + + CALLOC_ARRAY(source, 1); + source->odb = odb; + source->local = local; + source->path = xstrdup(path); + source->loose = odb_source_loose_new(source); + + return source; +} + +static struct odb_source *odb_add_alternate_recursively(struct object_database *odb, + const char *source, + int depth) +{ + struct odb_source *alternate = NULL; + struct strvec sources = STRVEC_INIT; + khiter_t pos; + int ret; + + if (!odb_is_source_usable(odb, source)) + goto error; + + alternate = odb_source_new(odb, source, false); + + /* add the alternate entry */ + *odb->sources_tail = alternate; + odb->sources_tail = &(alternate->next); + + pos = kh_put_odb_path_map(odb->source_by_path, alternate->path, &ret); + if (!ret) + BUG("source must not yet exist"); + kh_value(odb->source_by_path, pos) = alternate; + + /* recursively add alternates */ + read_info_alternates(alternate->path, &sources); + if (sources.nr && depth + 1 > 5) { + error(_("%s: ignoring alternate object stores, nesting too deep"), + source); + } else { + for (size_t i = 0; i < sources.nr; i++) + odb_add_alternate_recursively(odb, sources.v[i], depth + 1); + } + + error: + strvec_clear(&sources); + return alternate; +} + void odb_add_to_alternates_file(struct object_database *odb, const char *dir) { From f7dbd9fb2ea9b14b4df0949411205f4b5d284b41 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 11 Dec 2025 10:30:16 +0100 Subject: [PATCH 07/16] odb: read alternates via sources Adapt how we read alternates so that the interface is structured around the object database source we're reading from. This will eventually allow us to abstract away this behaviour with pluggable object databases so that every format can have its own mechanism for listing alternates. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/odb.c b/odb.c index dcf4a62cd2eaf2..c5ba26b85f20e1 100644 --- a/odb.c +++ b/odb.c @@ -199,19 +199,19 @@ static void parse_alternates(const char *string, strbuf_release(&buf); } -static void read_info_alternates(const char *relative_base, - struct strvec *out) +static void odb_source_read_alternates(struct odb_source *source, + struct strvec *out) { struct strbuf buf = STRBUF_INIT; char *path; - path = xstrfmt("%s/info/alternates", relative_base); + path = xstrfmt("%s/info/alternates", source->path); if (strbuf_read_file(&buf, path, 1024) < 0) { warn_on_fopen_errors(path); free(path); return; } - parse_alternates(buf.buf, '\n', relative_base, out); + parse_alternates(buf.buf, '\n', source->path, out); strbuf_release(&buf); free(path); @@ -257,7 +257,7 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database * kh_value(odb->source_by_path, pos) = alternate; /* recursively add alternates */ - read_info_alternates(alternate->path, &sources); + odb_source_read_alternates(alternate, &sources); if (sources.nr && depth + 1 > 5) { error(_("%s: ignoring alternate object stores, nesting too deep"), source); @@ -599,7 +599,7 @@ void odb_prepare_alternates(struct object_database *odb) return; parse_alternates(odb->alternate_db, PATH_SEP, NULL, &sources); - read_info_alternates(odb->sources->path, &sources); + odb_source_read_alternates(odb->sources, &sources); for (size_t i = 0; i < sources.nr; i++) odb_add_alternate_recursively(odb, sources.v[i], 0); From 221a877d4785030e07d20977418609257fd606d8 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 11 Dec 2025 10:30:17 +0100 Subject: [PATCH 08/16] odb: write alternates via sources Refactor writing of alternates so that the actual business logic is structured around the object database source we want to write the alternate to. Same as with the preceding commit, this will eventually allow us to have different logic for writing alternates depending on the backend used. Note that after the refactoring we start to call `odb_add_alternate_recursively()` unconditionally. This is fine though as we know to skip adding sources that are tracked already. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 51 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/odb.c b/odb.c index c5ba26b85f20e1..cc7f8324655e08 100644 --- a/odb.c +++ b/odb.c @@ -271,25 +271,28 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database * return alternate; } -void odb_add_to_alternates_file(struct object_database *odb, - const char *dir) +static int odb_source_write_alternate(struct odb_source *source, + const char *alternate) { struct lock_file lock = LOCK_INIT; - char *alts = repo_git_path(odb->repo, "objects/info/alternates"); + char *path = xstrfmt("%s/%s", source->path, "info/alternates"); FILE *in, *out; int found = 0; + int ret; - hold_lock_file_for_update(&lock, alts, LOCK_DIE_ON_ERROR); + hold_lock_file_for_update(&lock, path, LOCK_DIE_ON_ERROR); out = fdopen_lock_file(&lock, "w"); - if (!out) - die_errno(_("unable to fdopen alternates lockfile")); + if (!out) { + ret = error_errno(_("unable to fdopen alternates lockfile")); + goto out; + } - in = fopen(alts, "r"); + in = fopen(path, "r"); if (in) { struct strbuf line = STRBUF_INIT; while (strbuf_getline(&line, in) != EOF) { - if (!strcmp(dir, line.buf)) { + if (!strcmp(alternate, line.buf)) { found = 1; break; } @@ -298,20 +301,36 @@ void odb_add_to_alternates_file(struct object_database *odb, strbuf_release(&line); fclose(in); + } else if (errno != ENOENT) { + ret = error_errno(_("unable to read alternates file")); + goto out; } - else if (errno != ENOENT) - die_errno(_("unable to read alternates file")); if (found) { rollback_lock_file(&lock); } else { - fprintf_or_die(out, "%s\n", dir); - if (commit_lock_file(&lock)) - die_errno(_("unable to move new alternates file into place")); - if (odb->loaded_alternates) - odb_add_alternate_recursively(odb, dir, 0); + fprintf_or_die(out, "%s\n", alternate); + if (commit_lock_file(&lock)) { + ret = error_errno(_("unable to move new alternates file into place")); + goto out; + } } - free(alts); + + ret = 0; + +out: + free(path); + return ret; +} + +void odb_add_to_alternates_file(struct object_database *odb, + const char *dir) +{ + int ret = odb_source_write_alternate(odb->sources, dir); + if (ret < 0) + die(NULL); + if (odb->loaded_alternates) + odb_add_alternate_recursively(odb, dir, 0); } struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, From d4b732899e8b7571f2822b35e5cc7f55f6ce5e3d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 11 Dec 2025 11:53:07 +0900 Subject: [PATCH 09/16] Makefile: help macOS novices by mentioning MacPorts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since Aug 2006, the DarwinPorts project renamed themselves as MacPorts. Those who are not intimately familiar with the Opensource ecosystem around macOS from olden days, the name DarwinPorts may not ring a bell, even when they are using MacPorts. Signed-off-by: Junio C Hamano Reviewed-by: Carlo Marcelo Arenas Belón Signed-off-by: Junio C Hamano --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7e0f77e2988e3b..be027218a5d29d 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,8 @@ include shared.mak # and LDFLAGS appropriately. # # Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X, -# have DarwinPorts installed in /opt/local, but don't want GIT to +# have DarwinPorts (which is an old name for MacPorts) installed +# in /opt/local, but don't want GIT to # link against any libraries installed there. If defined you may # specify your own (or DarwinPort's) include directories and # library directories by defining CFLAGS and LDFLAGS appropriately. From a4a77e41fa0ee3d526993be47086bbfe3a115cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Thu, 11 Dec 2025 18:56:54 +0100 Subject: [PATCH 10/16] replay: move onto NULL check before first use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cmd_replay() aborts if the pointer "onto" is NULL after argument parsing, e.g. when specifying a non-existing commit with --onto. 15cd4ef1f4 (replay: make atomic ref updates the default behavior, 2025-11-06) added code that dereferences this pointer before the check. Switch their places to avoid a segmentation fault. Reported-by: Kristoffer Haugsbakk Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- builtin/replay.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builtin/replay.c b/builtin/replay.c index 6606a2c94bc671..312b8203cb525a 100644 --- a/builtin/replay.c +++ b/builtin/replay.c @@ -454,6 +454,9 @@ int cmd_replay(int argc, determine_replay_mode(repo, &revs.cmdline, onto_name, &advance_name, &onto, &update_refs); + if (!onto) /* FIXME: Should handle replaying down to root commit */ + die("Replaying down to root commit is not supported yet!"); + /* Build reflog message */ if (advance_name_opt) strbuf_addf(&reflog_msg, "replay --advance %s", advance_name_opt); @@ -472,9 +475,6 @@ int cmd_replay(int argc, } } - if (!onto) /* FIXME: Should handle replaying down to root commit */ - die("Replaying down to root commit is not supported yet!"); - if (prepare_revision_walk(&revs) < 0) { ret = error(_("error preparing revisions")); goto cleanup; From bab391761d1c7cff59d1c29ee546efc3e588473d Mon Sep 17 00:00:00 2001 From: K Jayatheerth Date: Fri, 12 Dec 2025 13:14:33 +0530 Subject: [PATCH 11/16] pull: move options[] array into function scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unless there are good reasons, it is customary to have the options[] array used with the parse-options API declared in function scope rather than at file scope. Move builtin/pull.c:cmd_pull()’s options[] array into the function to match that convention. Signed-off-by: K Jayatheerth Signed-off-by: Junio C Hamano --- builtin/pull.c | 283 ++++++++++++++++++++++++------------------------- 1 file changed, 141 insertions(+), 142 deletions(-) diff --git a/builtin/pull.c b/builtin/pull.c index 5ebd5296207061..3ff748e0b3ea60 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -119,148 +119,6 @@ static int opt_show_forced_updates = -1; static const char *set_upstream; static struct strvec opt_fetch = STRVEC_INIT; -static struct option pull_options[] = { - /* Shared options */ - OPT__VERBOSITY(&opt_verbosity), - OPT_PASSTHRU(0, "progress", &opt_progress, NULL, - N_("force progress reporting"), - PARSE_OPT_NOARG), - OPT_CALLBACK_F(0, "recurse-submodules", - &recurse_submodules_cli, N_("on-demand"), - N_("control for recursive fetching of submodules"), - PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), - - /* Options passed to git-merge or git-rebase */ - OPT_GROUP(N_("Options related to merging")), - OPT_CALLBACK_F('r', "rebase", &opt_rebase, - "(false|true|merges|interactive)", - N_("incorporate changes by rebasing rather than merging"), - PARSE_OPT_OPTARG, parse_opt_rebase), - OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, - N_("do not show a diffstat at the end of the merge"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG), - OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL, - N_("show a diffstat at the end of the merge"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL, - N_("(synonym to --stat)"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN), - OPT_PASSTHRU(0, "compact-summary", &opt_diffstat, NULL, - N_("show a compact-summary at the end of the merge"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "log", &opt_log, N_("n"), - N_("add (at most ) entries from shortlog to merge commit message"), - PARSE_OPT_OPTARG), - OPT_PASSTHRU(0, "signoff", &opt_signoff, NULL, - N_("add a Signed-off-by trailer"), - PARSE_OPT_OPTARG), - OPT_PASSTHRU(0, "squash", &opt_squash, NULL, - N_("create a single commit instead of doing a merge"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "commit", &opt_commit, NULL, - N_("perform a commit if the merge succeeds (default)"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "edit", &opt_edit, NULL, - N_("edit message before committing"), - PARSE_OPT_NOARG), - OPT_CLEANUP(&cleanup_arg), - OPT_PASSTHRU(0, "ff", &opt_ff, NULL, - N_("allow fast-forward"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL, - N_("abort if fast-forward is not possible"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG), - OPT_PASSTHRU(0, "verify", &opt_verify, NULL, - N_("control use of pre-merge-commit and commit-msg hooks"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL, - N_("verify that the named commit has a valid GPG signature"), - PARSE_OPT_NOARG), - OPT_BOOL(0, "autostash", &opt_autostash, - N_("automatically stash/stash pop before and after")), - OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), - N_("merge strategy to use"), - 0), - OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts, - N_("option=value"), - N_("option for selected merge strategy"), - 0), - OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"), - N_("GPG sign commit"), - PARSE_OPT_OPTARG), - OPT_SET_INT(0, "allow-unrelated-histories", - &opt_allow_unrelated_histories, - N_("allow merging unrelated histories"), 1), - - /* Options passed to git-fetch */ - OPT_GROUP(N_("Options related to fetching")), - OPT_PASSTHRU(0, "all", &opt_all, NULL, - N_("fetch from all remotes"), - PARSE_OPT_NOARG), - OPT_PASSTHRU('a', "append", &opt_append, NULL, - N_("append to .git/FETCH_HEAD instead of overwriting"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"), - N_("path to upload pack on remote end"), - 0), - OPT__FORCE(&opt_force, N_("force overwrite of local branch"), 0), - OPT_PASSTHRU('t', "tags", &opt_tags, NULL, - N_("fetch all tags and associated objects"), - PARSE_OPT_NOARG), - OPT_PASSTHRU('p', "prune", &opt_prune, NULL, - N_("prune remote-tracking branches no longer on remote"), - PARSE_OPT_NOARG), - OPT_PASSTHRU('j', "jobs", &max_children, N_("n"), - N_("number of submodules pulled in parallel"), - PARSE_OPT_OPTARG), - OPT_BOOL(0, "dry-run", &opt_dry_run, - N_("dry run")), - OPT_PASSTHRU('k', "keep", &opt_keep, NULL, - N_("keep downloaded pack"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"), - N_("deepen history of shallow clone"), - 0), - OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"), - N_("deepen history of shallow repository based on time"), - 0), - OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("ref"), - N_("deepen history of shallow clone, excluding ref"), - 0), - OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"), - N_("deepen history of shallow clone"), - 0), - OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL, - N_("convert to a complete repository"), - PARSE_OPT_NONEG | PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL, - N_("accept refs that update .git/shallow"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), - N_("specify fetch refmap"), - PARSE_OPT_NONEG), - OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch, - N_("server-specific"), - N_("option to transmit"), - 0), - OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL, - N_("use IPv4 addresses only"), - PARSE_OPT_NOARG), - OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL, - N_("use IPv6 addresses only"), - PARSE_OPT_NOARG), - OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"), - N_("report that we have only objects reachable from this object"), - 0), - OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates, - N_("check for forced-updates on all updated branches")), - OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL, - N_("set upstream for git pull/fetch"), - PARSE_OPT_NOARG), - - OPT_END() -}; - /** * Pushes "-q" or "-v" switches into arr to match the opt_verbosity level. */ @@ -1008,6 +866,147 @@ int cmd_pull(int argc, int can_ff; int divergent; int ret; + static struct option pull_options[] = { + /* Shared options */ + OPT__VERBOSITY(&opt_verbosity), + OPT_PASSTHRU(0, "progress", &opt_progress, NULL, + N_("force progress reporting"), + PARSE_OPT_NOARG), + OPT_CALLBACK_F(0, "recurse-submodules", + &recurse_submodules_cli, N_("on-demand"), + N_("control for recursive fetching of submodules"), + PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), + + /* Options passed to git-merge or git-rebase */ + OPT_GROUP(N_("Options related to merging")), + OPT_CALLBACK_F('r', "rebase", &opt_rebase, + "(false|true|merges|interactive)", + N_("incorporate changes by rebasing rather than merging"), + PARSE_OPT_OPTARG, parse_opt_rebase), + OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, + N_("do not show a diffstat at the end of the merge"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG), + OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL, + N_("show a diffstat at the end of the merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL, + N_("(synonym to --stat)"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN), + OPT_PASSTHRU(0, "compact-summary", &opt_diffstat, NULL, + N_("show a compact-summary at the end of the merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "log", &opt_log, N_("n"), + N_("add (at most ) entries from shortlog to merge commit message"), + PARSE_OPT_OPTARG), + OPT_PASSTHRU(0, "signoff", &opt_signoff, NULL, + N_("add a Signed-off-by trailer"), + PARSE_OPT_OPTARG), + OPT_PASSTHRU(0, "squash", &opt_squash, NULL, + N_("create a single commit instead of doing a merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "commit", &opt_commit, NULL, + N_("perform a commit if the merge succeeds (default)"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "edit", &opt_edit, NULL, + N_("edit message before committing"), + PARSE_OPT_NOARG), + OPT_CLEANUP(&cleanup_arg), + OPT_PASSTHRU(0, "ff", &opt_ff, NULL, + N_("allow fast-forward"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL, + N_("abort if fast-forward is not possible"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG), + OPT_PASSTHRU(0, "verify", &opt_verify, NULL, + N_("control use of pre-merge-commit and commit-msg hooks"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL, + N_("verify that the named commit has a valid GPG signature"), + PARSE_OPT_NOARG), + OPT_BOOL(0, "autostash", &opt_autostash, + N_("automatically stash/stash pop before and after")), + OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), + N_("merge strategy to use"), + 0), + OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts, + N_("option=value"), + N_("option for selected merge strategy"), + 0), + OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"), + N_("GPG sign commit"), + PARSE_OPT_OPTARG), + OPT_SET_INT(0, "allow-unrelated-histories", + &opt_allow_unrelated_histories, + N_("allow merging unrelated histories"), 1), + + /* Options passed to git-fetch */ + OPT_GROUP(N_("Options related to fetching")), + OPT_PASSTHRU(0, "all", &opt_all, NULL, + N_("fetch from all remotes"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('a', "append", &opt_append, NULL, + N_("append to .git/FETCH_HEAD instead of overwriting"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"), + N_("path to upload pack on remote end"), + 0), + OPT__FORCE(&opt_force, N_("force overwrite of local branch"), 0), + OPT_PASSTHRU('t', "tags", &opt_tags, NULL, + N_("fetch all tags and associated objects"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('p', "prune", &opt_prune, NULL, + N_("prune remote-tracking branches no longer on remote"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('j', "jobs", &max_children, N_("n"), + N_("number of submodules pulled in parallel"), + PARSE_OPT_OPTARG), + OPT_BOOL(0, "dry-run", &opt_dry_run, + N_("dry run")), + OPT_PASSTHRU('k', "keep", &opt_keep, NULL, + N_("keep downloaded pack"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"), + N_("deepen history of shallow clone"), + 0), + OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"), + N_("deepen history of shallow repository based on time"), + 0), + OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("ref"), + N_("deepen history of shallow clone, excluding ref"), + 0), + OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"), + N_("deepen history of shallow clone"), + 0), + OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL, + N_("convert to a complete repository"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL, + N_("accept refs that update .git/shallow"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), + N_("specify fetch refmap"), + PARSE_OPT_NONEG), + OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch, + N_("server-specific"), + N_("option to transmit"), + 0), + OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL, + N_("use IPv4 addresses only"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL, + N_("use IPv6 addresses only"), + PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"), + N_("report that we have only objects reachable from this object"), + 0), + OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates, + N_("check for forced-updates on all updated branches")), + OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL, + N_("set upstream for git pull/fetch"), + PARSE_OPT_NOARG), + + OPT_END() + }; if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv); From 007b8994d4fc49f4a68ee414db9e814736a3fc04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 13 Dec 2025 10:40:42 +0100 Subject: [PATCH 12/16] t4014: support Git version strings with spaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git --version reports its version with the prefix "git version ". Remove precisely this string instead of everything up to and including the rightmost space to avoid butchering version strings that contain spaces. This helps Apple's release of Git, which reports its version like this: "git version 2.50.1 (Apple Git-155)". Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- t/t4014-format-patch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 2782b1fc183e8f..21d6d0cd9ef679 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -980,7 +980,7 @@ test_expect_success 'format-patch --ignore-if-in-upstream HEAD' ' test_expect_success 'get git version' ' git_version=$(git --version) && - git_version=${git_version##* } + git_version=${git_version#git version } ' signature() { From 8467c95419acaa826a6c1ca0db0f36a3fd614ae4 Mon Sep 17 00:00:00 2001 From: Kristoffer Haugsbakk Date: Sat, 13 Dec 2025 14:46:56 +0100 Subject: [PATCH 13/16] doc: replay: mention no output on conflicts Some commands will produce output on stderr if there are conflicts, but git-replay(1) is completely silent. Explicitly spell that out. Signed-off-by: Kristoffer Haugsbakk Signed-off-by: Junio C Hamano --- Documentation/git-replay.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc index dcb26e8a8e88ca..6fbb527b9d87b9 100644 --- a/Documentation/git-replay.adoc +++ b/Documentation/git-replay.adoc @@ -81,6 +81,10 @@ the shape of the history being replayed. When using `--advance`, the number of refs updated is always one, but for `--onto`, it can be one or more (rebasing multiple branches simultaneously is supported). +There is no stderr output on conflicts; see the <> section below. + +[[exit-status]] EXIT STATUS ----------- From 03d7c9c457ba68f28269dcd607b9026ea6c6c9c8 Mon Sep 17 00:00:00 2001 From: Kristoffer Haugsbakk Date: Sat, 13 Dec 2025 14:46:57 +0100 Subject: [PATCH 14/16] replay: improve --contained and add to doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is no documentation for `--contained`. Start by copying the text from `replay_options` in `builtin/ replay.c`. But some people think that the existing text is a bit unclear; what does it mean for a branch to be contained in a revision range? Let’s include the implied commits here: the branches that point at commits in the range. Also use “update” instead of “advance”. “Update” is the verb commonly used in this context. Helped-by: Phillip Wood Helped-by: Junio C Hamano Signed-off-by: Kristoffer Haugsbakk Signed-off-by: Junio C Hamano --- Documentation/git-replay.adoc | 4 ++++ builtin/replay.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc index 6fbb527b9d87b9..1e2469b90341e2 100644 --- a/Documentation/git-replay.adoc +++ b/Documentation/git-replay.adoc @@ -42,6 +42,10 @@ The history is replayed on top of the and is updated to point at the tip of the resulting history. This is different from `--onto`, which uses the target only as a starting point without updating it. +--contained:: + Update all branches that point at commits in + . Requires `--onto`. + --ref-action[=]:: Control how references are updated. The mode can be: + diff --git a/builtin/replay.c b/builtin/replay.c index 6606a2c94bc671..9e5ad64cad66a6 100644 --- a/builtin/replay.c +++ b/builtin/replay.c @@ -377,7 +377,7 @@ int cmd_replay(int argc, N_("revision"), N_("replay onto given commit")), OPT_BOOL(0, "contained", &contained, - N_("advance all branches contained in revision-range")), + N_("update all branches that point at commits in ")), OPT_STRING(0, "ref-action", &ref_action, N_("mode"), N_("control ref update behavior (update|print)")), From 9ba08b30a117e6925a9e5e87c92b37de7396d3a4 Mon Sep 17 00:00:00 2001 From: Kristoffer Haugsbakk Date: Sat, 13 Dec 2025 14:46:58 +0100 Subject: [PATCH 15/16] doc: replay: link section using markup Signed-off-by: Kristoffer Haugsbakk Signed-off-by: Junio C Hamano --- Documentation/git-replay.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc index 1e2469b90341e2..22fd1b271afa35 100644 --- a/Documentation/git-replay.adoc +++ b/Documentation/git-replay.adoc @@ -19,7 +19,7 @@ the working tree and the index untouched. By default, updates the relevant references using an atomic transaction (all refs update or none). Use `--ref-action=print` to avoid automatic ref updates and instead get update commands that can be piped to `git update-ref --stdin` -(see the OUTPUT section below). +(see the <> section below). THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE. @@ -67,6 +67,7 @@ The default mode can be configured via the `replay.refAction` configuration vari include::rev-list-options.adoc[] +[[output]] OUTPUT ------ From c8d76f7325e75c6f0549fce29ea4f3d97eb079cb Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 22 Dec 2025 13:46:36 +0900 Subject: [PATCH 16/16] The 11th batch Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.53.0.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/RelNotes/2.53.0.adoc b/Documentation/RelNotes/2.53.0.adoc index 34216a59fe5fe6..e94a79516c2138 100644 --- a/Documentation/RelNotes/2.53.0.adoc +++ b/Documentation/RelNotes/2.53.0.adoc @@ -188,3 +188,6 @@ Fixes since v2.52 (merge 8cbbdc92f7 kh/doc-pre-commit-fix later to maint). (merge d4bc39a4d9 mh/doc-config-gui-gcwarning later to maint). (merge 41d425008a kh/doc-send-email-paragraph-fix later to maint). + (merge d4b732899e jc/macports-darwinports later to maint). + (merge bab391761d kj/pull-options-decl-cleanup later to maint). + (merge 007b8994d4 rs/t4014-git-version-string-fix later to maint).