From: Taylor Blau <me@ttaylorr.com>
To: git@vger.kernel.org
Cc: Junio C Hamano <gitster@pobox.com>, Jeff King <peff@peff.net>,
Elijah Newren <newren@gmail.com>
Subject: [PATCH v2 0/9] pack-bitmap: fix various pseudo-merge bugs
Date: Tue, 21 Apr 2026 16:01:52 -0400 [thread overview]
Message-ID: <cover.1776801694.git.me@ttaylorr.com> (raw)
In-Reply-To: <cover.1776124588.git.me@ttaylorr.com>
[Note to the maintainer: this series has been rebased onto the current
tip of master, which is 94f057755b7 (Git 2.54, 2026-04-19) at the time
of writing.]
This is a small reroll of my series to fix several bugs in the
pseudo-merge bitmap implementation. The main changes since last time
are:
- Fixed a use-after-realloc bug in the test helper introduced in the
first commit.
- Swapped the order of initializing and cleaning up repositories in the
new test scripts.
- Updated bitmapPseudoMerge.<name>.sampleRate's documentation to
describe the range as (0,1], and added a new commit fixing a broken
example in gitpacking(7).
As usual, a range-diff is included below for convenience. The original
cover letter is as follows:
========================================================================
This series fixes several bugs in the pseudo-merge bitmap implementation
that caused the pseudo-merge application path to be effectively broken
during fill-in traversal.
Peff noticed that this code path was never triggered by the existing
test suite, and investigating that observation uncovered a handful of
bugs, some compounding.
The first two patches introduce test infrastructure: a 'bitmap write'
test helper that gives tests precise control over which commits receive
individual bitmaps, and a set of "test_expect_failure" tests
demonstrating each bug.
The next four patches fix the bugs in the per-commit pseudo-merge
lookup:
- The pseudo-merge commit lookup table was sorted by OID rather than
by bit position, causing the reader's binary search to fail.
- The binary search in pseudo_merge_at() had its lo/hi updates
swapped.
- The extended pseudo-merge lookup path had three compounding bugs: a
wrong entry-size calculation in the writer, a misinterpretation of
extended table entries in the reader, and a silently-swallowed error
check.
The final two patches fix issues in pseudo-merge group selection:
- find_pseudo_merge_group_for_ref() did not parse commits before
inspecting their dates, so all candidates had date == 0 and were
unconditionally placed in the "stable" bucket.
- The config validation for bitmapPseudoMerge.*.sampleRate accepted 0,
which leads to a division by zero once the date classification is
fixed and the unstable code path is exercised.
There is also a small fix for a regex leak when the pattern key is
overridden in config.
Thanks in advance for your review!
Taylor Blau (9):
t/helper: add 'test-tool bitmap write' subcommand
t5333: demonstrate various pseudo-merge bugs
pack-bitmap-write: sort pseudo-merge commit lookup table in pack order
pack-bitmap: fix inverted binary search in `pseudo_merge_at()`
pack-bitmap: fix pseudo-merge lookup for shared commits
pack-bitmap: parse commits in `find_pseudo_merge_group_for_ref()`
pack-bitmap: reject pseudo-merge "sampleRate" of 0
Documentation: fix broken `sampleRate` in gitpacking(7)
pack-bitmap: prevent pattern leak on pseudo-merge re-assignment
Documentation/config/bitmap-pseudo-merge.adoc | 4 +-
Documentation/gitpacking.adoc | 4 +-
pack-bitmap-write.c | 23 +-
pseudo-merge.c | 19 +-
t/helper/test-bitmap.c | 113 ++++++++-
t/t5310-pack-bitmaps.sh | 24 ++
t/t5333-pseudo-merge-bitmaps.sh | 231 ++++++++++++++++++
7 files changed, 404 insertions(+), 14 deletions(-)
Range-diff against v1:
1: d5ef6b959fd ! 1: c0df35f8ebd t/helper: add 'test-tool bitmap write' subcommand
@@ t/helper/test-bitmap.c: static int bitmap_dump_pseudo_merge_objects(uint32_t n)
return test_bitmap_pseudo_merge_objects(the_repository, n);
}
-+struct bitmap_writer_data {
-+ struct packing_data packed;
-+ struct pack_idx_entry **index;
-+ uint32_t nr;
-+};
-+
+static int add_packed_object(const struct object_id *oid,
+ struct packed_git *pack,
+ uint32_t pos,
+ void *_data)
+{
-+ struct bitmap_writer_data *data = _data;
++ struct packing_data *packed = _data;
+ struct object_entry *entry;
+ struct object_info oi = OBJECT_INFO_INIT;
+ enum object_type type;
+
+ oi.typep = &type;
+
-+ entry = packlist_alloc(&data->packed, oid);
++ entry = packlist_alloc(packed, oid);
+ entry->idx.offset = nth_packed_object_offset(pack, pos);
+ if (packed_object_info(pack, entry->idx.offset, &oi) < 0)
+ die("could not get type of object %s",
+ oid_to_hex(oid));
+ oe_set_type(entry, type);
-+ oe_set_in_pack(&data->packed, entry, pack);
-+ data->index[data->nr++] = &entry->idx;
++ oe_set_in_pack(packed, entry, pack);
+
+ return 0;
+}
@@ t/helper/test-bitmap.c: static int bitmap_dump_pseudo_merge_objects(uint32_t n)
+static int bitmap_write(const char *basename)
+{
+ struct packed_git *p = NULL;
-+ struct bitmap_writer_data data = { 0 };
++ struct packing_data packed = { 0 };
+ struct bitmap_writer writer;
++ struct pack_idx_entry **index;
+ struct strbuf buf = STRBUF_INIT;
++ uint32_t i;
+
+ prepare_repo_settings(the_repository);
+ repo_for_each_pack(the_repository, p) {
@@ t/helper/test-bitmap.c: static int bitmap_dump_pseudo_merge_objects(uint32_t n)
+ if (open_pack_index(p))
+ die("cannot open pack index for '%s'", p->pack_name);
+
-+ prepare_packing_data(the_repository, &data.packed);
-+ ALLOC_ARRAY(data.index, p->num_objects);
++ prepare_packing_data(the_repository, &packed);
+
-+ for_each_object_in_pack(p, add_packed_object, &data,
++ for_each_object_in_pack(p, add_packed_object, &packed,
+ ODB_FOR_EACH_OBJECT_PACK_ORDER);
+
-+ bitmap_writer_init(&writer, the_repository, &data.packed, NULL);
-+ bitmap_writer_build_type_index(&writer, data.index);
++ /*
++ * Build the index array now that data.packed.objects[] is
++ * fully allocated (packlist_alloc() may have reallocated it
++ * during the loop above).
++ */
++ ALLOC_ARRAY(index, p->num_objects);
++ for (i = 0; i < p->num_objects; i++)
++ index[i] = &packed.objects[i].idx;
++
++ bitmap_writer_init(&writer, the_repository, &packed, NULL);
++ bitmap_writer_build_type_index(&writer, index);
+
+ while (strbuf_getline_lf(&buf, stdin) != EOF) {
+ struct object_id oid;
@@ t/helper/test-bitmap.c: static int bitmap_dump_pseudo_merge_objects(uint32_t n)
+ if (!c || repo_parse_commit(the_repository, c))
+ die("could not parse commit %s", buf.buf);
+
-+ bitmap_writer_push_commit(&writer, c, false);
++ bitmap_writer_push_commit(&writer, c, 0);
+ }
+
+ select_pseudo_merges(&writer);
@@ t/helper/test-bitmap.c: static int bitmap_dump_pseudo_merge_objects(uint32_t n)
+
+ bitmap_writer_set_checksum(&writer, p->hash);
+
-+ QSORT(data.index, p->num_objects, idx_oid_cmp);
++ QSORT(index, p->num_objects, idx_oid_cmp);
+
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, p->pack_name);
+ strbuf_strip_suffix(&buf, ".pack");
+ strbuf_addstr(&buf, ".bitmap");
-+ bitmap_writer_finish(&writer, data.index, buf.buf, 0);
++ bitmap_writer_finish(&writer, index, buf.buf, 0);
+
+ bitmap_writer_free(&writer);
+ strbuf_release(&buf);
-+ free(data.index);
-+ clear_packing_data(&data.packed);
++ free(index);
++ clear_packing_data(&packed);
+
+ return 0;
+}
@@ t/t5310-pack-bitmaps.sh: test_expect_success 'truncated bitmap fails gracefully
test_grep corrupted.bitmap.index stderr
'
-+test_expect_success 'test-tool bitmap write' '
-+ git init bitmap-write-helper &&
++test_expect_success 'test-tool bitmap write determines bitmap selection' '
+ test_when_finished "rm -fr bitmap-write-helper" &&
++ git init bitmap-write-helper &&
+ (
+ cd bitmap-write-helper &&
+
@@ t/t5310-pack-bitmaps.sh: test_expect_success 'truncated bitmap fails gracefully
+
+ pack="$(ls .git/objects/pack/pack-*.pack)" &&
+
-+ git rev-parse HEAD >commits &&
-+ test-tool bitmap write "$(basename $pack)" <commits &&
++ git rev-parse HEAD >in &&
++ test-tool bitmap write "$(basename $pack)" <in &&
+
-+ test-tool bitmap list-commits | sort >actual &&
-+ sort commits >expect &&
-+ test_cmp expect actual &&
++ test-tool bitmap list-commits >bitmaps.raw &&
++ sort bitmaps.raw >bitmaps &&
++ test_cmp in bitmaps &&
+
+ git rev-list --count --objects --use-bitmap-index HEAD >actual &&
+ git rev-list --count --objects HEAD >expect &&
2: f4899b668e2 ! 2: 11de3343726 t5333: demonstrate various pseudo-merge bugs
@@ t/t5333-pseudo-merge-bitmaps.sh: test_expect_success 'use pseudo-merge in bounda
'
+test_expect_failure 'apply pseudo-merges during fill-in traversal' '
-+ git init pseudo-merge-fill-in-traversal &&
+ test_when_finished "rm -fr pseudo-merge-fill-in-traversal" &&
++ git init pseudo-merge-fill-in-traversal &&
+ (
+ cd pseudo-merge-fill-in-traversal &&
+
@@ t/t5333-pseudo-merge-bitmaps.sh: test_expect_success 'use pseudo-merge in bounda
+'
+
+test_expect_failure 'apply pseudo-merges from multiple groups during fill-in' '
-+ git init pseudo-merge-fill-in-multi &&
+ test_when_finished "rm -fr pseudo-merge-fill-in-multi" &&
++ git init pseudo-merge-fill-in-multi &&
+ (
+ cd pseudo-merge-fill-in-multi &&
+
@@ t/t5333-pseudo-merge-bitmaps.sh: test_expect_success 'use pseudo-merge in bounda
+'
+
+test_expect_failure 'pseudo-merge commits are correctly classified by date' '
-+ git init pseudo-merge-date-classification &&
+ test_when_finished "rm -fr pseudo-merge-date-classification" &&
++ git init pseudo-merge-date-classification &&
+ (
+ cd pseudo-merge-date-classification &&
+
@@ t/t5333-pseudo-merge-bitmaps.sh: test_expect_success 'use pseudo-merge in bounda
+'
+
+test_expect_success 'sampleRate=0 does not cause division by zero' '
-+ git init pseudo-merge-sample-rate-zero &&
+ test_when_finished "rm -fr pseudo-merge-sample-rate-zero" &&
++ git init pseudo-merge-sample-rate-zero &&
+ (
+ cd pseudo-merge-sample-rate-zero &&
+
3: 1f5835e8c62 ! 3: 8d908ab415e pack-bitmap-write: sort pseudo-merge commit lookup table in pack order
@@ t/t5333-pseudo-merge-bitmaps.sh: test_expect_success 'use pseudo-merge in bounda
-test_expect_failure 'apply pseudo-merges during fill-in traversal' '
+test_expect_success 'apply pseudo-merges during fill-in traversal' '
- git init pseudo-merge-fill-in-traversal &&
test_when_finished "rm -fr pseudo-merge-fill-in-traversal" &&
+ git init pseudo-merge-fill-in-traversal &&
(
4: af9f651269d ! 4: 07f70a07c20 pack-bitmap: fix inverted binary search in `pseudo_merge_at()`
@@ t/t5333-pseudo-merge-bitmaps.sh: test_expect_success 'apply pseudo-merges during
-test_expect_failure 'apply pseudo-merges from multiple groups during fill-in' '
+test_expect_success 'apply pseudo-merges from multiple groups during fill-in' '
- git init pseudo-merge-fill-in-multi &&
test_when_finished "rm -fr pseudo-merge-fill-in-multi" &&
+ git init pseudo-merge-fill-in-multi &&
++ git init pseudo-merge-fill-in-multi &&
(
+ cd pseudo-merge-fill-in-multi &&
+
5: 01f1d6f08c6 = 5: 3ed0b39843f pack-bitmap: fix pseudo-merge lookup for shared commits
6: 6d74c0a177a ! 6: 95f847211f3 pack-bitmap: parse commits in `find_pseudo_merge_group_for_ref()`
@@ t/t5333-pseudo-merge-bitmaps.sh: test_expect_success 'apply pseudo-merges with o
-test_expect_failure 'pseudo-merge commits are correctly classified by date' '
+test_expect_success 'pseudo-merge commits are correctly classified by date' '
- git init pseudo-merge-date-classification &&
test_when_finished "rm -fr pseudo-merge-date-classification" &&
+ git init pseudo-merge-date-classification &&
(
cd pseudo-merge-date-classification &&
@@ t/t5333-pseudo-merge-bitmaps.sh: test_expect_failure 'pseudo-merge commits are c
-test_expect_success 'sampleRate=0 does not cause division by zero' '
+test_expect_failure 'sampleRate=0 does not cause division by zero' '
- git init pseudo-merge-sample-rate-zero &&
test_when_finished "rm -fr pseudo-merge-sample-rate-zero" &&
+ git init pseudo-merge-sample-rate-zero &&
(
7: 1b0f7295c21 ! 7: f8a01cfb893 pack-bitmap: reject pseudo-merge "sampleRate" of 0
@@ Commit message
Signed-off-by: Taylor Blau <me@ttaylorr.com>
+ ## Documentation/config/bitmap-pseudo-merge.adoc ##
+@@ Documentation/config/bitmap-pseudo-merge.adoc: will be updated more often than a reference pointing at an old commit.
+ bitmapPseudoMerge.<name>.sampleRate::
+ Determines the proportion of non-bitmapped commits (among
+ reference tips) which are selected for inclusion in an
+- unstable pseudo-merge bitmap. Must be between `0` and `1`
+- (inclusive). The default is `1`.
++ unstable pseudo-merge bitmap. Must be greater than `0` and
++ less than or equal to `1`. The default is `1`.
+
+ bitmapPseudoMerge.<name>.threshold::
+ Determines the minimum age of non-bitmapped commits (among
+
## pseudo-merge.c ##
@@ pseudo-merge.c: static int pseudo_merge_config(const char *var, const char *value,
}
@@ t/t5333-pseudo-merge-bitmaps.sh: test_expect_success 'pseudo-merge commits are c
-test_expect_failure 'sampleRate=0 does not cause division by zero' '
+test_expect_success 'sampleRate=0 does not cause division by zero' '
- git init pseudo-merge-sample-rate-zero &&
test_when_finished "rm -fr pseudo-merge-sample-rate-zero" &&
+ git init pseudo-merge-sample-rate-zero &&
(
-: ----------- > 8: c37156502c0 Documentation: fix broken `sampleRate` in gitpacking(7)
8: 8f4e0170952 = 9: b905fd5d0ae pack-bitmap: prevent pattern leak on pseudo-merge re-assignment
base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
--
2.54.0.9.gb905fd5d0ae
next prev parent reply other threads:[~2026-04-21 20:01 UTC|newest]
Thread overview: 46+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-13 23:56 [PATCH 0/8] pack-bitmap: fix various pseudo-merge bugs Taylor Blau
2026-04-13 23:56 ` [PATCH 1/8] t/helper: add 'test-tool bitmap write' subcommand Taylor Blau
2026-04-14 19:48 ` Junio C Hamano
2026-04-14 21:29 ` Taylor Blau
2026-04-14 21:34 ` Junio C Hamano
2026-04-14 21:40 ` Taylor Blau
2026-04-14 20:08 ` Junio C Hamano
2026-04-14 21:40 ` Taylor Blau
2026-04-19 0:24 ` Elijah Newren
2026-04-21 18:51 ` Taylor Blau
2026-04-13 23:56 ` [PATCH 2/8] t5333: demonstrate various pseudo-merge bugs Taylor Blau
2026-04-19 0:25 ` Elijah Newren
2026-04-13 23:56 ` [PATCH 3/8] pack-bitmap-write: sort pseudo-merge commit lookup table in pack order Taylor Blau
2026-04-13 23:56 ` [PATCH 4/8] pack-bitmap: fix inverted binary search in `pseudo_merge_at()` Taylor Blau
2026-04-13 23:56 ` [PATCH 5/8] pack-bitmap: fix pseudo-merge lookup for shared commits Taylor Blau
2026-04-13 23:56 ` [PATCH 6/8] pack-bitmap: parse commits in `find_pseudo_merge_group_for_ref()` Taylor Blau
2026-04-13 23:56 ` [PATCH 7/8] pack-bitmap: reject pseudo-merge "sampleRate" of 0 Taylor Blau
2026-04-19 0:26 ` Elijah Newren
2026-04-13 23:57 ` [PATCH 8/8] pack-bitmap: prevent pattern leak on pseudo-merge re-assignment Taylor Blau
2026-04-21 20:01 ` Taylor Blau [this message]
2026-04-21 20:01 ` [PATCH v2 1/9] t/helper: add 'test-tool bitmap write' subcommand Taylor Blau
2026-04-21 20:01 ` [PATCH v2 2/9] t5333: demonstrate various pseudo-merge bugs Taylor Blau
2026-04-21 20:02 ` [PATCH v2 3/9] pack-bitmap-write: sort pseudo-merge commit lookup table in pack order Taylor Blau
2026-04-21 20:02 ` [PATCH v2 4/9] pack-bitmap: fix inverted binary search in `pseudo_merge_at()` Taylor Blau
2026-04-21 20:02 ` [PATCH v2 5/9] pack-bitmap: fix pseudo-merge lookup for shared commits Taylor Blau
2026-04-21 20:02 ` [PATCH v2 6/9] pack-bitmap: parse commits in `find_pseudo_merge_group_for_ref()` Taylor Blau
2026-04-21 20:02 ` [PATCH v2 7/9] pack-bitmap: reject pseudo-merge "sampleRate" of 0 Taylor Blau
2026-04-21 20:02 ` [PATCH v2 8/9] Documentation: fix broken `sampleRate` in gitpacking(7) Taylor Blau
2026-04-21 20:02 ` [PATCH v2 9/9] pack-bitmap: prevent pattern leak on pseudo-merge re-assignment Taylor Blau
2026-04-22 1:37 ` [PATCH v2 0/9] pack-bitmap: fix various pseudo-merge bugs Elijah Newren
2026-05-11 2:53 ` Junio C Hamano
2026-05-12 0:48 ` Taylor Blau
2026-05-12 0:10 ` Taylor Blau
2026-05-12 0:46 ` [PATCH v3 " Taylor Blau
2026-05-12 0:46 ` [PATCH v3 1/9] t/helper: add 'test-tool bitmap write' subcommand Taylor Blau
2026-05-12 0:46 ` [PATCH v3 2/9] t5333: demonstrate various pseudo-merge bugs Taylor Blau
2026-05-12 0:46 ` [PATCH v3 3/9] pack-bitmap-write: sort pseudo-merge commit lookup table in pack order Taylor Blau
2026-05-12 0:46 ` [PATCH v3 4/9] pack-bitmap: fix inverted binary search in `pseudo_merge_at()` Taylor Blau
2026-05-12 0:47 ` [PATCH v3 5/9] pack-bitmap: fix pseudo-merge lookup for shared commits Taylor Blau
2026-05-12 0:47 ` [PATCH v3 6/9] pack-bitmap: parse commits in `find_pseudo_merge_group_for_ref()` Taylor Blau
2026-05-12 0:47 ` [PATCH v3 7/9] pack-bitmap: reject pseudo-merge "sampleRate" of 0 Taylor Blau
2026-05-12 0:47 ` [PATCH v3 8/9] Documentation: fix broken `sampleRate` in gitpacking(7) Taylor Blau
2026-05-12 0:47 ` [PATCH v3 9/9] pack-bitmap: prevent pattern leak on pseudo-merge re-assignment Taylor Blau
2026-05-12 1:38 ` [PATCH v3 0/9] pack-bitmap: fix various pseudo-merge bugs Junio C Hamano
2026-05-12 1:46 ` Taylor Blau
2026-05-12 1:49 ` Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cover.1776801694.git.me@ttaylorr.com \
--to=me@ttaylorr.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=newren@gmail.com \
--cc=peff@peff.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox