From 88249755a4e9fe4bfa0868871372752158b1f9fe Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Tue, 11 Aug 2026 14:14:46 +0200 Subject: [PATCH 01/32] git: avoid segfault on "git --shallow-file" without a value In "git.c", the other `handle_options()` options that take their value as a separate argument, like `--git-dir`, `--namespace` or `-C`, check that such an argument actually exists before using it, and error out with a message and the usage string otherwise. The `--shallow-file` option doesn't perform that check. It blindly advances past the option and then dereferences the next element of `argv`, which is the NULL terminator when no value was given. So `git --shallow-file` segfaults: $ git --shallow-file Segmentation fault (core dumped) Let's fix that by checking that a value was given, in the same way and with a message worded like the ones the other options use. While at it, let's also set the environment variable before advancing past the option, instead of advancing first and using `(*argv)[0]`, so that this option looks like the other ones. Note that all the in-tree callers passing `--shallow-file` to a `git` subprocess always pass a value after it, so they are not affected. In `upload-pack.c` that value is an empty string, which is still accepted. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- git.c | 10 +++++++--- t/t0041-usage.sh | 7 +++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/git.c b/git.c index e5f1811b6bb762..96df15b5cde1ed 100644 --- a/git.c +++ b/git.c @@ -304,11 +304,15 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) if (envchanged) *envchanged = 1; } else if (!strcmp(cmd, "--shallow-file")) { - (*argv)++; - (*argc)--; - setenv(GIT_SHALLOW_FILE_ENVIRONMENT, (*argv)[0], 1); + if (*argc < 2) { + fprintf(stderr, _("no file given for '%s' option\n" ), "--shallow-file"); + usage(git_usage_string); + } + setenv(GIT_SHALLOW_FILE_ENVIRONMENT, (*argv)[1], 1); if (envchanged) *envchanged = 1; + (*argv)++; + (*argc)--; } else if (!strcmp(cmd, "-C")) { if (*argc < 2) { fprintf(stderr, _("no directory given for '%s' option\n" ), "-C"); diff --git a/t/t0041-usage.sh b/t/t0041-usage.sh index 51af7cc0300efb..2a9c5eafcac2a6 100755 --- a/t/t0041-usage.sh +++ b/t/t0041-usage.sh @@ -107,4 +107,11 @@ test_expect_success 'for-each-ref usage error' ' test_grep "usage" actual.err ' +test_expect_success 'git --shallow-file without a value' ' + test_must_fail git --shallow-file >actual 2>actual.err && + test_line_count = 0 actual && + test_grep "no file given for " actual.err && + test_grep "usage" actual.err +' + test_done From dd6b35ff71a61c75af6f153fce14048c32d645cb Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 12 Aug 2026 06:39:43 +0000 Subject: [PATCH 02/32] serve: reject valueless promisor-remote capability d460267613da (Add 'promisor-remote' capability to protocol v2, 2025-02-18) added a receive callback which passes the capability value directly to mark_promisor_remotes_as_accepted(). However, a client can send the capability name without an '=' or value, in which case get_capability() supplies NULL and strbuf_split_str() dereferences it. Reject the missing argument before parsing it, and add a test covering this case. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- serve.c | 3 +++ t/t5701-git-serve.sh | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/serve.c b/serve.c index 49a6e39b1dd25c..29bed14af1c52c 100644 --- a/serve.c +++ b/serve.c @@ -46,6 +46,9 @@ static int promisor_remote_advertise(struct repository *r, static void promisor_remote_receive(struct repository *r, const char *remotes) { + if (!remotes) + die("promisor-remote capability requires an argument"); + mark_promisor_remotes_as_accepted(r, remotes); } diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index d4c28bae39e2ad..f00e25d3e276b6 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -69,6 +69,17 @@ test_expect_success 'request invalid capability' ' test_grep "unknown capability" err ' +test_expect_success 'promisor-remote capability requires an argument' ' + test-tool pkt-line pack >in <<-EOF && + command=ls-refs + object-format=$(test_oid algo) + promisor-remote + 0000 + EOF + test_must_fail test-tool serve-v2 --stateless-rpc 2>err in <<-EOF && agent=git/test From 14058ed717ece27a86dc4c0e72642d89624da5ff Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 12 Aug 2026 06:42:38 +0000 Subject: [PATCH 03/32] sequencer: remove unnecessary variable setting revs.pretty_given is only ever read in builtin/log.c, and nothing from builtin/log.c is ever called from sequencer.c. So setting this variable cannot do anything. This was introduced in commit 62db524779 ("rebase -i: generate the script via rebase--helper", 2017-07-14), which used `git rev-list` even though its commit message describes the logic as having been based on `git log`. Because of this, I am guessing this line was copied or ported from part of builtin/log.c without recognizing that this line was not doing anything and could be removed. It's certainly not doing anything now, though, so remove it. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- sequencer.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 57855b0066ac98..e395ec4cfc41d8 100644 --- a/sequencer.c +++ b/sequencer.c @@ -6176,7 +6176,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, revs.sort_order = REV_SORT_IN_GRAPH_ORDER; revs.topo_order = 1; - revs.pretty_given = 1; repo_config_get_string(the_repository, "rebase.instructionFormat", &format); if (!format || !*format) { free(format); From 5fb9b8b4a685ee2767a2e8a63e11ba26b77a1125 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 12 Aug 2026 12:11:46 +0200 Subject: [PATCH 04/32] t7900: adapt some tests to use a throwaway repository Many of the tests in t7900 operate inside the main trash repository that's set up by default by our test suite. This is overall quite fragile as we're exercising repository maintenance in those tests, and maintenance is of course intricately tied towards the on-disk state of a repository. Consequently, the tests can easily impact one another. Furthermore, in the next commit we'll have to modify the environment in a handful of those tests. As tests don't run in a subshell, doing so would impact all subsequent tests by default, as well. Adapt exactly those tests to use a throwaway repository. This makes the tests more neatly self-contained and allows us to trivially modify the environment in the next commit. Note that we adapt calls to `test_config ()` to use git-config(1) instead. This is because on the one hand we don't need the auto-revert logic of `test_config ()` as we're using a throwaway repository anyway. On the other hand it's not possible to use `test_config ()` as it uses `test_when_finished ()`, which errors out when we run it in a subshell. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/t7900-maintenance.sh | 70 ++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index d7f82e1bec163f..8b5614cf5910a6 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -61,41 +61,57 @@ test_expect_success 'run [--auto|--quiet] with gc strategy' ' ' test_expect_success 'maintenance.auto config option' ' - GIT_TRACE2_EVENT="$(pwd)/default" git commit --quiet --allow-empty -m 1 && - test_subcommand git maintenance run --auto --quiet --detach Date: Wed, 12 Aug 2026 12:11:47 +0200 Subject: [PATCH 05/32] t7900: fix flaky "maintenance.strategy" test One of our tests for whether "maintenance.strategy" is being respected in t7900 is flaky in our CI systems: + GIT_TRACE2_EVENT=/tmp/test-output/trash directory.t7900-maintenance/repo/trace2.txt git -c maintenance.strategy=incremental maintenance run --quiet + test_maintenance_tasks trace2.txt + cat + sed -ne s/.*"region_enter".*"category":"maintenance\([^"]*\)".*"label":"\([^"][^"]*\)".*/\2\1/p trace2.txt + test_cmp expect actual + test 2 -ne 2 + eval /usr/bin/diff -u "$@" + /usr/bin/diff -u expect actual --- expect 2026-08-07 06:20:51.388322602 +0000 +++ actual 2026-08-07 06:20:51.388322602 +0000 @@ -1,2 +0,0 @@ -gc foreground -gc When running with the "incremental" strategy, we expect two git-gc(1) tasks to have been executed, but sometimes the test simply doesn't execute any of those tasks. A first hunch may be that maybe the disk-state is sometimes different and thus we decide not to run maintenance. But git-maintenance(1) doesn't run with the "--auto" switch, so we should execute those tasks regardless of the on-disk state. But there's a second condition that may cause us to not execute tasks, namely when the "maintenance.lock" file exists due to a concurrently running git-maintenance(1) process. We usually disable auto-maintenance from detaching in our test suite to avoid exactly these kinds of race conditions by exporting `GIT_TEST_MAINT_AUTO_DETACH=false`. But in t7900 we unset "GIT_TEST_MAINT_AUTO_DETACH" and thus enable the auto-detach logic. The intent of this is to exercise git-maintenance(1) closer to how it would run in a real-world scenario, but it does cause us to race when the detached maintenance job that was triggered by `test_commit()` lives long enough. We could trivially fix this race by disabling auto-maintenance for this specific test. But that doesn't fix this class of races in this test suite: while I haven't seen any of the other tests fail in the same way, a bunch of them have this race, as well. Instead, let's retain "GIT_TEST_MAINT_AUTO_DETACH" and only unset it as required. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/t7900-maintenance.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 8b5614cf5910a6..d228a5e1822643 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -7,9 +7,6 @@ test_description='git maintenance builtin' GIT_TEST_COMMIT_GRAPH=0 GIT_TEST_MULTI_PACK_INDEX=0 -# Ensure that auto-maintenance detaches as usual. -sane_unset GIT_TEST_MAINT_AUTO_DETACH - test_lazy_prereq XMLLINT ' xmllint --version ' @@ -65,6 +62,7 @@ test_expect_success 'maintenance.auto config option' ' git init repo && ( cd repo && + sane_unset GIT_TEST_MAINT_AUTO_DETACH && GIT_TRACE2_EVENT="$(pwd)/default" git commit --quiet --allow-empty -m 1 && test_subcommand git maintenance run --auto --quiet --detach Date: Wed, 12 Aug 2026 08:03:09 +0000 Subject: [PATCH 06/32] http: die on curl_easy_duphandle failure in get_active_slot get_active_slot() duplicates the default curl handle via curl_easy_duphandle() to create a per-slot session handle. The return value is stored directly in slot->curl without checking for NULL. curl_easy_duphandle() can return NULL when memory allocation fails internally, and the libcurl documentation explicitly states this possibility. When this happens, slot->curl is NULL and the very next operation (curl_easy_setopt on line 1632 for CURLOPT_COOKIEFILE) passes NULL as the curl handle, which is undefined behavior in libcurl and typically crashes. Every HTTP operation in git goes through get_active_slot(), so this affects all remote-https, remote-http, and HTTP-based operations (clone, fetch, push over HTTP, bundle-uri downloads). Add a NULL check and die() with a clear message. There is no reasonable recovery from a failed handle duplication: the process is out of memory and cannot perform any HTTP operation. Pointed out by Coverity. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- http.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/http.c b/http.c index b4e7b8d00b3cdc..8f1d6d1f56eb38 100644 --- a/http.c +++ b/http.c @@ -1608,6 +1608,8 @@ struct active_request_slot *get_active_slot(void) if (!slot->curl) { slot->curl = curl_easy_duphandle(curl_default); + if (!slot->curl) + die("curl_easy_duphandle failed"); curl_session_count++; } From 633ac346eef2bd7f8b6e699f0298e87c2b8ed106 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 12 Aug 2026 08:03:10 +0000 Subject: [PATCH 07/32] config: propagate launch_editor() failure in show_editor() show_editor() calls launch_editor() to open the user's editor on the configuration file, but discards the return value and unconditionally returns 0 (success). When the editor fails to launch (e.g., $EDITOR is not found, or the editor exits with a nonzero status), the caller receives no indication that anything went wrong. This affects "git config edit" and "git config --edit": the command silently succeeds even when the editor could not be started. In contrast, other editor-launching paths in git (such as "git commit" and "git rebase --edit-todo") properly propagate editor failures and exit with an error. Check the return value and propagate the failure by returning -1. The two callers (cmd_config_edit at line 1315 and the legacy cmd_config at line 1478) both propagate this return to handle_builtin, which translates negative returns into an error exit. Pointed out by Coverity. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/config.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/builtin/config.c b/builtin/config.c index 8d8ec0beead220..1307fdb0d61afa 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -1313,7 +1313,10 @@ static int show_editor(struct config_location_options *opts) else if (errno != EEXIST) die_errno(_("cannot create configuration file %s"), config_file); } - launch_editor(config_file, NULL, NULL); + if (launch_editor(config_file, NULL, NULL)) { + free(config_file); + return -1; + } free(config_file); return 0; From 47568fee949526145bc2a87cd253d8df48b61efc Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 12 Aug 2026 08:03:11 +0000 Subject: [PATCH 08/32] reftable: handle block-writer initialization errors 2d5dbb37b284 (reftable/block: handle allocation failures, 2024-10-02) taught `writer_reinit_block_writer()` to report initialization failures and updated its callers, but `reftable_writer_new()` continued to ignore the return value. Consequently, the constructor could report success after block-writer initialization had failed. Propagate the error and release the constructor's allocations instead of returning an unusable writer. Pointed out by GPT-5.6 Sol and Claude Opus 4.8. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- reftable/writer.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/reftable/writer.c b/reftable/writer.c index d969a6a0210e8b..073b9bbd8924ef 100644 --- a/reftable/writer.c +++ b/reftable/writer.c @@ -150,6 +150,7 @@ int reftable_writer_new(struct reftable_writer **out, { struct reftable_write_options opts = {0}; struct reftable_writer *wp; + int err; if (_opts) opts = *_opts; @@ -177,7 +178,12 @@ int reftable_writer_new(struct reftable_writer **out, wp->opts = opts; wp->hash_id = hash_id; wp->flush = flush_func; - writer_reinit_block_writer(wp, REFTABLE_BLOCK_TYPE_REF); + err = writer_reinit_block_writer(wp, REFTABLE_BLOCK_TYPE_REF); + if (err < 0) { + reftable_free(wp->block); + reftable_free(wp); + return err; + } *out = wp; From f8121b74798bd52ea6af99f70cb3bace2b44e953 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 12 Aug 2026 08:03:12 +0000 Subject: [PATCH 09/32] reftable/block: check deflateInit() return value block_writer_init() allocates a z_stream and calls deflateInit() to prepare it for compressing log records. The return value of deflateInit() is silently discarded. If zlib initialization fails (e.g., Z_MEM_ERROR when the system is under memory pressure), the z_stream is left in an undefined state. Subsequent deflate() calls in block_writer_finish() then operate on this uninitialized stream. Current zlib/zlib-ng versions handle such a stream gracefully, by returning `Z_STREAM_ERROR`, so in practice it would likely not result in catastrophic error. The function already uses REFTABLE_ZLIB_ERROR for deflate() failures later in the code path, so returning the same error code for deflateInit() failure is consistent. Pointed out by Coverity. Assisted-by: Claude Opus 4.6 Helped-by: Junio C Hamano Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- reftable/block.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/reftable/block.c b/reftable/block.c index 920b3f448674f1..c12fedc5a231ae 100644 --- a/reftable/block.c +++ b/reftable/block.c @@ -87,7 +87,10 @@ int block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *block, REFTABLE_CALLOC_ARRAY(bw->zstream, 1); if (!bw->zstream) return REFTABLE_OUT_OF_MEMORY_ERROR; - deflateInit(bw->zstream, 9); + if (deflateInit(bw->zstream, 9) != Z_OK) { + REFTABLE_FREE_AND_NULL(bw->zstream); + return REFTABLE_ZLIB_ERROR; + } } return 0; From e3ddc2e5295d41987fdf59b9f2d9194d15a3f34e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 12 Aug 2026 08:03:13 +0000 Subject: [PATCH 10/32] reftable tests: check reftable_table_init_ref_iterator() return test_reftable_table__seek_once() and test_reftable_table__reseek() both call reftable_table_init_ref_iterator() without checking its return value. This function returns an int error code (0 on success, negative on failure). Every other reftable function call in these same tests checks the return via cl_assert_equal_i() or cl_assert(), making this omission inconsistent. If the iterator initialization ever fails (e.g., due to a memory allocation failure in the reftable internals), the test would proceed to seek and read with an uninitialized iterator, producing misleading test results or crashes rather than a clear assertion failure. Check the return value via cl_assert_equal_i(ret, 0), consistent with the surrounding code. Pointed out by Coverity. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/unit-tests/u-reftable-table.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/t/unit-tests/u-reftable-table.c b/t/unit-tests/u-reftable-table.c index fae478ee044c48..6f444f8cf94fb0 100644 --- a/t/unit-tests/u-reftable-table.c +++ b/t/unit-tests/u-reftable-table.c @@ -29,7 +29,8 @@ void test_reftable_table__seek_once(void) ret = reftable_table_new(&table, &source, "name"); cl_assert(!ret); - reftable_table_init_ref_iterator(table, &it); + ret = reftable_table_init_ref_iterator(table, &it); + cl_assert_equal_i(ret, 0); ret = reftable_iterator_seek_ref(&it, ""); cl_assert(!ret); ret = reftable_iterator_next_ref(&it, &ref); @@ -71,7 +72,8 @@ void test_reftable_table__reseek(void) ret = reftable_table_new(&table, &source, "name"); cl_assert(!ret); - reftable_table_init_ref_iterator(table, &it); + ret = reftable_table_init_ref_iterator(table, &it); + cl_assert_equal_i(ret, 0); for (size_t i = 0; i < 5; i++) { ret = reftable_iterator_seek_ref(&it, ""); From ec428c66462bcd33766729180999a5c50d8ffc67 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 12 Aug 2026 08:03:14 +0000 Subject: [PATCH 11/32] last-modified: handle repo_parse_commit() failures last_modified_run() and process_parent() call repo_parse_commit() without checking the return value at three sites. When a commit object is corrupt or unavailable (e.g., a shallow clone boundary or a missing object in a partial clone), the parse fails and the commit's internal fields (parents, tree, date) are not populated. The consequences depend on which call site fails: At line 417 (the main walk loop), c->parents stays NULL after a failed parse. The parent-walking loop at line 440 simply does not execute, silently treating the unparsable commit as a root commit. This produces incorrect "last modified" results: paths changed in ancestors beyond the corrupt commit are attributed to the wrong commit or not reported at all. At line 423 (the --not exclusion walk), n->parents stays NULL, causing the exclusion walk to stop prematurely. Commits that should be excluded from the output may be incorrectly included. At line 293 (process_parent), the parent's tree and parents are unavailable, so diff operations against it produce wrong results and the parent's own ancestors are never enqueued for walking. Skip unparsable commits by checking the return value and continuing to the next iteration (or returning early in process_parent). This matches the defensive pattern used in other revision walkers such as limit_list() and get_revision_internal(). Pointed out by Coverity. Assisted-by: Claude Opus 4.6 Helped-by: Junio C Hamano Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/last-modified.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/builtin/last-modified.c b/builtin/last-modified.c index 5478182f2e95c2..3846244dfc0cc1 100644 --- a/builtin/last-modified.c +++ b/builtin/last-modified.c @@ -290,7 +290,8 @@ static void process_parent(struct last_modified *lm, { struct bitmap *active_p; - repo_parse_commit(lm->rev.repo, parent); + if (repo_parse_commit(lm->rev.repo, parent)) + return; active_p = active_paths_for(lm, parent); /* @@ -414,12 +415,14 @@ static int last_modified_run(struct last_modified *lm) * Otherwise, make sure that 'c' isn't reachable from anything * in the '--not' queue. */ - repo_parse_commit(lm->rev.repo, c); + if (repo_parse_commit(lm->rev.repo, c)) + goto cleanup; while ((n = prio_queue_get(¬_queue))) { struct commit_list *np; - repo_parse_commit(lm->rev.repo, n); + if (repo_parse_commit(lm->rev.repo, n)) + continue; for (np = n->parents; np; np = np->next) { if (!(np->item->object.flags & PARENT2)) { From 02b9662a6ed42946c32ca3e5b2aead336348748f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 12 Aug 2026 08:03:15 +0000 Subject: [PATCH 12/32] compat/pread: check initial lseek for errors git_pread() saves the current file offset via lseek(fd, 0, SEEK_CUR) and later restores it. If the initial lseek fails (e.g., the fd is a pipe or otherwise non-seekable), current_offset is -1. This negative value is later passed to lseek(fd, -1, SEEK_SET) at line 16, which sets the file position to an unintended location (or fails with EINVAL on some platforms). Check the initial lseek return value and return -1 immediately if it fails, consistent with the error handling for the other lseek calls in the same function. Pointed out by Coverity. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- compat/pread.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compat/pread.c b/compat/pread.c index 484e6d4c716ef6..ac7d058cb895db 100644 --- a/compat/pread.c +++ b/compat/pread.c @@ -7,6 +7,8 @@ ssize_t git_pread(int fd, void *buf, size_t count, off_t offset) ssize_t rc; current_offset = lseek(fd, 0, SEEK_CUR); + if (current_offset < 0) + return -1; if (lseek(fd, offset, SEEK_SET) < 0) return -1; From af6250659514706f81266eb3a912f8fa87cff5ca Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 12 Aug 2026 08:03:16 +0000 Subject: [PATCH 13/32] transport-helper: check dup() return in get_exporter get_exporter() duplicates helper->in via dup() and stores the result in fastexport->out. If dup() fails (fd exhaustion), it returns -1. The child_process machinery interprets out = -1 as "create a pipe for stdout", which would silently change the fast-export process's output wiring: instead of sending data back through the helper's input fd, it would write to a new pipe that nobody reads from. Check the return value and report the error before proceeding. Pointed out by Coverity. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- transport-helper.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/transport-helper.c b/transport-helper.c index 80f90eb7bace6f..31883b244ec407 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -487,6 +487,8 @@ static int get_exporter(struct transport *transport, /* we need to duplicate helper->in because we want to use it after * fastexport is done with it. */ fastexport->out = dup(helper->in); + if (fastexport->out < 0) + return error_errno(_("could not dup helper output fd")); strvec_push(&fastexport->args, "fast-export"); strvec_push(&fastexport->args, "--use-done-feature"); strvec_push(&fastexport->args, data->signed_tags ? From 78ef560657742d51da5b4adb09a72693214ebf59 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 12 Aug 2026 08:03:17 +0000 Subject: [PATCH 14/32] transport-helper: warn when export-marks file cannot be finalized When push_refs_with_export() finalizes a successful push, it writes the fast-export marks file to a .tmp sibling and rename()s it into place. The return value of rename() is currently ignored. If the rename fails (permission denied, full disk, or an antivirus product locking the destination on Windows), the .tmp file is left behind and the existing export_marks file remains stale; the next fast-export operation that resumes from it then silently operates on inconsistent bookkeeping. The push itself succeeded by that point, so promoting this to a fatal error would be inappropriate. Emit warning_errno() naming both paths so the user can recover manually, and keep returning 0. Flagged by Coverity as CID 1427723 ("Unchecked return value"). Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- transport-helper.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/transport-helper.c b/transport-helper.c index 31883b244ec407..ed0543f1ad84c4 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -1184,7 +1184,9 @@ static int push_refs_with_export(struct transport *transport, if (data->export_marks) { strbuf_addf(&buf, "%s.tmp", data->export_marks); - rename(buf.buf, data->export_marks); + if (rename(buf.buf, data->export_marks)) + warning_errno(_("could not rename '%s' to '%s'"), + buf.buf, data->export_marks); strbuf_release(&buf); } From 2f93092642c9c38d4cc4597d24be75a05a97011f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 12 Aug 2026 08:03:18 +0000 Subject: [PATCH 15/32] bisect: check strbuf_getline_lf return when reading terms get_terms() in builtin/bisect.c and read_bisect_terms() in bisect.c both read the BISECT_TERMS file but do not check the strbuf_getline_lf() return values. If the file is truncated (e.g., a partial write from a crash or disk-full condition), strbuf_getline_lf returns EOF and the strbuf remains empty. strbuf_detach then returns an empty string, and the term names silently become "" instead of the expected "bad"/"good" or custom terms. In get_terms(), check for EOF and return -1 on truncation, matching the existing -1 return for a missing file. In read_bisect_terms(), die with a descriptive message when a line cannot be read, consistent with the die_errno for a non-ENOENT open failure in the same function. Unlike get_terms(), read_bisect_terms() returns void and uses die() for all error paths, so the die is the appropriate error handling here. Pointed out by Coverity. Assisted-by: Claude Opus 4.6 Helped-by: Junio C Hamano Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- bisect.c | 6 ++++-- builtin/bisect.c | 11 +++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/bisect.c b/bisect.c index 94c7028d2a746a..c2ef5da462f161 100644 --- a/bisect.c +++ b/bisect.c @@ -1019,10 +1019,12 @@ void read_bisect_terms(char **read_bad, char **read_good) die_errno(_("could not read file '%s'"), filename); } } else { - strbuf_getline_lf(&str, fp); + if (strbuf_getline_lf(&str, fp) == EOF) + die(_("could not read bad term from file '%s'"), filename); free(*read_bad); *read_bad = strbuf_detach(&str, NULL); - strbuf_getline_lf(&str, fp); + if (strbuf_getline_lf(&str, fp) == EOF) + die(_("could not read good term from file '%s'"), filename); free(*read_good); *read_good = strbuf_detach(&str, NULL); } diff --git a/builtin/bisect.c b/builtin/bisect.c index 798e28f5012d31..69ab7ea24849bd 100644 --- a/builtin/bisect.c +++ b/builtin/bisect.c @@ -498,9 +498,16 @@ static int get_terms(struct bisect_terms *terms) } free_terms(terms); - strbuf_getline_lf(&str, fp); + if (strbuf_getline_lf(&str, fp) == EOF) { + res = -1; + goto finish; + } terms->term_bad = strbuf_detach(&str, NULL); - strbuf_getline_lf(&str, fp); + if (strbuf_getline_lf(&str, fp) == EOF) { + res = -1; + FREE_AND_NULL(terms->term_bad); + goto finish; + } terms->term_good = strbuf_detach(&str, NULL); finish: From 211ba0c0c8e4c4e1e32ccfcd3ef70781ca12a1f3 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 12 Aug 2026 08:03:19 +0000 Subject: [PATCH 16/32] bisect: check get_terms return at all call sites Six callers of get_terms() silently discard its return value. When get_terms fails (missing or truncated BISECT_TERMS file), the term strings remain NULL or empty, causing confusing downstream behavior: commands like "bisect next" or "bisect run" proceed with empty term strings, producing nonsensical ref names (refs/bisect/ with no suffix) and misleading error messages. Let's not discard the return value, but handle an error with the same message `bisect_terms()` already uses when reading the terms failed. Pointed out by Coverity. There is one slight complication here: One caller _needs_ the return value to indicate an error when the `BISECT_TERMS` file is absent, all the other call sites are totally okay with a "missing" `BISECT_TERMS` file. To address that, extend the function signature of `get_terms()` to indicate which behavior the caller wants. Assisted-by: Claude Opus 4.6 Helped-by: Patrick Steinhardt Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/bisect.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/builtin/bisect.c b/builtin/bisect.c index 69ab7ea24849bd..ceb60b06261748 100644 --- a/builtin/bisect.c +++ b/builtin/bisect.c @@ -485,7 +485,7 @@ static int bisect_next_check(const struct bisect_terms *terms, return decide_next(terms, current_term, !state.nr_good, !state.nr_bad); } -static int get_terms(struct bisect_terms *terms) +static int get_terms(struct bisect_terms *terms, int file_missing_is_ok) { struct strbuf str = STRBUF_INIT; FILE *fp = NULL; @@ -493,7 +493,7 @@ static int get_terms(struct bisect_terms *terms) fp = fopen(git_path_bisect_terms(), "r"); if (!fp) { - res = -1; + res = file_missing_is_ok ? 0 : -1; goto finish; } @@ -519,7 +519,7 @@ static int get_terms(struct bisect_terms *terms) static int bisect_terms(struct bisect_terms *terms, const char *option) { - if (get_terms(terms)) + if (get_terms(terms, 0)) return error(_("no terms defined")); if (!option) { @@ -1057,7 +1057,8 @@ static int process_replay_line(struct bisect_terms *terms, struct strbuf *line) rev = word_end + strspn(word_end, " \t"); *word_end = '\0'; /* NUL-terminate the word */ - get_terms(terms); + if (get_terms(terms, 1)) + return error(_("no terms defined")); if (check_and_set_terms(terms, p)) return -1; @@ -1383,7 +1384,8 @@ static int cmd_bisect__next(int argc, const char **argv UNUSED, const char *pref if (argc) return error(_("'%s' requires 0 arguments"), "git bisect next"); - get_terms(&terms); + if (get_terms(&terms, 1)) + return error(_("no terms defined")); res = bisect_next(&terms, prefix); free_terms(&terms); return res; @@ -1417,7 +1419,8 @@ static int cmd_bisect__skip(int argc, const char **argv, const char *prefix UNUS struct bisect_terms terms = { 0 }; set_terms(&terms, "bad", "good"); - get_terms(&terms); + if (get_terms(&terms, 1)) + return error(_("no terms defined")); res = bisect_skip(&terms, argc, argv); free_terms(&terms); return res; @@ -1429,7 +1432,8 @@ static int cmd_bisect__visualize(int argc, const char **argv, const char *prefix int res; struct bisect_terms terms = { 0 }; - get_terms(&terms); + if (get_terms(&terms, 1)) + return error(_("no terms defined")); res = bisect_visualize(&terms, argc, argv); free_terms(&terms); return res; @@ -1443,7 +1447,8 @@ static int cmd_bisect__run(int argc, const char **argv, const char *prefix UNUSE if (!argc) return error(_("'%s' failed: no command provided."), "git bisect run"); - get_terms(&terms); + if (get_terms(&terms, 1)) + return error(_("no terms defined")); res = bisect_run(&terms, argc, argv); free_terms(&terms); return res; @@ -1482,7 +1487,8 @@ int cmd_bisect(int argc, usage_with_options(git_bisect_usage, options); set_terms(&terms, "bad", "good"); - get_terms(&terms); + if (get_terms(&terms, 1)) + return error(_("no terms defined")); if (check_and_set_terms(&terms, argv[0]) || !one_of(argv[0], terms.term_good, terms.term_bad, NULL)) usage_msg_optf(_("unknown command: '%s'"), git_bisect_usage, From 5f87f65af63a37c16d0a2c46525e92c678ef9951 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 12 Aug 2026 08:03:20 +0000 Subject: [PATCH 17/32] bisect: handle dup() failure when redirecting stdout To capture the output of each verdict command, bisect_run() temporarily redirects stdout to a temporary file via the classic dup(1) / dup2() pair, restoring it afterwards. The return value of dup(1) is not checked, however. When it fails, the saved descriptor is -1, which is then passed to close() (the issue Coverity flags), and the matching dup2() that is meant to restore stdout also fails, leaving the process with stdout still pointing at the temporary file for the remainder of the run. Treat a failed dup(1) or dup2(..., 1) as a fatal error for this bisect step: close the temporary file descriptor, report the error via error_errno(), and break out of the loop so the existing cleanup path handles the rest, just as on other failure paths in this function. Reported by Coverity as CID 1508242 ("Improper use of negative value"). Assisted-by: Opus 4.7 Helped-by: Patrick Steinhardt Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/bisect.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/builtin/bisect.c b/builtin/bisect.c index ceb60b06261748..be42468af60c85 100644 --- a/builtin/bisect.c +++ b/builtin/bisect.c @@ -1308,7 +1308,14 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv) fflush(stdout); saved_stdout = dup(1); - dup2(temporary_stdout_fd, 1); + if (saved_stdout < 0 || + dup2(temporary_stdout_fd, 1) < 0) { + res = error_errno(_("could not duplicate stdout")); + if (saved_stdout >= 0) + close(saved_stdout); + close(temporary_stdout_fd); + break; + } res = bisect_state(terms, 1, &new_state); From 073d4d64609490d4bd4f6dd1a36c597d8b15f91b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:39 +0000 Subject: [PATCH 18/32] diff-delta: widen `struct delta_index`' size fields to `size_t` Preparation for widening the delta-encoding API to `size_t` in subsequent commits, which is what lets pack-objects drop the `cast_size_t_to_ulong()` shims that 606c192380 (odb, packfile: use size_t for streaming object sizes, 2026-05-08) had to leave behind in `get_delta()` and `try_delta()` because their downstream consumers were still narrow. The struct is private to diff-delta.c, so widening its fields in isolation is a no-op at runtime: the values stored continue to fit in 32 bits on Windows because the public API around it still truncates. Splitting it out keeps the API-change commit focused on caller updates. Since the `memsize` attribute is returned by the `sizeof_delta_index()` function verbatim, that function's return type is adjusted, too. Assisted-by: Opus 4.7 Helped-by: Patrick Steinhardt Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- delta.h | 2 +- diff-delta.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/delta.h b/delta.h index eb5c6d2fdb9c51..ab0279168cf32b 100644 --- a/delta.h +++ b/delta.h @@ -28,7 +28,7 @@ void free_delta_index(struct delta_index *index); * * Given pointer must be what create_delta_index() returned, or NULL. */ -unsigned long sizeof_delta_index(struct delta_index *index); +size_t sizeof_delta_index(struct delta_index *index); /* * create_delta: create a delta from given index for the given buffer diff --git a/diff-delta.c b/diff-delta.c index 43c339f01061ca..9e1f9e6f9515e9 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -125,9 +125,9 @@ struct unpacked_index_entry { }; struct delta_index { - unsigned long memsize; + size_t memsize; const void *src_buf; - unsigned long src_size; + size_t src_size; unsigned int hash_mask; struct index_entry *hash[FLEX_ARRAY]; }; @@ -140,7 +140,7 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) struct unpacked_index_entry *entry, **hash; struct index_entry *packed_entry, **packed_hash; void *mem; - unsigned long memsize; + size_t memsize; if (!buf || !bufsize) return NULL; @@ -302,7 +302,7 @@ void free_delta_index(struct delta_index *index) free(index); } -unsigned long sizeof_delta_index(struct delta_index *index) +size_t sizeof_delta_index(struct delta_index *index) { if (index) return index->memsize; From 92b77c4a331cde83cb0d4bc59f7cba8820f55155 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:40 +0000 Subject: [PATCH 19/32] delta: widen `create_delta_index()` parameter to `size_t` The sole caller (`try_delta()` in builtin/pack-objects.c) passes an `unsigned long`, which promotes safely, so no caller fixups are needed. Splitting it out keeps the `diff_delta()`/`create_delta()` widening, which does ripple to several callers, in its own commit. Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- delta.h | 2 +- diff-delta.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/delta.h b/delta.h index ab0279168cf32b..12075c54c5a0ab 100644 --- a/delta.h +++ b/delta.h @@ -14,7 +14,7 @@ struct delta_index; * using free_delta_index(). */ struct delta_index * -create_delta_index(const void *buf, unsigned long bufsize); +create_delta_index(const void *buf, size_t bufsize); /* * free_delta_index: free the index created by create_delta_index() diff --git a/diff-delta.c b/diff-delta.c index 9e1f9e6f9515e9..bcc331af3e16ce 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -132,7 +132,7 @@ struct delta_index { struct index_entry *hash[FLEX_ARRAY]; }; -struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) +struct delta_index * create_delta_index(const void *buf, size_t bufsize) { unsigned int i, hsize, hmask, entries, prev_val, *hash_count; const unsigned char *data, *buffer = buf; From cfd8970fa78f40e9e3d470e8a1112e21c7c778b0 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:41 +0000 Subject: [PATCH 20/32] pack-objects: widen delta-cache accounting to `size_t` These three are a single accounting tuple (the globals tracking cumulative cached-delta bytes, plus the helper that compares them against an incoming delta size) and are latently 32-bit on Windows where `unsigned long` != `size_t`: a pack with many large cached deltas could wrap silently. The widening is internally consistent on its own: the additions and subtractions against delta_cache_size already come from `size_t` sources (`DELTA_SIZE()` returns `size_t`), and `delta_cacheable()`'s sole caller in `try_delta()` still passes `unsigned long`, which promotes. Prerequisite for dropping `try_delta()`'s `cast_size_t_to_ulong()` shims, which becomes possible once 1create_delta()` and `diff_delta()` are widened in a later commit. Note: since `max_delta_cache_size` changes data type to `size_t`, a pair of new helpers is introduced to parse config values of that type, too. Assisted-by: Opus 4.7 Helped-by: Patrick Steinhardt Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/pack-objects.c | 10 +++++----- config.c | 9 +++++++++ config.h | 3 +++ parse.c | 9 +++++++++ parse.h | 1 + 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 27048bbb4dd3c9..ee3eeee7abf92d 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -260,8 +260,8 @@ static int exclude_promisor_objects_best_effort; static int use_delta_islands; -static unsigned long delta_cache_size = 0; -static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE; +static size_t delta_cache_size = 0; +static size_t max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE; static unsigned long cache_max_small_delta_size = 1000; static unsigned long window_memory_limit = 0; @@ -2687,8 +2687,8 @@ struct unpacked { unsigned depth; }; -static int delta_cacheable(unsigned long src_size, unsigned long trg_size, - unsigned long delta_size) +static int delta_cacheable(size_t src_size, size_t trg_size, + size_t delta_size) { if (max_delta_cache_size && delta_cache_size + delta_size > max_delta_cache_size) return 0; @@ -3684,7 +3684,7 @@ static int git_pack_config(const char *k, const char *v, return 0; } if (!strcmp(k, "pack.deltacachesize")) { - max_delta_cache_size = git_config_int(k, v, ctx->kvi); + max_delta_cache_size = git_config_size_t(k, v, ctx->kvi); return 0; } if (!strcmp(k, "pack.deltacachelimit")) { diff --git a/config.c b/config.c index 6a0de86e3ae958..010a58d3076cdb 100644 --- a/config.c +++ b/config.c @@ -1268,6 +1268,15 @@ ssize_t git_config_ssize_t(const char *name, const char *value, return ret; } +size_t git_config_size_t(const char *name, const char *value, + const struct key_value_info *kvi) +{ + size_t ret; + if (!git_parse_size_t(value, &ret)) + die_bad_number(name, value, kvi); + return ret; +} + double git_config_double(const char *name, const char *value, const struct key_value_info *kvi) { diff --git a/config.h b/config.h index 31fe3e29611e11..b66dd08007c97a 100644 --- a/config.h +++ b/config.h @@ -282,6 +282,9 @@ unsigned long git_config_ulong(const char *, const char *, ssize_t git_config_ssize_t(const char *, const char *, const struct key_value_info *); +size_t git_config_size_t(const char *, const char *, + const struct key_value_info *); + /** * Identically to `git_config_double`, but for double-precision floating point * values. diff --git a/parse.c b/parse.c index d77f28046a0916..266bbd539be8dc 100644 --- a/parse.c +++ b/parse.c @@ -134,6 +134,15 @@ int git_parse_ssize_t(const char *value, ssize_t *ret) return 1; } +int git_parse_size_t(const char *value, size_t *ret) +{ + uintmax_t tmp; + if (!git_parse_unsigned(value, &tmp, maximum_signed_value_of_type(size_t))) + return 0; + *ret = tmp; + return 1; +} + int git_parse_double(const char *value, double *ret) { char *end; diff --git a/parse.h b/parse.h index a6dd37c4cba273..db742f35fb0e8e 100644 --- a/parse.h +++ b/parse.h @@ -4,6 +4,7 @@ int git_parse_signed(const char *value, intmax_t *ret, intmax_t max); int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max); int git_parse_ssize_t(const char *, ssize_t *); +int git_parse_size_t(const char *, size_t *); int git_parse_ulong(const char *, unsigned long *); int git_parse_uint(const char *value, unsigned int *ret); int git_parse_int(const char *value, int *ret); From e0af3c839e0a290b0f234626aca6cbba716a2158 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:42 +0000 Subject: [PATCH 21/32] pack-objects: widen `free_unpacked()` return to `size_t` `free_unpacked()` sums two byte counts: `sizeof_delta_index()` and `SIZE(n->entry)`. The latter has been `size_t` since the prior topic "More work supporting objects larger than 4GB on Windows" widened `SIZE()`/`oe_size()` to `size_t`, so accumulating it into an `unsigned long` return was a silent Windows-only truncation on a packing run with many large objects. The sole caller, `find_deltas()`, still holds its own `mem_usage` in an `unsigned long` for now, and therefore still truncates silently. Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/pack-objects.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index ee3eeee7abf92d..a31151ee2aaf0a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2955,9 +2955,9 @@ static unsigned int check_delta_limit(struct object_entry *me, unsigned int n) return m; } -static unsigned long free_unpacked(struct unpacked *n) +static size_t free_unpacked(struct unpacked *n) { - unsigned long freed_mem = sizeof_delta_index(n->index); + size_t freed_mem = sizeof_delta_index(n->index); free_delta_index(n->index); n->index = NULL; if (n->data) { From 58f35eea9bc553ed96bc916a26c414c843ba9d0c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:43 +0000 Subject: [PATCH 22/32] pack-objects: widen `mem_usage` and `try_delta()`'s out-param to `size_t` The pair must move together because `find_deltas()` passes `&mem_usage` to `try_delta()`: widening either alone breaks the type match. `mem_usage` accumulates per-object byte counts already computed in `size_t` (`SIZE()` and `sizeof_delta_index()` reach here through `free_unpacked()`, now `size_t`), and was the last 32-bit-on-Windows narrowing point in the delta-window memory accounting chain. With this commit, that chain uses `size_t` consistently except for `sizeof_delta_index()`'s still-narrow return, whose value is bounded by `create_delta_index()`'s entries cap. `window_memory_limit` (config-driven via `git_config_ulong()`) stays `unsigned long`: it is only compared against `mem_usage` and promotes. Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/pack-objects.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index a31151ee2aaf0a..0bc259534134c4 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2787,7 +2787,7 @@ size_t oe_get_size_slow(struct packing_data *pack, } static int try_delta(struct unpacked *trg, struct unpacked *src, - unsigned max_depth, unsigned long *mem_usage) + unsigned max_depth, size_t *mem_usage) { struct object_entry *trg_entry = trg->entry; struct object_entry *src_entry = src->entry; @@ -2974,7 +2974,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, { uint32_t i, idx = 0, count = 0; struct unpacked *array; - unsigned long mem_usage = 0; + size_t mem_usage = 0; CALLOC_ARRAY(array, window); From 9efb6d57b7db529b88e57d13c2693a5ef97ea794 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:44 +0000 Subject: [PATCH 23/32] delta: widen `create_delta()` and `diff_delta()` to `size_t` Last stop in the delta-encoding API widening for >4 GiB blobs on Windows: with `create_delta_index()` done in the prior commit and `create_delta()`/`diff_delta()` finished here, every byte count that crosses delta.h is now `size_t`. The struct fields they store into have been `size_t` since the diff-delta struct widening. The API change must move with all callers in the same commit (the build only passes when every `&delta_size` matches the new `size_t*`). Caller updates are kept minimal: * builtin/pack-objects.c `get_delta()` and `try_delta()`: widen only the local `delta_size` variable; the surrounding unsigned-long locals and their `cast_size_t_to_ulong()` shims are out of scope here and will be cleaned up in their own commits. * builtin/fast-import.c, diff.c, t/helper/test-pack-deltas.c: keep the local unsigned-long delta size (each feeds a still- unsigned-long downstream consumer: zlib's `avail_in`, `deflate_it()`, the test helper's own `do_compress()`), and bridge via a temporary `size_t` plus `cast_size_t_to_ulong()`. The new casts are paid back in later topics that widen those consumers. * t/helper/test-delta.c: widen the local outright (no downstream consumer beyond the test's own `out_size`, which is already `size_t`). Note that GCC struggles a bit to figure out that `deltalen` is always initialized before it is used; To help it along, we initialize it to 0. This work-around will go away in a later patch series when `deltalen` can be widened to `size_t`. Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/fast-import.c | 6 ++++-- builtin/pack-objects.c | 6 ++++-- delta.h | 10 +++++----- diff-delta.c | 4 ++-- diff.c | 4 +++- t/helper/test-delta.c | 2 +- t/helper/test-pack-deltas.c | 5 +++-- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/builtin/fast-import.c b/builtin/fast-import.c index aa656c5195d366..1c6e5366c2ce06 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -962,7 +962,7 @@ static int store_object( struct object_entry *e; unsigned char hdr[96]; struct object_id oid; - unsigned long hdrlen, deltalen; + unsigned long hdrlen, deltalen = 0; struct git_hash_ctx c; git_zstream s; struct repo_config_values *cfg = repo_config_values(the_repository); @@ -998,11 +998,13 @@ static int store_object( if (last && last->data.len && last->data.buf && last->depth < max_depth && dat->len > the_hash_algo->rawsz) { + size_t deltalen_st; delta_count_attempts_by_type[type]++; delta = diff_delta(last->data.buf, last->data.len, dat->buf, dat->len, - &deltalen, dat->len - the_hash_algo->rawsz); + &deltalen_st, dat->len - the_hash_algo->rawsz); + deltalen = cast_size_t_to_ulong(deltalen_st); } else delta = NULL; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 0bc259534134c4..b1b645e069a8e7 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -353,7 +353,8 @@ static void index_commit_for_bitmap(struct commit *commit) static void *get_delta(struct object_entry *entry) { - unsigned long size, base_size, delta_size; + unsigned long size, base_size; + size_t delta_size; void *buf, *base_buf, *delta_buf; enum object_type type; size_t size_st = 0, base_size_st = 0; @@ -2791,7 +2792,8 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, { struct object_entry *trg_entry = trg->entry; struct object_entry *src_entry = src->entry; - unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz; + unsigned long trg_size, src_size, sizediff, max_size, sz; + size_t delta_size; unsigned ref_depth; enum object_type type; void *delta_buf; diff --git a/delta.h b/delta.h index 12075c54c5a0ab..42a211905dd2b7 100644 --- a/delta.h +++ b/delta.h @@ -42,8 +42,8 @@ size_t sizeof_delta_index(struct delta_index *index); */ void * create_delta(const struct delta_index *index, - const void *buf, unsigned long bufsize, - unsigned long *delta_size, unsigned long max_delta_size); + const void *buf, size_t bufsize, + size_t *delta_size, size_t max_delta_size); /* * diff_delta: create a delta from source buffer to target buffer @@ -54,9 +54,9 @@ create_delta(const struct delta_index *index, * updated with its size. The returned buffer must be freed by the caller. */ static inline void * -diff_delta(const void *src_buf, unsigned long src_bufsize, - const void *trg_buf, unsigned long trg_bufsize, - unsigned long *delta_size, unsigned long max_delta_size) +diff_delta(const void *src_buf, size_t src_bufsize, + const void *trg_buf, size_t trg_bufsize, + size_t *delta_size, size_t max_delta_size) { struct delta_index *index = create_delta_index(src_buf, src_bufsize); if (index) { diff --git a/diff-delta.c b/diff-delta.c index bcc331af3e16ce..7cbedeb5071bcc 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -318,8 +318,8 @@ size_t sizeof_delta_index(struct delta_index *index) void * create_delta(const struct delta_index *index, - const void *trg_buf, unsigned long trg_size, - unsigned long *delta_size, unsigned long max_size) + const void *trg_buf, size_t trg_size, + size_t *delta_size, size_t max_size) { unsigned int i, val; off_t outpos, moff; diff --git a/diff.c b/diff.c index 2a9d0d86871139..69eb2f76a4e51c 100644 --- a/diff.c +++ b/diff.c @@ -3647,9 +3647,11 @@ static void emit_binary_diff_body(struct diff_options *o, delta = NULL; deflated = deflate_it(two->ptr, two->size, &deflate_size); if (one->size && two->size) { + size_t delta_size_st = 0; delta = diff_delta(one->ptr, one->size, two->ptr, two->size, - &delta_size, deflate_size); + &delta_size_st, deflate_size); + delta_size = cast_size_t_to_ulong(delta_size_st); if (delta) { void *to_free = delta; orig_size = delta_size; diff --git a/t/helper/test-delta.c b/t/helper/test-delta.c index 8223a60229229e..d807afef751b48 100644 --- a/t/helper/test-delta.c +++ b/t/helper/test-delta.c @@ -32,7 +32,7 @@ int cmd__delta(int argc, const char **argv) die_errno("unable to read '%s'", argv[3]); if (argv[1][1] == 'd') { - unsigned long delta_size; + size_t delta_size; out_buf = diff_delta(from.buf, from.len, data.buf, data.len, &delta_size, 0); diff --git a/t/helper/test-pack-deltas.c b/t/helper/test-pack-deltas.c index 840797cf0dbabb..5e0f7268427003 100644 --- a/t/helper/test-pack-deltas.c +++ b/t/helper/test-pack-deltas.c @@ -49,7 +49,7 @@ static void write_ref_delta(struct hashfile *f, { unsigned char header[MAX_PACK_OBJECT_HEADER]; unsigned long delta_size, compressed_size, hdrlen; - size_t size, base_size; + size_t size, base_size, delta_size_st = 0; enum object_type type; void *base_buf, *delta_buf; void *buf = odb_read_object(the_repository->objects, @@ -65,7 +65,8 @@ static void write_ref_delta(struct hashfile *f, die("unable to read %s", oid_to_hex(base)); delta_buf = diff_delta(base_buf, base_size, - buf, size, &delta_size, 0); + buf, size, &delta_size_st, 0); + delta_size = cast_size_t_to_ulong(delta_size_st); compressed_size = do_compress(&delta_buf, delta_size); From 4211a2b892e5be96c8437ff8fc0a2688bba47f20 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:45 +0000 Subject: [PATCH 24/32] packfile, git-zlib: widen `use_pack()` and zstream avail fields to `size_t` Bundling the two widenings: four call sites pass `&stream.avail_in` directly to `use_pack()`, and widening either type fencepost alone would force a bridge variable at each. Doing both together is the simpler end state and is the prerequisite for the `do_compress()` widening in the next commit, which is what lets `write_no_reuse_object()` lose its last `cast_size_t_to_ulong()` shim. The unsigned-long locals widened at the other `use_pack()` callers (avail / remaining / left) hold pack-window sizes bounded by `core.packedGitWindowSize`, so the change is type consistency rather than a new >4GB capability. `git_zstream.avail_in`/`avail_out` likewise reach zlib's `uInt` fields only after `zlib_buf_cap()`'s 1 GiB cap, so the wrapper already accepted `size_t`-shaped inputs in practice. Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/pack-objects.c | 8 ++++---- git-zlib.c | 2 +- git-zlib.h | 4 ++-- pack-check.c | 4 ++-- packfile.c | 4 ++-- packfile.h | 3 ++- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index b1b645e069a8e7..7e35d0da4c1d5d 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -488,7 +488,7 @@ static void copy_pack_data(struct hashfile *f, off_t len) { unsigned char *in; - unsigned long avail; + size_t avail; while (len) { in = use_pack(p, w_curs, offset, &avail); @@ -2260,7 +2260,7 @@ static void check_object(struct object_entry *entry, uint32_t object_index) struct object_id base_ref; struct object_entry *base_entry; unsigned long used, used_0; - unsigned long avail; + size_t avail; off_t ofs; unsigned char *buf, c; enum object_type type; @@ -2756,8 +2756,8 @@ size_t oe_get_size_slow(struct packing_data *pack, struct pack_window *w_curs; unsigned char *buf; enum object_type type; - unsigned long used, avail; - size_t size; + unsigned long used; + size_t avail, size; if (e->type_ != OBJ_OFS_DELTA && e->type_ != OBJ_REF_DELTA) { size_t sz; diff --git a/git-zlib.c b/git-zlib.c index d21adb3bf5b15e..a3b32d9a8685aa 100644 --- a/git-zlib.c +++ b/git-zlib.c @@ -33,7 +33,7 @@ static const char *zerr_to_string(int status) /* uLong is 32-bit on Windows, even on 64-bit systems */ #define ULONG_MAX_VALUE maximum_unsigned_value_of_type(uLong) -static inline uInt zlib_buf_cap(unsigned long len) +static inline uInt zlib_buf_cap(size_t len) { return (ZLIB_BUF_MAX < len) ? ZLIB_BUF_MAX : len; } diff --git a/git-zlib.h b/git-zlib.h index 44380e8ad38305..0b24b15bd05f7f 100644 --- a/git-zlib.h +++ b/git-zlib.h @@ -5,8 +5,8 @@ typedef struct git_zstream { struct z_stream_s z; - unsigned long avail_in; - unsigned long avail_out; + size_t avail_in; + size_t avail_out; size_t total_in; size_t total_out; unsigned char *next_in; diff --git a/pack-check.c b/pack-check.c index 5adfb3f2726fb3..befb860472f418 100644 --- a/pack-check.c +++ b/pack-check.c @@ -34,7 +34,7 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, uint32_t data_crc = crc32(0, NULL, 0); do { - unsigned long avail; + size_t avail; void *data = use_pack(p, w_curs, offset, &avail); if (avail > len) avail = len; @@ -71,7 +71,7 @@ static int verify_packfile(struct repository *r, r->hash_algo->init_fn(&ctx); do { - unsigned long remaining; + size_t remaining; unsigned char *in = use_pack(p, w_curs, offset, &remaining); offset += remaining; if (!pack_sig_ofs) diff --git a/packfile.c b/packfile.c index 78c389e6f35e22..7fbe47ca18f86e 100644 --- a/packfile.c +++ b/packfile.c @@ -704,7 +704,7 @@ static int in_window(struct repository *r, struct pack_window *win, unsigned char *use_pack(struct packed_git *p, struct pack_window **w_cursor, off_t offset, - unsigned long *left) + size_t *left) { struct pack_window *win = *w_cursor; @@ -1228,7 +1228,7 @@ int unpack_object_header(struct packed_git *p, size_t *sizep) { unsigned char *base; - unsigned long left; + size_t left; unsigned long used; enum object_type type; diff --git a/packfile.h b/packfile.h index defb6f442cca09..820d247d054645 100644 --- a/packfile.h +++ b/packfile.h @@ -402,7 +402,8 @@ uint32_t get_pack_fanout(struct packed_git *p, uint32_t value); struct object_database; -unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *); +unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, + size_t *); void close_pack_windows(struct packed_git *); void close_pack(struct packed_git *); void unuse_pack(struct pack_window **); From 335f9960052634dcdf584eefac565affb49ef2ff Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:46 +0000 Subject: [PATCH 25/32] archive-zip: widen `zlib_deflate_raw()`'s maxsize local to `size_t` Prep for the upcoming `git_deflate_bound()` widening to `size_t`: the local that catches its return needs to be `size_t` too, otherwise the widening would introduce a silent Windows narrowing here. No semantic effect with the current unsigned-long-returning `git_deflate_bound()` (`size_t == unsigned long` on this caller's platforms today). Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- archive-zip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archive-zip.c b/archive-zip.c index 97ea8d60d6187b..a487d4c0413355 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -206,7 +206,7 @@ static void *zlib_deflate_raw(void *data, unsigned long size, unsigned long *compressed_size) { git_zstream stream; - unsigned long maxsize; + size_t maxsize; void *buffer; int result; From 1f324b91f7f943740843995455a5a6f049b5f293 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:47 +0000 Subject: [PATCH 26/32] diff: widen `deflate_it()`'s bound local from int to `size_t` Fixes a pre-existing silent narrowing from `git_deflate_bound()`'s `unsigned long` return into an `int` local: anything past 2 GiB has always wrapped negative here and then been re-extended to `size_t` inside `xmalloc()`. Also prep for the upcoming `git_deflate_bound()` widening to `size_t`, which would extend the narrowing further if `bound` stayed `int`. Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- diff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff.c b/diff.c index 69eb2f76a4e51c..c14f69719bd852 100644 --- a/diff.c +++ b/diff.c @@ -3609,7 +3609,7 @@ static unsigned char *deflate_it(char *data, unsigned long size, unsigned long *result_size) { - int bound; + size_t bound; unsigned char *deflated; git_zstream stream; struct repo_config_values *cfg = repo_config_values(the_repository); From 9cb9f418ecdfd136bd50f32cb4a417935cf563be Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:48 +0000 Subject: [PATCH 27/32] http-push: widen `start_put()`'s size local from `ssize_t` to `size_t` The local is initialised from `git_deflate_bound()` (an unsigned upper bound on the deflated output, never negative) and used in exactly three places: the initialising assignment, `strbuf_grow(buf, size)` whose parameter is already `size_t`, and `stream.avail_out` which became `size_t` in the prior commit. There is no comparison against zero or a negative value, no subtraction, no arithmetic that depends on signedness, and no path that would assign a signed quantity to it. The original `ssize_t` was the wrong type to begin with: a `git_deflate_bound()` result above `SSIZE_MAX` would have wrapped negative on assignment and then implicitly re-extended to a huge `size_t` at `strbuf_grow()`/`stream.avail_out`, requesting an absurd allocation. That is not a real-world concern for the object sizes http-push pushes today, but it is also the reason the type needs to move to `size_t` before `git_deflate_bound()` itself is widened. Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin 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 3c23cbba27a9ec..2a07d1425961fd 100644 --- a/http-push.c +++ b/http-push.c @@ -367,7 +367,7 @@ static void start_put(struct transfer_request *request) void *unpacked; size_t len; int hdrlen; - ssize_t size; + size_t size; git_zstream stream; struct repo_config_values *cfg = repo_config_values(the_repository); From aed4048938bde401a3d22207a4cce167385c8ba7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:49 +0000 Subject: [PATCH 28/32] t/helper/test-pack-deltas: widen `do_compress()`'s maxsize local to `size_t` Prep for the upcoming `git_deflate_bound()` widening to `size_t`. The local is only ever the return value of `git_deflate_bound()` and the `xmalloc()`/`stream.avail_out` sizes derived from it; widening it has no semantic effect today. Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/helper/test-pack-deltas.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/helper/test-pack-deltas.c b/t/helper/test-pack-deltas.c index 5e0f7268427003..959705fecaf144 100644 --- a/t/helper/test-pack-deltas.c +++ b/t/helper/test-pack-deltas.c @@ -22,7 +22,7 @@ static unsigned long do_compress(void **pptr, unsigned long size) { git_zstream stream; void *in, *out; - unsigned long maxsize; + size_t maxsize; git_deflate_init(&stream, 1); maxsize = git_deflate_bound(&stream, size); From b4b9a8cdbd218a145c89685467fda03c9c2df07c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:50 +0000 Subject: [PATCH 29/32] git-zlib: widen `git_deflate_bound()` to `size_t` All four `unsigned long`/`int`/`ssize_t` receivers across archive-zip, diff, http-push and t/helper/test-pack-deltas were widened to `size_t` in the prior commits, and remote-curl and fast-import were already there. With every caller prepared, both the parameter and the return type can now move without introducing any silent narrowing. For inputs above zlib's `uLong` range (i.e. >4 GiB on platforms where `uLong` is 32-bit, notably 64-bit Windows), defer to zlib's stored-block formula (the same fallback it would itself use, see https://github.com/madler/zlib/blob/v1.3.2/deflate.c#L832-L928 keeping in mind that for large sizes, the `storelen` would be relevant, also compare with https://github.com/madler/zlib/issues/549 for a fuller story) plus the worst-case wrapper overhead. The existing path through `deflateBound()` is unchanged for inputs that fit. Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- git-zlib.c | 16 ++++++++++++++-- git-zlib.h | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/git-zlib.c b/git-zlib.c index a3b32d9a8685aa..1c94f90497ee47 100644 --- a/git-zlib.c +++ b/git-zlib.c @@ -167,9 +167,21 @@ int git_inflate(git_zstream *strm, int flush) return status; } -unsigned long git_deflate_bound(git_zstream *strm, unsigned long size) +size_t git_deflate_bound(git_zstream *strm, size_t size) { - return deflateBound(&strm->z, size); +#if SIZE_MAX > ULONG_MAX + if (size > maximum_unsigned_value_of_type(uLong)) + /* + * deflateBound() takes uLong, which is 32-bit on + * Windows. For inputs above that range, return zlib's + * stored-block formula (the conservative path it would + * itself use for an unknown stream state) plus the + * worst-case wrapper overhead. + */ + return size + (size >> 5) + (size >> 7) + (size >> 11) + + 7 + 18; +#endif + return deflateBound(&strm->z, (uLong)size); } void git_deflate_init(git_zstream *strm, int level) diff --git a/git-zlib.h b/git-zlib.h index 0b24b15bd05f7f..9248d11ca9622c 100644 --- a/git-zlib.h +++ b/git-zlib.h @@ -25,6 +25,6 @@ void git_deflate_end(git_zstream *); int git_deflate_abort(git_zstream *); int git_deflate_end_gently(git_zstream *); int git_deflate(git_zstream *, int flush); -unsigned long git_deflate_bound(git_zstream *, unsigned long); +size_t git_deflate_bound(git_zstream *, size_t); #endif /* GIT_ZLIB_H */ From d50ac11724e5e418b11059c43feca79b40268be8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 13 Aug 2026 14:55:51 +0000 Subject: [PATCH 30/32] packfile: widen `unpack_object_header_buffer()` to `size_t` As part of the ongoing effort to replace `unsigned long` data types with `size_t` wherever appropriate (mainly to fix all those problems on Windows with objects larger than 4GB), let's also adjust the return type and the type of the `len` parameter of this function. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/pack-objects.c | 6 ++---- oss-fuzz/fuzz-pack-headers.c | 2 +- packfile.c | 10 ++++------ packfile.h | 3 ++- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 7e35d0da4c1d5d..037ae4d7272aed 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2259,8 +2259,7 @@ static void check_object(struct object_entry *entry, uint32_t object_index) int have_base = 0; struct object_id base_ref; struct object_entry *base_entry; - unsigned long used, used_0; - size_t avail; + size_t used, used_0, avail; off_t ofs; unsigned char *buf, c; enum object_type type; @@ -2756,8 +2755,7 @@ size_t oe_get_size_slow(struct packing_data *pack, struct pack_window *w_curs; unsigned char *buf; enum object_type type; - unsigned long used; - size_t avail, size; + size_t used, avail, size; if (e->type_ != OBJ_OFS_DELTA && e->type_ != OBJ_REF_DELTA) { size_t sz; diff --git a/oss-fuzz/fuzz-pack-headers.c b/oss-fuzz/fuzz-pack-headers.c index ef61ab577c5098..e44afe0b8d232b 100644 --- a/oss-fuzz/fuzz-pack-headers.c +++ b/oss-fuzz/fuzz-pack-headers.c @@ -9,7 +9,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) size_t len; unpack_object_header_buffer((const unsigned char *)data, - (unsigned long)size, &type, &len); + size, &type, &len); return 0; } diff --git a/packfile.c b/packfile.c index 7fbe47ca18f86e..59d0c83c4037c4 100644 --- a/packfile.c +++ b/packfile.c @@ -1134,12 +1134,11 @@ int packfile_store_count_objects(struct packfile_store *store, return ret; } -unsigned long unpack_object_header_buffer(const unsigned char *buf, - unsigned long len, enum object_type *type, size_t *sizep) +size_t unpack_object_header_buffer(const unsigned char *buf, size_t len, + enum object_type *type, size_t *sizep) { unsigned shift; - size_t size, c; - unsigned long used = 0; + size_t size, c, used = 0; c = buf[used++]; *type = (c >> 4) & 7; @@ -1228,8 +1227,7 @@ int unpack_object_header(struct packed_git *p, size_t *sizep) { unsigned char *base; - size_t left; - unsigned long used; + size_t left, used; enum object_type type; /* use_pack() assures us we have [base, base + 20) available diff --git a/packfile.h b/packfile.h index 820d247d054645..dcf1152644c431 100644 --- a/packfile.h +++ b/packfile.h @@ -458,7 +458,8 @@ off_t find_pack_entry_one(const struct object_id *oid, struct packed_git *); int is_pack_valid(struct packed_git *); void *unpack_entry(struct repository *r, struct packed_git *, off_t, enum object_type *, size_t *); -unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, size_t *sizep); +size_t unpack_object_header_buffer(const unsigned char *buf, size_t len, + enum object_type *type, size_t *sizep); size_t get_size_from_delta(struct packed_git *, struct pack_window **, off_t); int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, size_t *); off_t get_delta_base(struct packed_git *p, struct pack_window **w_curs, From 764243bdf45b4282623a69427f4f068bd960876b Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Fri, 14 Aug 2026 02:06:26 +0000 Subject: [PATCH 31/32] diff: avoid misleading statement about -l option In commit 6623a528e00b (doc: clarify documentation for rename/copy limits, 2021-07-15), the wording around rename limit options and config variables were updated to point out that only the quadratic portion of rename detection (or "exhaustive portion of rename/copy detection" as used in that commit) was limited by these options, because exact rename detection and basename-guided rename detection (which both run in time linear in the number of files) still run before this limit is checked. However, the short help message wasn't updated at the time; update it too. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- diff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff.c b/diff.c index 2a9d0d86871139..dcd82acc9404e6 100644 --- a/diff.c +++ b/diff.c @@ -6186,7 +6186,7 @@ struct option *add_diff_options(const struct option *opts, N_("continue listing the history of a file beyond renames"), PARSE_OPT_NOARG, diff_opt_follow), OPT_INTEGER('l', NULL, &options->rename_limit, - N_("prevent rename/copy detection if the number of rename/copy targets exceeds given limit")), + N_("limit to cheap rename/copy detection if the number of rename/copy targets exceeds this value")), OPT_GROUP(N_("Diff algorithm options")), OPT_CALLBACK_F(0, "minimal", options, NULL, From 2c3adbb2c475981e340c79fdc5e7f4f9b5d9054e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 24 Aug 2026 13:17:38 -0700 Subject: [PATCH 32/32] The 18th batch Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.56.0.adoc | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Documentation/RelNotes/2.56.0.adoc b/Documentation/RelNotes/2.56.0.adoc index 331f851521f574..d33dd57996a486 100644 --- a/Documentation/RelNotes/2.56.0.adoc +++ b/Documentation/RelNotes/2.56.0.adoc @@ -117,6 +117,10 @@ UI, Workflows & Features is now terminated with a newline so that Perl avoids appending its internal source location data. + * The '--shallow-file' option of 'git' command requires a value, but the + code did not check the presence of a value and instead segfaulted + without one, which has been corrected. + Performance, Internal Implementation, Development Support etc. -------------------------------------------------------------- @@ -407,6 +411,14 @@ Performance, Internal Implementation, Development Support etc. early when one side's exclusive commits in the queue are exhausted, yielding significant speedups for queries with one-sided histories. + * A handful of code paths have been corrected to check return values + from functions like curl_easy_duphandle(), deflateInit(), lseek(), + dup(), and strbuf_getline_lf(), resolving several Coverity warnings + about unchecked returns. + + * The setting of a now-unused member '.pretty_given' in the sequencer + machinery has been removed. + Fixes since v2.55 ----------------- @@ -638,3 +650,22 @@ Fixes since v2.55 replace outdated terminology, define key terms upfront, and document how comment lines in the input are treated. (merge 4515c86fd9 kh/doc-trailers later to maint). + + * The 'pack-objects' and delta-encoding code paths have been updated to + use 'size_t' instead of 'unsigned long' for object sizes and offset + limits, avoiding potential truncation issues on 64-bit Windows. + (merge d50ac11724 js/pack-objects-delta-size-t later to maint). + + * A client requesting the promisor-remote capability without a value + caused a null pointer dereference, which has been corrected by + rejecting a request without an argument. + (merge dd6b35ff71 en/serve-promisor-remote-fix later to maint). + + * Various tests in 't7900-maintenance.sh' have been updated to use a + throwaway repository, and auto-detaching of maintenance tasks is now + disabled for these tests to fix flaky races with concurrent background + maintenance jobs. + (merge 2775d8bcd1 ps/t7900-deflake-maintenance later to maint). + + * The help text for the '-l' option of 'git diff' has been updated. + (merge 764243bdf4 en/diff-l-opt-help later to maint).