From 69c2c21f05a2aec95f1ef61f861051c289b03dd4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 5 Jun 2026 02:25:58 +0200 Subject: [PATCH 01/12] 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. Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin --- diff-delta.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/diff-delta.c b/diff-delta.c index 43c339f01061ca..b6b65d76078030 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; From d92a5d4dec8a87c412e9dbdb72285e8f7dca61ec Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 5 Jun 2026 02:42:39 +0200 Subject: [PATCH 02/12] 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 --- delta.h | 2 +- diff-delta.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/delta.h b/delta.h index eb5c6d2fdb9c51..a19586d789d89b 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 b6b65d76078030..c93ac425940eae 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 4ef28865498b33cebc954fccf18a3368f3d114b4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 5 Jun 2026 09:03:51 +0200 Subject: [PATCH 03/12] 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. Assisted-by: Opus 4.7 Signed-off-by: Johannes Schindelin --- builtin/pack-objects.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index e3760b34925bbf..f89628a7608e1b 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; @@ -2688,8 +2688,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; From 07d01200a48c79ba6b3da594d29d685b2c5865d0 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 5 Jun 2026 09:08:53 +0200 Subject: [PATCH 04/12] 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 --- 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 f89628a7608e1b..4737a6a32cd77d 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2972,9 +2972,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 7dca16010249768558efc21f522fba8240aeb2f3 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 5 Jun 2026 09:24:29 +0200 Subject: [PATCH 05/12] 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 --- 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 4737a6a32cd77d..63ceeb736f3237 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2804,7 +2804,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; @@ -2991,7 +2991,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 e1ae83ba0378ad5d4278e220584a3fbc37a1dc4e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 5 Jun 2026 09:46:26 +0200 Subject: [PATCH 06/12] 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 --- 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 63ceeb736f3237..315ea0ed7e69e0 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; @@ -2808,7 +2809,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 a19586d789d89b..59ccaaa0e0f625 100644 --- a/delta.h +++ b/delta.h @@ -42,8 +42,8 @@ unsigned long 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 c93ac425940eae..15210e8381e1c7 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -318,8 +318,8 @@ unsigned long 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 8353bc03c175d1eb3618e96832f62562bf6b9976 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 5 Jun 2026 11:26:01 +0200 Subject: [PATCH 07/12] 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 --- builtin/pack-objects.c | 8 ++++---- git-zlib.h | 4 ++-- pack-check.c | 4 ++-- packfile.c | 4 ++-- packfile.h | 3 ++- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 315ea0ed7e69e0..cedda6ba9c57b8 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); @@ -2261,7 +2261,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; @@ -2773,8 +2773,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.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 1d1b23b6cc782f..629fe46a6a6f27 100644 --- a/packfile.c +++ b/packfile.c @@ -620,7 +620,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; @@ -960,7 +960,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 2329a697014a66..3cff8bdcb93b2d 100644 --- a/packfile.h +++ b/packfile.h @@ -240,7 +240,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 acffd232acad12f31c6ea685dbc07712e097ecef Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 5 Jun 2026 11:44:13 +0200 Subject: [PATCH 08/12] 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 --- 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 b89d28c8aa929dcf4efd40ee8174f5a78aac9eff Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 5 Jun 2026 12:05:22 +0200 Subject: [PATCH 09/12] 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 --- 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 2d4d19c5fbeff50340308b8cffbab21156807b49 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 5 Jun 2026 12:12:57 +0200 Subject: [PATCH 10/12] 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 --- 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 617960d9cae58fc621ce846dbc4935bce6bf321e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 5 Jun 2026 12:17:13 +0200 Subject: [PATCH 11/12] 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 --- 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 ab911cf55647ed335042f5ac3a6490c36c3ef1d7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 5 Jun 2026 12:21:20 +0200 Subject: [PATCH 12/12] 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 for an unknown stream state) 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 --- 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 d21adb3bf5b15e..ebbbcc6d1a5077 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 */