Merge branch 'ps/for-each-ref-in-fixes' into jch

A handful of places used refs_for_each_ref_in() API incorrectly,
which has been corrected.

* ps/for-each-ref-in-fixes:
  bisect: simplify string_list memory handling
  bisect: fix misuse of `refs_for_each_ref_in()`
  pack-bitmap: fix bug with exact ref match in "pack.preferBitmapTips"
  pack-bitmap: deduplicate logic to iterate over preferred bitmap tips
This commit is contained in:
Junio C Hamano
2026-02-23 14:25:43 -08:00
8 changed files with 137 additions and 43 deletions

View File

@@ -160,12 +160,13 @@ pack.usePathWalk::
processes. See linkgit:git-pack-objects[1] for full details.
pack.preferBitmapTips::
Specifies a ref hierarchy (e.g., "refs/heads/"); can be
given multiple times to specify more than one hierarchies.
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".
commit at the tip of a reference that is contained in any of
the configured hierarchies.
+
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.

View File

@@ -1180,8 +1180,7 @@ int estimate_bisect_steps(int all)
static int mark_for_removal(const struct reference *ref, void *cb_data)
{
struct string_list *refs = cb_data;
char *bisect_ref = xstrfmt("refs/bisect%s", ref->name);
string_list_append(refs, bisect_ref);
string_list_append(refs, ref->name);
return 0;
}
@@ -1190,16 +1189,15 @@ int bisect_clean_state(void)
int result = 0;
/* There may be some refs packed during bisection */
struct string_list refs_for_removal = STRING_LIST_INIT_NODUP;
refs_for_each_ref_in(get_main_ref_store(the_repository),
"refs/bisect", mark_for_removal,
(void *) &refs_for_removal);
string_list_append(&refs_for_removal, xstrdup("BISECT_HEAD"));
string_list_append(&refs_for_removal, xstrdup("BISECT_EXPECTED_REV"));
struct string_list refs_for_removal = STRING_LIST_INIT_DUP;
refs_for_each_fullref_in(get_main_ref_store(the_repository),
"refs/bisect/", NULL, mark_for_removal,
&refs_for_removal);
string_list_append(&refs_for_removal, "BISECT_HEAD");
string_list_append(&refs_for_removal, "BISECT_EXPECTED_REV");
result = refs_delete_refs(get_main_ref_store(the_repository),
"bisect: remove", &refs_for_removal,
REF_NO_DEREF);
refs_for_removal.strdup_strings = 1;
string_list_clear(&refs_for_removal, 0);
unlink_or_warn(git_path_bisect_ancestors_ok());
unlink_or_warn(git_path_bisect_log());

View File

