From 0ff1a605b864b806655421f293ba5d732d7b81a9 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Sun, 12 Jul 2026 18:11:53 -0700 Subject: [PATCH 1/4] t/helper: teach pack-deltas to list delta entries In the following commit(s), some tests will need to distinguish between `REF_DELTA`s and `OFS_DELTA`s to exercise a new '--no-ref-delta' option for 'pack-objects'. Existing tools report delta relationships, but not how their bases are represented in the pack. Teach 'test-tool pack-deltas' a '--list-deltas' mode. For each delta entry, print the object ID, its REF_DELTA or OFS_DELTA type, and the base object ID or pack offset, respectively. This lets tests inspect pack headers without open-coding a parser. Signed-off-by: Taylor Blau Signed-off-by: Junio C Hamano --- t/helper/test-pack-deltas.c | 69 +++++++++++++++++++++++++++++++++++++ t/t5300-pack-object.sh | 8 +++-- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/t/helper/test-pack-deltas.c b/t/helper/test-pack-deltas.c index 840797cf0d..4ba6fe2dd3 100644 --- a/t/helper/test-pack-deltas.c +++ b/t/helper/test-pack-deltas.c @@ -7,6 +7,7 @@ #include "hash.h" #include "hex.h" #include "pack.h" +#include "packfile.h" #include "pack-objects.h" #include "parse-options.h" #include "setup.h" @@ -15,6 +16,7 @@ static const char *usage_str[] = { "test-tool pack-deltas --num-objects ", + "test-tool pack-deltas --list-deltas .idx", NULL }; @@ -80,19 +82,86 @@ static void write_ref_delta(struct hashfile *f, free(delta_buf); } +static int list_delta(const struct object_id *oid, + struct packed_git *p, + uint32_t pos, + void *_w_curs) +{ + struct pack_window **w_curs = _w_curs; + off_t obj_offset = nth_packed_object_offset(p, pos); + off_t cur = obj_offset; + size_t size; + enum object_type type = unpack_object_header(p, w_curs, &cur, + &size); + + if (type < 0) + die("unable to parse object at position %"PRIu32, pos); + if (type != OBJ_REF_DELTA && type != OBJ_OFS_DELTA) + return 0; + + if (type == OBJ_REF_DELTA) { + struct object_id base_oid; + const unsigned char *base = use_pack(p, w_curs, cur, + NULL); + + oidread(&base_oid, base, p->repo->hash_algo); + printf("%s REF_DELTA %s\n", oid_to_hex(oid), + oid_to_hex(&base_oid)); + } else { + off_t base_offset = get_delta_base(p, w_curs, &cur, + type, obj_offset); + + if (!base_offset) + die("unable to read base of object %s", oid_to_hex(oid)); + printf("%s OFS_DELTA %"PRIuMAX"\n", oid_to_hex(oid), + (uintmax_t)base_offset); + } + + return 0; +} + +static void list_deltas(const char *idx_name) +{ + struct packed_git *p; + struct pack_window *w_curs = NULL; + + p = add_packed_git(the_repository, idx_name, strlen(idx_name), 1); + if (!p || open_pack_index(p)) + die("unable to open pack index %s", idx_name); + + if (for_each_object_in_pack(p, list_delta, &w_curs, + ODB_FOR_EACH_OBJECT_PACK_ORDER)) + die("unable to iterate over objects in %s", idx_name); + + unuse_pack(&w_curs); + close_pack(p); + free(p); +} + int cmd__pack_deltas(int argc, const char **argv) { int num_objects = -1; + int list_deltas_mode = 0; struct hashfile *f; struct strbuf line = STRBUF_INIT; struct option options[] = { OPT_INTEGER('n', "num-objects", &num_objects, N_("the number of objects to write")), + OPT_BOOL(0, "list-deltas", &list_deltas_mode, + N_("list REF_DELTA and OFS_DELTA entries")), OPT_END() }; argc = parse_options(argc, argv, NULL, options, usage_str, 0); + if (list_deltas_mode) { + if (argc != 1 || num_objects >= 0) + usage_with_options(usage_str, options); + setup_git_directory(the_repository); + list_deltas(argv[0]); + return 0; + } + if (argc || num_objects < 0) usage_with_options(usage_str, options); diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 73445782e7..4bee490ff6 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -190,7 +190,9 @@ test_expect_success 'unpack without delta (core.fsyncmethod=batch)' ' test_expect_success 'pack with REF_DELTA' ' packname_2=$(git pack-objects --progress test-2 stderr) && - check_deltas stderr -gt 0 + check_deltas stderr -gt 0 && + test-tool pack-deltas --list-deltas test-2-$packname_2.idx >deltas && + test_grep " REF_DELTA " deltas ' test_expect_success 'unpack with REF_DELTA' ' @@ -204,7 +206,9 @@ test_expect_success 'unpack with REF_DELTA (core.fsyncmethod=batch)' ' test_expect_success 'pack with OFS_DELTA' ' packname_3=$(git pack-objects --progress --delta-base-offset test-3 \ stderr) && - check_deltas stderr -gt 0 + check_deltas stderr -gt 0 && + test-tool pack-deltas --list-deltas test-3-$packname_3.idx >deltas && + test_grep " OFS_DELTA " deltas ' test_expect_success 'unpack with OFS_DELTA' ' From 5e4971b28ec3953094dbfe874fdd7baa7f8d1d47 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Sun, 12 Jul 2026 18:11:57 -0700 Subject: [PATCH 2/4] pack-objects: introduce `--no-ref-delta` Some consumers of 'pack-objects' may wish to avoid packs which contain `REF_DELTA` entries. For instance, a 'receive-pack' implementation which retains the resulting pack without building an index of object IDs may prefer every delta base to be discoverable from an earlier entry in the same pack. Teach 'pack-objects' a new `--no-ref-delta` option to avoid writing `REF_DELTA` entries, without changing whether `OFS_DELTA` is allowed. When used without `--delta-base-offset`, no delta representation remains, so avoid delta search entirely. Otherwise, allow new deltas whose bases appear earlier in the same pack. For now, disable delta- and bitmap-reuse under `--no-ref-delta`, since either may copy an existing `REF_DELTA` entry. This is overly pessimistic, but simplifies the changes in this commit. The next commit re-enables reuse in the cases which do not require `REF_DELTA`. Signed-off-by: Taylor Blau Signed-off-by: Junio C Hamano --- Documentation/git-pack-objects.adoc | 8 ++++- builtin/pack-objects.c | 16 ++++++--- t/t5300-pack-object.sh | 52 +++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/Documentation/git-pack-objects.adoc b/Documentation/git-pack-objects.adoc index 65cd00c152..5e42e4429d 100644 --- a/Documentation/git-pack-objects.adoc +++ b/Documentation/git-pack-objects.adoc @@ -10,7 +10,8 @@ SYNOPSIS -------- [verse] 'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied] - [--no-reuse-delta] [--delta-base-offset] [--non-empty] + [--no-reuse-delta] [--delta-base-offset] [--no-ref-delta] + [--non-empty] [--local] [--incremental] [--window=] [--depth=] [--revs [--unpacked | --all]] [--keep-pack=] [--cruft] [--cruft-expiration=