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 <ttaylorr@openai.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Taylor Blau committed Jul 12, 2026 at 18:11 UTC 0ff1a605b864b806655421f293ba5d732d7b81a9
2 files changed +75 -2
t/helper/test-pack-deltas.c
+69
@@ -7,6 +7,7 @@
7 #include "hash.h"
8 #include "hex.h"
9 #include "pack.h"
10 +#include "packfile.h"
11 #include "pack-objects.h"
12 #include "parse-options.h"
13 #include "setup.h"
@@ -15,6 +16,7 @@
16
17 static const char *usage_str[] = {
18 "test-tool pack-deltas --num-objects <num-objects>",
19 + "test-tool pack-deltas --list-deltas <pack>.idx",
20 NULL
21 };
22
@@ -80,19 +82,86 @@ static void write_ref_delta(struct hashfile *f,
82 free(delta_buf);
83 }
84
85 +static int list_delta(const struct object_id *oid,
86 + struct packed_git *p,
87 + uint32_t pos,
88 + void *_w_curs)
89 +{
90 + struct pack_window **w_curs = _w_curs;
91 + off_t obj_offset = nth_packed_object_offset(p, pos);
92 + off_t cur = obj_offset;
93 + size_t size;
94 + enum object_type type = unpack_object_header(p, w_curs, &cur,
95 + &size);
96 +
97 + if (type < 0)
98 + die("unable to parse object at position %"PRIu32, pos);
99 + if (type != OBJ_REF_DELTA && type != OBJ_OFS_DELTA)
100 + return 0;
101 +
102 + if (type == OBJ_REF_DELTA) {
103 + struct object_id base_oid;
104 + const unsigned char *base = use_pack(p, w_curs, cur,
105 + NULL);
106 +
107 + oidread(&base_oid, base, p->repo->hash_algo);
108 + printf("%s REF_DELTA %s\n", oid_to_hex(oid),
109 + oid_to_hex(&base_oid));
110 + } else {
111 + off_t base_offset = get_delta_base(p, w_curs, &cur,
112 + type, obj_offset);
113 +
114 + if (!base_offset)
115 + die("unable to read base of object %s", oid_to_hex(oid));
116 + printf("%s OFS_DELTA %"PRIuMAX"\n", oid_to_hex(oid),
117 + (uintmax_t)base_offset);
118 + }
119 +
120 + return 0;
121 +}
122 +
123 +static void list_deltas(const char *idx_name)
124 +{
125 + struct packed_git *p;
126 + struct pack_window *w_curs = NULL;
127 +
128 + p = add_packed_git(the_repository, idx_name, strlen(idx_name), 1);
129 + if (!p || open_pack_index(p))
130 + die("unable to open pack index %s", idx_name);
131 +
132 + if (for_each_object_in_pack(p, list_delta, &w_curs,
133 + ODB_FOR_EACH_OBJECT_PACK_ORDER))
134 + die("unable to iterate over objects in %s", idx_name);
135 +
136 + unuse_pack(&w_curs);
137 + close_pack(p);
138 + free(p);
139 +}
140 +
141 int cmd__pack_deltas(int argc, const char **argv)
142 {
143 int num_objects = -1;
144 + int list_deltas_mode = 0;
145 struct hashfile *f;
146 struct strbuf line = STRBUF_INIT;
147 struct option options[] = {
148 OPT_INTEGER('n', "num-objects", &num_objects, N_("the number of objects to write")),
149 + OPT_BOOL(0, "list-deltas", &list_deltas_mode,
150 + N_("list REF_DELTA and OFS_DELTA entries")),
151 OPT_END()
152 };
153
154 argc = parse_options(argc, argv, NULL,
155 options, usage_str, 0);
156
157 + if (list_deltas_mode) {
158 + if (argc != 1 || num_objects >= 0)
159 + usage_with_options(usage_str, options);
160 + setup_git_directory(the_repository);
161 + list_deltas(argv[0]);
162 + return 0;
163 + }
164 +
165 if (argc || num_objects < 0)
166 usage_with_options(usage_str, options);
167
t/t5300-pack-object.sh
+6 -2
@@ -190,7 +190,9 @@ test_expect_success 'unpack without delta (core.fsyncmethod=batch)' '
190
191 test_expect_success 'pack with REF_DELTA' '
192 packname_2=$(git pack-objects --progress test-2 <obj-list 2>stderr) &&
193 - check_deltas stderr -gt 0
193 + check_deltas stderr -gt 0 &&
194 + test-tool pack-deltas --list-deltas test-2-$packname_2.idx >deltas &&
195 + test_grep " REF_DELTA " deltas
196 '
197
198 test_expect_success 'unpack with REF_DELTA' '
@@ -204,7 +206,9 @@ test_expect_success 'unpack with REF_DELTA (core.fsyncmethod=batch)' '
206 test_expect_success 'pack with OFS_DELTA' '
207 packname_3=$(git pack-objects --progress --delta-base-offset test-3 \
208 <obj-list 2>stderr) &&
207 - check_deltas stderr -gt 0
209 + check_deltas stderr -gt 0 &&
210 + test-tool pack-deltas --list-deltas test-3-$packname_3.idx >deltas &&
211 + test_grep " OFS_DELTA " deltas
212 '
213
214 test_expect_success 'unpack with OFS_DELTA' '