@@ -4561,22 +4561,6 @@ static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNU
return 0;
}
static void mark_bitmap_preferred_tips(void)
{
struct string_list_item *item;
const struct string_list *preferred_tips;
preferred_tips = bitmap_preferred_tips(the_repository);
if (!preferred_tips)
return;
for_each_string_list_item(item, preferred_tips) {
refs_for_each_ref_in(get_main_ref_store(the_repository),
item->string, mark_bitmap_preferred_tip,
NULL);
}
}
static inline int is_oid_uninteresting(struct repository *repo,
struct object_id *oid)
{
@@ -4717,7 +4701,8 @@ static void get_object_list(struct rev_info *revs, struct strvec *argv)
load_delta_islands(the_repository, progress);
if (write_bitmap_index)
mark_bitmap_preferred_tips();
for_each_preferred_bitmap_tip(the_repository, mark_bitmap_preferred_tip,
NULL);
if (!fn_show_object)
fn_show_object = show_object;

View File

@@ -3314,7 +3314,7 @@ int bitmap_is_midx(struct bitmap_index *bitmap_git)
return !!bitmap_git->midx;
}
const struct string_list *bitmap_preferred_tips(struct repository *r)
static const struct string_list *bitmap_preferred_tips(struct repository *r)
{
const struct string_list *dest;
@@ -3323,6 +3323,33 @@ const struct string_list *bitmap_preferred_tips(struct repository *r)
return NULL;
}
void for_each_preferred_bitmap_tip(struct repository *repo,
each_ref_fn cb, void *cb_data)
{
struct string_list_item *item;
const struct string_list *preferred_tips;
struct strbuf buf = STRBUF_INIT;
preferred_tips = bitmap_preferred_tips(repo);
if (!preferred_tips)
return;
for_each_string_list_item(item, preferred_tips) {
const char *pattern = item->string;
if (!ends_with(pattern, "/")) {
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/", pattern);
pattern = buf.buf;
}
refs_for_each_ref_in(get_main_ref_store(repo),
pattern, cb, cb_data);
}
strbuf_release(&buf);
}
int bitmap_is_preferred_refname(struct repository *r, const char *refname)
{
const struct string_list *preferred_tips = bitmap_preferred_tips(r);

View File

@@ -5,6 +5,7 @@
#include "khash.h"
#include "pack.h"
#include "pack-objects.h"
#include "refs.h"
#include "string-list.h"
struct commit;
@@ -99,6 +100,13 @@ int for_each_bitmapped_object(struct bitmap_index *bitmap_git,
show_reachable_fn show_reach,
void *payload);
/*
* Iterate over all references that are configured as preferred bitmap tips via
* "pack.preferBitmapTips" and invoke the callback on each function.
*/
void for_each_preferred_bitmap_tip(struct repository *repo,
each_ref_fn cb, void *cb_data);
#define GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL \
"GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL"
@@ -182,7 +190,6 @@ char *pack_bitmap_filename(struct packed_git *p);
int bitmap_is_midx(struct bitmap_index *bitmap_git);
const struct string_list *bitmap_preferred_tips(struct repository *r);
int bitmap_is_preferred_refname(struct repository *r, const char *refname);
int verify_bitmap_files(struct repository *r);

View File

@@ -40,7 +40,6 @@ static int midx_snapshot_ref_one(const struct reference *ref, void *_data)
void midx_snapshot_refs(struct repository *repo, struct tempfile *f)
{
struct midx_snapshot_ref_data data;
const struct string_list *preferred = bitmap_preferred_tips(repo);
data.repo = repo;
data.f = f;
@@ -51,16 +50,9 @@ void midx_snapshot_refs(struct repository *repo, struct tempfile *f)
die(_("could not open tempfile %s for writing"),
get_tempfile_path(f));
if (preferred) {
struct string_list_item *item;
data.preferred = 1;
for_each_string_list_item(item, preferred)
refs_for_each_ref_in(get_main_ref_store(repo),
item->string,
midx_snapshot_ref_one, &data);
data.preferred = 0;
}
data.preferred = 1;
for_each_preferred_bitmap_tip(repo, midx_snapshot_ref_one, &data);
data.preferred = 0;
refs_for_each_ref(get_main_ref_store(repo),
midx_snapshot_ref_one, &data);

View File

@@ -466,6 +466,47 @@ test_bitmap_cases () {
)
'
test_expect_success 'pack.preferBitmapTips interprets patterns as hierarchy' '
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/tag %H" HEAD >refs &&
git update-ref --stdin <refs &&
# Create the bitmap.
git repack -adb &&
test-tool bitmap list-commits | sort >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.raw >commits &&
comm -13 commits-with-bitmap commits >commits-wo-bitmap &&
test_file_not_empty commits-wo-bitmap &&
commit_id=$(head commits-wo-bitmap) &&
ref_without_bitmap=$(git for-each-ref --points-at="$commit_id" --format="%(refname)") &&
# When passing the full refname we do not expect a
# bitmap to be generated, as it should be interpreted
# as if a slash was appended to the pattern.
git -c pack.preferBitmapTips="$ref_without_bitmap" repack -adb &&
test-tool bitmap list-commits >after &&
test_grep ! "$commit_id" after &&
# But if we pass the parent directory of the ref we
# should see a bitmap.
ref_namespace=$(dirname "$ref_without_bitmap") &&
git -c pack.preferBitmapTips="$ref_namespace" 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 &&

View File

@@ -1345,4 +1345,47 @@ test_expect_success 'bitmapped packs are stored via the BTMP chunk' '
)
'
test_expect_success 'pack.preferBitmapTips interprets patterns as hierarchy' '
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 <refs &&
# Create the bitmap via the MIDX.
git repack -adb --write-midx &&
test-tool bitmap list-commits | sort >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.raw >commits &&
comm -13 commits-with-bitmap commits >commits-wo-bitmap &&
test_file_not_empty commits-wo-bitmap &&
commit_id=$(head commits-wo-bitmap) &&
ref_without_bitmap=$(git for-each-ref --points-at="$commit_id" --format="%(refname)") &&
# When passing the full refname we do not expect a bitmap to be
# generated, as it should be interpreted as if a slash was
# appended to the pattern.
rm .git/objects/pack/multi-pack-index* &&
git -c pack.preferBitmapTips="$ref_without_bitmap" repack -adb --write-midx &&
test-tool bitmap list-commits >after &&
test_grep ! "$commit_id" after &&
# But if we pass the parent directory of the ref we should see
# a bitmap.
ref_namespace=$(dirname "$ref_without_bitmap") &&
rm .git/objects/pack/multi-pack-index* &&
git -c pack.preferBitmapTips="$ref_namespace" repack -adb --write-midx &&
test-tool bitmap list-commits >after &&
test_grep "$commit_id" after
)
'
test_done