pack-objects: support reuse with `--no-ref-delta`

The previous commit disables delta- and bitmap-reuse entirely whenever pack-objects is given '--no-ref-delta' for the sake of simplicity. This is overly pessimistic. When '--delta-base-offset' is also given, delta reuse can remain enabled. A reused delta whose base is written earlier in the output can be encoded as an `OFS_DELTA`, even when its source copy was encoded as a `REF_DELTA`. Preferred bases and external thin-pack bases are different: neither appears in the output, so deltas against either still require encoding the object as a `REF_DELTA`, and thus cannot be reused. Without '--delta-base-offset', delta reuse remains disabled, since no delta representation remains. Bitmap reuse follows a different path, since selected entries may be copied without passing through the code which chooses a delta representation. When given '--no-ref-delta', we must inspect candidate objects individually, and leave `REF_DELTA` entries to the normal object path outside of pack-reuse. We must likewise avoid the special-case for reusing either the single or preferred pack corresponding to the bitmap by whole `eword_t`'s at a time. Signed-off-by: Taylor Blau <ttaylorr@openai.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Taylor Blau committed Jul 12, 2026 at 18:12 UTC aca978d38668f74766e14cd75b4461ae356e47bb
5 files changed +71 -16
builtin/pack-objects.c
+13 -4
@@ -2207,6 +2207,13 @@ static int can_reuse_delta(const struct object_id *base_oid,
2207 */
2208 base = packlist_find(&to_pack, base_oid);
2209 if (base) {
2210 + /*
2211 + * A preferred base is omitted from the resulting pack, so it
2212 + * can only be referenced by object ID.
2213 + */
2214 + if (base->preferred_base && !allow_ref_delta)
2215 + return 0;
2216 +
2217 if (!in_same_island(&delta->idx.oid, &base->idx.oid))
2218 return 0;
2219 *base_out = base;
@@ -2218,7 +2225,8 @@ static int can_reuse_delta(const struct object_id *base_oid,
2225 * even if it was buried too deep in history to make it into the
2226 * packing list.
2227 */
2221 - if (thin && bitmap_has_oid_in_uninteresting(bitmap_git, base_oid)) {
2228 + if (allow_ref_delta && thin &&
2229 + bitmap_has_oid_in_uninteresting(bitmap_git, base_oid)) {
2230 if (use_delta_islands) {
2231 if (!in_same_island(&delta->idx.oid, base_oid))
2232 return 0;
@@ -4668,7 +4676,7 @@ static int pack_options_allow_reuse(void)
4676 !ignore_packed_keep_on_disk &&
4677 !ignore_packed_keep_in_core &&
4678 (!local || !have_non_local_packs) &&
4671 - !incremental && allow_ref_delta;
4679 + !incremental && (allow_ref_delta || allow_ofs_delta);
4680 }
4681
4682 static int get_object_list_from_bitmap(struct rev_info *revs)
@@ -4690,7 +4698,8 @@ static int get_object_list_from_bitmap(struct rev_info *revs)
4698 &reuse_packfiles,
4699 &reuse_packfiles_nr,
4700 &reuse_packfile_bitmap,
4693 - allow_pack_reuse == MULTI_PACK_REUSE);
4701 + allow_pack_reuse == MULTI_PACK_REUSE,
4702 + allow_ref_delta);
4703
4704 if (reuse_packfiles) {
4705 reuse_packfile_objects = bitmap_popcount(reuse_packfile_bitmap);
@@ -5317,7 +5326,7 @@ int cmd_pack_objects(int argc,
5326 if (unpack_unreachable || keep_unreachable || pack_loose_unreachable)
5327 use_internal_rev_list = 1;
5328
5320 - if (!reuse_object || !allow_ref_delta)
5329 + if (!reuse_object || (!allow_ref_delta && !allow_ofs_delta))
5330 reuse_delta = 0;
5331 if (cfg->pack_compression_level == -1)
5332 cfg->pack_compression_level = Z_DEFAULT_COMPRESSION;
pack-bitmap.c
+20 -10
@@ -2267,7 +2267,8 @@ static int try_partial_reuse(struct bitmap_index *bitmap_git,
2267 uint32_t pack_pos,
2268 off_t offset,
2269 struct bitmap *reuse,
2270 - struct pack_window **w_curs)
2270 + struct pack_window **w_curs,
2271 + int allow_ref_delta)
2272 {
2273 off_t delta_obj_offset;
2274 enum object_type type;
@@ -2286,6 +2287,9 @@ static int try_partial_reuse(struct bitmap_index *bitmap_git,
2287 uint32_t base_pos;
2288 uint32_t base_bitmap_pos;
2289
2290 + if (type == OBJ_REF_DELTA && !allow_ref_delta)
2291 + return 0;
2292 +
2293 /*
2294 * Find the position of the base object so we can look it up
2295 * in our bitmaps. If we can't come up with an offset, or if
@@ -2358,20 +2362,19 @@ static int try_partial_reuse(struct bitmap_index *bitmap_git,
2362
2363 static void reuse_partial_packfile_from_bitmap_1(struct bitmap_index *bitmap_git,
2364 struct bitmapped_pack *pack,
2361 - struct bitmap *reuse)
2365 + struct bitmap *reuse,
2366 + int allow_ref_delta)
2367 {
2368 struct bitmap *result = bitmap_git->result;
2369 struct pack_window *w_curs = NULL;
2370 size_t pos = pack->bitmap_pos / BITS_IN_EWORD;
2371
2367 - if (!pack->bitmap_pos) {
2372 + if (allow_ref_delta && !pack->bitmap_pos) {
2373 /*
2374 * If we're processing the first (in the case of a MIDX, the
2375 * preferred pack) or the only (in the case of single-pack
2371 - * bitmaps) pack, then we can reuse whole words at a time.
2372 - *
2373 - * This is because we know that any deltas in this range *must*
2374 - * have their bases chosen from the same pack, since:
2376 + * bitmaps) pack, then any delta in this range must have its
2377 + * base chosen from the same pack:
2378 *
2379 * - In the single pack case, there is no other pack to choose
2380 * them from.
@@ -2380,6 +2383,10 @@ static void reuse_partial_packfile_from_bitmap_1(struct bitmap_index *bitmap_git
2383 * all ties are broken in favor of that pack (i.e. the one
2384 * we're currently processing). So any duplicate bases will be
2385 * resolved in favor of the pack we're processing.
2386 + *
2387 + * When REF_DELTAs are allowed, we can therefore reuse whole
2388 + * words at a time without inspecting object headers. Otherwise,
2389 + * inspect each object below to avoid reusing a REF_DELTA entry.
2390 */
2391 while (pos < result->word_alloc &&
2392 pos < pack->bitmap_nr / BITS_IN_EWORD &&
@@ -2429,7 +2436,8 @@ static void reuse_partial_packfile_from_bitmap_1(struct bitmap_index *bitmap_git
2436 }
2437
2438 if (try_partial_reuse(bitmap_git, pack, bit_pos,
2432 - pack_pos, ofs, reuse, &w_curs) < 0) {
2439 + pack_pos, ofs, reuse, &w_curs,
2440 + allow_ref_delta) < 0) {
2441 /*
2442 * try_partial_reuse indicated we couldn't reuse
2443 * any bits, so there is no point in trying more
@@ -2464,7 +2472,8 @@ void reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
2472 struct bitmapped_pack **packs_out,
2473 size_t *packs_nr_out,
2474 struct bitmap **reuse_out,
2467 - int multi_pack_reuse)
2475 + int multi_pack_reuse,
2476 + int allow_ref_delta)
2477 {
2478 struct repository *r = bitmap_repo(bitmap_git);
2479 struct bitmapped_pack *packs = NULL;
@@ -2559,7 +2568,8 @@ void reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
2568 reuse = bitmap_word_alloc(word_alloc);
2569
2570 for (i = 0; i < packs_nr; i++)
2562 - reuse_partial_packfile_from_bitmap_1(bitmap_git, &packs[i], reuse);
2571 + reuse_partial_packfile_from_bitmap_1(bitmap_git, &packs[i], reuse,
2572 + allow_ref_delta);
2573
2574 if (bitmap_is_empty(reuse)) {
2575 free(packs);
pack-bitmap.h
+2 -1
@@ -116,7 +116,8 @@ void reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
116 struct bitmapped_pack **packs_out,
117 size_t *packs_nr_out,
118 struct bitmap **reuse_out,
119 - int multi_pack_reuse);
119 + int multi_pack_reuse,
120 + int allow_ref_delta);
121 int rebuild_existing_bitmaps(struct bitmap_index *, struct packing_data *mapping,
122 kh_oid_map_t *reused_bitmaps, int show_progress);
123 void free_bitmap_index(struct bitmap_index *);
t/t5300-pack-object.sh
+20 -1
@@ -229,6 +229,20 @@ test_expect_success 'pack without REF_DELTA with OFS_DELTA' '
229 test_grep ! " REF_DELTA " deltas
230 '
231
232 +test_expect_success 'pack without REF_DELTA reuses deltas as OFS_DELTA' '
233 + # Install the REF_DELTA pack above and disable delta search, so any
234 + # output delta must be a reused REF_DELTA rewritten as OFS_DELTA.
235 + test_when_finished "rm -f .git/objects/pack/pack-$packname_2.*" &&
236 + git index-pack --stdin <test-2-${packname_2}.pack >/dev/null &&
237 +
238 + git pack-objects --window=0 --delta-base-offset \
239 + --no-ref-delta --stdout <obj-list >reused.pack &&
240 + git index-pack -o reused.idx reused.pack &&
241 + test-tool pack-deltas --list-deltas reused.idx >deltas &&
242 + test_grep " OFS_DELTA " deltas &&
243 + test_grep ! " REF_DELTA " deltas
244 +'
245 +
246 test_expect_success 'pack without REF_DELTA skips excluded delta bases' '
247 test_when_finished "git read-tree $tree" &&
248
@@ -253,7 +267,12 @@ test_expect_success 'pack without REF_DELTA skips excluded delta bases' '
267 test_grep ! " OFS_DELTA " deltas &&
268 test_grep " REF_DELTA " deltas &&
269
256 - git pack-objects --thin --stdout --revs \
270 + # Store the REF_DELTA entries above and disable delta search below,
271 + # so any output delta would have to reuse an excluded-base
272 + # REF_DELTA.
273 + git index-pack --stdin <thin-fixed.pack >/dev/null &&
274 +
275 + git pack-objects --thin --window=0 --stdout --revs \
276 --delta-base-offset --no-ref-delta \
277 <thin-revs >no-ref-thin.pack &&
278 git index-pack --fix-thin --stdin no-ref-thin-fixed.pack \
t/t5332-multi-pack-reuse.sh
+16
@@ -111,6 +111,22 @@ test_expect_success 'reuse all objects from all packs' '
111 test_pack_objects_reused_all 9 3
112 '
113
114 +test_expect_success '--no-ref-delta reuses REF_DELTA-free bitmapped packs' '
115 + # Whole-word reuse is unavailable under --no-ref-delta, so reusing
116 + # every object below exercises the per-object bitmap path.
117 + : >trace2.txt &&
118 + GIT_TRACE2_EVENT="$PWD/trace2.txt" \
119 + git pack-objects --stdout --revs --all --delta-base-offset \
120 + --no-ref-delta >got.pack &&
121 +
122 + test_pack_reused 9 <trace2.txt &&
123 + test_packs_reused 3 <trace2.txt &&
124 +
125 + git index-pack --strict -o got.idx got.pack &&
126 + test-tool pack-deltas --list-deltas got.idx >deltas &&
127 + test_grep ! " REF_DELTA " deltas
128 +'
129 +
130 test_expect_success 'reuse objects from first pack with middle gap' '
131 for i in D E F
132 do