git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v3 00/11] Handle cloning of objects larger than 4GB on Windows

From
Johannes Schindelin via GitGitGadget <gitgitgadget@gmail.com>
Date
May 8, 2026, 08:16 UTC
Message-ID
<pull.2102.v3.git.1778228209.gitgitgadget@gmail.com>
In-Reply-To
<pull.2102.v2.git.1777914508.gitgitgadget@gmail.com>

On Windows, unsigned long is 32-bit even on 64-bit systems. This causes multiple problems when Git handles objects larger than 4GB. This patch series is a very targeted fix for a very early part of the problem: it addresses the most fundamental truncation points that prevent a >4GB object from surviving a clone at all.

Specifically, this fixes:
 * zlib's uLong wrapping and triggering BUG() assertions in the git_zstream
   wrapper
 * Object sizes being truncated in pack streaming, delta headers, and
   index-pack/unpack-objects
 * pack-objects re-encoding reused pack entries with a truncated size,
   producing corrupt packs on the wire

Many other code paths still use unsigned long for object sizes (e.g., cat-file -s, object_info.sizep, the delta machinery) and will need their own conversions. This series does not attempt to fix those.

Based on work by @LordKiRon in git-for-windows/git#6076.

For testing, add a test helper that synthesizes a pack with a >4GB blob and regression tests that clone it via both the unpack-objects and index-pack code paths using file:// transport. Since these test cases are quite slow (even after optimizing the pack generation part, the git clone test has no chance but to hash 2x4GB of data), they are marked as EXPENSIVE. To ensure that they are passing well in advance of any release, the CI is changed to run them in the CI builds of relatively infrequent integration branch updates.

Changes since v2:
 * Now uses the proper data type for the varint decoding value (thanks,
   Torsten!)
 * The callers that now would silently narrow size_t to unsigned long
   properly check and error out instead (thanks, Torsten!)
Changes since v1:
 * dramatically accelerated the test helper that generates 4GB pack files,
   via two separate strategies:
   1. using the "unsafe" SHA-1 for the blob OID computation.
   2. using pre-computed "Lego blocks" to construct the 4GB packs needed in
      the test cases, where the size (and therefore the involved OIDs) are
      well-known in advance.
 * even with these improvements, the actual git clone is still slow (of
   course, because it cannot use any of those shortcuts), therefore the
   tests are marked as EXPENSIVE.
 * to exercise those tests nevertheless, the last patch lets all EXPENSIVE
   test cases be run for the integration branches other than seen.
Johannes Schindelin (11):
  index-pack, unpack-objects: use size_t for object size
  git-zlib: handle data streams larger than 4GB
  odb, packfile: use size_t for streaming object sizes
  delta, packfile: use size_t for delta header sizes
  test-tool: add a helper to synthesize large packfiles
  t5608: add regression test for >4GB object clone
  test-tool synthesize: use the unsafe hash for speed
  test-tool synthesize: precompute pack for 4 GiB + 1
  test-tool synthesize: add precomputed SHA-256 pack for 4 GiB + 1
  t5608: mark >4GB tests as EXPENSIVE
  ci: run expensive tests on push builds to integration branches
 Makefile                     |   1 +
 builtin/index-pack.c         |   8 +-
 builtin/pack-objects.c       |  34 ++-
 builtin/unpack-objects.c     |   4 +-
 ci/lib.sh                    |   9 +
 compat/zlib-compat.h         |   2 +
 delta.h                      |  14 +-
 git-zlib.c                   |  25 +-
 git-zlib.h                   |   4 +-
 object-file.c                |  12 +-
 odb/streaming.c              |  13 +-
 odb/streaming.h              |   2 +-
 oss-fuzz/fuzz-pack-headers.c |   2 +-
 pack-bitmap.c                |   2 +-
 pack-check.c                 |   6 +-
 packfile.c                   |  57 ++--
 packfile.h                   |   4 +-
 t/helper/meson.build         |   1 +
 t/helper/test-synthesize.c   | 541 +++++++++++++++++++++++++++++++++++
 t/helper/test-tool.c         |   1 +
 t/helper/test-tool.h         |   1 +
 t/t5608-clone-2gb.sh         |  37 +++
 22 files changed, 724 insertions(+), 56 deletions(-)
 create mode 100644 t/helper/test-synthesize.c
base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2102%2Fdscho%2Ffix-large-clones-on-windows-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2102/dscho/fix-large-clones-on-windows-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/2102
Range-diff vs v2:
  1:  dc660106ea !  1:  311cdc601d index-pack, unpack-objects: use size_t for object size
     @@ Commit message
          64-bit platforms, and ensuring the shift arithmetic occurs in 64-bit
          space.
      
     +    Declare the per-byte continuation variable `c` as size_t as well,
     +    matching the canonical varint decoder unpack_object_header_buffer()
     +    in packfile.c. With c as size_t the expression (c & 0x7f) << shift
     +    is naturally size_t-typed, so the explicit cast that an earlier
     +    iteration carried at the use site is no longer needed.
     +
     +    While at it, add the same overflow guard that
     +    unpack_object_header_buffer() carries: if the cumulative shift would
     +    exceed bitsizeof(size_t) - 7, refuse the input rather than invoking
     +    undefined behavior. Unlike unpack_object_header_buffer(), which
     +    labels this case "bad object header", report it as the platform
     +    limit it actually is: a header may be perfectly well-formed and
     +    still encode a size we cannot represent locally (notably on a
     +    32-bit build consuming a packfile produced on a 64-bit host).
     +
          This was originally authored by LordKiRon <https://github.com/LordKiRon>,
          who preferred not to reveal their real name and therefore agreed that I
          take over authorship.
      
     +    Helped-by: Torsten Bögershausen <tboegi@web.de>
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
       ## builtin/index-pack.c ##
     @@ builtin/index-pack.c: static void *unpack_raw_entry(struct object_entry *obj,
       {
       	unsigned char *p;
      -	unsigned long size, c;
     -+	size_t size;
     -+	unsigned long c;
     ++	size_t size, c;
       	off_t base_offset;
       	unsigned shift;
       	void *data;
      @@ builtin/index-pack.c: static void *unpack_raw_entry(struct object_entry *obj,
     + 	size = (c & 15);
     + 	shift = 4;
     + 	while (c & 0x80) {
     ++		if ((bitsizeof(size_t) - 7) < shift)
     ++			die(_("object size too large for this platform"));
       		p = fill(1);
       		c = *p;
       		use(1);
     --		size += (c & 0x7f) << shift;
     -+		size += ((size_t)c & 0x7f) << shift;
     - 		shift += 7;
     - 	}
     - 	obj->size = size;
      
       ## builtin/unpack-objects.c ##
      @@ builtin/unpack-objects.c: static void unpack_one(unsigned nr)
     @@ builtin/unpack-objects.c: static void unpack_one(unsigned nr)
       	unsigned shift;
       	unsigned char *pack;
      -	unsigned long size, c;
     -+	size_t size;
     -+	unsigned long c;
     ++	size_t size, c;
       	enum object_type type;
       
       	obj_list[nr].offset = consumed_bytes;
      @@ builtin/unpack-objects.c: static void unpack_one(unsigned nr)
     + 	size = (c & 15);
     + 	shift = 4;
     + 	while (c & 0x80) {
     ++		if ((bitsizeof(size_t) - 7) < shift)
     ++			die(_("object size too large for this platform"));
       		pack = fill(1);
       		c = *pack;
       		use(1);
     --		size += (c & 0x7f) << shift;
     -+		size += ((size_t)c & 0x7f) << shift;
     - 		shift += 7;
     - 	}
     - 
  2:  92f4327b1f =  2:  c611913194 git-zlib: handle data streams larger than 4GB
  3:  3a539061c5 !  3:  b789f57de9 odb, packfile: use size_t for streaming object sizes
     @@ Commit message
          temporary variables where the types differ, with comments noting the
          truncation limitation for code paths that still use unsigned long.
      
     +    Widening the producers to size_t in this way introduces a handful of
     +    silent size_t -> unsigned long narrowings on Windows, all in
     +    builtin/pack-objects.c, where the consumers are still typed
     +    unsigned long. Make those narrowings explicit with
     +    cast_size_t_to_ulong() so they assert loudly the moment an object
     +    actually exceeds ULONG_MAX bytes:
     +
     +      - oe_get_size_slow() returns unsigned long but holds a size_t
     +        locally; cast at the return.
     +      - write_reuse_object() passes a size_t into check_pack_inflate(),
     +        whose expect parameter is unsigned long; cast at the call.
     +      - check_object() routes a size_t through SET_SIZE() and
     +        SET_DELTA_SIZE(), both of which take unsigned long via
     +        oe_set_size() / oe_set_delta_size(); cast at the three call
     +        sites in the OBJ_OFS_DELTA / OBJ_REF_DELTA branches and in the
     +        non-delta default arm.
     +
     +    The cast-only treatment is deliberately a stop-gap. Properly
     +    widening oe_set_size, oe_get_size_slow's return type,
     +    check_pack_inflate's expect parameter, object_info.sizep,
     +    patch_delta, and the OE_SIZE_BITS bit-fields cascades into a series
     +    that is too large to be reviewable, so the proper widening is
     +    deferred to a follow-up topic. Until then,
     +    cast_size_t_to_ulong() at least makes the truncation explicit at
     +    the source: it documents the boundary, and on a 64-bit non-Windows
     +    platform it is a no-op.
     +
          This was originally authored by LordKiRon <https://github.com/LordKiRon>,
          who preferred not to reveal their real name and therefore agreed that I
          take over authorship.
      
     +    Helped-by: Torsten Bögershausen <tboegi@web.de>
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
       ## builtin/pack-objects.c ##
     @@ builtin/pack-objects.c: static off_t write_reuse_object(struct hashfile *f, stru
       
       	if (DELTA(entry))
       		type = (allow_ofs_delta && DELTA(entry)->idx.offset) ?
     +@@ builtin/pack-objects.c: static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,
     + 	datalen -= entry->in_pack_header_size;
     + 
     + 	if (!pack_to_stdout && p->index_version == 1 &&
     +-	    check_pack_inflate(p, &w_curs, offset, datalen, entry_size)) {
     ++	    check_pack_inflate(p, &w_curs, offset, datalen,
     ++			       cast_size_t_to_ulong(entry_size))) {
     + 		error(_("corrupt packed object for %s"),
     + 		      oid_to_hex(&entry->idx.oid));
     + 		unuse_pack(&w_curs);
      @@ builtin/pack-objects.c: static void write_reused_pack_one(struct packed_git *reuse_packfile,
       {
       	off_t offset, next, cur;
     @@ builtin/pack-objects.c: static void check_object(struct object_entry *entry, uin
       
       		buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
       
     +@@ builtin/pack-objects.c: static void check_object(struct object_entry *entry, uint32_t object_index)
     + 		default:
     + 			/* Not a delta hence we've already got all we need. */
     + 			oe_set_type(entry, entry->in_pack_type);
     +-			SET_SIZE(entry, in_pack_size);
     ++			SET_SIZE(entry, cast_size_t_to_ulong(in_pack_size));
     + 			entry->in_pack_header_size = used;
     + 			if (oe_type(entry) < OBJ_COMMIT || oe_type(entry) > OBJ_BLOB)
     + 				goto give_up;
     +@@ builtin/pack-objects.c: static void check_object(struct object_entry *entry, uint32_t object_index)
     + 		if (have_base &&
     + 		    can_reuse_delta(&base_ref, entry, &base_entry)) {
     + 			oe_set_type(entry, entry->in_pack_type);
     +-			SET_SIZE(entry, in_pack_size); /* delta size */
     +-			SET_DELTA_SIZE(entry, in_pack_size);
     ++			SET_SIZE(entry, cast_size_t_to_ulong(in_pack_size)); /* delta size */
     ++			SET_DELTA_SIZE(entry, cast_size_t_to_ulong(in_pack_size));
     + 
     + 			if (base_entry) {
     + 				SET_DELTA(entry, base_entry);
      @@ builtin/pack-objects.c: unsigned long oe_get_size_slow(struct packing_data *pack,
       	struct pack_window *w_curs;
       	unsigned char *buf;
     @@ builtin/pack-objects.c: unsigned long oe_get_size_slow(struct packing_data *pack
       	}
       
       	p = oe_in_pack(pack, e);
     +@@ builtin/pack-objects.c: unsigned long oe_get_size_slow(struct packing_data *pack,
     + 
     + 	unuse_pack(&w_curs);
     + 	packing_data_unlock(&to_pack);
     +-	return size;
     ++	return cast_size_t_to_ulong(size);
     + }
     + 
     + static int try_delta(struct unpacked *trg, struct unpacked *src,
      
       ## object-file.c ##
      @@ object-file.c: int odb_source_loose_read_object_stream(struct odb_read_stream **out,
  4:  3274cba862 =  4:  8e87a4e71f delta, packfile: use size_t for delta header sizes
  5:  afa74a3a2b =  5:  34fec4a32d test-tool: add a helper to synthesize large packfiles
  6:  a3019888d8 =  6:  88f992903f t5608: add regression test for >4GB object clone
  7:  859e93e7a9 =  7:  4f207c8a47 test-tool synthesize: use the unsafe hash for speed
  8:  29b9a74e91 =  8:  2751c21c6e test-tool synthesize: precompute pack for 4 GiB + 1
  9:  8e6e720804 =  9:  3a006d96c3 test-tool synthesize: add precomputed SHA-256 pack for 4 GiB + 1
 10:  5b44410b2f = 10:  86c09af4f5 t5608: mark >4GB tests as EXPENSIVE
 11:  1eaaa7fad7 = 11:  2159f6a271 ci: run expensive tests on push builds to integration branches
-- 
gitgitgadget
Previous: Junio C HamanoNext: Johannes Schindelin via GitGitGadget
Message 45 of 60 in “Handle cloning of objects larger than 4GB on Windows”
  1. 0/6 Handle cloning of objects larger than 4GB on WindowsJohannes Schindelin via GitGitGadget, Apr 28, 2026
  2. 1/6 index-pack, unpack-objects: use size_t for object sizeJohannes Schindelin via GitGitGadget, Apr 28, 2026
  3. Torsten BögershausenApr 30, 2026
  4. Johannes SchindelinMay 3, 2026
  5. 2/6 git-zlib: handle data streams larger than 4GBJohannes Schindelin via GitGitGadget, Apr 28, 2026
  6. 3/6 odb, packfile: use size_t for streaming object sizesJohannes Schindelin via GitGitGadget, Apr 28, 2026
  7. 4/6 delta, packfile: use size_t for delta header sizesJohannes Schindelin via GitGitGadget, Apr 28, 2026
  8. Derrick StoleeApr 29, 2026
  9. Johannes SchindelinMay 3, 2026
  10. 5/6 test-tool: add a helper to synthesize large packfilesJohannes Schindelin via GitGitGadget, Apr 28, 2026
  11. 6/6 t5608: add regression test for >4GB object cloneJohannes Schindelin via GitGitGadget, Apr 28, 2026
  12. Derrick StoleeApr 29, 2026
  13. Jeff KingMay 1, 2026
  14. Derrick StoleeMay 1, 2026
  15. Johannes SchindelinMay 4, 2026
  16. Derrick StoleeApr 29, 2026
  17. 00/11 Handle cloning of objects larger than 4GB on WindowsJohannes Schindelin via GitGitGadget, May 4, 2026
  18. 01/11 index-pack, unpack-objects: use size_t for object sizeJohannes Schindelin via GitGitGadget, May 4, 2026
  19. Torsten BögershausenMay 5, 2026
  20. Johannes SchindelinMay 8, 2026
  21. Torsten BögershausenMay 8, 2026
  22. Junio C HamanoMay 10, 2026
  23. Torsten BögershausenMay 10, 2026
  24. 02/11 git-zlib: handle data streams larger than 4GBJohannes Schindelin via GitGitGadget, May 4, 2026
  25. 03/11 odb, packfile: use size_t for streaming object sizesJohannes Schindelin via GitGitGadget, May 4, 2026
  26. Torsten BögershausenMay 5, 2026
  27. Johannes SchindelinMay 8, 2026
  28. 04/11 delta, packfile: use size_t for delta header sizesJohannes Schindelin via GitGitGadget, May 4, 2026
  29. 05/11 test-tool: add a helper to synthesize large packfilesJohannes Schindelin via GitGitGadget, May 4, 2026
  30. 06/11 t5608: add regression test for >4GB object cloneJohannes Schindelin via GitGitGadget, May 4, 2026
  31. 07/11 test-tool synthesize: use the unsafe hash for speedJohannes Schindelin via GitGitGadget, May 4, 2026
  32. 08/11 test-tool synthesize: precompute pack for 4 GiB + 1Johannes Schindelin via GitGitGadget, May 4, 2026
  33. Derrick StoleeMay 4, 2026
  34. Johannes SchindelinMay 5, 2026
  35. 09/11 test-tool synthesize: add precomputed SHA-256 pack for 4 GiB + 1Johannes Schindelin via GitGitGadget, May 4, 2026
  36. 10/11 t5608: mark >4GB tests as EXPENSIVEJohannes Schindelin via GitGitGadget, May 4, 2026
  37. 11/11 ci: run expensive tests on push builds to integration branchesJohannes Schindelin via GitGitGadget, May 4, 2026
  38. Derrick StoleeMay 4, 2026
  39. Junio C HamanoMay 5, 2026
  40. Junio C HamanoMay 5, 2026
  41. Johannes SchindelinMay 6, 2026
  42. Junio C HamanoMay 7, 2026
  43. Patrick SteinhardtMay 7, 2026
  44. Junio C HamanoMay 8, 2026
  45. 00/11 Handle cloning of objects larger than 4GB on WindowsJohannes Schindelin via GitGitGadget, May 8, 2026
  46. 01/11 index-pack, unpack-objects: use size_t for object sizeJohannes Schindelin via GitGitGadget, May 8, 2026
  47. 02/11 git-zlib: handle data streams larger than 4GBJohannes Schindelin via GitGitGadget, May 8, 2026
  48. 03/11 odb, packfile: use size_t for streaming object sizesJohannes Schindelin via GitGitGadget, May 8, 2026
  49. 04/11 delta, packfile: use size_t for delta header sizesJohannes Schindelin via GitGitGadget, May 8, 2026
  50. 05/11 test-tool: add a helper to synthesize large packfilesJohannes Schindelin via GitGitGadget, May 8, 2026
  51. 06/11 t5608: add regression test for >4GB object cloneJohannes Schindelin via GitGitGadget, May 8, 2026
  52. 07/11 test-tool synthesize: use the unsafe hash for speedJohannes Schindelin via GitGitGadget, May 8, 2026
  53. 08/11 test-tool synthesize: precompute pack for 4 GiB + 1Johannes Schindelin via GitGitGadget, May 8, 2026
  54. 09/11 test-tool synthesize: add precomputed SHA-256 pack for 4 GiB + 1Johannes Schindelin via GitGitGadget, May 8, 2026
  55. 10/11 t5608: mark >4GB tests as EXPENSIVEJohannes Schindelin via GitGitGadget, May 8, 2026
  56. 11/11 ci: run expensive tests on push builds to integration branchesJohannes Schindelin via GitGitGadget, May 8, 2026
  57. ci: enable EXPENSIVE for contributor buildsJunio C Hamano, May 10, 2026
  58. Patrick SteinhardtMay 11, 2026
  59. Junio C HamanoMay 11, 2026
  60. Patrick SteinhardtMay 11, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.