From 2b9665c4e82a1a62d93a64e9892574d6e03ec019 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Apr 2026 00:14:49 -0400 Subject: [PATCH 01/19] convert: add const to fix strchr() warnings C23 versions of libc (like recent glibc) may provide generic versions of strchr() that match constness between the input and return value. The idea being that the compiler can detect when it implicitly converts a const pointer into a non-const one (which then emits a warning). There are a few cases here where the result pointer does not need to be non-const at all, and we should mark it as such. That silences the warning (and avoids any potential problems with trying to write via those pointers). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- convert.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/convert.c b/convert.c index a34ec6ecdc057e..eae36c8a5936f4 100644 --- a/convert.c +++ b/convert.c @@ -1168,7 +1168,8 @@ static int ident_to_worktree(const char *src, size_t len, struct strbuf *buf, int ident) { struct object_id oid; - char *to_free = NULL, *dollar, *spc; + char *to_free = NULL; + const char *dollar, *spc; int cnt; if (!ident) From 2fb6a18782ff8f2d97b44c8812f9027f3812f970 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Apr 2026 00:14:51 -0400 Subject: [PATCH 02/19] http: add const to fix strchr() warnings The "path" field of a "struct repo" (a custom http-push struct, not to be confused with "struct repository) is a pointer into a const argv string, and is never written to. The compiler does not traditionally complain about assigning from a const pointer because it happens via strchr(). But with some C23 libc versions (notably recent glibc), it has started to do so. Let's mark the field as const to silence the warnings. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- http-push.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-push.c b/http-push.c index 9ae6062198e14f..96df6344ee6245 100644 --- a/http-push.c +++ b/http-push.c @@ -99,7 +99,7 @@ static struct object_list *objects; struct repo { char *url; - char *path; + const char *path; int path_len; int has_info_refs; int can_update_info_refs; From eedc7ecc66aefa085aae9bf51b56aa11eeb23950 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Apr 2026 00:14:56 -0400 Subject: [PATCH 03/19] transport-helper: drop const to fix strchr() warnings We implicitly drop the const from our "key" variable when we do: char *p = strchr(key, ' '); which causes compilation with some C23 versions of libc (notably recent glibc) to complain. We need "p" to remain writable, since we assign NUL over the space we found. We can solve this by also making "key" writable. This works because it comes from a strbuf, which is itself a writable string. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- transport-helper.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/transport-helper.c b/transport-helper.c index 4d95d84f9e4d05..4614036c99a5a0 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -781,7 +781,8 @@ static int push_update_ref_status(struct strbuf *buf, if (starts_with(buf->buf, "option ")) { struct object_id old_oid, new_oid; - const char *key, *val; + char *key; + const char *val; char *p; if (!state->hint || !(state->report || state->new_report)) From 031d29d6fbf12284d391c23f04d15970c3bac11c Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Apr 2026 00:14:58 -0400 Subject: [PATCH 04/19] pager: explicitly cast away strchr() constness When we do: char *cp = strchr(argv[i], '='); it implicitly removes the constness from argv[i]. We need "cp" to remain writable (since we overwrite it with a NUL). In theory we should be able to drop the const from argv[i], because it is a sub-pointer into our duplicated pager_env variable. But we get it from split_cmdline(), which uses the traditional "const char **" type for argv. This is overly limiting, but changing it would be awkward for all the other callers of split_cmdline(). Let's do an explicit cast with a note about why it is OK. This is enough to silence compiler warnings about the implicit const problems. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- pager.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pager.c b/pager.c index 5531fff50eb73f..35b210e0484f90 100644 --- a/pager.c +++ b/pager.c @@ -108,10 +108,11 @@ const char *git_pager(struct repository *r, int stdout_is_tty) static void setup_pager_env(struct strvec *env) { - const char **argv; + char **argv; int i; char *pager_env = xstrdup(PAGER_ENV); - int n = split_cmdline(pager_env, &argv); + /* split_cmdline splits in place, so we know the result is writable */ + int n = split_cmdline(pager_env, (const char ***)&argv); if (n < 0) die("malformed build-time PAGER_ENV: %s", From 21c57efc77ccdef2d6186874024593ded59ecc65 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Apr 2026 00:15:01 -0400 Subject: [PATCH 05/19] run-command: explicitly cast away constness when assigning to void We do this: char *equals = strchr(*e, '='); which implicitly removes the constness from "*e" and cause the compiler to complain. We never write to "equals", but later assign it to a string_list util field, which is defined as non-const "void *". We have to cast somewhere, but doing so at the assignment to util is the least-bad place, since that is the source of the confusion. Sadly we are still open to accidentally writing to the string via the util pointer, but that is the cost of using void pointers, which lose all type information. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- run-command.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run-command.c b/run-command.c index 32c290ee6a221f..d6980c79b356fd 100644 --- a/run-command.c +++ b/run-command.c @@ -604,11 +604,11 @@ static void trace_add_env(struct strbuf *dst, const char *const *deltaenv) /* Last one wins, see run-command.c:prep_childenv() for context */ for (e = deltaenv; e && *e; e++) { struct strbuf key = STRBUF_INIT; - char *equals = strchr(*e, '='); + const char *equals = strchr(*e, '='); if (equals) { strbuf_add(&key, *e, equals - *e); - string_list_insert(&envs, key.buf)->util = equals + 1; + string_list_insert(&envs, key.buf)->util = (void *)(equals + 1); } else { string_list_insert(&envs, *e)->util = NULL; } From bc4fd55984ce8c0fb99c8672ef2702acbbd98521 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Apr 2026 00:15:03 -0400 Subject: [PATCH 06/19] find_last_dir_sep(): convert inline function to macro The find_last_dir_sep() function is implemented as an inline function which takes in a "const char *" and returns a "char *" via strrchr(). That means that just like strrchr(), it quietly removes the const from our pointer, which could lead to accidentally writing to the resulting string. But C23 versions of libc (including recent glibc) annotate strrchr() such that the compiler can detect when const is implicitly lost, and it now complains about the call in this inline function. We can't just switch the return type of the function to "const char *", though. Some callers really do want a non-const string to be returned (and are OK because they are feeding a non-const string into the function). The most general solution is for us to annotate find_last_dir_sep() in the same way that is done for strrchr(). But doing so relies on using C23 generics, which we do not otherwise require. Since this inline function is wrapping a single call to strrchr(), we can take a shortcut. If we implement it as a macro, then the original type information is still available to strrchr(), and it does the check for us. Note that this is just one implementation of find_last_dir_sep(). There is an alternate implementation in compat/win32/path-utils.h. It doesn't suffer from the same warning, as it does not use strrchr() and just casts away const explicitly. That's not ideal, and eventually we may want to conditionally teach it the same C23 generic trick that strrchr() uses. But it has been that way forever, and our goal here is just quieting new warnings, not improving const-checking. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git-compat-util.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/git-compat-util.h b/git-compat-util.h index 4b4ea2498f13ef..4bb59b31017e46 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -335,11 +335,7 @@ static inline int is_path_owned_by_current_uid(const char *path, #endif #ifndef find_last_dir_sep -static inline char *git_find_last_dir_sep(const char *path) -{ - return strrchr(path, '/'); -} -#define find_last_dir_sep git_find_last_dir_sep +#define find_last_dir_sep(path) strrchr((path), '/') #endif #ifndef has_dir_sep From 25e5ceb9ee21d8806c9a3651e4f10241155f6e14 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Apr 2026 00:15:05 -0400 Subject: [PATCH 07/19] pseudo-merge: fix disk reads from find_pseudo_merge() The goal of this commit was to fix a const warning when compiling with new versions of glibc, but ended up untangling a much deeper problem. The find_pseudo_merge() function does a bsearch() on the "commits" pointer of a pseudo_merge_map. This pointer ultimately comes from memory mapped from the on-disk bitmap file, and is thus not writable. The "commits" array is correctly marked const, but the result from bsearch() is returned directly as a non-const pseudo_merge_commit struct. Since new versions of glibc annotate bsearch() in a way that detects the implicit loss of const, the compiler now warns. My first instinct was that we should be returning a const struct. That requires apply_pseudo_merges_for_commit() to mark its local pointer as const. But that doesn't work! If the offset field has the high-bit set, we look it up in the extended table via nth_pseudo_merge_ext(). And that function then feeds our const struct to read_pseudo_merge_commit_at(), which writes into it by byte-swapping from the on-disk mmap. But I think this points to a larger problem with find_pseudo_merge(). It is not just that the return value is missing const, but it is missing that byte-swapping! And we know that byte-swapping is needed here, because the comparator we use for bsearch() also calls our read_pseudo_merge_commit_at() helper. So I think the interface is all wrong here. We should not be returning a pointer to a struct which was cast from on-disk data. We should be filling in a caller-provided struct using the bytes we found, byte-swapping the values. That of course raises the dual question: how did this ever work, and does it work now? The answer to the first part is: this code does not seem to be triggered in the test suite at all. If we insert a BUG("foo") call into apply_pseudo_merges_for_commit(), it never triggers. So I think there is something wrong or missing from the test setup, and this bears further investigation. Sadly the answer to the second part ("does it work now") is still "no idea". I _think_ this takes us in a positive direction, but my goal here is mainly to quiet the compiler warning. Further bug-hunting on this experimental feature can be done separately. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- pseudo-merge.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/pseudo-merge.c b/pseudo-merge.c index a2d5bd85f95ebf..ff18b6c364245e 100644 --- a/pseudo-merge.c +++ b/pseudo-merge.c @@ -638,14 +638,21 @@ static int pseudo_merge_commit_cmp(const void *va, const void *vb) return 0; } -static struct pseudo_merge_commit *find_pseudo_merge(const struct pseudo_merge_map *pm, - uint32_t pos) +static int find_pseudo_merge(const struct pseudo_merge_map *pm, uint32_t pos, + struct pseudo_merge_commit *out) { + const unsigned char *at; + if (!pm->commits_nr) - return NULL; + return 0; - return bsearch(&pos, pm->commits, pm->commits_nr, - PSEUDO_MERGE_COMMIT_RAWSZ, pseudo_merge_commit_cmp); + at = bsearch(&pos, pm->commits, pm->commits_nr, + PSEUDO_MERGE_COMMIT_RAWSZ, pseudo_merge_commit_cmp); + if (!at) + return 0; + + read_pseudo_merge_commit_at(out, at); + return 1; } int apply_pseudo_merges_for_commit(const struct pseudo_merge_map *pm, @@ -653,16 +660,15 @@ int apply_pseudo_merges_for_commit(const struct pseudo_merge_map *pm, struct commit *commit, uint32_t commit_pos) { struct pseudo_merge *merge; - struct pseudo_merge_commit *merge_commit; + struct pseudo_merge_commit merge_commit; int ret = 0; - merge_commit = find_pseudo_merge(pm, commit_pos); - if (!merge_commit) + if (!find_pseudo_merge(pm, commit_pos, &merge_commit)) return 0; - if (merge_commit->pseudo_merge_ofs & ((uint64_t)1<<63)) { + if (merge_commit.pseudo_merge_ofs & ((uint64_t)1<<63)) { struct pseudo_merge_commit_ext ext = { 0 }; - off_t ofs = merge_commit->pseudo_merge_ofs & ~((uint64_t)1<<63); + off_t ofs = merge_commit.pseudo_merge_ofs & ~((uint64_t)1<<63); uint32_t i; if (pseudo_merge_ext_at(pm, &ext, ofs) < -1) { @@ -673,11 +679,11 @@ int apply_pseudo_merges_for_commit(const struct pseudo_merge_map *pm, } for (i = 0; i < ext.nr; i++) { - if (nth_pseudo_merge_ext(pm, &ext, merge_commit, i) < 0) + if (nth_pseudo_merge_ext(pm, &ext, &merge_commit, i) < 0) return ret; merge = pseudo_merge_at(pm, &commit->object.oid, - merge_commit->pseudo_merge_ofs); + merge_commit.pseudo_merge_ofs); if (!merge) return ret; @@ -687,7 +693,7 @@ int apply_pseudo_merges_for_commit(const struct pseudo_merge_map *pm, } } else { merge = pseudo_merge_at(pm, &commit->object.oid, - merge_commit->pseudo_merge_ofs); + merge_commit.pseudo_merge_ofs); if (!merge) return ret; From cefb8b7b47415aff6ad3ecdc6f3c546fa7abfd16 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Apr 2026 00:15:07 -0400 Subject: [PATCH 08/19] skip_prefix(): check const match between in and out params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The skip_prefix() function takes in a "const char *" string, and returns via a "const char **" out-parameter that points somewhere in that string. This is fine if you are operating on a const string, like: const char *in = ...; const char *out; if (skip_prefix(in, "foo", &out)) ...look at out... It is also OK if "in" is not const but "out" is, as we add an implicit const when we pass "in" to the function. But there's another case where this is limiting. If we want both fields to be non-const, like: char *in = ...; char *out; if (skip_prefix(in, "foo", &out)) *out = '\0'; it doesn't work. The compiler will complain about the type mismatch in passing "&out" to a parameter which expects "const char **". So to make this work, we have to do an explicit cast. But such a cast is ugly, and also means that we run afoul of making this mistake: const char *in = ...; char *out; if (skip_prefix(in, "foo", (const char **)&out)) *out = '\0'; which causes us to write to the memory pointed by "in", which was const. We can imagine these four cases as: (1) const in, const out (2) non-const in, const out (3) non-const in, non-const out (4) const in, non-const out Cases (1) and (2) work now. We would like case (3) to work but it doesn't. But we would like to catch case (4) as a compile error. So ideally the rule is "the out-parameter must be at least as const as the in-parameter". We can do this with some macro trickery. We wrap skip_prefix() in a macro so that it has access to the real types of in/out. And then we pass those parameters through another macro which: 1. Fails if the "at least as const" rule is not filled. 2. Casts to match the signature of the real skip_prefix(). There are a lot of ways to implement the "fails" part. You can use __builtin_types_compatible_p() to check, and then either our BUILD_ASSERT macros or _Static_assert to fail. But that requires some conditional compilation based on compiler feature. That's probably OK (the fallback would be to just cast without catching case 4). But we can do better. The macro I have here uses a ternary with a dead branch that tries to assign "in" to "out", which should work everywhere and lets the compiler catch the problem in the usual way. With an input like this: int foo(const char *x, const char **y); #define foo(in,out) foo((in), CONST_OUTPARAM((in), (out))) void ok_const(const char *x, const char **y) { foo(x, y); } void ok_nonconst(char *x, char **y) { foo(x, y); } void ok_add_const(char *x, const char **y) { foo(x, y); } void bad_drop_const(const char *x, char **y) { foo(x, y); } gcc reports: foo.c: In function ‘bad_drop_const’: foo.c:2:35: error: assignment discards ‘const’ qualifier from pointer target type [-Werror=discarded-qualifiers] 2 | ((const char **)(0 ? ((*(out) = (in)),(out)) : (out))) | ^ foo.c:4:31: note: in expansion of macro ‘CONST_OUTPARAM’ 4 | #define foo(in,out) foo((in), CONST_OUTPARAM((in), (out))) | ^~~~~~~~~~~~~~ foo.c:23:9: note: in expansion of macro ‘foo’ 23 | foo(x, y); | ^~~ It's a bit verbose, but I think makes it reasonably clear what's going on. Using BUILD_ASSERT_OR_ZERO() ends up much worse. Using _Static_assert you can be a bit more informative, but that's not something we use at all yet in our code-base (it's an old gnu-ism later standardized in C11). Our generic macro only works for "const char **", which is something we could improve by using typeof(in). But that introduces more portability questions, and also some weird corner cases (e.g., around implicit void conversion). This patch just introduces the concept. We'll make use of it in future patches. Note that we rename skip_prefix() to skip_prefix_impl() here, to avoid expanding the macro when defining the function. That's not strictly necessary since we could just define the macro after defining the inline function. But that would not be the case for a non-inline function (and we will apply this technique to them later, and should be consistent). It also gives us more freedom about where to define the macro. I did so right above the definition here, which I think keeps the relevant bits together. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git-compat-util.h | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/git-compat-util.h b/git-compat-util.h index 4bb59b31017e46..e9629b2a9dc6d4 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -463,6 +463,21 @@ void set_warn_routine(report_fn routine); report_fn get_warn_routine(void); void set_die_is_recursing_routine(int (*routine)(void)); +/* + * Check that an out-parameter that is "at least as const as" a matching + * in-parameter. For example, skip_prefix() will return "out" that is a subset + * of "str". So: + * + * const str, const out: ok + * non-const str, const out: ok + * non-const str, non-const out: ok + * const str, non-const out: compile error + * + * See the skip_prefix macro below for an example of use. + */ +#define CONST_OUTPARAM(in, out) \ + ((const char **)(0 ? ((*(out) = (in)),(out)) : (out))) + /* * If the string "str" begins with the string found in "prefix", return true. * The "out" parameter is set to "str + strlen(prefix)" (i.e., to the point in @@ -479,8 +494,10 @@ void set_die_is_recursing_routine(int (*routine)(void)); * [skip prefix if present, otherwise use whole string] * skip_prefix(name, "refs/heads/", &name); */ -static inline bool skip_prefix(const char *str, const char *prefix, - const char **out) +#define skip_prefix(str, prefix, out) \ + skip_prefix_impl((str), (prefix), CONST_OUTPARAM((str), (out))) +static inline bool skip_prefix_impl(const char *str, const char *prefix, + const char **out) { do { if (!*prefix) { From d3cd819e8bb21189b7bf3b2718898b610b85b119 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Apr 2026 00:15:10 -0400 Subject: [PATCH 09/19] pkt-line: make packet_reader.line non-const The "line" member of a packet_reader struct is marked as const. This kind of makes sense, because it's not its own allocated buffer that should be freed, and we often use const to indicate that. But it is always writable, because it points into the non-const "buffer" member. And we rely on this writability in places like send-pack and receive-pack, where we parse incoming packet contents by writing NULs over delimiters. This has traditionally worked because we implicitly cast away the constness with strchr() like: const char *head; char *p; head = reader->line; p = strchr(head, ' '); Since C23 libc provides a generic strchr() to detect this implicit const removal, this now generate a compiler warning on some platforms (like recent glibc). We can fix it by marking "line" as non-const, as well as a few intermediate variables (like "head" in the above example). Note that by itself, switching to a non-const variable would cause problems with this line in send-pack.c: if (!skip_prefix(reader->line, "unpack ", &reader->line)) But due to our skip_prefix() magic introduced in the previous commit, this compiles fine (both the in and out-parameters are non-const, so we know it is safe). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/receive-pack.c | 7 ++++--- pkt-line.h | 2 +- send-pack.c | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index e34edff406959a..a6af16c4e7e03f 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1025,8 +1025,8 @@ static int read_proc_receive_report(struct packet_reader *reader, for (;;) { struct object_id old_oid, new_oid; - const char *head; - const char *refname; + char *head; + char *refname; char *p; enum packet_read_status status; @@ -1050,7 +1050,8 @@ static int read_proc_receive_report(struct packet_reader *reader, } *p++ = '\0'; if (!strcmp(head, "option")) { - const char *key, *val; + char *key; + const char *val; if (!hint || !(report || new_report)) { if (!once++) diff --git a/pkt-line.h b/pkt-line.h index 3b33cc64f34dcc..e6cf85e34ee3c4 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -184,7 +184,7 @@ struct packet_reader { int pktlen; /* the last line read */ - const char *line; + char *line; /* indicates if a line has been peeked */ int line_peeked; diff --git a/send-pack.c b/send-pack.c index 07ecfae4de92a2..b4361d5610dc91 100644 --- a/send-pack.c +++ b/send-pack.c @@ -175,8 +175,8 @@ static int receive_status(struct repository *r, ret = receive_unpack_status(reader); while (1) { struct object_id old_oid, new_oid; - const char *head; - const char *refname; + char *head; + char *refname; char *p; if (packet_reader_read(reader) != PACKET_READ_NORMAL) break; @@ -190,7 +190,8 @@ static int receive_status(struct repository *r, *p++ = '\0'; if (!strcmp(head, "option")) { - const char *key, *val; + char *key; + const char *val; if (!hint || !(report || new_report)) { if (!once++) From c39512600f85aa88f368dc6bd13baeb183ae52ad Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Apr 2026 00:15:12 -0400 Subject: [PATCH 10/19] range-diff: drop const to fix strstr() warnings This is another case where we implicitly drop the "const" from a pointer by feeding it to strstr() and assigning the result to a non-const pointer. This is OK in practice, since the const pointer originally comes from a writable source (a strbuf), but C23 libc implementations have started to complain about it. We do write to the output pointer, so it needs to remain non-const. We can just switch the input pointer to also be non-const in this case. By itself that would run into problems with calls to skip_prefix(), but since that function has now been taught to match in/out constness automatically, it just works without us doing anything further. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- range-diff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/range-diff.c b/range-diff.c index 2712a9a107ab06..8e2dd2eb193eb9 100644 --- a/range-diff.c +++ b/range-diff.c @@ -88,7 +88,7 @@ static int read_patches(const char *range, struct string_list *list, line = contents.buf; size = contents.len; for (; size > 0; size -= len, line += len) { - const char *p; + char *p; char *eol; eol = memchr(line, '\n', size); From 8a0566b42b133b73423c801a7ab6f356de69f51a Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Apr 2026 00:15:14 -0400 Subject: [PATCH 11/19] http: drop const to fix strstr() warning In redact_sensitive_header(), a C23 implementation of libc will complain that strstr() assigns the result from "const char *cookie" to "char *semicolon". Ultimately the memory is writable. We're fed a strbuf, generate a const pointer "sensitive_header" within it using skip_iprefix(), and then assign the result to "cookie". So we can solve this by dropping the const from "cookie" and "sensitive_header". However, this runs afoul of skip_iprefix(), which wants a "const char **" for its out-parameter. We can solve that by teaching skip_iprefix() the same "make sure out is at least as const as in" magic that we recently taught to skip_prefix(). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git-compat-util.h | 6 ++++-- http.c | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/git-compat-util.h b/git-compat-util.h index e9629b2a9dc6d4..4ddac619921a71 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -902,8 +902,10 @@ static inline size_t xsize_t(off_t len) * is done via tolower(), so it is strictly ASCII (no multi-byte characters or * locale-specific conversions). */ -static inline bool skip_iprefix(const char *str, const char *prefix, - const char **out) +#define skip_iprefix(str, prefix, out) \ + skip_iprefix_impl((str), (prefix), CONST_OUTPARAM((str), (out))) +static inline bool skip_iprefix_impl(const char *str, const char *prefix, + const char **out) { do { if (!*prefix) { diff --git a/http.c b/http.c index 8ea1b9d1f68c16..8801bd22feed4a 100644 --- a/http.c +++ b/http.c @@ -726,7 +726,7 @@ static int has_proxy_cert_password(void) static int redact_sensitive_header(struct strbuf *header, size_t offset) { int ret = 0; - const char *sensitive_header; + char *sensitive_header; if (trace_curl_redact && (skip_iprefix(header->buf + offset, "Authorization:", &sensitive_header) || @@ -743,7 +743,7 @@ static int redact_sensitive_header(struct strbuf *header, size_t offset) } else if (trace_curl_redact && skip_iprefix(header->buf + offset, "Cookie:", &sensitive_header)) { struct strbuf redacted_header = STRBUF_INIT; - const char *cookie; + char *cookie; while (isspace(*sensitive_header)) sensitive_header++; From f1b8a4d10888922eacaa9552d9925d3330ed5d8b Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Apr 2026 00:15:16 -0400 Subject: [PATCH 12/19] refs/files-backend: drop const to fix strchr() warning In show_one_reflog_ent(), we're fed a writable strbuf buffer, which we parse into the various reflog components. We write a NUL over email_end to tie off one of the fields, and thus email_end must be non-const. But with a C23 implementation of libc, strchr() will now complain when assigning the result to a non-const pointer from a const one. So we can fix this by making the source pointer non-const. But there's a catch. We derive that source pointer by parsing the line with parse_oid_hex_algop(), which requires a const pointer for its out-parameter. We can work around that by teaching it to use our CONST_OUTPARAM() trick, just like skip_prefix(). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- hex.c | 6 +++--- hex.h | 6 ++++-- refs/files-backend.c | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/hex.c b/hex.c index 865a232167553d..bc756722ca623b 100644 --- a/hex.c +++ b/hex.c @@ -54,9 +54,9 @@ int get_oid_hex(const char *hex, struct object_id *oid) return get_oid_hex_algop(hex, oid, the_hash_algo); } -int parse_oid_hex_algop(const char *hex, struct object_id *oid, - const char **end, - const struct git_hash_algo *algop) +int parse_oid_hex_algop_impl(const char *hex, struct object_id *oid, + const char **end, + const struct git_hash_algo *algop) { int ret = get_oid_hex_algop(hex, oid, algop); if (!ret) diff --git a/hex.h b/hex.h index e9ccb54065c0bc..1e9a65d83a4f6b 100644 --- a/hex.h +++ b/hex.h @@ -40,8 +40,10 @@ char *oid_to_hex(const struct object_id *oid); /* same static buffer */ * other invalid character. end is only updated on success; otherwise, it is * unmodified. */ -int parse_oid_hex_algop(const char *hex, struct object_id *oid, const char **end, - const struct git_hash_algo *algo); +#define parse_oid_hex_algop(hex, oid, end, algo) \ + parse_oid_hex_algop_impl((hex), (oid), CONST_OUTPARAM((hex), (end)), (algo)) +int parse_oid_hex_algop_impl(const char *hex, struct object_id *oid, const char **end, + const struct git_hash_algo *algo); /* * These functions work like get_oid_hex and parse_oid_hex, but they will parse diff --git a/refs/files-backend.c b/refs/files-backend.c index 7ce0d574781ffd..ad543ad7513248 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -2190,7 +2190,7 @@ static int show_one_reflog_ent(struct files_ref_store *refs, char *email_end, *message; timestamp_t timestamp; int tz; - const char *p = sb->buf; + char *p = sb->buf; /* old SP new SP name SP time TAB msg LF */ if (!sb->len || sb->buf[sb->len - 1] != '\n' || From 58589c20e555de187c8026ac721467919595543f Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 4 Apr 2026 01:42:11 -0400 Subject: [PATCH 13/19] git-compat-util: fix CONST_OUTPARAM typo and indentation There's a typo in the comment, making it hard to understand. And the macro itself is indented with spaces rather than tab. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git-compat-util.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-compat-util.h b/git-compat-util.h index 4ddac619921a71..ae1bdc90a4cd6a 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -464,7 +464,7 @@ report_fn get_warn_routine(void); void set_die_is_recursing_routine(int (*routine)(void)); /* - * Check that an out-parameter that is "at least as const as" a matching + * Check that an out-parameter is "at least as const as" a matching * in-parameter. For example, skip_prefix() will return "out" that is a subset * of "str". So: * @@ -476,7 +476,7 @@ void set_die_is_recursing_routine(int (*routine)(void)); * See the skip_prefix macro below for an example of use. */ #define CONST_OUTPARAM(in, out) \ - ((const char **)(0 ? ((*(out) = (in)),(out)) : (out))) + ((const char **)(0 ? ((*(out) = (in)),(out)) : (out))) /* * If the string "str" begins with the string found in "prefix", return true. From 5bdb9883bda41024e3c47f1cddf69977d82fe3a9 Mon Sep 17 00:00:00 2001 From: Kristoffer Haugsbakk Date: Sun, 5 Apr 2026 12:31:59 +0200 Subject: [PATCH 14/19] doc: replace git config --list/-l with `list` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace uses of `git config --list` (short or long) with the subcommand `list` since `--list` is deprecated. We will change the “man page” phrasing in gitcvs-migration(7) in the next commit, since we are already visiting that sentence. But note that we leave the “man page” phrasing in the sentence that we touch in gittutorial(7) since it’s a tutorial and not a manual page. We can be more wordy in a tutorial context. Signed-off-by: Kristoffer Haugsbakk Signed-off-by: Junio C Hamano --- Documentation/git-var.adoc | 2 +- Documentation/gitcvs-migration.adoc | 2 +- Documentation/gitprotocol-v2.adoc | 2 +- Documentation/gittutorial.adoc | 2 +- Documentation/technical/api-trace2.adoc | 2 +- Documentation/user-manual.adoc | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Documentation/git-var.adoc b/Documentation/git-var.adoc index b606c2d649979f..697c10adedcca6 100644 --- a/Documentation/git-var.adoc +++ b/Documentation/git-var.adoc @@ -22,7 +22,7 @@ OPTIONS Display the logical variables. In addition, all the variables of the Git configuration file .git/config are listed as well. (However, the configuration variables listing functionality - is deprecated in favor of `git config -l`.) + is deprecated in favor of `git config list`.) EXAMPLES -------- diff --git a/Documentation/gitcvs-migration.adoc b/Documentation/gitcvs-migration.adoc index 1cd1283d0f817d..2883834b714701 100644 --- a/Documentation/gitcvs-migration.adoc +++ b/Documentation/gitcvs-migration.adoc @@ -49,7 +49,7 @@ them first before running git pull. ================================ The 'pull' command knows where to get updates from because of certain configuration variables that were set by the first 'git clone' -command; see `git config -l` and the linkgit:git-config[1] man +command; see `git config list` and the linkgit:git-config[1] man page for details. ================================ diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc index f985cb4c474953..ac5102b64f99ec 100644 --- a/Documentation/gitprotocol-v2.adoc +++ b/Documentation/gitprotocol-v2.adoc @@ -659,7 +659,7 @@ use by the client, MUST indicate prerequisites (in any) with standard applicable. + The advertised URI may alternatively contain a plaintext file that `git -config --list` would accept (with the `--file` option). The key-value +config list` would accept (with the `--file` option). The key-value pairs in this list are in the `bundle.*` namespace (see linkgit:git-config[1]). diff --git a/Documentation/gittutorial.adoc b/Documentation/gittutorial.adoc index f89ad30cf652b4..519b8d8be2cc46 100644 --- a/Documentation/gittutorial.adoc +++ b/Documentation/gittutorial.adoc @@ -432,7 +432,7 @@ bob$ git config --get remote.origin.url ------------------------------------- (The complete configuration created by `git clone` is visible using -`git config -l`, and the linkgit:git-config[1] man page +`git config list`, and the linkgit:git-config[1] man page explains the meaning of each option.) Git also keeps a pristine copy of Alice's `master` branch under the diff --git a/Documentation/technical/api-trace2.adoc b/Documentation/technical/api-trace2.adoc index cf493dae03f3ac..918e517c2e6edb 100644 --- a/Documentation/technical/api-trace2.adoc +++ b/Documentation/technical/api-trace2.adoc @@ -1253,7 +1253,7 @@ it. $ git config --system color.ui never $ git config --global color.ui always $ git config --local color.ui auto -$ git config --list --show-scope | grep 'color.ui' +$ git config list --show-scope | grep 'color.ui' system color.ui=never global color.ui=always local color.ui=auto diff --git a/Documentation/user-manual.adoc b/Documentation/user-manual.adoc index 64009baf370231..5ec65cebe29dd7 100644 --- a/Documentation/user-manual.adoc +++ b/Documentation/user-manual.adoc @@ -2865,7 +2865,7 @@ stored in Git configuration variables, which you can see using linkgit:git-config[1]: ------------------------------------------------- -$ git config -l +$ git config list core.repositoryformatversion=0 core.filemode=true core.logallrefupdates=true From 57177ad139c3d83116459c5a8ec323b8d2a050f4 Mon Sep 17 00:00:00 2001 From: Kristoffer Haugsbakk Date: Sun, 5 Apr 2026 12:32:00 +0200 Subject: [PATCH 15/19] =?UTF-8?q?doc:=20gitcvs-migration:=20rephrase=20?= =?UTF-8?q?=E2=80=9Cman=20page=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let’s change the phrasing around the `linkgit` while we’re visiting this file (see previous commit[1]). We use the section syntax to refer to man pages, so writing “man page” next to it is a bit redundant. We can be more concise and just lean on the preposition “in”. And in order to avoid this double “git”: see `git config list` in git-config(1) ... We can rephrase to the subcommand, which is a typical pattern (config or option followed by “in git-command(1)”). † 1: Which also discusses why we do not change a similar phrasing in gittutorial(7) Signed-off-by: Kristoffer Haugsbakk Signed-off-by: Junio C Hamano --- Documentation/gitcvs-migration.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Documentation/gitcvs-migration.adoc b/Documentation/gitcvs-migration.adoc index 2883834b714701..905d08cd5f991c 100644 --- a/Documentation/gitcvs-migration.adoc +++ b/Documentation/gitcvs-migration.adoc @@ -49,8 +49,7 @@ them first before running git pull. ================================ The 'pull' command knows where to get updates from because of certain configuration variables that were set by the first 'git clone' -command; see `git config list` and the linkgit:git-config[1] man -page for details. +command; see the subcommand `list` in linkgit:git-config[1] for details. ================================ You can update the shared repository with your changes by first committing From 295fb82264cc8be565a5da8259b103cbc6a41728 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 6 Apr 2026 13:27:26 +0000 Subject: [PATCH 16/19] t6600: test --maximal-only and --independent Add a test that verifies the 'git rev-list --maximal-only' option produces the same set of commits as 'git merge-base --independent'. This equivalence was noted when the feature was first created, but we are about to update the implementation to use a common algorithm in this case where the user intention is identical. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- t/t6600-test-reach.sh | 45 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh index 2613075894282d..dc0421ed2f3726 100755 --- a/t/t6600-test-reach.sh +++ b/t/t6600-test-reach.sh @@ -837,4 +837,49 @@ test_expect_success 'rev-list --maximal-only (range)' ' --first-parent --exclude-first-parent-only ' +test_expect_success 'rev-list --maximal-only matches merge-base --independent' ' + # Mix of independent and dependent + git merge-base --independent \ + refs/heads/commit-5-2 \ + refs/heads/commit-3-2 \ + refs/heads/commit-2-5 >expect && + sort expect >expect.sorted && + git rev-list --maximal-only \ + refs/heads/commit-5-2 \ + refs/heads/commit-3-2 \ + refs/heads/commit-2-5 >actual && + sort actual >actual.sorted && + test_cmp expect.sorted actual.sorted && + + # All independent commits. + git merge-base --independent \ + refs/heads/commit-5-2 \ + refs/heads/commit-4-3 \ + refs/heads/commit-3-4 \ + refs/heads/commit-2-5 >expect && + sort expect >expect.sorted && + git rev-list --maximal-only \ + refs/heads/commit-5-2 \ + refs/heads/commit-4-3 \ + refs/heads/commit-3-4 \ + refs/heads/commit-2-5 >actual && + sort actual >actual.sorted && + test_cmp expect.sorted actual.sorted && + + # Only one independent. + git merge-base --independent \ + refs/heads/commit-1-1 \ + refs/heads/commit-4-2 \ + refs/heads/commit-4-4 \ + refs/heads/commit-8-4 >expect && + sort expect >expect.sorted && + git rev-list --maximal-only \ + refs/heads/commit-1-1 \ + refs/heads/commit-4-2 \ + refs/heads/commit-4-4 \ + refs/heads/commit-8-4 >actual && + sort actual >actual.sorted && + test_cmp expect.sorted actual.sorted +' + test_done From e8e5453ab8794cf29afe0a616d74319442b676bd Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 6 Apr 2026 13:27:27 +0000 Subject: [PATCH 17/19] p6011: add perf test for rev-list --maximal-only Add a performance test that compares 'git rev-list --maximal-only' against 'git merge-base --independent'. These two commands are asking essentially the same thing, but the rev-list implementation is more generic and hence slower. These performance tests will demonstrate that in the current state and also be used to show the equivalence in the future. We also add a case with '--since' to force the generic walk logic for rev-list even when we make that future change to use the merge-base algorithm on a simple walk. When run on my copy of git.git, I see these results: Test HEAD ---------------------------------------------- 6011.2: merge-base --independent 0.03 6011.3: rev-list --maximal-only 0.06 6011.4: rev-list --maximal-only --since 0.06 These numbers are low, but the --independent calculation is interesting due to having a lot of local branches that are actually independent. Running the same test on a fresh clone of the Linux kernel repository shows a larger difference between the algorithms, especially because the --independent algorithm is extremely fast when there are no independent references selected: Test HEAD ---------------------------------------------- 6011.2: merge-base --independent 0.00 6011.3: rev-list --maximal-only 0.70 6011.4: rev-list --maximal-only --since 0.70 Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- t/perf/p6011-rev-list-maximal.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100755 t/perf/p6011-rev-list-maximal.sh diff --git a/t/perf/p6011-rev-list-maximal.sh b/t/perf/p6011-rev-list-maximal.sh new file mode 100755 index 00000000000000..e868e83ff847b3 --- /dev/null +++ b/t/perf/p6011-rev-list-maximal.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +test_description='Test --maximal-only and --independent options' + +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success 'setup' ' + git for-each-ref --format="%(*objecttype) %(objecttype) %(objectname)" \ + "refs/heads/*" "refs/tags/*" | + sed -n -e "s/^commit commit //p" -e "s/^ commit //p" | + head -n 50 >commits && + git commit-graph write --reachable +' + +test_perf 'merge-base --independent' ' + git merge-base --independent $(cat commits) >/dev/null +' + +test_perf 'rev-list --maximal-only' ' + git rev-list --maximal-only $(cat commits) >/dev/null +' + +test_perf 'rev-list --maximal-only --since' ' + git rev-list --maximal-only --since=2000-01-01 $(cat commits) >/dev/null +' + +test_done From b0ba57daa86dfba0a6551613452d1cb2301266a2 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 6 Apr 2026 13:27:28 +0000 Subject: [PATCH 18/19] rev-list: use reduce_heads() for --maximal-only The 'git rev-list --maximal-only' option filters the output to only independent commits. A commit is independent if it is not reachable from other listed commits. Currently this is implemented by doing a full revision walk and marking parents with CHILD_VISITED to skip non-maximal commits. The 'git merge-base --independent' command computes the same result using reduce_heads(), which uses the more efficient remove_redundant() algorithm. This is significantly faster because it avoids walking the entire commit graph. Add a fast path in rev-list that detects when --maximal-only is the only interesting option and all input commits are positive (no revision ranges). In this case, use reduce_heads() directly instead of doing a full revision walk. In order to preserve the rest of the output filtering, this computation is done opportunistically in a new prepare_maximal_independent() method when possible. If successful, it populates revs->commits with the list of independent commits and set revs->no_walk to prevent any other walk from occurring. This allows us to have any custom output be handled using the existing output code hidden inside traverse_commit_list_filtered(). A new test is added to demonstrate that this output is preserved. The fast path is only used when no other flags complicate the walk or output format: no UNINTERESTING commits, no limiting options (max-count, age filters, path filters, grep filters), no output formatting beyond plain OIDs, and no object listing flags. Running the p6011 performance test for my copy of git.git, I see the following improvement with this change: Test HEAD~1 HEAD ------------------------------------------------------------ 6011.2: merge-base --independent 0.03 0.03 +0.0% 6011.3: rev-list --maximal-only 0.06 0.03 -50.0% 6011.4: rev-list --maximal-only --since 0.06 0.06 +0.0% And for a fresh clone of the Linux kernel repository, I see: Test HEAD~1 HEAD ------------------------------------------------------------ 6011.2: merge-base --independent 0.00 0.00 = 6011.3: rev-list --maximal-only 0.70 0.00 -100.0% 6011.4: rev-list --maximal-only --since 0.70 0.70 +0.0% In both cases, the performance is indeed matching the behavior of 'git merge-base --independent', as expected. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/rev-list.c | 59 ++++++++++++++++++++++++++++++++++++++++ t/t6000-rev-list-misc.sh | 31 +++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 854d82ece368b3..8f63003709242e 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -25,6 +25,7 @@ #include "oidset.h" #include "oidmap.h" #include "packfile.h" +#include "commit-reach.h" #include "quote.h" #include "strbuf.h" @@ -633,6 +634,61 @@ static int try_bitmap_disk_usage(struct rev_info *revs, return 0; } +/* + * If revs->maximal_only is set and no other walk modifiers are provided, + * run a faster computation to filter the independent commits and prepare + * them for output. Set revs->no_walk to prevent later walking. + * + * If this algorithm doesn't apply, then no changes are made to revs. + */ +static void prepare_maximal_independent(struct rev_info *revs) +{ + struct commit_list *c; + + if (!revs->maximal_only) + return; + + for (c = revs->commits; c; c = c->next) { + if (c->item->object.flags & UNINTERESTING) + return; + } + + if (revs->limited || + revs->topo_order || + revs->first_parent_only || + revs->reverse || + revs->max_count >= 0 || + revs->skip_count >= 0 || + revs->min_age != (timestamp_t)-1 || + revs->max_age != (timestamp_t)-1 || + revs->min_parents > 0 || + revs->max_parents >= 0 || + revs->prune_data.nr || + revs->count || + revs->left_right || + revs->boundary || + revs->tag_objects || + revs->tree_objects || + revs->blob_objects || + revs->filter.choice || + revs->reflog_info || + revs->diff || + revs->grep_filter.pattern_list || + revs->grep_filter.header_list || + revs->verbose_header || + revs->print_parents || + revs->edge_hint || + revs->unpacked || + revs->no_kept_objects || + revs->line_level_traverse) + return; + + reduce_heads_replace(&revs->commits); + + /* Modify 'revs' to only output this commit list. */ + revs->no_walk = 1; +} + int cmd_rev_list(int argc, const char **argv, const char *prefix, @@ -875,6 +931,9 @@ int cmd_rev_list(int argc, if (prepare_revision_walk(&revs)) die("revision walk setup failed"); + + prepare_maximal_independent(&revs); + if (revs.tree_objects) mark_edges_uninteresting(&revs, show_edge, 0); diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh index d0a2a866100d56..a95ba576fa24b2 100755 --- a/t/t6000-rev-list-misc.sh +++ b/t/t6000-rev-list-misc.sh @@ -263,4 +263,35 @@ test_expect_success 'rev-list --boundary incompatible with --maximal-only' ' test_grep "cannot be used together" err ' +test_expect_success 'rev-list --maximal-only and --pretty' ' + test_when_finished rm -rf repo && + + git init repo && + test_commit -C repo 1 && + oid1=$(git -C repo rev-parse HEAD) && + test_commit -C repo 2 && + oid2=$(git -C repo rev-parse HEAD) && + git -C repo checkout --detach HEAD~1 && + test_commit -C repo 3 && + oid3=$(git -C repo rev-parse HEAD) && + + cat >expect <<-EOF && + commit $oid3 + $oid3 + commit $oid2 + $oid2 + EOF + + git -C repo rev-list --pretty="%H" --maximal-only $oid1 $oid2 $oid3 >out && + test_cmp expect out && + + cat >expect <<-EOF && + $oid3 + $oid2 + EOF + + git -C repo log --pretty="%H" --maximal-only $oid1 $oid2 $oid3 >out && + test_cmp expect out +' + test_done From 60f07c4f5c5f81c8a994d9e06b31a4a3a1679864 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 9 Apr 2026 11:21:36 -0700 Subject: [PATCH 19/19] A bit more for -rc2 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.54.0.adoc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Documentation/RelNotes/2.54.0.adoc b/Documentation/RelNotes/2.54.0.adoc index 30ec959e7e5bf2..3fa25e06f2d1d6 100644 --- a/Documentation/RelNotes/2.54.0.adoc +++ b/Documentation/RelNotes/2.54.0.adoc @@ -124,6 +124,9 @@ UI, Workflows & Features * Handling of signed commits and tags in fast-import has been made more configurable. + * "git config list" is the official way to spell "git config -l" and + "git config --list". Use it to update the documentation. + Performance, Internal Implementation, Development Support etc. -------------------------------------------------------------- @@ -316,6 +319,14 @@ Performance, Internal Implementation, Development Support etc. log/diff machinery is being reworked a bit to make the feature compatible with more diff options, like -S/G. + * Further work to adjust the codebase for C23 that changes functions + like strchr() that discarded constness when they return a pointer into + a const string to preserve constness. + + * "git rev-list --maximal-only" has been optimized by borrowing the + logic used by "git show-branch --independent", which computes the + same kind of information much more efficiently. + Fixes since v2.53 -----------------