| Age | Commit message (Collapse) | Author | Files | Lines |
|
Move the logic for formatting bool-or-string config values into a
helper. This parsing has always been gentle, so this is not unlocking
new behavior. This extraction is only to match the formatting of the
other cases that do need a behavior change.
Signed-off-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Move the logic for formatting bool-or-int config values into a helper
method and use gentle parsing when needed.
Signed-off-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Move the logic for formatting bool config values into a helper method
and use gentle parsing when needed.
This makes 'git config list --type=bool' not fail when coming across a
non-boolean value. Such unparseable values are filtered out quietly.
Signed-off-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Move the logic for formatting int64 config values into a helper method
and use gentle parsing when needed.
Signed-off-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Previously, the --type=<X> argument to 'git config list' was ignored and
did nothing. Now, we add the use of format_config() to the
show_all_config() function so each key-value pair is attempted to be
parsed. This is our first use of the 'gently' parameter with a nonzero
value.
When listing multiple values, our initial settings for the output format
is different. Add a new init helper to specify the fact that keys should
be shown and also add the default delimiters as they were unset in some
cases.
Our intention is that if there is an error in parsing, then the row is
not output. This is necessary to avoid the caller needing to build their
own validator to understand the difference between valid, canonicalized
types and other raw string values. The raw values will always be
available to the user if they do not specify the --type=<X> option.
The current behavior is more complicated, including error messages on
bad parsing or potentially complete failure of the command. We add
tests at this point that demonstrate the current behavior so we can
witness the fix in future changes that parse these values quietly and
gently.
This is a change in behavior! We are starting to respect an option that
was previously ignored, leading to potential user confusion. This is
probably still a good option, since the --type argument did not change
behavior at all previously, so users can get the behavior they expect by
removing the --type argument or adding the --no-type argument.
t1300-config.sh is updated with the current behavior of this formatting
logic to justify the upcoming refactoring of format_config() that will
incrementally fix some of these cases to be more user-friendly.
Signed-off-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
This parameter is set to 0 for all current callers and is UNUSED.
However, we will start using this option in future changes and in a
critical change that requires gentle parsing (not using die()) to try
parsing all values in a list.
Signed-off-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
In anticipation of using format_config() in this method, move
show_all_config() lower in the file without changes.
Signed-off-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Replace calls to `refs_for_each_fullref_in()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Replace calls to `refs_for_each_namespaced_ref()` with the newly
introduced `refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Replace calls to `refs_for_each_glob_ref()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Replace calls to `refs_for_each_glob_ref_in()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Replace calls to `refs_for_each_rawref_in()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Replace calls to `refs_for_each_rawref()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Replace calls to `refs_for_each_ref_in()` with the newly introduced
`refs_for_each_ref_ext()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Improve verification of the passed-in for-each-ref options:
- Require that the `refs` store must be given. It's arguably very
surprising that we simply return successfully in case the ref store
is a `NULL` pointer.
- When expected to trim ref prefixes we will `BUG()` in case the
refname would become empty or in case we're expected to trim a
longer prefix than the refname is long. As such, this case is only
guaranteed to _not_ `BUG()` in case the caller also specified a
prefix. And furthermore, that prefix must end in a trailing slash,
as otherwise it may produce an exact match that could lead us to
trim to the empty string.
An audit shows that there are no callsites that rely on either of these
behaviours, so this should not result in a functional change.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
The function `refs_for_each_fullref_in_prefixes()` can be used to
iterate over all references part of any of the user-provided prefixes.
In contrast to the `prefix` parameter of `refs_for_each_ref_ext()` it
knows to handle the case well where multiple of the passed-in prefixes
start with a common prefix by computing longest common prefixes and then
iterating over those.
While we could move this logic into `refs_for_each_ref_ext()`, this one
feels somewhat special as we perform multiple iterations. But what we
_can_ do is to generalize how this function works: instead of accepting
only a small handful of parameters, we can have it accept the full
options structure.
One obvious exception is that the caller must not provide a prefix via
the options. But this case can be easily detected.
Refactor the code accordingly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
The function `refs_for_each_namespaced_ref()` iterates through all
references that are part of the current ref namespace. This namespace
can be configured by setting the `GIT_NAMESPACE` environment variable
and is then retrieved by calling `get_git_namespace()`.
If a namespace is configured, then we:
- Obviously only yield refs that exist in this namespace.
- Rewrite exclude patterns so that they work for the given namespace,
if any namespace is currently configured.
Port this logic to `refs_for_each_ref_ext()` by adding a new `namespace`
field to the options structure. This gives callers more flexibility as
they can decide by themselves whether they want to use the globally
configured or an arbitrary other namespace.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
The function `refs_for_each_glob_ref_in()` can be used to iterate
through all refs in a specific prefix with globbing. The logic to handle
this is currently hosted by `refs_for_each_glob_ref_in()`, which sets up
a callback function that knows to filter out refs that _don't_ match the
given globbing pattern.
The way we do this is somewhat inefficient though: even though the
function is expected to only yield refs in the given prefix, we still
end up iterating through _all_ references, regardless of whether or not
their name matches the given prefix.
Extend `refs_for_each_ref_ext()` so that it can handle patterns and
adapt `refs_for_each_glob_ref_in()` to use it. This means we continue to
use the same callback-based infrastructure to filter individual refs via
the globbing pattern, but we can now also use the other functionality of
the `_ext()` variant.
Most importantly, this means that we now properly handle the prefix.
This results in a performance improvement when using a prefix where a
significant majority of refs exists outside of the prefix. The following
benchmark is an extreme case, with 1 million refs that exist outside the
prefix and a single ref that exists inside it:
Benchmark 1: git rev-parse --branches=refs/heads/* (rev = HEAD~)
Time (mean ± σ): 115.9 ms ± 0.7 ms [User: 113.0 ms, System: 2.4 ms]
Range (min … max): 114.9 ms … 117.8 ms 25 runs
Benchmark 2: git rev-parse --branches=refs/heads/* (rev = HEAD)
Time (mean ± σ): 1.1 ms ± 0.1 ms [User: 0.3 ms, System: 0.7 ms]
Range (min … max): 1.0 ms … 2.3 ms 2092 runs
Summary
git rev-parse --branches=refs/heads/* (rev = HEAD) ran
107.01 ± 6.49 times faster than git rev-parse --branches=refs/heads/* (rev = HEAD~)
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
In the refs subsystem we have a proliferation of functions that all
iterate through references. (Almost) all of these functions internally
call `do_for_each_ref()` and provide slightly different arguments so
that one can control different aspects of its behaviour. This approach
doesn't really scale: every time there is a slightly different use case
for iterating through refs we create another new function.
This combinatorial explosion doesn't make a lot of sense: it leads to
confusing interfaces and heightens the maintenance burden.
Refactor the code to become more composable by:
- Exposing `do_for_each_ref()` as `refs_for_each_ref_ext()`.
- Introducing an options structure that lets the caller control
individual options.
This gives us a much better foundation to build on going forward.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Similar to the preceding commit, rename `each_ref_fn` to better match
our current best practices around how we name things.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
The enum `do_for_each_ref_flags` and its individual values don't match
to our current best practices when it comes to naming things. Rename it
to `refs_for_each_flag`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Move the `do_for_each_ref_flags` enum further up. This prepares for
subsequent changes, where the flags will be used by more functions.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
The function `refs_head_ref_namespaced()` is somewhat special when
compared to most of the other functions that take a callback function:
while `refs_for_each_*()` functions yield multiple refs,
`refs_heasd_ref_namespaced()` will only yield at most the HEAD ref of
the current namespace. As such, the function is related to
`refs_head_ref()` and not to the for-each functions.
Move the function to be located next to `refs_head_ref()` to clarify.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Remove the unused `refs_for_each_include_root_ref()` function.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
It was reported [1] that git-fsck(1) may sometimes run into an infinite
loop when processing packfiles. This bug was bisected to c31bad4f7d
(packfile: track packs via the MRU list exclusively, 2025-10-30), which
refactored our lsit of packfiles to only be tracked via an MRU list,
exclusively. This isn't entirely surprising: any caller that iterates
through the list of packfiles and then hits `find_pack_entry()`, for
example because they read an object from it, may cause the MRU list to
be updated. And if the caller is unlucky, this may cause the mentioned
infinite loop.
While this mechanism is somewhat fragile, it is still surprising that we
encounter it when verifying the packfile. We iterate through objects in
a given pack one by one and then read them via their offset, and doing
this shouldn't ever end up in `find_pack_entry()`.
But there is an edge case here: when the object in question is a blob
bigger than "core.largeFileThreshold", then we will be careful to not
read it into memory. Instead, we read it via an object stream by calling
`odb_read_object_stream()`, and that function will perform an object
lookup via `odb_read_object_info()`. So in the case where there are at
least two blobs in two different packfiles, and both of these blobs
exceed "core.largeFileThreshold", then we'll run into an infinite loop
because we'll always update the MRU.
We could fix this by improving `repo_for_each_pack()` to not update the
MRU, and this would address the issue. But the fun part is that using
`odb_read_object_stream()` is the wrong thing to do in the first place:
it may open _any_ instance of this object, so we ultimately cannot be
sure that we even verified the object in our given packfile.
Fix this bug by creating the object stream for the packed object
directly via `packfile_read_object_stream()`. Add a test that would have
caused the infinite loop.
[1]: <20260222183710.2963424-1-sandals@crustytoothpaste.net>
Reported-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
The function `packfile_store_read_object_stream()` takes as input an
object ID and then constructs a `struct odb_read_stream` from it. In a
subsequent commit we'll want to create an object stream for a given
combination of packfile and offset though, which is not something that
can currently be done.
Extract a new function `packfile_read_object_stream()` that makes this
functionality available.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
The function `stream_object_signature()` is responsible for verifying
whether the given object ID matches the actual hash of the object's
contents. In contrast to `check_object_signature()` it does so in a
streaming fashion so that we don't have to load the full object into
memory.
In a subsequent commit we'll want to adapt one of its callsites to pass
a preconstructed stream. Prepare for this by accepting a stream as input
that the caller needs to assemble.
While at it, improve the error reporting in `parse_object_with_flags()`
to tell apart the two failure modes.
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
The `test-tool genrandom` test helper can be used to generate random
data, either as an infinite stream or with a specified number of bytes.
The way we handle parsing the number of bytes is lacking though:
- We don't have good error handling, so if the caller for example uses
`test-tool genrandom 200xyz` then we'll end up generating 200 bytes
of random data successfully.
- Many callers want to generate e.g. 1 kilobyte or megabyte of data,
but they have to either use unwieldy numbers like 1048576, or they
have to precompute them.
Fix both of these issues by using `git_parse_ulong()` to parse the
argument. This function has better error handling, and it knows to
handle unit suffixes.
Adapt a couple of our tests to use suffixes instead of manual
computations.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Even though the "struct odb_transaction" member is at the beginning
of the containing "struct odb_transaction_files", i.e., at offset 0,
using container_of() to add offset 0 to a NULL pointer gets flagged
as a bad behaviour under SANITIZE=undefined.
Use container_of_or_null() to work around this issue.
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
In add_pack(), we allocate l.remaining_objects with llist_init() before
calling open_pack_index(). If open_pack_index() fails we return NULL
without freeing the allocated list, leaking the memory.
Fix by calling llist_free(l.remaining_objects) on the error path before
returning.
Signed-off-by: Sahitya Chandra <sahityajb@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Extract skip_slashes() to avoid repeating the same is_dir_sep()
loop in multiple places inside normalize_path_copy_len().
Keep the dot-component handling inline to preserve the original
control flow and readability, as suggested in review.
No functional changes. Behavior verified with t0060-path-utils.sh.
Signed-off-by: Pushkar Singh <pushkarkumarsingh1970@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Replace 'test -f' with the helper function 'test_path_is_file'
to provide better error messages upon failure.
Signed-off-by: Lambert Duclos-de Guise <lambertddg@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Due to the use of DEFAULT_ABBREV, we cannot get rid of our usage of
USE_THE_REPOSITORY_VARIABLE. We have removed all other uses of
the_repository before, but without removing that definition, they keep
coming back.
Define the_repository to make it a compilation error so that they don't
come back any more; the repo parameter plumbed through the various
functions can be used instead.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Due to the use of DEFAULT_ABBREV, we cannot get rid of our usage of
USE_THE_REPOSITORY_VARIABLE. However, we have removed all other uses of
the_repository in merge-ort a few times. But they keep coming back.
Define the_repository to make it a compilation error so that they don't
come back any more.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
We have a perfectly valid repository available and do not need to use
the_hash_algo (a shorthand for the_repository->hash_algo), so use the
known repository instead.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
We have a perfectly valid repository available and do not need to use
the_repository.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
In order to get rid of a usage of the_repository, we need to know the
value of opt->repo; pass it along to write_tree(). Once we have the
repository, though, we no longer need to pass
opt->repo->hash_algo->rawsz, we can have write_tree() look up that value
itself.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Prefetching of blobs from promisor remotes was added to diff in
7fbbcb21b162 (diff: batch fetching of missing blobs, 2019-04-05). In
that commit,
https://lore.kernel.org/git/20190405170934.20441-1-jonathantanmy@google.com/
was squashed into
https://lore.kernel.org/git/44de02e584f449481e6fb00cf35d74adf0192e9d.1553895166.git.jonathantanmy@google.com/
without the extra explanation about the squashed changes being added to
the commit message; in particular, this explanation from that first link
is absent:
> Also, prefetch only if the repository being diffed is the_repository
> (because we do not support lazy fetching for any other repository
> anyway).
Then, later, this checking was spread from diff.c to diffcore-rename.c
and diffcore-break.c by 95acf11a3dc3 (diff: restrict when prefetching
occurs, 2020-04-07) and then further split in d331dd3b0c82
(diffcore-rename: allow different missing_object_cb functions,
2021-06-22). I also copied the logic from prefetching blobs from
diff.c to merge-ort.c in 2bff554b23e8 (merge-ort: add prefetching for
content merges, 2021-06-22).
The reason for all these checks was noted above -- we only supported
lazy fetching for the_repository. However, that changed with
ef830cc43412 (promisor-remote: teach lazy-fetch in any repo,
2021-06-17), so these checks are now unnecessary. Remove them.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
The 'flags' and 'track_flags' fields in symlinks.c are used
strictly as a collection of bits (using bitwise operators including
&, |, ~). Using a signed integer for bitmasks may lead to undefined
behavior with shift operations and logic errors if the MSB is touched.
Change these fields from 'int' to 'unsigned int' to match our usage
patterns.
Signed-off-by: Tian Yuchen <a3205153416@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Test update.
* ak/t9812-test-path-is-helpers:
t9812: modernize test path helpers
|
|
"git diff --anchored=<text>" has been optimized.
* pw/diff-anchored-optim:
diff --anchored: avoid checking unmatched lines
|
|
A CodingGuidelines update.
* jc/doc-cg-c-comment:
CodingGuidelines: document // comments
|
|
Small clean-up of xdiff library to remove unnecessary data
duplication.
* pw/xdiff-cleanups:
xdiff: remove unused data from xdlclass_t
xdiff: remove "line_hash" field from xrecord_t
|
|
emit_path() uses the global the_hash_algo even though a local repository is
already available via struct diff_options *opt.
Replace these uses with opt->repo->hash_algo. With no remaining reliance on
global states in this file, drop the dependency on 'environment.h' and remove
'#define USE_THE_REPOSITORY_VARIABLE'.
This follows earlier cleanups to introduce opt->repo in tree-diff.c [1][2].
[1]- https://lore.kernel.org/git/20180921155739.14407-21-pclouds@gmail.com/
[2]- https://lore.kernel.org/git/20260109213021.2546-2-l.s.r@web.de/
Signed-off-by: Shreyansh Paliwal <shreyanshpaliwalcmsmn@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
`should_ignore_subtree_split_commit` detects subtrees which are
outside of the current path --prefix and ignores them. This can
speed up splits of repositories that have many subtrees.
Since its inception [1], every iteration of this logic [2], [3]
incorrectly excludes commits. This alters the split history. The
split history and its commit hashes are API contract, so this is
not permissible.
While a commit from a different subtree may look like it doesn't
contribute anything to a split, sometimes it does. Merge commits
are a particular hot spot. For these, the pruning logic in
`copy_or_skip` performs:
1. a check for "treesame" parents
2. two different common ancestry checks
These checks operate on the **split history**, not the input
history. The split history omits commits that do not affect the
--prefix. This can significantly alter the ancestry of a merge.
In order to determine if `copy_or_skip` will skip a merge, it
is likely necessary to compute all the split history... which
is what `should_ignore_subtree_split_commit` tries to avoid.
To make this logic API-preserving, we could gate it behind a
new CLI argument. The present implementation is actually a
speed penalty in many cases, however, so this is not done here.
Remove the `should_ignore_subtree_split_commit` logic. This
fixes the regression reported in [4].
[1]: 98ba49ccc2 (subtree: fix split processing with multiple subtrees present, 2023-12-01)
[2]: 83f9dad7d6 (contrib/subtree: fix split with squashed subtrees, 2025-09-09)
[3]: 28a7e27cff (contrib/subtree: detect rewritten subtree commits, 2026-01-09)
[4]: <20251230170719.845029-1-george@mail.dietrich.pub>
Reported-by: George <george@mail.dietrich.pub>
Reported-by: Christian Heusel <christian@heusel.eu>
Signed-off-by: Colin Stagner <ask+git@howdoi.land>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Add history depth checks to some of the subtree unit tests.
These checks were previously introduced as part of 28a7e27cff
(contrib/subtree: detect rewritten subtree commits, 2026-01-09),
which has since been reverted.
Signed-off-by: Colin Stagner <ask+git@howdoi.land>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
Patch series e7b07376e5 (Merge branch 'rs/subtree-fixes',
2018-10-26) corrects several defects in `git subtree split`.
The defects affect `split --rejoin` and merge commit processing.
There is no test coverage for this, and e7b07376e5 did not
introduce any.
Convert the minimum working example [1] from the original patch
submission [2] into test cases.
[1]: https://gist.github.com/FoxFireX/1b794384612b7fd5e7cd157cff96269e
[2]: <20180928183540.48968-1-roger.strain@swri.org>
Signed-off-by: Colin Stagner <ask+git@howdoi.land>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
When passing a relative path like --directory=./some/sub, the leading
"./" caused apply to prepend it literally to patch filenames, resulting
in an error (invalid path).
There may be more cases like this where users pass some/./path to the
directory which can easily be normalized to an acceptable path, so
these changes try to normalize the path before using it.
Signed-off-by: Joaquim Rocha <joaquim@amutable.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
|
* 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
|