delta, packfile: use size_t for delta header sizes

The delta header decoding functions return unsigned long, which truncates on Windows for objects larger than 4GB. Introduce size_t variants get_delta_hdr_size_sz() and get_size_from_delta_sz() that preserve the full 64-bit size, and use them in packed_object_info() where the size is needed for streaming decisions. 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. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed May 8, 2026 at 08:16 UTC 17fa0775966dd9769864768c0fdc76d3cc2a01fb
2 files changed +36 -11
delta.h
+12 -2
@@ -86,8 +86,11 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
86 * This must be called twice on the delta data buffer, first to get the
87 * expected source buffer size, and again to get the target buffer size.
88 */
89 -static inline unsigned long get_delta_hdr_size(const unsigned char **datap,
90 - const unsigned char *top)
89 +/*
90 + * Size_t variant that doesn't truncate - use for >4GB objects on Windows.
91 + */
92 +static inline size_t get_delta_hdr_size_sz(const unsigned char **datap,
93 + const unsigned char *top)
94 {
95 const unsigned char *data = *datap;
96 size_t cmd, size = 0;
@@ -98,6 +101,13 @@ static inline unsigned long get_delta_hdr_size(const unsigned char **datap,
101 i += 7;
102 } while (cmd & 0x80 && data < top);
103 *datap = data;
104 + return size;
105 +}
106 +
107 +static inline unsigned long get_delta_hdr_size(const unsigned char **datap,
108 + const unsigned char *top)
109 +{
110 + size_t size = get_delta_hdr_size_sz(datap, top);
111 return cast_size_t_to_ulong(size);
112 }
113
packfile.c
+24 -9
@@ -1161,9 +1161,12 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
1161 return used;
1162 }
1163
1164 -unsigned long get_size_from_delta(struct packed_git *p,
1165 - struct pack_window **w_curs,
1166 - off_t curpos)
1164 +/*
1165 + * Size_t variant for >4GB delta results on Windows.
1166 + */
1167 +static size_t get_size_from_delta_sz(struct packed_git *p,
1168 + struct pack_window **w_curs,
1169 + off_t curpos)
1170 {
1171 const unsigned char *data;
1172 unsigned char delta_head[20], *in;
@@ -1210,10 +1213,18 @@ unsigned long get_size_from_delta(struct packed_git *p,
1213 data = delta_head;
1214
1215 /* ignore base size */
1213 - get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
1216 + get_delta_hdr_size_sz(&data, delta_head+sizeof(delta_head));
1217
1218 /* Read the result size */
1216 - return get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
1219 + return get_delta_hdr_size_sz(&data, delta_head+sizeof(delta_head));
1220 +}
1221 +
1222 +unsigned long get_size_from_delta(struct packed_git *p,
1223 + struct pack_window **w_curs,
1224 + off_t curpos)
1225 +{
1226 + size_t size = get_size_from_delta_sz(p, w_curs, curpos);
1227 + return cast_size_t_to_ulong(size);
1228 }
1229
1230 int unpack_object_header(struct packed_git *p,
@@ -1618,14 +1629,18 @@ static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_off
1629 ret = -1;
1630 goto out;
1631 }
1621 - *oi->sizep = get_size_from_delta(p, &w_curs, tmp_pos);
1622 - if (*oi->sizep == 0) {
1632 + /*
1633 + * Use size_t variant to avoid die() on >4GB deltas.
1634 + * oi->sizep is unsigned long, so truncation may occur,
1635 + * but streaming code uses its own size_t tracking.
1636 + */
1637 + size = get_size_from_delta_sz(p, &w_curs, tmp_pos);
1638 + if (size == 0) {
1639 ret = -1;
1640 goto out;
1641 }
1626 - } else {
1627 - *oi->sizep = size;
1642 }
1643 + *oi->sizep = (unsigned long)size;
1644 }
1645
1646 if (oi->disk_sizep || (oi->mtimep && p->is_cruft)) {