pack-bitmap-write: sort pseudo-merge commit lookup table in pack order

The pseudo-merge commit lookup table stores each commit's position in the pack- or pseudo-pack order, and is used to perform a binary search in order to determine which pseudo-merge(s) a given commit belongs to. However, the table was previously sorted in lexical order (via `oid_array_sort()`), causing the binary search to fail. While this causes pseudo-merge bitmaps to be de-facto broken for fill-in traversal, there are a couple of important points to keep in mind: * Pseudo-merge application during the initial phases of a bitmap-based traversal are applied via `cascade_pseudo_merges_1()`. This function enumerates the known pseudo-merges and determines if its parents are a subset of the traversal roots. This is a different path than the fill-in traversal, where we are looking for any pseudo-merges which may be satisfied after visiting some commit along an object walk, which involves the aforementioned (broken) binary search. As a consequence, any pseudo-merges we apply at this stage are done so correctly. * While this bug makes applying pseudo-merges during fill-in traversal effectively broken, it does not produce wrong results. Instead of applying the *wrong* pseudo-merge, we will simply fail to find satisfied pseudo-merges, leaving the traversal to use the existing fill-in routines. Fix this by sorting the table by bit position before writing, matching the order that the reader's binary search expects. This does produce a change the on-disk format insofar as the actual code now complies with the documented format (for more details, refer to: Documentation/technical/bitmap-format.adoc). Given that this never worked in the first place, such a change should be OK to perform. If an out-of-tree implementation of pseudo-merges happened to generate bitmaps that comply with the documented format, they will continue to be read and interpreted as normal. 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 ff21343a597cfa5d6cc5ea463f5941644d9bf754
2 files changed +21 -2
pack-bitmap-write.c
+20 -1
@@ -819,6 +819,20 @@ static void write_selected_commits_v1(struct bitmap_writer *writer,
819 }
820 }
821
822 +static int pseudo_merge_commit_pos_cmp(const void *_va, const void *_vb,
823 + void *_data)
824 +{
825 + struct bitmap_writer *writer = _data;
826 + uint32_t pos_a = find_object_pos(writer, _va, NULL);
827 + uint32_t pos_b = find_object_pos(writer, _vb, NULL);
828 +
829 + if (pos_a < pos_b)
830 + return -1;
831 + if (pos_a > pos_b)
832 + return 1;
833 + return 0;
834 +}
835 +
836 static void write_pseudo_merges(struct bitmap_writer *writer,
837 struct hashfile *f)
838 {
@@ -876,7 +890,12 @@ static void write_pseudo_merges(struct bitmap_writer *writer,
890 oid_array_append(&commits, &kh_key(writer->pseudo_merge_commits, i));
891 }
892
879 - oid_array_sort(&commits);
893 + /*
894 + * Sort the commits by their bit position so that the lookup
895 + * table can be binary searched by the reader (see
896 + * find_pseudo_merge()).
897 + */
898 + QSORT_S(commits.oid, commits.nr, pseudo_merge_commit_pos_cmp, writer);
899
900 /* write lookup table (non-extended) */
901 for (i = 0; i < commits.nr; i++) {
t/t5333-pseudo-merge-bitmaps.sh
+1 -1
@@ -462,7 +462,7 @@ test_expect_success 'use pseudo-merge in boundary traversal' '
462 )
463 '
464
465 -test_expect_failure 'apply pseudo-merges during fill-in traversal' '
465 +test_expect_success 'apply pseudo-merges during fill-in traversal' '
466 test_when_finished "rm -fr pseudo-merge-fill-in-traversal" &&
467 git init pseudo-merge-fill-in-traversal &&
468 (