* Re: [PATCH v13 2/6] branch: let delete_branches warn instead of error on bulk refusal
From: Harald Nordgren @ 2026-06-09 7:52 UTC (permalink / raw)
To: Junio C Hamano
Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
Johannes Sixt, Phillip Wood
In-Reply-To: <xmqq4ijcvb64.fsf@gitster.g>
> This breaks t5404, t5514, and t5505, which contradicts with
> "Existing callers are unaffected".
>
> What's going on? It is troubling that the breakage happens without
> even getting merged with other topics in-flight, which means that
> the environment you are developing in and testing on and the
> environment that I apply patches on, integrate and test (something
> based on Debian testing) are somehow behaving differently.
>
> "cd t && sh t5404-*.sh -i -v" ends like so:
>
> expecting success of 5404.7 'already deleted tracking branches ignored':
> git branch -d -r origin/b3 &&
> git push origin :b3 >output 2>&1 &&
> ! grep "^error: " output
>
> error: the branch 'origin/b3' is not fully merged
> hint: If you are sure you want to delete it, run 'git branch -D origin/b3'
> hint: Disable this message with "git config set advice.forceDeleteBranch false"
> not ok 7 - already deleted tracking branches ignored
> #
> # git branch -d -r origin/b3 &&
> # git push origin :b3 >output 2>&1 &&
> # ! grep "^error: " output
> #
> 1..7
>
> but it may be possible that earlier steps are behaving differently
> with the patches applied. I didn't dig further but I think the CI
> in the recent past have been affected by the same breakage.
Thanks for directing my attention to this.
The GitHub CI has been broken for some time, maybe I should have told
you about this earlier, but it coincided with a period where other
open source projects I worked on also had mass CI failures, so I
chalked it up to upstream issues (GitHub, Linux, etc). But it seems to
have not gone away.
All of my GitHub pull requests have broken tests (see e.g. which a
quite minimal change: https://github.com/git/git/pull/2313). This
makes it harder to detect actual issues. But of course it's not an
excuse.
Harald
^ permalink raw reply
* Re: [PATCH 00/16] odb: make packed object source a proper `struct odb_source`
From: Patrick Steinhardt @ 2026-06-09 7:27 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
In-Reply-To: <CAOLa=ZT9PLFeVpyKph=jQOz_BHXhYgKO=-3SV_VP6p4oXLxZpg@mail.gmail.com>
On Mon, Jun 08, 2026 at 09:15:09AM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> > Hi,
> >
> > this patch series converts the "packed" source into a proper `struct
> > odb_source`. It's thus the equivalent to [1], which did the same thing
> > for the "loose" source.
> >
> > This series here is unfortunately a bit bigger, mostly because I'm also
> > renaming `struct packfile_store` to `struct odb_source_packed`. Back
> > when I introduced the packfile store I didn't yet have the full vision
> > of how the final layout will look like, so I didn't have the foresight
> > yet to call it `struct odb_source_packed`. But now that the layout has
> > materialized I think it's sensible to adjust its naming to match all the
> > other sources that we have.
> >
> > Also: I don't have anything else in the pipeline anymore that moves
> > around large pieces of our code in the vicinity of the object database.
> > So after this series got merged, subsequent changes should be of a more
> > incremental nature.
> >
> > This series is built on top of 9ac3f193c0 (The 11th batch, 2026-06-02)
> > with ps/odb-source-loose at ef4778bcba (odb/source-loose: drop pointer
> > to the "files" source, 2026-06-01) merged into it.
>
> This was a good read. The commits towards the end are mostly simple code
> movements. Overall the series looks to be in good shape.
Thanks for your review! Will send a new version later today.
Patrick
^ permalink raw reply
* Re: [PATCH 02/16] packfile: move packed source into "odb/" subsystem
From: Patrick Steinhardt @ 2026-06-09 7:27 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
In-Reply-To: <CAOLa=ZQ8K53yyopSOp4_Gc-Gpq6ULA0xW6gH5OCWdWNHEyRysw@mail.gmail.com>
On Mon, Jun 08, 2026 at 08:09:06AM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/odb/source-packed.h b/odb/source-packed.h
> > new file mode 100644
> > index 0000000000..c17068a4f1
> > --- /dev/null
> > +++ b/odb/source-packed.h
> > @@ -0,0 +1,80 @@
> > +#ifndef ODB_SOURCE_PACKED_H
> > +#define ODB_SOURCE_PACKED_H
> > +
> > +#include "odb/source.h"
> > +#include "strmap.h"
> > +
> > +struct packfile_list {
> > + struct packfile_list_entry *head, *tail;
> > +};
> > +
> > +struct packfile_list_entry {
> > + struct packfile_list_entry *next;
> > + struct packed_git *pack;
> > +};
> > +
>
> So this is exposed, because outside of the odb, we also use packfiles in
> the transport layer. That makes me wonder if these two structures are
> better kept alonsigde `struct packed_git` in 'packfile.h'.
Yeah, this is quite awkward indeed as the struct and function
declarations are now split up across "packfile.h" and
"odb/source-packed.h". The reason though is that there's a cyclic
dependency between the two headers, so we have to move the code around.
Arguably though, the better fix would be to move it into a standalone
file "packfile-list.{c,h}". Will adapt the code accordingly.
Patrick
^ permalink raw reply
* Re: [PATCH 11/16] odb/source-packed: wire up `count_objects()` callback
From: Patrick Steinhardt @ 2026-06-09 7:27 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
In-Reply-To: <CAOLa=ZQdGMo83KggkmeeKYMR475TFqLn=o-nJz4QEUX2njgaOA@mail.gmail.com>
On Mon, Jun 08, 2026 at 09:12:06AM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> [snip]
>
> > diff --git a/odb/source-packed.c b/odb/source-packed.c
> > index a61c809c8c..013d8a50f8 100644
> > --- a/odb/source-packed.c
> > +++ b/odb/source-packed.c
> > @@ -338,6 +338,39 @@ static int odb_source_packed_for_each_object(struct odb_source *source,
> > return ret;
> > }
> >
> > +static int odb_source_packed_count_objects(struct odb_source *source,
> > + enum odb_count_objects_flags flags UNUSED,
> > + unsigned long *out)
> > +{
> > + struct odb_source_packed *packed = odb_source_packed_downcast(source);
> > + struct packfile_list_entry *e;
> > + struct multi_pack_index *m;
> > + unsigned long count = 0;
> > + int ret;
> > +
> > + m = get_multi_pack_index(&packed->files->base);
> > + if (m)
> > + count += m->num_objects + m->num_objects_in_base;
> > +
> > + for (e = packfile_store_get_packs(packed); e; e = e->next) {
> > + if (e->pack->multi_pack_index)
> > + continue;
> > + if (open_pack_index(e->pack)) {
> > + ret = -1;
> > + goto out;
> > + }
> > +
> > + count += e->pack->num_objects;
> > + }
> > +
> > + *out = count;
> > + ret = 0;
> > +
> > +out:
> > + return ret;
> > +}
> > +
> > +
>
> Nit: extra newline.
Good catch, fixed locally.
Patrick
^ permalink raw reply
* Re: [PATCH 04/16] odb/source-packed: start converting to a proper `struct odb_source`
From: Patrick Steinhardt @ 2026-06-09 7:27 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git
In-Reply-To: <CAOLa=ZQst6ucwvtVOfXC6g1ZcP9_UZAwRyAXfQdjL7WcJ6ZzxQ@mail.gmail.com>
On Mon, Jun 08, 2026 at 08:29:04AM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/odb/source-packed.c b/odb/source-packed.c
> > index 12e785be48..f81a990cbd 100644
> > --- a/odb/source-packed.c
> > +++ b/odb/source-packed.c
> > + CALLOC_ARRAY(packed, 1);
> > + odb_source_init(&packed->base, parent->base.odb, ODB_SOURCE_PACKED,
> > + parent->base.path, parent->base.local);
> > + packed->files = parent;
> > + strmap_init(&packed->packs_by_path);
> > +
> > + packed->base.free = odb_source_packed_free;
> > +
> > + if (!is_absolute_path(parent->base.path))
> > + chdir_notify_register(NULL, odb_source_packed_reparent, packed);
> > +
>
> Tangent: seems like no one sets the 'name' field within
> `chdir_notify_register()`. It is meant for tracing purposes, but if no
> one is using it, we might as well remove it...? Perhaps #leftoverbits
There are some callers: `chdir_notify_reparent()` calls
`chdir_notify_register()` with a name, and the reference backends call
that function with names.
Ultimately though I think that this infrastructure is somewhat misguided
overall: we use this to update relative paths after chdir(3p), but if we
stored absolute paths in the first place then we wouldn't have to care
about the paths changing at all.
I plan to revisit this infra for the object database going forward: we
expose and use `struct odb_source::path` in various other subsystems,
including user-facing ones. This is inherently wrong though, as there
may be sources that don't even have an on-disk path. So there's a need
to drop that field and make it an internal implementation detail of the
source's backend. And once we've done that, we can just as well start to
store absolute paths.
For the reference backends we can already do that refactoring now-ish.
I'll send a patch series later today.
Patrick
^ permalink raw reply
* Re: [PATCH] docs: fix typos
From: Kristoffer Haugsbakk @ 2026-06-09 6:34 UTC (permalink / raw)
To: Tuomas Ahola, git
In-Reply-To: <20260604131457.19215-1-taahol@utu.fi>
On Thu, Jun 4, 2026, at 15:14, Tuomas Ahola wrote:
> Fix some typos and grammar errors in comments and documentation files.
>
> Signed-off-by: Tuomas Ahola <taahol@utu.fi>
> ---
>
> Notes:
> Written mostly as an exercise on how to submit patches that depend
> on other topics.
I’ve been thinking of how to handle typos for a few days now. ;) The
following does not apply to this submission since the maintainer said
that he will apply it.
Anyway, it struck me that you might sometimes want to apply the typofix
on top of the original branch *if* the branch is scheduled to be merged
to more than just `master`.
So e.g. this does *not* apply to topic kh/name-rev-custom-format since
that topic is not scheduled for a maintenance branch (`maint`). But:
>
> $ git log --oneline --first-parent v2.54.0..
> d19e9182ab (HEAD -> ta/typofixes) docs: fix typos
> 5a7e9cc03d Merge branch 'ta/approxidate-noon-fix'
Your topic is. See `RelNotes`:
(merge b809304101 ta/approxidate-noon-fix later to maint).
So it might make sense in such cases to post a patch to
be applied on top of the topic.
Just a thought for later.
> f03649d802 Merge branch 'kh/name-rev-custom-format'
> 023a226b4b Merge branch 'jc/neuter-sideband-fixup'
>
> As can be seen, these topics have already graduated to master:
>
> $ git cherry master
> + d19e9182ab097a722e32d459a9a58c8985831e3b
>[snip]
^ permalink raw reply
* Re: [PATCH GSoC RFC v12 04/12] t1006: split test utility functions into new "lib-cat-file.sh"
From: Chandra Pratap @ 2026-06-09 6:28 UTC (permalink / raw)
To: Pablo Sabater
Cc: eric.peijian, calvinwan, chriscool, git, jltobler, jonathantanmy,
karthik.188, toon
In-Reply-To: <20260608-ps-eric-work-rebase-v12-4-5338b766e658@gmail.com>
On Mon, 8 Jun 2026 at 15:44, Pablo Sabater <pabloosabaterr@gmail.com> wrote:
>
> From: Eric Ju <eric.peijian@gmail.com>
>
> This refactor extracts utility functions from the cat-file's test
> script "t1006-cat-file.sh" into a new "lib-cat-file.sh" dedicated
> library file. The goal is to improve code reuse and readability,
> enabling future tests to leverage these utilities without duplicating
> code.
Hmm, seems like a premature change to me. Do any of the subsequent
commits require this refactor? Maybe the follow-up series that enables
%objecttype support needs it? Did someone request this change in v11's
feedback?
If any of those are true, I think it's worthwhile mentioning it here. That will
make it easier to determine whether this change is truly necessary.
> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
> ---
> t/lib-cat-file.sh | 16 ++++++++++++++++
> t/t1006-cat-file.sh | 13 +------------
> 2 files changed, 17 insertions(+), 12 deletions(-)
>
> diff --git a/t/lib-cat-file.sh b/t/lib-cat-file.sh
> new file mode 100644
> index 0000000000..44af232d74
> --- /dev/null
> +++ b/t/lib-cat-file.sh
> @@ -0,0 +1,16 @@
> +# Library of git-cat-file related test functions.
> +
> +# Print a string without a trailing newline.
> +echo_without_newline () {
> + printf '%s' "$*"
> +}
> +
> +# Print a string without newlines and replace them with a NULL character (\0).
> +echo_without_newline_nul () {
> + echo_without_newline "$@" | tr '\n' '\0'
> +}
> +
> +# Calculate the length of a string.
> +strlen () {
> + echo_without_newline "$1" | wc -c | sed -e 's/^ *//'
> +}
> diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
> index 8e2c52652c..8360f3bbd9 100755
> --- a/t/t1006-cat-file.sh
> +++ b/t/t1006-cat-file.sh
> @@ -4,6 +4,7 @@ test_description='git cat-file'
>
> . ./test-lib.sh
> . "$TEST_DIRECTORY/lib-loose.sh"
> +. "$TEST_DIRECTORY"/lib-cat-file.sh
>
> test_cmdmode_usage () {
> test_expect_code 129 "$@" 2>err &&
> @@ -99,18 +100,6 @@ do
> '
> done
>
> -echo_without_newline () {
> - printf '%s' "$*"
> -}
> -
> -echo_without_newline_nul () {
> - echo_without_newline "$@" | tr '\n' '\0'
> -}
> -
> -strlen () {
> - echo_without_newline "$1" | wc -c | sed -e 's/^ *//'
> -}
> -
> run_tests () {
> type=$1
> object_name="$2"
>
> --
> 2.54.0
^ permalink raw reply
* [GSoC Blog] Week 1&2 : Improve Disk Space Recovery for Partial Clones
From: Siddharth Shrimali @ 2026-06-09 6:24 UTC (permalink / raw)
To: git; +Cc: Christian Couder, Siddharth Asthana, Siddharth Shrimali
Hello everyone,
I have published my GSoC blog about my recent progress
on the project.
I apologize for the delay in sharing the Week 1 update on time.
I am sharing it alongside Week 2's progress.
- Week 1 Update: https://siddharth.shrimali.info/#post/4
- Week 2 Update: https://siddharth.shrimali.info/#post/5
Please feel free to review my work and share your feedback.
Always open to discussions! ;)
Regards,
Siddharth Shrimali
^ permalink raw reply
* Re: [PATCH GSoC RFC v12 02/12] git-compat-util: add strtoul_ul() with error handling
From: Chandra Pratap @ 2026-06-09 6:20 UTC (permalink / raw)
To: Pablo Sabater
Cc: eric.peijian, calvinwan, chriscool, git, jltobler, jonathantanmy,
karthik.188, toon
In-Reply-To: <20260608-ps-eric-work-rebase-v12-2-5338b766e658@gmail.com>
On Mon, 8 Jun 2026 at 15:44, Pablo Sabater <pabloosabaterr@gmail.com> wrote:
>
> From: Eric Ju <eric.peijian@gmail.com>
>
> We already have strtoul_ui() and similar functions that provide proper
> error handling using strtoul from the standard library. However,
> there isn't currently a variant that returns an unsigned long.
>
> This variant is needed in a subsequent commit.
>
> Add strtoul_ul() to address this gap, enabling the
Nit: extra space here. Also, this could be conciser. Maybe something like:
"This variant is needed in a subsequent commit to enable returning an
unsigned long with proper error handling."
> return of an unsigned long with proper error handling.
>
> Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
> ---
> git-compat-util.h | 20 ++++++++++++++++++++
> 1 file changed, 20 insertions(+)
>
> diff --git a/git-compat-util.h b/git-compat-util.h
> index 8809776407..4bf569f35c 100644
> --- a/git-compat-util.h
> +++ b/git-compat-util.h
> @@ -975,6 +975,26 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)
> return 0;
> }
>
> +/*
> + * Convert a string to an unsigned long using the standard library's strtoul,
> + * with additional error handling to ensure robustness.
> + */
> +static inline int strtoul_ul(char const *s, int base, unsigned long *result)
> +{
> + unsigned long ul;
> + char *p;
> +
> + errno = 0;
> + /* negative values would be accepted by strtoul */
> + if (strchr(s, '-'))
> + return -1;
> + ul = strtoul(s, &p, base);
> + if (errno || *p || p == s)
> + return -1;
> + *result = ul;
> + return 0;
> +}
> +
> static inline int strtol_i(char const *s, int base, int *result)
> {
> long ul;
>
> --
> 2.54.0
^ permalink raw reply
* Re: [PATCH 2/3] config: add GIT_CONFIG_INCLUDES
From: Patrick Steinhardt @ 2026-06-09 6:06 UTC (permalink / raw)
To: Derrick Stolee; +Cc: Derrick Stolee via GitGitGadget, git, gitster
In-Reply-To: <dd971b9e-2c13-4521-b991-b9bee1c5bf5b@gmail.com>
On Mon, Jun 08, 2026 at 03:38:55PM -0400, Derrick Stolee wrote:
> On 6/8/2026 10:34 AM, Patrick Steinhardt wrote:
> > On Mon, Jun 08, 2026 at 01:57:05PM +0000, Derrick Stolee via GitGitGadget wrote:
> > That raises the question whether we can introduce the configuration in a
> > way that it allows a bit more flexibility than just "yes"/"no", like for
> > example an allow-list of locations that should be evaluated. But maybe
> > I'm overthinking this.
> I see. So we can say "avoid including into the repository worktree" but
> that will probably be incomplete.
>
> There is room for nuance in future expansions, if we can find a creative
> way to handle that nuance. For now, I think I would still want an ability
> to turn the entire feature off, at least for certain tools that care.
Yup, that's fine with me. Thanks!
Patrick
^ permalink raw reply
* Re: [PATCH] ref-filter: restore prefix-scoped iteration
From: Patrick Steinhardt @ 2026-06-09 6:05 UTC (permalink / raw)
To: Tamir Duberstein
Cc: Junio C Hamano, git, Karthik Nayak, Victoria Dye, ZheNing Hu
In-Reply-To: <CAJ-ks9m9gq-=JB-gqeKaL4YOLSfrP2Cm0DytZjuC3OetG-UVbA@mail.gmail.com>
On Mon, Jun 08, 2026 at 06:39:48PM -0400, Tamir Duberstein wrote:
> On Mon, Jun 8, 2026 at 2:36 PM Junio C Hamano <gitster@pobox.com> wrote:
> >
> > Tamir Duberstein <tamird@gmail.com> writes:
> >
> > > diff --git a/ref-filter.c b/ref-filter.c
> > > index 1da4c0e60d..2388a57b39 100644
> > > --- a/ref-filter.c
> > > +++ b/ref-filter.c
> > > @@ -3315,19 +3315,31 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, refs_for
> > > prefix = "refs/tags/";
> > >
> > > if (prefix) {
> >
> > Below, adding an extra call to get_main_ref_store(the_repository)
> > makes one line unnecessarily split and harder to read. How about
> > doing
> >
> > struct ref_store *store = get_main_ref_store(the_repository);
> >
> > upfront here, and then use that to replace these two calls of
> > get_main_ref_store(the_repository)?
>
> Yep, done in v2.
>
> Thanks for the review!
>
> By the way, how long should I wait before sending new versions of my
> patches? I have 4 outstanding at the moment.
I typically aim to send at most one version per day per patch series.
This avoids that you're "flooding" the mailing list with too many
versions of the same series, allows you to address feedback from
multiple folks in batches, and it gives you enough time to think about
the feedback without having to rush anything.
Whether I actually do end up sending a series depends on a couple of
factors:
- How big is the series? The bigger it is the more time I give folks
to perform reviews.
- How substantial were the reviews you received? Is it just a couple
of small typos? Then it probably makes sense to wait one or two more
days to get some more involved reviews. Is it something that
requires signifciant rework? Then I'd send out soon so that others
don't review a patch series that will change significantly anyway.
- How close to being merged is the series? The closer it is the less
substantial the reviews will (hopefully) get, so it makes sense to
reroll a bit faster even if you only received minor feedback.
So there isn't really a golden rule to follow here, but a lot of this
depends on gut feeling. You probably won't have that feeling yet when
starting out in a new project, but that's fine. In case we see that
behaviour doesn't quite match the norm we'll typically give a hint that
the contributor should slow down or maybe send a new iteration.
Patrick
^ permalink raw reply
* Re: [GSoC PATCH v2 0/4] teach git repo info to handle path keys
From: K Jayatheerth @ 2026-06-09 5:00 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, a3205153416, jltobler, kumarayushjha123, lucasseikioshiro,
phillip.wood, sandals
In-Reply-To: <xmqqcxy0vevi.fsf@gitster.g>
On Tue, Jun 9, 2026 at 4:06 AM Junio C Hamano <gitster@pobox.com> wrote:
>
> K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:
>
> > 2. Should we consider a default option?
> > Currently we have path.gitdir.absolute. Should we consider an
> > option where a plain `path.gitdir` returns some default?
>
> Probably not. It will invite folks wanting to tweak the default
> between absolute and relative, rendering this feature useless for
> robust scripting. You do not necessarily want to save typing in
> plumbing interface. You want to reduce ambiguity by reducing more
> than one ways to do a thing down to just one way, and as long as
> that one way is not overly verbose, you are fine.
Makes sense.
Explicit keys like path.gitdir.absolute and path.gitdir.relative are
unambiguous for scripting,
and saving a few keystrokes isn't worth introducing a configurable
default that would make the output unpredictable.
I'll actually drop question 2 from the open questions in v3's cover letter.
Thanks!
Regards,
- K Jayatheerth
^ permalink raw reply
* Re: [GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting
From: K Jayatheerth @ 2026-06-09 4:41 UTC (permalink / raw)
To: Justin Tobler
Cc: git, a3205153416, gitster, kumarayushjha123, lucasseikioshiro,
phillip.wood, sandals
In-Reply-To: <aicDOlJdUrgMi3sA@denethor>
> > + if (format == PATH_FORMAT_UNMODIFIED) {
> > + strbuf_addstr(buf, path);
> > + return;
> > + }
> > +
> > + if (format == PATH_FORMAT_RELATIVE) {
>
> nit: we could just continue the "else if" chain here instead of
> restarting it.
Ahh, good catch!
True we can.
> > + strbuf_realpath_forgiving(&canonical_buf, path, 1);
> > + strbuf_addbuf(buf, &canonical_buf);
>
> Do we need `canonical_buf` here? Can we just add the path to `buf`
> directly?
>
canonical_buf is necessary if I my understanding is correct.
We can't pass buf directly to strbuf_realpath_forgiving() because it
resets its destination buffer before writing.
Since format_path() has append semantics, doing so would clobber any
existing content in buf.
The intermediate canonical_buf is needed to keep that safe.
> > +void format_path(struct strbuf *buf, const char *path,
> > + const char *prefix, enum path_format format);
> > +
>
> Ok so in this patch we are just adding the new path formatting
> interface and will integrate it in the next one. Overall the direction
> of this patch looks good to me.
Yup, that's correct!
> > and update print_path() to act as a light wrapper around the new shared
> > engine. Resolve user-provided formatting flags directly within rev-parse
> > to pass the final determined path_format to format_path().
>
> So if the format isn't explicitly set by the user via the
> `--path-format` option, the default formatting strategy used depends on
> the path being printed. IOW, there is no consistent default path format
> here.
>
Yes, that's correct.
> > + struct strbuf sb = STRBUF_INIT;
> > + enum path_format fmt = (arg_path_format != -1) ? arg_path_format : def_format;
>
> hmmm, so `arg_path_format` specifies what the user-provided format and
> acts as a sentinel to signal there is no value provided and the fallback
> format needs to be used. This feels a tad bit awkward to me.
>
> I wonder if we should introduce a PATH_FORMAT_DEFAULT to the
> `path_format` enum that maps to one of the existing enum values in
> `path.c:format_path()`. Here in `print_path()`, we could then intercept
> a PATH_FORMAT_DEFAULT value and override it to the specified
> `def_format`. I'm not sure if this is ultimately that much better
> though.
>
> -Justin
You're right that the -1 is awkward
it forces arg_path_format to be an int rather than the enum type
itself, which loses type safety.
PATH_FORMAT_DEFAULT is cleaner in that regard, but it pushes the "what
does default mean?" question into format_path()
which currently has no notion of a fallback.
Since the fallback is call-site specific (each path type in rev-parse
has its own default),
I'd rather keep that logic in print_path() where the context lives.
A middle ground would be adding PATH_FORMAT_DEFAULT to the enum but
not handling it in format_path().
---
enum path_format_type format = PATH_FORMAT_DEFAULT;
/* ... */
static void print_path(const char *path, const char *prefix,
enum path_format_type format,
enum path_format_type def_format)
{
struct strbuf sb = STRBUF_INIT;
enum path_format_type fmt =
(format == PATH_FORMAT_DEFAULT) ? def_format : format;
format_path(&sb, path, prefix, fmt);
puts(sb.buf);
strbuf_release(&sb);
}
---
> > + format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
>
> For absolute paths, I don't think we actually need the prefix, but
> providing it doesn't probably matter too much either way.
>
Yeah, true. Since the relative had the prefix
I just added the prefix here too.
It is consistent.
> >
> > +test_repo_info_path () {
> > + field_name=$1
> > + expect_absolute_eval=$2
> > + expect_relative=$3
> > + env_prefix=$4
>
> nit: I was a bit uncertain regarding the purpose of env_prefix here.
> Since the env_prefix is not used by any tests yet, I wonder if it we
> should delay adding it until the next patch. If we want to reduce churn
> though, I think we could also swap the order of patch 3 and 4.
>
Good point
I will actually swap 3 and 4
It is just better tbh.
(
> > + cd test-repo/sub &&
> > + expect_absolute=$(eval "$expect_absolute_eval") &&
>
> Can we just compute `expect_absolute` prior to passing it instead of
> using eval here?
>
Yes, I plan to follow Lucas's suggestion from his review.
passing a repo_name parameter and capturing $PWD before the cd to
construct the absolute path at helper-call time.
That avoids eval entirely and also addresses his other concerns about
test isolation. Will fix in v3.
> > +test_expect_success 'setup test repository layout for path fields' '
> > + git init test-repo &&
> > + mkdir -p test-repo/sub
> > +'
> > +
> > +test_repo_info_path 'gitdir' 'echo "$(cd .. && pwd)/.git"' '../.git'
>
> hmmm, do we expect the path suffix to be the same between relative and
> absolute paths for all test cases? If so, we could just have a single
> `expect_path_suffix` argument and let the helper compute the appropriate
> absolute and relative paths internally.
>
Yes it is consistent between absolute and relative.
This is a good suggestion.
Also aligns with what Lucas said.
Thank you,
This will help building v3 much smoother.
Regards,
- K Jayatheerth
^ permalink raw reply
* Re: [PATCH v2] ls-files: filter pathspec before lstat
From: Tamir Duberstein @ 2026-06-09 3:48 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, René Scharfe, Patrick Steinhardt, Jeff King
In-Reply-To: <xmqqecigtm5z.fsf@gitster.g>
On Mon, Jun 8, 2026 at 8:42 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Junio C Hamano <gitster@pobox.com> writes:
>
> > Please make sure that your v2 is a response to v1; otherwise loses
> > sight of the previous iteration.
> >
> >> Changes in v2:
> >> - Restrict early matching to one pathspec, avoiding the regression Jeff
> >> demonstrated with many pathspecs.
> >> - Add all-matching and many-pathspec performance results.
> >> - Drop the Assisted-by trailer.
> >> - Link to v1: https://patch.msgid.link/20260607-ls-files-pathspec-lstat-v1-1-8cf40b730146@gmail.com
> >
> > And it is *not* a replacement to force human to follow such a link.
> >
> > Instead, please make sure each piece of your e-mail identifies where
> > it fits in the discussion thread by pointing the message of the
> > previous round with its In-Reply-To: header.
>
> I won't complain about them individually, but it seems that all the
> other v2 in different topics from you share the same problem.
>
> Documentation/SubmittingPatches expect that the messages on the same
> topic are threaded with In-Reply-To: headers; e-mail based workflow
> tools like "b4" offer a useful feature that lets the user to feed
> the message ID of an earlier round and fetch the messages in the
> latest round. As the message IDs of an earlier round that have
> become commits for v1 are known in the refs/notes/amlog notes
> (published at the usual places), replacing a topic with its newer
> iteration becomes:
>
> (0) check out the previous round.
>
> (1) learn the message ID of the previous round we have checked out
> using notes/amlog (e.g., "git show -s --notes=amlog HEAD"),
>
> (2) detach the HEAD at the base of the previous round (roughly "git
> checkout master...HEAD", but not always),
>
> (3) ask "b4 am" to fetch the latest round of the thread the message
> we found in step (1) belongs to, and apply these new patches,
>
> (4) run "git range-diff @{-1}...HEAD".
>
> which is very much automatable.
>
> Unless an author breaks the thread, that is.
Yes, heard loud and clear. As I mentioned on the other thread, I
followed kernel conventions here by using b4. That's my fault. Sorry
about that. I'll do the proper thing in future mailings.
^ permalink raw reply
* Re: [PATCH v2] ls-files: filter pathspec before lstat
From: Junio C Hamano @ 2026-06-09 3:42 UTC (permalink / raw)
To: Tamir Duberstein; +Cc: git, René Scharfe, Patrick Steinhardt, Jeff King
In-Reply-To: <xmqqv7bstmw8.fsf@gitster.g>
Junio C Hamano <gitster@pobox.com> writes:
> Please make sure that your v2 is a response to v1; otherwise loses
> sight of the previous iteration.
>
>> Changes in v2:
>> - Restrict early matching to one pathspec, avoiding the regression Jeff
>> demonstrated with many pathspecs.
>> - Add all-matching and many-pathspec performance results.
>> - Drop the Assisted-by trailer.
>> - Link to v1: https://patch.msgid.link/20260607-ls-files-pathspec-lstat-v1-1-8cf40b730146@gmail.com
>
> And it is *not* a replacement to force human to follow such a link.
>
> Instead, please make sure each piece of your e-mail identifies where
> it fits in the discussion thread by pointing the message of the
> previous round with its In-Reply-To: header.
I won't complain about them individually, but it seems that all the
other v2 in different topics from you share the same problem.
Documentation/SubmittingPatches expect that the messages on the same
topic are threaded with In-Reply-To: headers; e-mail based workflow
tools like "b4" offer a useful feature that lets the user to feed
the message ID of an earlier round and fetch the messages in the
latest round. As the message IDs of an earlier round that have
become commits for v1 are known in the refs/notes/amlog notes
(published at the usual places), replacing a topic with its newer
iteration becomes:
(0) check out the previous round.
(1) learn the message ID of the previous round we have checked out
using notes/amlog (e.g., "git show -s --notes=amlog HEAD"),
(2) detach the HEAD at the base of the previous round (roughly "git
checkout master...HEAD", but not always),
(3) ask "b4 am" to fetch the latest round of the thread the message
we found in step (1) belongs to, and apply these new patches,
(4) run "git range-diff @{-1}...HEAD".
which is very much automatable.
Unless an author breaks the thread, that is.
^ permalink raw reply
* Re: [PATCH v2] ls-files: filter pathspec before lstat
From: Tamir Duberstein @ 2026-06-09 3:38 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, René Scharfe, Patrick Steinhardt, Jeff King
In-Reply-To: <xmqqv7bstmw8.fsf@gitster.g>
On Mon, Jun 8, 2026 at 8:26 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Tamir Duberstein <tamird@gmail.com> writes:
>
> > show_files() checks whether each index entry is deleted or modified
> > before show_ce() applies the pathspec. prune_index() avoids most of this
> > work for pathspecs with a common directory prefix, but a top-level name
> > or leading wildcard leaves every entry to be checked.
> > ...
>
> Please make sure that your v2 is a response to v1; otherwise loses
> sight of the previous iteration.
>
> > Changes in v2:
> > - Restrict early matching to one pathspec, avoiding the regression Jeff
> > demonstrated with many pathspecs.
> > - Add all-matching and many-pathspec performance results.
> > - Drop the Assisted-by trailer.
> > - Link to v1: https://patch.msgid.link/20260607-ls-files-pathspec-lstat-v1-1-8cf40b730146@gmail.com
>
> And it is *not* a replacement to force human to follow such a link.
>
> Instead, please make sure each piece of your e-mail identifies where
> it fits in the discussion thread by pointing the message of the
> previous round with its In-Reply-To: header.
>
> Thanks.
Apologies, I used b4 which follows kernel rules. I'll follow this
guidance in the future.
^ permalink raw reply
* Re: [PATCH v2] ls-files: filter pathspec before lstat
From: Junio C Hamano @ 2026-06-09 3:26 UTC (permalink / raw)
To: Tamir Duberstein; +Cc: git, René Scharfe, Patrick Steinhardt, Jeff King
In-Reply-To: <20260608-ls-files-pathspec-lstat-v2-1-fb734b28422e@gmail.com>
Tamir Duberstein <tamird@gmail.com> writes:
> show_files() checks whether each index entry is deleted or modified
> before show_ce() applies the pathspec. prune_index() avoids most of this
> work for pathspecs with a common directory prefix, but a top-level name
> or leading wildcard leaves every entry to be checked.
> ...
Please make sure that your v2 is a response to v1; otherwise loses
sight of the previous iteration.
> Changes in v2:
> - Restrict early matching to one pathspec, avoiding the regression Jeff
> demonstrated with many pathspecs.
> - Add all-matching and many-pathspec performance results.
> - Drop the Assisted-by trailer.
> - Link to v1: https://patch.msgid.link/20260607-ls-files-pathspec-lstat-v1-1-8cf40b730146@gmail.com
And it is *not* a replacement to force human to follow such a link.
Instead, please make sure each piece of your e-mail identifies where
it fits in the discussion thread by pointing the message of the
previous round with its In-Reply-To: header.
Thanks.
^ permalink raw reply
* [GSoC] [Blog] week 2: Improving the new git repo command
From: K Jayatheerth @ 2026-06-09 3:10 UTC (permalink / raw)
To: GIT Mailing-list, Justin Tobler, Lucas Seiki Oshiro
In-Reply-To: <CA+rGoLee083Whzi3b9CP3Hxrq_cz58enN67ZQq5r0koczKeU1A@mail.gmail.com>
Hey everyone,
My Week 2 GSoC blog is live!
https://jayatheerth.com/blogs/gsoc/week-2-feedback1
Feel free to give it a read and share any feedback ; )
Regards,
- K Jayatheerth
^ permalink raw reply
* Re: [GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting
From: K Jayatheerth @ 2026-06-09 2:47 UTC (permalink / raw)
To: Lucas Seiki Oshiro
Cc: git, a3205153416, gitster, jltobler, kumarayushjha123,
phillip.wood, sandals
In-Reply-To: <22E79E77-BCC3-4622-BD39-F4ED7DDA9511@gmail.com>
>
> Nitpick: the documentation is clear to me, but maybe the function name
> "format" and the parameter name "buf" can mislead the user to think
> that it only formats the path without appending to the existing string
> in `buf`. My suggestion is to rename them to something like
> `append_formatted_path` and `dest`, respectively.
>
Ok, that's a good point!
I will add this in the next series!
>
> > +test_repo_info_path () {
> > + field_name=$1
> > + expect_absolute_eval=$2
> > + expect_relative=$3
> > + env_prefix=$4
>
> This helper function needs a documentation.
>
Alright, I will add that.
> > + test_expect_success "query individual key: path.$field_name.absolute${env_prefix:+ ($env_prefix)}" '
>
> This makes the output polluted. What about changing it by something like:
>
> test_expect_success "absolute: $label' '...'
> test_expect_success "relative: $label' '...'
>
> with a custom label?
>
Ahh, interesting.
I agree, I will look into this!
> > +
> > +test_expect_success 'setup test repository layout for path fields' '
> > + git init test-repo &&
> > + mkdir -p test-repo/sub
> > +'
>
> The helper function `test_repo_info_path` is relying too much on the
> existence of the `test-repo`. I think it would be better to add a new
> parameter `repo_name` (or similar) because
>
> 1. You could move this creation to the helper function and
> you won't need to place the test after that creation
>
> 2. You could use different for each (test_repo_info_path call, path format)
> pair. Currently, if more than one test fails, its result is overwritten
> and the `expect` and `actual` files from the trash directory will be
> the last of the broken tests.
>
> 3. You won't need to use the hacky 'echo "$(cd .. && pwd)'
>
> This applies my suggestions (feel free to use, adapt or discard it):
>
Thanks!
That is helpful.
Regards,
- K Jayatheerth
^ permalink raw reply
* [PATCH v2] ls-files: filter pathspec before lstat
From: Tamir Duberstein @ 2026-06-09 2:37 UTC (permalink / raw)
To: git
Cc: René Scharfe, Patrick Steinhardt, Junio C Hamano, Jeff King,
Tamir Duberstein
show_files() checks whether each index entry is deleted or modified
before show_ce() applies the pathspec. prune_index() avoids most of this
work for pathspecs with a common directory prefix, but a top-level name
or leading wildcard leaves every entry to be checked.
For a single pathspec, match it before lstat() in the deleted and
modified modes. Keep the later match in show_ce() so --error-unmatch is
satisfied only by entries that are actually shown.
match_pathspec() is linear in the number of pathspec items. Applying it
early for every item can therefore multiply the work for commands with
many pathspecs, especially when lstat() shows that no entries are
modified. Restrict the early check to one pathspec. Callers with
multiple pathspecs retain the existing lstat()-first order.
On a repository with 859,211 index entries, a 19,931,862-byte index,
and 25,303,439 packed objects occupying 21.13 GiB, I exported $parent
and $this to binaries built from the parent and this commit, then ran:
hyperfine --warmup 0 --runs 3 \
--command-name parent \
'$parent -c core.fsmonitor=false ls-files --deleted -- README.md' \
--command-name 'this commit' \
'$this -c core.fsmonitor=false ls-files --deleted -- README.md'
The results were:
parent this commit
elapsed 60.742 s 1.061 s
user 1.117 s 0.963 s
system 10.740 s 0.042 s
For an all-matching pathspec, I used a checkout with 859,940 index
entries and ran:
hyperfine --warmup 0 --runs 3 \
--command-name parent \
'$parent -c core.fsmonitor=false ls-files --deleted -- "*"' \
--command-name 'this commit' \
'$this -c core.fsmonitor=false ls-files --deleted -- "*"'
I repeated the benchmark with the commands reversed. The results were:
parent this commit
parent first elapsed 56.807 s 64.618 s
user 1.256 s 1.270 s
system 10.633 s 11.068 s
patched first elapsed 63.361 s 64.316 s
user 1.238 s 1.280 s
system 10.296 s 11.864 s
The patched user-time means were 14 ms and 42 ms higher in the two
orderings. Elapsed time changed by several seconds when the order was
reversed, so those results do not show a stable wall-time ordering.
Jeff King pointed out that a preliminary match for each of many literal
pathspecs can be much more expensive. On a generated repository with
10,000 clean files, I recorded the paths with "git ls-files >paths".
With $v1 exported to a binary built from the implementation sent in v1,
I ran:
hyperfine --warmup 2 --runs 10 \
--command-name parent \
'$parent ls-files -m -- $(cat paths) >/dev/null' \
--command-name 'this commit' \
'$this ls-files -m -- $(cat paths) >/dev/null'
I replaced $this with $v1 in a second invocation. The wall-clock means
and standard deviations were:
mean standard deviation
parent, final run 110.1 ms 4.1 ms
this commit 104.9 ms 2.2 ms
parent, v1 run 112.5 ms 6.6 ms
unguarded v1 494.1 ms 17.2 ms
The guarded result matches the parent within the observed variation,
while avoiding the regression in v1.
All three revisions were built with -O3, -mcpu=native, and ThinLTO
using Apple clang 21.0.0 on macOS 26.5. The machine was a MacBook Pro
(Mac16,6) with a 16-core Apple M4 Max (12 performance and four
efficiency cores) and 128 GB RAM.
Link: https://lore.kernel.org/r/20260607-ls-files-pathspec-lstat-v1-1-8cf40b730146@gmail.com
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Tamir Duberstein <tamird@gmail.com>
---
A selective pathspec should let ls-files --deleted and --modified avoid
statting entries that cannot be shown. Match a single pathspec before
accessing the worktree, while preserving the existing lstat-first order
for multiple pathspecs whose matching cost grows linearly.
---
Changes in v2:
- Restrict early matching to one pathspec, avoiding the regression Jeff
demonstrated with many pathspecs.
- Add all-matching and many-pathspec performance results.
- Drop the Assisted-by trailer.
- Link to v1: https://patch.msgid.link/20260607-ls-files-pathspec-lstat-v1-1-8cf40b730146@gmail.com
---
builtin/ls-files.c | 11 +++++++++++
t/meson.build | 1 +
t/perf/p3010-ls-files.sh | 31 +++++++++++++++++++++++++++++++
t/t3010-ls-files-killed-modified.sh | 18 ++++++++++++++++++
4 files changed, 61 insertions(+)
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index e1a22b41b9..8d7158652b 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -450,6 +450,17 @@ static void show_files(struct repository *repo, struct dir_struct *dir)
continue;
if (ce_skip_worktree(ce))
continue;
+ /*
+ * match_pathspec() is linear in pathspec.nr, so prefilter only
+ * the single-pathspec case. Only entries shown by show_ce()
+ * satisfy --error-unmatch.
+ */
+ if (pathspec.nr == 1 &&
+ !match_pathspec(repo->index, &pathspec, fullname.buf,
+ fullname.len, max_prefix_len, NULL,
+ S_ISDIR(ce->ce_mode) ||
+ S_ISGITLINK(ce->ce_mode)))
+ continue;
stat_err = lstat(fullname.buf, &st);
if (stat_err && (errno != ENOENT && errno != ENOTDIR))
error_errno("cannot lstat '%s'", fullname.buf);
diff --git a/t/meson.build b/t/meson.build
index 2af8d01279..ee8086e6ef 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -1140,6 +1140,7 @@ benchmarks = [
'perf/p1500-graph-walks.sh',
'perf/p1501-rev-parse-oneline.sh',
'perf/p2000-sparse-operations.sh',
+ 'perf/p3010-ls-files.sh',
'perf/p3400-rebase.sh',
'perf/p3404-rebase-interactive.sh',
'perf/p4000-diff-algorithms.sh',
diff --git a/t/perf/p3010-ls-files.sh b/t/perf/p3010-ls-files.sh
new file mode 100755
index 0000000000..ae14449432
--- /dev/null
+++ b/t/perf/p3010-ls-files.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+test_description='Tests ls-files worktree performance'
+
+. ./perf-lib.sh
+
+test_perf_large_repo
+test_checkout_worktree
+
+test_expect_success 'select a zero-prefix pathspec' '
+ tracked_file=$(git ls-files | sed -n 1p) &&
+ test -n "$tracked_file" &&
+ pathspec="?${tracked_file#?}" &&
+ test_export pathspec
+'
+
+test_perf 'ls-files --deleted with pathspec' '
+ git -c core.fsmonitor=false ls-files --deleted \
+ -- "$pathspec" >/dev/null
+'
+
+test_perf 'ls-files --deleted with all-matching pathspec' '
+ git -c core.fsmonitor=false ls-files --deleted -- "*" >/dev/null
+'
+
+test_perf 'ls-files --modified with pathspec' '
+ git -c core.fsmonitor=false ls-files --modified \
+ -- "$pathspec" >/dev/null
+'
+
+test_done
diff --git a/t/t3010-ls-files-killed-modified.sh b/t/t3010-ls-files-killed-modified.sh
index 7af4532cd1..6e38e10219 100755
--- a/t/t3010-ls-files-killed-modified.sh
+++ b/t/t3010-ls-files-killed-modified.sh
@@ -124,4 +124,22 @@ test_expect_success 'validate git ls-files -m output.' '
test_cmp .expected .output
'
+test_expect_success 'worktree modes honor wildcard pathspecs' '
+ cat >.expected <<-\EOF &&
+ path2/file2
+ path3/file3
+ EOF
+ git ls-files --deleted -- "path?/file?" >.output &&
+ test_cmp .expected .output &&
+
+ cat >.expected <<-\EOF &&
+ path7
+ path8
+ EOF
+ git ls-files --modified --error-unmatch -- "path[78]" >.output &&
+ test_cmp .expected .output &&
+
+ test_must_fail git ls-files --modified --error-unmatch -- path10
+'
+
test_done
---
base-commit: 9ac3f193c05c2237e2b14ebaa1149e9fc8a1abe0
change-id: 20260607-ls-files-pathspec-lstat-885125a5d644
Best regards,
--
Tamir Duberstein <tamird@gmail.com>
^ permalink raw reply related
* [PATCH v2 2/2] ref-filter: memoize --contains with generations
From: Tamir Duberstein @ 2026-06-09 2:36 UTC (permalink / raw)
To: git
Cc: Jeff King, Karthik Nayak, Junio C Hamano, Victoria Dye,
Derrick Stolee, Elijah Newren, Tamir Duberstein
In-Reply-To: <20260608-ref-filter-memoized-contains-v2-0-e72720344a7c@gmail.com>
git branch and git for-each-ref call repo_is_descendant_of() for
each candidate selected by --contains or --no-contains. Each call
starts a new graph walk, so refs with shared history repeatedly
traverse the same commits.
ffc4b8012d (tag: speed up --contains calculation, 2011-06-11)
introduced a depth-first walk for git tag that caches positive and
negative answers across candidates. ee2bd06b0f (ref-filter: implement
'--contains' option, 2015-07-07) preserved both implementations when
ref-filter learned --contains.
The memoized walk is not always faster. Without generation numbers,
a negative check can walk to the root even when the breadth-first
merge-base walk finds a nearby divergence. With generation numbers,
the depth-first walk can stop below the oldest target while still
reusing answers across candidates.
Keep the existing memoized selection for git tag. Select it for other
ref-filter callers when generation numbers are enabled, and retain
the breadth-first walk otherwise.
When generation numbers are unavailable, repo_is_descendant_of() can
return -1 if ancestry cannot be read. The ref-filter Boolean interface
treated that error as a match. Check it and exit instead. The memoized
path already dies on the same parse failure, so both selected paths now
fail rather than return a result.
Add p1500 cases for up to 8,192 packed refs along one first-parent
history and for sibling refs near the tip with generation numbers
forced off.
On a checkout with 62,174 remote-tracking refs and generation numbers
enabled, I ran:
hyperfine --warmup 0 --runs 3 \
--command-name parent \
'"$parent" branch -r --contains c78ae85f3ce7e >/dev/null' \
--command-name this-commit \
'"$this" branch -r --contains c78ae85f3ce7e >/dev/null'
The results were:
parent this commit
elapsed 104.365 s 467.7 ms
user 93.702 s 220.2 ms
system 0.723 s 182.7 ms
The wall-time standard deviations were 11.356 seconds and 133.8
milliseconds, respectively. Separate runs without redirection produced
the same output with SHA-256
2466f6e2b72aa16b1a2126eddb81c8a1b2764ee251204ac034c191a925aa896f.
Both revisions were built with the default -O2 flags using Apple
clang 21.0.0 on macOS 26.5. The machine was a MacBook Pro (Mac16,6)
with a 16-core Apple M4 Max (12 performance and four efficiency
cores) and 128 GB RAM.
Link: https://lore.kernel.org/git/1445163904-24611-1-git-send-email-Karthik.188@gmail.com/
Link: https://lore.kernel.org/r/20230324191009.GA536967@coredump.intra.peff.net
Link: https://lore.kernel.org/git/20260527070510.3510836-1-krka@spotify.com/
Link: https://lore.kernel.org/r/20260608223430.GA340696@coredump.intra.peff.net
Suggested-by: Jeff King <peff@peff.net>
Signed-off-by: Tamir Duberstein <tamird@gmail.com>
---
commit-reach.c | 13 +++++++++--
commit-reach.h | 7 ++++++
t/perf/p1500-graph-walks.sh | 49 +++++++++++++++++++++++++++++++++++++++++-
t/t6301-for-each-ref-errors.sh | 22 +++++++++++++++++++
4 files changed, 88 insertions(+), 3 deletions(-)
diff --git a/commit-reach.c b/commit-reach.c
index 65b618959b..83a48004ef 100644
--- a/commit-reach.c
+++ b/commit-reach.c
@@ -821,9 +821,18 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
int commit_contains(struct ref_filter *filter, struct commit *commit,
struct commit_list *list, struct contains_cache *cache)
{
- if (filter->with_commit_tag_algo)
+ int result;
+
+ if (!list)
+ return 1;
+ if (filter->with_commit_tag_algo ||
+ generation_numbers_enabled(the_repository))
return contains_tag_algo(commit, list, cache) == CONTAINS_YES;
- return repo_is_descendant_of(the_repository, commit, list);
+
+ result = repo_is_descendant_of(the_repository, commit, list);
+ if (result < 0)
+ exit(128);
+ return result;
}
int can_all_from_reach_with_flag(struct object_array *from,
diff --git a/commit-reach.h b/commit-reach.h
index f908d305b1..da6796a354 100644
--- a/commit-reach.h
+++ b/commit-reach.h
@@ -79,6 +79,13 @@ enum contains_result {
define_commit_slab(contains_cache, enum contains_result);
+/*
+ * Return whether "commit" is a descendant of any commit in "list". An empty
+ * list matches.
+ *
+ * The memoized traversal records answers in "cache" for one fixed "list".
+ * Clear it before changing the list.
+ */
int commit_contains(struct ref_filter *filter, struct commit *commit,
struct commit_list *list, struct contains_cache *cache);
diff --git a/t/perf/p1500-graph-walks.sh b/t/perf/p1500-graph-walks.sh
index 5b23ce5db9..99b54e274b 100755
--- a/t/perf/p1500-graph-walks.sh
+++ b/t/perf/p1500-graph-walks.sh
@@ -32,12 +32,47 @@ test_expect_success 'setup' '
echo "X:$line" >>test-tool-tags || return 1
done &&
- commit=$(git commit-tree $(git rev-parse HEAD^{tree})) &&
+ git rev-list --first-parent --max-count=8192 HEAD >contains-commits &&
+ test_file_not_empty contains-commits &&
+ git update-ref refs/contains-perf-base "$(tail -n 1 contains-commits)" &&
+ awk "{
+ printf \"update refs/contains-perf/%04d %s\\n\", NR, \$1
+ }" contains-commits |
+ git update-ref --stdin &&
+ git pack-refs --include "refs/contains-perf/*" &&
+
+ tree=$(git rev-parse HEAD^{tree}) &&
+ base=$(git rev-parse HEAD) &&
+ target=$(echo target | git commit-tree "$tree" -p "$base") &&
+ git update-ref refs/contains-diverged/target "$target" &&
+ for i in $(test_seq 1 4)
+ do
+ commit=$(echo candidate-$i |
+ git commit-tree "$tree" -p "$base") &&
+ git update-ref refs/contains-diverged/candidate-$i "$commit" ||
+ return 1
+ done &&
+
+ commit=$(git commit-tree "$tree") &&
git update-ref refs/heads/disjoint-base $commit &&
git commit-graph write --reachable
'
+test_expect_success 'verify contains results' '
+ git for-each-ref --contains=refs/contains-perf-base \
+ refs/contains-perf/ >actual &&
+ test_line_count = $(wc -l <contains-commits) actual &&
+
+ echo refs/contains-diverged/target >expect &&
+ GIT_TEST_COMMIT_GRAPH=0 \
+ git -c core.commitGraph=false for-each-ref \
+ --format="%(refname)" \
+ --contains=refs/contains-diverged/target \
+ refs/contains-diverged/ >actual &&
+ test_cmp expect actual
+'
+
test_perf 'ahead-behind counts: git for-each-ref' '
git for-each-ref --format="%(ahead-behind:HEAD)" --stdin <refs
'
@@ -62,6 +97,18 @@ test_perf 'contains: git tag --merged' '
xargs git tag --merged=HEAD <tags
'
+test_perf 'contains: git for-each-ref --contains' '
+ git for-each-ref --contains=refs/contains-perf-base \
+ refs/contains-perf/ >/dev/null
+'
+
+test_perf 'contains without generations: divergent refs' '
+ GIT_TEST_COMMIT_GRAPH=0 \
+ git -c core.commitGraph=false for-each-ref \
+ --contains=refs/contains-diverged/target \
+ refs/contains-diverged/ >/dev/null
+'
+
test_perf 'is-base check: test-tool reach (refs)' '
test-tool reach get_branch_base_for_tip <test-tool-refs
'
diff --git a/t/t6301-for-each-ref-errors.sh b/t/t6301-for-each-ref-errors.sh
index e06feb06e9..72b27c8be3 100755
--- a/t/t6301-for-each-ref-errors.sh
+++ b/t/t6301-for-each-ref-errors.sh
@@ -52,6 +52,28 @@ test_expect_success 'Missing objects are reported correctly' '
test_must_be_empty brief-err
'
+test_expect_success 'missing ancestors are reported by contains filters' '
+ test_when_finished "git update-ref -d refs/heads/missing-parent" &&
+ {
+ echo "tree $(git rev-parse HEAD^{tree})" &&
+ echo "parent $MISSING" &&
+ git cat-file commit HEAD |
+ sed -n -e "/^author /p" -e "/^committer /p" &&
+ echo &&
+ echo "missing parent"
+ } >commit &&
+ broken=$(git hash-object -t commit -w commit) &&
+ git update-ref refs/heads/missing-parent "$broken" &&
+ for option in --contains --no-contains
+ do
+ test_must_fail git for-each-ref "$option=HEAD" \
+ refs/heads/missing-parent >out 2>err &&
+ test_must_be_empty out &&
+ test_grep "parse commit $MISSING" err ||
+ return 1
+ done
+'
+
test_expect_success 'ahead-behind requires an argument' '
test_must_fail git for-each-ref \
--format="%(ahead-behind)" 2>err &&
--
2.54.0.501.g0fb508de08
^ permalink raw reply related
* [PATCH v2 1/2] commit-reach: handle cycles in contains walk
From: Tamir Duberstein @ 2026-06-09 2:36 UTC (permalink / raw)
To: git
Cc: Jeff King, Karthik Nayak, Junio C Hamano, Victoria Dye,
Derrick Stolee, Elijah Newren, Tamir Duberstein
In-Reply-To: <20260608-ref-filter-memoized-contains-v2-0-e72720344a7c@gmail.com>
git tag --contains uses a memoized traversal that assumes commit
ancestry is acyclic. Replacement refs can violate that assumption,
causing the traversal to revisit a commit already on its stack
indefinitely.
Mark commits while they are active. If the traversal encounters an
active commit, discard the cache because it cannot distinguish answers
produced by the interrupted walk. Then fall back to the cycle-safe
reachability walk for that candidate.
Signed-off-by: Tamir Duberstein <tamird@gmail.com>
---
commit-reach.c | 30 ++++++++++++++++++++++++++----
commit-reach.h | 3 ++-
t/t7004-tag.sh | 21 +++++++++++++++++++++
3 files changed, 49 insertions(+), 5 deletions(-)
diff --git a/commit-reach.c b/commit-reach.c
index 9b3ea46d6f..65b618959b 100644
--- a/commit-reach.c
+++ b/commit-reach.c
@@ -708,7 +708,8 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)
/*
* Test whether the candidate is contained in the list.
- * Do not recurse to find out, though, but return -1 if inconclusive.
+ * Do not recurse to find out, though, but return CONTAINS_UNKNOWN if
+ * inconclusive.
*/
static enum contains_result contains_test(struct commit *candidate,
const struct commit_list *want,
@@ -744,7 +745,7 @@ static void push_to_contains_stack(struct commit *candidate, struct contains_sta
}
static enum contains_result contains_tag_algo(struct commit *candidate,
- const struct commit_list *want,
+ struct commit_list *want,
struct contains_cache *cache)
{
struct contains_stack contains_stack = { 0, 0, NULL };
@@ -765,6 +766,7 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
if (result != CONTAINS_UNKNOWN)
return result;
+ *contains_cache_at(cache, candidate) = CONTAINS_IN_PROGRESS;
push_to_contains_stack(candidate, &contains_stack);
while (contains_stack.nr) {
struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
@@ -776,8 +778,8 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
contains_stack.nr--;
}
/*
- * If we just popped the stack, parents->item has been marked,
- * therefore contains_test will return a meaningful yes/no.
+ * A parent may have just been popped and marked, or may still
+ * be active when replacement refs create a cycle.
*/
else switch (contains_test(parents->item, want, cache, cutoff)) {
case CONTAINS_YES:
@@ -787,13 +789,33 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
case CONTAINS_NO:
entry->parents = parents->next;
break;
+ case CONTAINS_IN_PROGRESS:
+ /*
+ * Partial negative answers are not safe across a cycle.
+ * Discard them and use the cycle-safe reachability walk.
+ */
+ goto cycle;
case CONTAINS_UNKNOWN:
+ *contains_cache_at(cache, parents->item) =
+ CONTAINS_IN_PROGRESS;
push_to_contains_stack(parents->item, &contains_stack);
break;
}
}
free(contains_stack.contains_stack);
return contains_test(candidate, want, cache, cutoff);
+
+cycle:
+ free(contains_stack.contains_stack);
+ clear_contains_cache(cache);
+ init_contains_cache(cache);
+
+ result = repo_is_descendant_of(the_repository, candidate, want);
+ if (result < 0)
+ exit(128);
+ *contains_cache_at(cache, candidate) =
+ result ? CONTAINS_YES : CONTAINS_NO;
+ return result ? CONTAINS_YES : CONTAINS_NO;
}
int commit_contains(struct ref_filter *filter, struct commit *commit,
diff --git a/commit-reach.h b/commit-reach.h
index 3f3a563d8a..f908d305b1 100644
--- a/commit-reach.h
+++ b/commit-reach.h
@@ -73,7 +73,8 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid);
enum contains_result {
CONTAINS_UNKNOWN = 0,
CONTAINS_NO,
- CONTAINS_YES
+ CONTAINS_YES,
+ CONTAINS_IN_PROGRESS
};
define_commit_slab(contains_cache, enum contains_result);
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index d918005dd9..1ed91bb66e 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -1611,6 +1611,27 @@ test_expect_success 'checking that first commit is in all tags (hash)' '
test_cmp expected actual
'
+test_expect_success 'tag --contains handles cyclic replacement histories' '
+ first=$(git rev-parse HEAD~2) &&
+ second=$(git rev-parse HEAD~) &&
+ third=$(git rev-parse HEAD) &&
+ test_when_finished "
+ git replace -d $first
+ git replace -d $third
+ git tag -d cycle-a cycle-b
+ " &&
+ git tag cycle-a "$first" &&
+ git tag cycle-b "$third" &&
+ git replace --graft "$first" "$third" "$second" &&
+ git replace --graft "$third" "$first" &&
+ cat >expected <<-\EOF &&
+ cycle-a
+ cycle-b
+ EOF
+ git tag --contains="$second" --list "cycle-*" >actual &&
+ test_cmp expected actual
+'
+
# other ways of specifying the commit
test_expect_success 'checking that first commit is in all tags (tag)' '
cat >expected <<-\EOF &&
--
2.54.0.501.g0fb508de08
^ permalink raw reply related
* [PATCH v2 0/2] Reuse --contains traversal results
From: Tamir Duberstein @ 2026-06-09 2:36 UTC (permalink / raw)
To: git
Cc: Jeff King, Karthik Nayak, Junio C Hamano, Victoria Dye,
Derrick Stolee, Elijah Newren, Tamir Duberstein
The memoized traversal used by git tag avoids repeating graph walks for
refs with shared history. Extend it to the other ref-filter users after
making the existing traversal safe for cycles introduced by replacement
refs.
Signed-off-by: Tamir Duberstein <tamird@gmail.com>
---
Changes in v2:
- Split cycle handling into a preparatory patch.
- Exercise cycle handling through the existing git tag path.
- Move perf result verification out of setup.
- Link to v1: https://patch.msgid.link/20260607-ref-filter-memoized-contains-v1-1-a1972dde9c76@gmail.com
---
Tamir Duberstein (2):
commit-reach: handle cycles in contains walk
ref-filter: memoize --contains with generations
commit-reach.c | 43 ++++++++++++++++++++++++++++++------
commit-reach.h | 10 ++++++++-
t/perf/p1500-graph-walks.sh | 49 +++++++++++++++++++++++++++++++++++++++++-
t/t6301-for-each-ref-errors.sh | 22 +++++++++++++++++++
t/t7004-tag.sh | 21 ++++++++++++++++++
5 files changed, 137 insertions(+), 8 deletions(-)
---
base-commit: 9ac3f193c05c2237e2b14ebaa1149e9fc8a1abe0
change-id: 20260607-ref-filter-memoized-contains-7cb6b3bccad1
Best regards,
--
Tamir Duberstein <tamird@gmail.com>
^ permalink raw reply
* [PATCH v2] ref-filter: restore prefix-scoped iteration
From: Tamir Duberstein @ 2026-06-09 2:34 UTC (permalink / raw)
To: git
Cc: Karthik Nayak, Patrick Steinhardt, Junio C Hamano, Victoria Dye,
ZheNing Hu, Tamir Duberstein
Commit dabecb9db2 (for-each-ref: introduce a '--start-after' option,
2025-07-15) changed single-kind branch, remote-tracking branch, and tag
enumeration in do_filter_refs() from constructing an iterator with the
namespace prefix to constructing an unscoped iterator and applying the
prefix with ref_iterator_seek().
Before that change, refs_for_each_fullref_in() passed the namespace
prefix during iterator construction. That helper has since been
replaced by refs_for_each_ref_ext().
The files backend primes its loose-ref cache for the construction
prefix before it opens packed refs. An empty construction prefix
therefore reads every loose ref, and a later seek cannot undo that I/O.
Consequently, git branch, git branch --remotes, and git tag scale with
unrelated loose refs.
Patrick Steinhardt observed during review that iterator construction
and seeking accepted similar strings but assigned them different state
semantics. Junio C Hamano then pointed out that no current command can
combine start_after with this single-kind path, but future branch or
tag support would need to keep the namespace while moving the cursor.
Keep the existing start_after path unchanged. The iterator API cannot
currently seek to one string while retaining another as its prefix:
an unflagged seek clears the prefix, while REF_ITERATOR_SEEK_SET_PREFIX
replaces it with the seek string.
For the commands affected by this regression, which do not set
start_after, pass the namespace prefix during iterator construction so
that loose refs are scoped before the packed-refs snapshot is opened.
This fixes the current regression without deleting the ref-filter state
discussed during review or changing its dormant behavior.
Add REFFILES-gated performance cases with one branch, one
remote-tracking branch, one tag, and 10,000 unrelated loose refs. The
benchmarks were run with:
GIT_PERF_REPEAT_COUNT=5 GIT_PERF_MAKE_OPTS=-j8 \
t/perf/run a89346e34a . -- p6300-for-each-ref.sh
The following are the best of five runs, with each run invoking the
command ten times. Times are elapsed seconds with user and system CPU
seconds in parentheses:
a89346e34a this commit
branch 2.74(0.13+2.56) 0.11(0.04+0.04)
branch --remotes 2.81(0.13+2.62) 0.12(0.04+0.04)
tag 3.01(0.14+2.82) 0.11(0.04+0.04)
Both revisions used the default -O2 build flags and a config.mak
containing only "NO_REGEX = NeedsStartEnd". They were built with Apple
clang 21.0.0 on macOS 26.5. The machine was a MacBook Pro (Mac16,6)
with a 16-core Apple M4 Max (12 performance and four efficiency cores)
and 128 GB RAM.
Link: https://lore.kernel.org/git/aGZidwwlToWThkn8@pks.im/
Link: https://lore.kernel.org/git/xmqqikjq7s16.fsf@gitster.g/
Fixes: dabecb9db2b2 ("for-each-ref: introduce a '--start-after' option")
Assisted-by: Codex gpt-5.5
Signed-off-by: Tamir Duberstein <tamird@gmail.com>
---
The series is based on a89346e34a (maint) because the regression has
been present in released versions since Git 2.51.0.
---
Changes in v2:
- Extract local variable `store`.
- Link to v1: https://patch.msgid.link/20260605-fix-git-branch-regression-v1-1-02f40ad40929@gmail.com
---
ref-filter.c | 28 +++++++++++++++++++---------
t/perf/p6300-for-each-ref.sh | 39 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 57 insertions(+), 10 deletions(-)
diff --git a/ref-filter.c b/ref-filter.c
index 1da4c0e60d..5cbc007d64 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -3315,19 +3315,29 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, refs_for
prefix = "refs/tags/";
if (prefix) {
- struct ref_iterator *iter;
+ struct ref_store *store = get_main_ref_store(the_repository);
- iter = refs_ref_iterator_begin(get_main_ref_store(the_repository),
- "", NULL, 0, 0);
+ if (filter->start_after) {
+ struct ref_iterator *iter;
+
+ iter = refs_ref_iterator_begin(store, "", NULL, 0, 0);
- if (filter->start_after)
ret = start_ref_iterator_after(iter, filter->start_after);
- else
- ret = ref_iterator_seek(iter, prefix,
- REF_ITERATOR_SEEK_SET_PREFIX);
+ if (!ret)
+ ret = do_for_each_ref_iterator(iter, fn,
+ cb_data);
+ } else {
+ /*
+ * Pass the prefix during construction because the files
+ * backend primes loose refs before a later seek can
+ * narrow the iterator.
+ */
+ struct refs_for_each_ref_options opts = {
+ .prefix = prefix,
+ };
- if (!ret)
- ret = do_for_each_ref_iterator(iter, fn, cb_data);
+ ret = refs_for_each_ref_ext(store, fn, cb_data, &opts);
+ }
} else if (filter->kind & FILTER_REFS_REGULAR) {
ret = for_each_fullref_in_pattern(filter, fn, cb_data);
}
diff --git a/t/perf/p6300-for-each-ref.sh b/t/perf/p6300-for-each-ref.sh
index fa7289c752..ed9c1c6a19 100755
--- a/t/perf/p6300-for-each-ref.sh
+++ b/t/perf/p6300-for-each-ref.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-test_description='performance of for-each-ref'
+test_description='performance of ref-filter users'
. ./perf-lib.sh
test_perf_fresh_repo
@@ -84,4 +84,41 @@ test_expect_success 'pack refs' '
'
run_tests "packed"
+test_expect_success REFFILES 'setup many unrelated loose refs' '
+ git init scoped &&
+ test_commit -C scoped --no-tag base &&
+ test_seq $ref_count_per_type |
+ sed "s,.*,update refs/custom/unrelated_& HEAD," |
+ git -C scoped update-ref --stdin &&
+ git -C scoped update-ref refs/remotes/origin/main HEAD &&
+ git -C scoped update-ref refs/tags/only HEAD
+'
+
+test_perf "branch (many unrelated loose refs)" --prereq REFFILES "
+ (
+ cd scoped &&
+ for i in \$(test_seq $test_iteration_count); do
+ git branch --format='%(refname)' >/dev/null
+ done
+ )
+"
+
+test_perf "branch --remotes (many unrelated loose refs)" --prereq REFFILES "
+ (
+ cd scoped &&
+ for i in \$(test_seq $test_iteration_count); do
+ git branch --remotes --format='%(refname)' >/dev/null
+ done
+ )
+"
+
+test_perf "tag (many unrelated loose refs)" --prereq REFFILES "
+ (
+ cd scoped &&
+ for i in \$(test_seq $test_iteration_count); do
+ git tag --format='%(refname)' >/dev/null
+ done
+ )
+"
+
test_done
---
base-commit: a89346e34a937f001e5d397ee62224e3e9852040
change-id: 20260605-fix-git-branch-regression-9e4236f18091
Best regards,
--
Tamir Duberstein <tamird@gmail.com>
^ permalink raw reply related
* [PATCH v2] describe: limit default ref iteration to tags
From: Tamir Duberstein @ 2026-06-09 2:32 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Jeff King, Patrick Steinhardt, Tamir Duberstein
Unless --all is given, get_name() rejects every ref outside refs/tags/.
The rejection happens only after the ref backend has enumerated the ref,
so repositories with many other refs spend most of a simple describe
invocation visiting refs which cannot affect its result.
Commit 8a5a1884e9 (Avoid accessing non-tag refs in git-describe unless
--all is requested, 2008-02-24) moved this rejection before object
lookup, but left iteration unscoped. Pass the existing refs/tags/
restriction to the iterator unless --all is given so the backend can
avoid unrelated refs.
The benchmark checkout had 120,532 refs, of which 330 were tags. With
`$repo` naming the checkout, `$commit` an exactly tagged commit, and
`$parent` and `$this` the two binaries, I ran:
hyperfine --warmup 3 --runs 15 \
--command-name parent \
'$parent -C $repo describe --exact-match $commit' \
--command-name 'this commit' \
'$this -C $repo describe --exact-match $commit'
The results were:
Benchmark 1: parent
Time (mean ± σ): 171.7 ms ± 18.5 ms [User: 23.9 ms, System: 133.6 ms]
Range (min … max): 142.3 ms … 198.3 ms 15 runs
Benchmark 2: this commit
Time (mean ± σ): 9.9 ms ± 1.1 ms [User: 3.3 ms, System: 4.7 ms]
Range (min … max): 8.8 ms … 13.1 ms 15 runs
Summary
this commit ran
17.35 ± 2.63 times faster than parent
Both revisions were built with -O3, -mcpu=native, and ThinLTO using
Apple clang 21.0.0 on macOS 26.5. The machine was a MacBook Pro
(Mac16,6) with a 16-core Apple M4 Max (12 performance and four
efficiency cores) and 128 GB RAM.
Signed-off-by: Tamir Duberstein <tamird@gmail.com>
---
Changes in v2:
- Exercise the performance test with both ref backends.
- Keep the ref count local to its setup test.
- Report native hyperfine output for an exact-tag lookup.
- Link to v1: https://patch.msgid.link/20260607-describe-tag-ref-scope-v1-1-653d232b86b5@gmail.com
---
builtin/describe.c | 3 +++
t/perf/p6100-describe.sh | 15 +++++++++++++++
2 files changed, 18 insertions(+)
diff --git a/builtin/describe.c b/builtin/describe.c
index 1c47d7c0b7..3532c8ff22 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -740,6 +740,9 @@ int cmd_describe(int argc,
return ret;
}
+ if (!all)
+ for_each_ref_opts.prefix = "refs/tags/";
+
hashmap_init(&names, commit_name_neq, NULL, 0);
refs_for_each_ref_ext(get_main_ref_store(the_repository),
get_name, NULL, &for_each_ref_opts);
diff --git a/t/perf/p6100-describe.sh b/t/perf/p6100-describe.sh
index 069f91ce49..ed9f1abe18 100755
--- a/t/perf/p6100-describe.sh
+++ b/t/perf/p6100-describe.sh
@@ -27,4 +27,19 @@ test_perf 'describe HEAD with one tag' '
git describe --match=new HEAD
'
+test_expect_success 'set up many unrelated refs' '
+ ref_count=10000 &&
+ git tag -m tip tip HEAD &&
+ for i in $(test_seq $ref_count)
+ do
+ printf "create refs/heads/describe-perf/%05d HEAD\n" $i ||
+ return 1
+ done >instructions &&
+ git update-ref --stdin <instructions
+'
+
+test_perf 'describe exact tag with many unrelated refs' '
+ git describe --exact-match HEAD
+'
+
test_done
---
base-commit: 9ac3f193c05c2237e2b14ebaa1149e9fc8a1abe0
change-id: 20260607-describe-tag-ref-scope-7d00ae140a58
Best regards,
--
Tamir Duberstein <tamird@gmail.com>
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox