From: Patrick Steinhardt Date: Fri, 30 Jan 2026 13:27:43 GMT Subject: [PATCH v2 2/4] pack-bitmap: fix bug with exact ref match in "pack.preferBitmapTips" Message-ID: <20260130-b4-pks-fix-for-each-ref-in-misuse-v2-2-0449b198a681@pks.im> In-Reply-To: <20260130-b4-pks-fix-for-each-ref-in-misuse-v2-0-0449b198a681@pks.im> The "pack.preferBitmapTips" configuration allows the user to specify which references should be preferred when generating bitmaps. This option is typically expected to be set to a reference prefix, like for example "refs/heads/". It's not unreasonable though for a user to configure one specific reference as preferred. But if they do, they'll hit a `BUG()`: $ git -c pack.preferBitmapTips=refs/heads/main repack -adb BUG: ../refs/iterator.c:366: attempt to trim too many characters error: pack-objects died of signal 6 The root cause for this bug is how we enumerate these references. We call `refs_for_each_ref_in()`, which will: - Yield all references that have a user-specified prefix. - Trim each of these references so that the prefix is removed. Typically, this function is called with a trailing slash, like "refs/heads/", and in that case things work alright. But if the function is called with the name of an existing reference then we'll try to trim the full reference name, which would leave us with an empty name. And as this would not really leave us with anything sensible, we call `BUG()` instead of yielding this reference. One could argue that this is a bug in `refs_for_each_ref_in()`. But the question then becomes what the correct behaviour would be: - Do we want to skip exact matches? In our case we certainly don't want that, as the user has asked us to generate a bitmap for it. - Do we want to yield the reference with the empty refname? That would lead to a somewhat weird result. Neither of these feel like viable options, so calling `BUG()` feels like a sensible way out. The root cause ultimately is that we even try to trim the whole refname in the first place. There are two possible ways to fix this issue: - We can fix the bug by using `refs_for_each_fullref_in()` instead, which does not strip the prefix at all. Consequently, we would now start to accept all references that start with the configured prefix, including exact matches. So if we had "refs/heads/main", we would both match "refs/heads/main" and "refs/heads/main-branch". - Or we can fix the bug by appending a slash to the prefix if it doesn't already have one. This would mean that we only match ref hierarchies that start with this prefix. The first fix leaves the user with strictly _more_ configuration options: they can have prefix matches by not appending a slash to the configuration, and they can have ref hierarchy matches by appending one. Apply this fix and clarify the documentation accordingly. Signed-off-by: Patrick Steinhardt --- Documentation/config/pack.adoc | 7 +++---- pack-bitmap.c | 4 ++-- t/t5310-pack-bitmaps.sh | 35 +++++++++++++++++++++++++++++++++++ t/t5319-multi-pack-index.sh | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/Documentation/config/pack.adoc b/Documentation/config/pack.adoc index 75402d5579..929d781552 100644 --- a/Documentation/config/pack.adoc +++ b/Documentation/config/pack.adoc @@ -161,11 +161,10 @@ pack.usePathWalk:: pack.preferBitmapTips:: When selecting which commits will receive bitmaps, prefer a - commit at the tip of any reference that is a suffix of any value - of this configuration over any other commits in the "selection - window". + commmit at the tip of a reference that matches any of the + configured prefixes. + -Note that setting this configuration to `refs/foo` does not mean that +Note that setting this configuration to `refs/foo/` does not mean that the commits at the tips of `refs/foo/bar` and `refs/foo/baz` will necessarily be selected. This is because commits are selected for bitmaps from within a series of windows of variable length. diff --git a/pack-bitmap.c b/pack-bitmap.c index 2f5cb34009..8d3b5ac037 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -3334,8 +3334,8 @@ void for_each_preferred_bitmap_tip(struct repository *repo, return; for_each_string_list_item(item, preferred_tips) { - refs_for_each_ref_in(get_main_ref_store(repo), - item->string, cb, cb_data); + refs_for_each_fullref_in(get_main_ref_store(repo), + item->string, NULL, cb, cb_data); } } diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index 6718fb98c0..7ef91b502c 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -466,6 +466,41 @@ test_bitmap_cases () { ) ' + test_expect_success 'pack.preferBitmapTips can use direct refname' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + # Create enough commits that not all will receive bitmap + # coverage even if they are all at the tip of some reference. + test_commit_bulk --message="%s" 103 && + git log --format="create refs/tags/%s %H" HEAD >refs && + git update-ref --stdin commits-with-bitmap && + + # Verify that we have at least one commit that did not + # receive a bitmap. + git rev-list HEAD >commits.raw && + sort commits && + comm -13 commits-with-bitmap commits >commits-wo-bitmap && + test_file_not_empty commits-wo-bitmap && + commit_id=$(head commits-wo-bitmap) && + + # We now create a reference for this commit and repack + # with "preferBitmapTips" pointing to that exact + # reference. The expectation is that it will now be + # covered by a bitmap. + git update-ref refs/heads/cover-me "$commit_id" && + git -c pack.preferBitmapTips=refs/heads/cover-me repack -adb && + test-tool bitmap list-commits >after && + test_grep "$commit_id" after + ) + ' + test_expect_success 'complains about multiple pack bitmaps' ' rm -fr repo && git init repo && diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index faae98c7e7..40d36118bd 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -1345,4 +1345,40 @@ test_expect_success 'bitmapped packs are stored via the BTMP chunk' ' ) ' +test_expect_success 'pack.preferBitmapTips can use direct refname' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + # Create enough commits that not all will receive bitmap + # coverage even if they are all at the tip of some reference. + test_commit_bulk --message="%s" 103 && + git log --format="create refs/tags/%s %H" HEAD >refs && + git update-ref --stdin commits-with-bitmap && + + # Verify that we have at least one commit that did not + # receive a bitmap. + git rev-list HEAD >commits.raw && + sort commits && + comm -13 commits-with-bitmap commits >commits-wo-bitmap && + test_file_not_empty commits-wo-bitmap && + commit_id=$(head commits-wo-bitmap) && + + # We now create a reference for this commit and repack + # with "preferBitmapTips" pointing to that exact + # reference. The expectation is that it will now be + # covered by a bitmap. + git update-ref refs/heads/cover-me "$commit_id" && + rm .git/objects/pack/multi-pack-index* && + git -c pack.preferBitmapTips=refs/heads/cover-me repack -adb --write-midx && + test-tool bitmap list-commits >after && + test_grep "$commit_id" after + ) +' + test_done -- 2.53.0.rc2.206.g60c1bca835.dirty