t/helper: add 'test-tool bitmap write' subcommand

In f16eb1c091 (pseudo-merge: fix disk reads from find_pseudo_merge(), 2026-03-31), we noted that `apply_pseudo_merges_for_commit()` is never triggered by the existing test suite, and that this bears further investigation. This patch is the first one to begin that investigation. The following patches will expose and fix a variety of bugs in the implementation of pseudo-merge bitmaps. In order to do so, however, many of these tests require very precise selection of which commits receive bitmaps and which do not. To date, there isn't a standard approach to easily facilitate this. Address this by introducing a `test-tool bitmap write` subcommand that writes a bitmap for a given packfile, reading the set of commits which should receive individual bitmaps from stdin like so: test-tool bitmap write <pack-basename> </path/to/commits.list , where "<pack-basename>" is the filename for a specific packfile (e.g., "pack-abc123.pack"), and "/path/to/commits.list" is a list of commit OIDs which will receive bitmaps. The helper respects `bitmapPseudoMerge.*` configuration for creating pseudo-merge bitmaps alongside the regular commit bitmaps. Signed-off-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Taylor Blau committed May 11, 2026 at 20:46 UTC bc2f6115693874b2e8feed80ff5539743d257920
2 files changed +136 -1
t/helper/test-bitmap.c
+112 -1
@@ -2,7 +2,10 @@
2
3 #include "test-tool.h"
4 #include "git-compat-util.h"
5 +#include "hex.h"
6 +#include "odb.h"
7 #include "pack-bitmap.h"
8 +#include "pseudo-merge.h"
9 #include "setup.h"
10
11 static int bitmap_list_commits(void)
@@ -35,6 +38,111 @@ static int bitmap_dump_pseudo_merge_objects(uint32_t n)
38 return test_bitmap_pseudo_merge_objects(the_repository, n);
39 }
40
41 +static int add_packed_object(const struct object_id *oid,
42 + struct packed_git *pack,
43 + uint32_t pos,
44 + void *_data)
45 +{
46 + struct packing_data *packed = _data;
47 + struct object_entry *entry;
48 + struct object_info oi = OBJECT_INFO_INIT;
49 + enum object_type type;
50 +
51 + oi.typep = &type;
52 +
53 + entry = packlist_alloc(packed, oid);
54 + entry->idx.offset = nth_packed_object_offset(pack, pos);
55 + if (packed_object_info(pack, entry->idx.offset, &oi) < 0)
56 + die("could not get type of object %s",
57 + oid_to_hex(oid));
58 + oe_set_type(entry, type);
59 + oe_set_in_pack(packed, entry, pack);
60 +
61 + return 0;
62 +}
63 +
64 +static int idx_oid_cmp(const void *va, const void *vb)
65 +{
66 + const struct pack_idx_entry *a = *(const struct pack_idx_entry **)va;
67 + const struct pack_idx_entry *b = *(const struct pack_idx_entry **)vb;
68 +
69 + return oidcmp(&a->oid, &b->oid);
70 +}
71 +
72 +static int bitmap_write(const char *basename)
73 +{
74 + struct packed_git *p = NULL;
75 + struct packing_data packed = { 0 };
76 + struct bitmap_writer writer;
77 + struct pack_idx_entry **index;
78 + struct strbuf buf = STRBUF_INIT;
79 + uint32_t i;
80 +
81 + prepare_repo_settings(the_repository);
82 + repo_for_each_pack(the_repository, p) {
83 + if (!strcmp(pack_basename(p), basename))
84 + break;
85 + }
86 +
87 + if (!p)
88 + die("could not find pack '%s'", basename);
89 +
90 + if (open_pack_index(p))
91 + die("cannot open pack index for '%s'", p->pack_name);
92 +
93 + prepare_packing_data(the_repository, &packed);
94 +
95 + for_each_object_in_pack(p, add_packed_object, &packed,
96 + ODB_FOR_EACH_OBJECT_PACK_ORDER);
97 +
98 + /*
99 + * Build the index array now that data.packed.objects[] is
100 + * fully allocated (packlist_alloc() may have reallocated it
101 + * during the loop above).
102 + */
103 + ALLOC_ARRAY(index, p->num_objects);
104 + for (i = 0; i < p->num_objects; i++)
105 + index[i] = &packed.objects[i].idx;
106 +
107 + bitmap_writer_init(&writer, the_repository, &packed, NULL);
108 + bitmap_writer_build_type_index(&writer, index);
109 +
110 + while (strbuf_getline_lf(&buf, stdin) != EOF) {
111 + struct object_id oid;
112 + struct commit *c;
113 +
114 + if (get_oid_hex(buf.buf, &oid))
115 + die("invalid OID: %s", buf.buf);
116 +
117 + c = lookup_commit(the_repository, &oid);
118 + if (!c || repo_parse_commit(the_repository, c))
119 + die("could not parse commit %s", buf.buf);
120 +
121 + bitmap_writer_push_commit(&writer, c, 0);
122 + }
123 +
124 + select_pseudo_merges(&writer);
125 + if (bitmap_writer_build(&writer) < 0)
126 + die("failed to build bitmaps");
127 +
128 + bitmap_writer_set_checksum(&writer, p->hash);
129 +
130 + QSORT(index, p->num_objects, idx_oid_cmp);
131 +
132 + strbuf_reset(&buf);
133 + strbuf_addstr(&buf, p->pack_name);
134 + strbuf_strip_suffix(&buf, ".pack");
135 + strbuf_addstr(&buf, ".bitmap");
136 + bitmap_writer_finish(&writer, index, buf.buf, 0);
137 +
138 + bitmap_writer_free(&writer);
139 + strbuf_release(&buf);
140 + free(index);
141 + clear_packing_data(&packed);
142 +
143 + return 0;
144 +}
145 +
146 int cmd__bitmap(int argc, const char **argv)
147 {
148 setup_git_directory();
@@ -51,13 +159,16 @@ int cmd__bitmap(int argc, const char **argv)
159 return bitmap_dump_pseudo_merge_commits(atoi(argv[2]));
160 if (argc == 3 && !strcmp(argv[1], "dump-pseudo-merge-objects"))
161 return bitmap_dump_pseudo_merge_objects(atoi(argv[2]));
162 + if (argc == 3 && !strcmp(argv[1], "write"))
163 + return bitmap_write(argv[2]);
164
165 usage("\ttest-tool bitmap list-commits\n"
166 "\ttest-tool bitmap list-commits-with-offset\n"
167 "\ttest-tool bitmap dump-hashes\n"
168 "\ttest-tool bitmap dump-pseudo-merges\n"
169 "\ttest-tool bitmap dump-pseudo-merge-commits <n>\n"
60 - "\ttest-tool bitmap dump-pseudo-merge-objects <n>");
170 + "\ttest-tool bitmap dump-pseudo-merge-objects <n>\n"
171 + "\ttest-tool bitmap write <pack-basename> < <commit-list>");
172
173 return -1;
174 }
t/t5310-pack-bitmaps.sh
+24
@@ -648,4 +648,28 @@ test_expect_success 'truncated bitmap fails gracefully (lookup table)' '
648 test_grep corrupted.bitmap.index stderr
649 '
650
651 +test_expect_success 'test-tool bitmap write determines bitmap selection' '
652 + test_when_finished "rm -fr bitmap-write-helper" &&
653 + git init bitmap-write-helper &&
654 + (
655 + cd bitmap-write-helper &&
656 +
657 + test_commit_bulk 64 &&
658 + git repack -ad &&
659 +
660 + pack="$(ls .git/objects/pack/pack-*.pack)" &&
661 +
662 + git rev-parse HEAD >in &&
663 + test-tool bitmap write "$(basename $pack)" <in &&
664 +
665 + test-tool bitmap list-commits >bitmaps.raw &&
666 + sort bitmaps.raw >bitmaps &&
667 + test_cmp in bitmaps &&
668 +
669 + git rev-list --count --objects --use-bitmap-index HEAD >actual &&
670 + git rev-list --count --objects HEAD >expect &&
671 + test_cmp expect actual
672 + )
673 +'
674 +
675 test_done