Git development
 help / color / mirror / Atom feed
* Re: [PATCH v4] ref-filter: restore prefix-scoped iteration
From: Junio C Hamano @ 2026-06-18 15:54 UTC (permalink / raw)
  To: Tamir Duberstein
  Cc: git, Karthik Nayak, Patrick Steinhardt, Victoria Dye, ZheNing Hu
In-Reply-To: <20260612-fix-git-branch-regression-v4-1-f150038c02f4@gmail.com>

Tamir Duberstein <tamird@gmail.com> writes:

> Changes in v4:
> - Explain the historical references in the commit message.
> - Run the new performance cases with both ref backends.
> - Drop the Assisted-by trailer.
> - Link to v3: https://patch.msgid.link/20260610-fix-git-branch-regression-v3-1-6fd48fad7a53@gmail.com

This seems to fully address comments by Patrick in
https://lore.kernel.org/git/aivx-7VOKE_TC50R@pks.im/

Let me mark the topic for 'next'.  Thanks all who discussed this patch.

^ permalink raw reply

* Re: [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config
From: Justin Tobler @ 2026-06-18 15:53 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Jeff King
In-Reply-To: <ajQF1yyCUOdzC4Jq@pks.im>

On 26/06/18 04:51PM, Patrick Steinhardt wrote:
> On Thu, Jun 18, 2026 at 09:15:00AM -0500, Justin Tobler wrote:
> > Could we embed an `initialized` boolean in `struct ref_store` that gets
> > set when the ref store is properly initialized and use that as a signal
> > instead? I'm not sure how complex introducing this would be though.
> 
> We could, but I'm not sure what that would really buy us. It would
> basically be one more bit of state that we have to track going forward,
> and thus one more source of inconsistencies.

My naive thought here is that if the ref store knows when it is
initialized, this could be used as a more reliable signal by
`include_by_branch()`. I guess the problem though would be that, at that
point in time, we are still inside `ref_store_init()` and thus the ref
store is not fully initialized anyways. 

I was hoping we could avoid the hack of temporarily setting the ref
format here, but introducing state specific to tracking whether its ok
to parse onbranch conditions in the config is probably not worth it I
guess.

-Justin

^ permalink raw reply

* Re: [PATCH v2 0/7] Introduce fetch.followRemoteHEAD config variable
From: Junio C Hamano @ 2026-06-18 15:47 UTC (permalink / raw)
  To: Matt Hunter; +Cc: git, Bence Ferdinandy, Jeff King
In-Reply-To: <DJBVYP58YNTU.LQ7VXFIQE84H@lfurio.us>

"Matt Hunter" <m@lfurio.us> writes:

>> Other than that, this looks excellent.  Thanks.
>
> Thanks for the great feedback and consideration!
>
> If you like, I can apply the appropriate NEEDSWORK comment, possibly add
> a warning to 'fetch.followRemoteHEAD' parsing (matching the 'remote'
> side), and we can call this good to go for now.

Sounds like a plan.  Thanks.

^ permalink raw reply

* Re: [PATCH] SubmittingPatches: address design critiques
From: Junio C Hamano @ 2026-06-18 15:40 UTC (permalink / raw)
  To: Michael Montalbo; +Cc: git
In-Reply-To: <CAC2QwmJdF+YzAQE3WDEaUrurLVkYcAA0Cgs1YAqyxYcQ0jKfqA@mail.gmail.com>

Michael Montalbo <mmontalbo@gmail.com> writes:

> Two small suggestions: open with a direct imperative and replace
> "effort in the implementation" with "effort on the implementation".
>
>     [B]e particularly mindful of...
>     ... too much effort [on] the implementation...

Yeah, I started from something like that then toned it down, but I
agree that it is more appropriate to be more assertive here.

> Maybe it would help to spell out what the explanation/justification is
> for more explicitly (though it may be a bit redundant with the
> "meaningful message" blurb):

Yeah, and it depends on what kind of higher level issues the
reviewer comment is about, so ...

>     Make sure that any new version explains and justifies those
>     design decisions more clearly: why the change is worth making,
>     what alternatives were considered, and why the chosen approach
>     is correct.  Put that justification in the cover letter, your
>     responses, and the revised commit messages.  Aim to make the
>     reviewers say "it is now clear why we may want to do this with
>     the updated version".

... I find the beginning part of the above very much better than
what I had in my version, but part of the first sentence after the
colon is probably a bit too much.

I'll send out v3 sometime before the next week.

Thanks.


^ permalink raw reply

* Re: [PATCH v2] SubmittingPatches: address design critiques
From: Michael Montalbo @ 2026-06-18 15:36 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Junio C Hamano <gitster@pobox.com> wrote:
> +You should be particularly mindful of critiques regarding the
> +high-level design or viability of your proposal (e.g., questioning
> +whether the feature is worth implementing, or if the chosen approach
> +is appropriate).  Defend your design decisions on the list first, to
> +avoid wasting effort on an implementation whose design is not yet
> +solid.
> ++
> +Make sure that any new version is accompanied by a much clearer
> +explanation and justification (in the cover letter, your responses,
> +and in the revised commit messages).  Aim to make the reviewers say
> +"it is now clear why we may want to do this with the updated version".
> ++
> +Topics that fail to address fundamental design critiques without
> +resolution will not be considered ready for merging.

This wording looks good to me.

^ permalink raw reply

* Re: [PATCH] SubmittingPatches: address design critiques
From: Michael Montalbo @ 2026-06-18 15:17 UTC (permalink / raw)
  To: Michael Montalbo; +Cc: git, Junio C Hamano

On Wed, Jun 17, 2026 at 8:53 PM Michael Montalbo <mmontalbo@gmail.com> wrote:
>
> Junio C Hamano wrote:
> > +You would want to be particularly mindful of critiques regarding the
> > +high-level design or viability of your proposal (e.g., questioning
> > +whether the feature is worth implementing, or if the chosen approach
> > +is appropriate).  You want to defend your design decisions on the list
> > +first, because you do not want to spend too much effort in the
> > +implementation if the design is not yet solid.
>
> Two small suggestions: open with a direct imperative and replace
> "effort in the implementation" with "effort on the implementation".
>
>     [B]e particularly mindful of...
>     ... too much effort [on] the implementation...

(resending to fix threading)

Maybe we can even more directly say:

Do not spend too much effort on the implementation if the design is not
yet solid. Be able to defend your design decisions on the list first.

^ permalink raw reply

* Re: [PATCH v2 0/7] More work supporting objects larger than 4GB on Windows
From: Junio C Hamano @ 2026-06-18 15:08 UTC (permalink / raw)
  To: Patrick Steinhardt
  Cc: Johannes Schindelin via GitGitGadget, git, Kristofer Karlsson,
	Johannes Schindelin
In-Reply-To: <ajPhBn7n1wR-sii4@pks.im>

Patrick Steinhardt <ps@pks.im> writes:

> On Mon, Jun 15, 2026 at 11:52:22AM +0000, Johannes Schindelin via GitGitGadget wrote:
>> This patch series tries to address the problems pointed out by the expensive
>> tests that now run in CI: t5608 and t7508 verify various aspects about
>> objects larger than 4GB, which Git does not currently handle correctly when
>> run on a platform where size_t is 64-bit and unsigned long is 32-bit.
>> 
>> Changes vs v1:
>> 
>>  * Rebased onto master, which merged ps/odb-source-loose (with which these
>>    patches previously conflicted rather badly).
>>  * Removed superfluous size_t s variables (thanks, Patrick!).
>
> I skimmed those parts that I was previously commenting on and am
> happy with those changes. Thanks!

Thanks.  I looked at them and found nothing iffy, either.

^ permalink raw reply

* Re: [PATCH] Fix typo in MaintNotes regarding versioning scheme
From: Junio C Hamano @ 2026-06-18 15:08 UTC (permalink / raw)
  To: Tuomas Ahola; +Cc: Silas Poulson, gitgitgadget, git
In-Reply-To: <20260618114837.0_RVf%taahol@utu.fi>

Tuomas Ahola <taahol@utu.fi> writes:

> Junio C Hamano <gitster@pobox.com> wrote:
>
>> Silas Poulson <silas@dyalog.com> writes:
>> 
>> > I'm aware this is a very minor change, but it would be good to not let 
>> > this fall through the cracks.
>> 
>> Thanks for noticing a typo.
>> 
>> Will update before the next issue is sent to the mailing list.  No
>> point in changing it before that.
>
> On that occasion, please consider also these fixes:

Thanks.  Will squash in.  The next issue of Maintotes will come
right after 2.55 final gets tagged, so we have a bit more time.



^ permalink raw reply

* Re: t5563-simple-http-auth failures with v2.55.0-rc0
From: Matthew John Cheetham @ 2026-06-18 15:02 UTC (permalink / raw)
  To: Todd Zullinger; +Cc: git@vger.kernel.org
In-Reply-To: <20260618144953.l6Ng-dvv@teonanacatl.net>

On 2026-06-18 15:49, Todd Zullinger wrote:

> I saw Fedora picked up curl-8.21.0-rc3 this morning and
> confirmed it resolves the git test failures.  Someone else
> has already commented on the upstream curl issue to note
> that.
Thanks for confirming rc3 fixes things!

The CURLOPT_USERPWD = ":" behaviour was never codified in curl's 
documentation, but given the fix they've put in place it's probably a 
good sign we can continue to rely on this.

I may propose a patch to curl documentation's patch to say that this
behaviour is expected and supported, if the they don't do it themselves 
soon already.

Thanks,
Matthew


^ permalink raw reply

* Re: [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config
From: Patrick Steinhardt @ 2026-06-18 14:51 UTC (permalink / raw)
  To: Justin Tobler; +Cc: git, Karthik Nayak, Jeff King
In-Reply-To: <ajP7W7KsXz4Wk262@denethor>

On Thu, Jun 18, 2026 at 09:15:00AM -0500, Justin Tobler wrote:
> On 26/06/18 07:59AM, Patrick Steinhardt wrote:
> > On Wed, Jun 17, 2026 at 01:41:40PM -0500, Justin Tobler wrote:
> > > Is this really the best signal to indicate that a repository ref store
> > > has not been initialized? Temporarily setting the storage format to
> > > REF_STORAGE_FORMAT_UNKNOWN feels rather awkward and suggests to me that
> > > `include_by_branch()` probably shouldn't be using it to begin with if
> > > its not reliable.
> > 
> > True, but we don't really have a better signal to the best of my
> > knowledge. Ideally, we'd be able to use the existence `r->refs_private`
> > as signal. But that doesn't really work as the reference database is
> > lazily constructed, and the recursion happens in the exact function that
> > would construct it in the first place. And there indeed are cases where
> > reading the configuration is the first caller of `get_main_ref_store()`.
> 
> Ok, my first thought was also whether we could use the existence of the
> ref store as a signal, but I guess that won't work here.
> 
> > My first internal iteration tried to make this non-lazily constructed so
> > that we can use it as a proper signal. But that led to a bunch of
> > problems where we now parsed configuration way earlier than we currently
> > do, and that in turn led to all kinds of errors. I was able to fix all
> > of those errors except one: we expect `git config set` to work in a
> > misconfigured repository so that the user can fix the misconfig without
> > having to manually edit the Git configuration files. But when
> > constructing the refdb eagerly we will die early in such cases.
> > 
> > We could again work around that issue, but that unfortunately evolved
> > into a proper mess that I eventually discarded as unworkable. I think
> > this is an inherent design flaw: constructing the refdb requires us to
> > be able to parse the configuration, but constructing the configuration
> > may require us to construct the refdb. So this awkwardness is built into
> > Git's design, unfortunately.
> > 
> > So I'd really love to have a better signal, as I fully agree that the
> > above workaround is nothing more but a hack. But I'm just not sure what
> > that signal would be. And this version here does exactly what we want:
> > we honor "onbranch" conditionals in all cases, except when constructing
> > the main reference store. Even if it's ugly.
> 
> Could we embed an `initialized` boolean in `struct ref_store` that gets
> set when the ref store is properly initialized and use that as a signal
> instead? I'm not sure how complex introducing this would be though.

We could, but I'm not sure what that would really buy us. It would
basically be one more bit of state that we have to track going forward,
and thus one more source of inconsistencies.

Patrick

^ permalink raw reply

* Re: t5563-simple-http-auth failures with v2.55.0-rc0
From: Todd Zullinger @ 2026-06-18 14:49 UTC (permalink / raw)
  To: Matthew John Cheetham; +Cc: git@vger.kernel.org
In-Reply-To: <20260612180203.s2qSgDUs@teonanacatl.net>

Hi,

I wrote:
> Matthew John Cheetham wrote:
>> Thanks for the report. The failure is not in Git, it is a libcurl
>> behaviour change, and there is already an open upstream issue:
>> 
>>   https://github.com/curl/curl/issues/21943
>>   "Negotiate ignored with --anyauth" (Dan Fandrich, 2026-06-10)
>> 
>> Dan also bisected it to the same commit I had locally,
>> `8f71d0fde515` ("creds: hold credentials", curl PR #21548).
> [...]
>> Daniel Stenberg has acknowledged the curl issue but has not yet
>> posted a fix. I will follow curl#21943 and, if the upstream answer
>> is "the new behaviour is intended", come back here with a proposal
>> for what Git should do about `http.emptyAuth` and test 18.
> 
> Excellent.  This is it good hands all around.
[...]
> 
> If there is a curl update, I imagine it will be picked up
> reasonably quickly in Fedora (and elsewhere that was testing
> 8.21.0 release candidates) and there will hopefully be no
> strong need to make any changes on the git side.

I saw Fedora picked up curl-8.21.0-rc3 this morning and
confirmed it resolves the git test failures.  Someone else
has already commented on the upstream curl issue to note
that.

Thanks,

-- 
Todd

^ permalink raw reply

* Re: [PATCH v2] SubmittingPatches: address design critiques
From: Kristoffer Haugsbakk @ 2026-06-18 14:43 UTC (permalink / raw)
  To: Junio C Hamano, git
In-Reply-To: <xmqqpl1oteoi.fsf@gitster.g>

On Thu, Jun 18, 2026, at 10:50, Junio C Hamano wrote:
> Contributors sometimes fail to answer fundamental design or
> viability comments from reviewers and submit subsequent rounds
> without addressing them.  When design decisions are resolved on the
> mailing list, the final justification should be recorded in the
> commit messages.

It’s useful to explain design decisions etc. that were not committed to
in the commit message. It’s yet another thing that might be far from
obvious during review, but when the message is written only the option
that was landed on is explained.

>[snip]
> diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
> index f042bb5aaf..bbe759f3d9 100644
> --- a/Documentation/SubmittingPatches
> +++ b/Documentation/SubmittingPatches
> @@ -51,6 +51,21 @@ area.
>    respond to them with "Reply-All" on the mailing list, while taking
>    them into account while preparing an updated set of patches.
>  +
> +You should be particularly mindful of critiques regarding the
> +high-level design or viability of your proposal (e.g., questioning
> +whether the feature is worth implementing, or if the chosen approach
> +is appropriate).  Defend your design decisions on the list first, to
> +avoid wasting effort on an implementation whose design is not yet
> +solid.

To take it to the other extreme, there are “contributions” which are
design decisions *only*, no implementation. Namely emails which sketch a
design like a new option. Those are often met with a stock response
saying:[1] submit some code, doesn’t have to implement it fully, but we
will need some code to proceed here. I’ve never understood that stance,
and I don’t think it harmonizes with the sentence here of sparing effort
if the design is not solid.

Many of these emails are straightforward.

1. I want this thing
2. I can implement it but I don’t know if *would be* accepted (and
   implementing things here would be a lot of effort for me, personally)
3. Would it be?

And the only thing I personally would change is to weaken “would be?” to
“could be?”. Then with that out in the world, maybe someone finds this
message this day or the next week or month and reports that at least
they would like this thing.

Some people could change something small in this code base as easily as
they could propose the change idea for it (so it seems to me). They
would just have to take a walk and watch a movie to let their
subconscious process whether it was a good idea or not... But for most
people, committing to implementing something in a new codebase without
spooky assistance (...) is a task that takes a lot of effort.

To illustrate a related point, there are at least three kinds of Git
users out there:

1. Experienced C developers
2. Experienced Git developers (heavy overlap with (1))
3. Experienced and partisan Git users (knows it, believes in it)

You can imagine someone from group number 1 who is *not* in group number
3 use a weekend to implement something. But then when it is submitted it
turns out that is a very “centralized CVS” idea which doesn’t fit into
git(1) at all. That’s easily spotted by group number 3 by just looking
at the proposed docs or design. Now that group number 1 individual might
just have a bunch of code that is dead weight for any proper Git
workflow.

So I don’t understand this canned response.

There is the unofficial project idea tracker on the GitGitGadget
fork. But it seems weird to post things there and not on the mailing
list.

† 1: From `git show todo:CannedResponses`:

     | [make us come to you, begging]
     |
     | I've seen from time to time people ask "I am thinking of doing this;
     | will a patch be accepted?  If so, I'll work on it." before showing
     | any work, and my response always has been:
     |
     |  (1) We don't know how useful and interesting your contribution would
     |      be for our audience, until we see it; and
     |
     |  (2) If you truly believe in your work (find it useful, find writing
     |      it fun, etc.), that would be incentive enough for you to work
     |      on it, whether or not the result will land in my tree.  You
     |      should instead aim for something so brilliant that we would
     |      come to you begging for your permission to include it in our
     |      project.

    Note that point (2) is a bit more ambitious than it looks; if the
    idea is to implement your own thing for your own fork/tree and then
    maintain it yourself if upstream git.git doesn not accept it, well,
    now you need to learn how to maintain a fork for however long you
    want that feature. If you did not already know that.

> ++
> +Make sure that any new version is accompanied by a much clearer
> +explanation and justification (in the cover letter, your responses,
> +and in the revised commit messages).  Aim to make the reviewers say
> +"it is now clear why we may want to do this with the updated version".
> ++
> +Topics that fail to address fundamental design critiques without
> +resolution will not be considered ready for merging.

Nicely explained.
> ++
>  It is often beneficial to allow some time for reviewers to provide
>  feedback before sending a new version, rather than sending an updated
>  series immediately after receiving a review. This helps collect broader
> @@ -323,6 +338,10 @@ The body should provide a meaningful commit message, which:
>
>  . alternate solutions considered but discarded, if any.
>
> +. records the resolution of design or viability concerns raised by the
> +  community during the review, if any, ensuring the historical record
> +  explains why the chosen approach was accepted over alternatives.
> +

Okay.

>  [[present-tense]]
>[snip]

^ permalink raw reply

* [PATCH] help: prompt user to run corrected command on typo
From: calicomills @ 2026-06-18 14:26 UTC (permalink / raw)
  To: git; +Cc: gitster

From 0dc9e5c4593611b75e7003e8fdbea9370524c05b Mon Sep 17 00:00:00 2001
From: calicomills <jishnuck26@gmail.com>
Date: Thu, 18 Jun 2026 19:47:12 +0530
Subject: [PATCH] help: prompt user to run corrected command on typo

When a user mistypes a git command and there is exactly one similar
command, git currently prints a suggestion but exits, requiring the
user to retype the corrected command manually.

Instead, when stdin and stderr are both connected to a terminal and
there is a single best match, prompt the user with:

  Did you mean 'git checkout neo'? [y/N]

The full corrected invocation (command + original arguments) is shown
in the prompt so the user knows exactly what will run. Answering 'y'
re-executes git with the corrected command and all original arguments.
Answering anything else exits as before.

When there are multiple similarly-named commands, or when running
non-interactively (scripts, pipes), the original behaviour of printing
the suggestion list and exiting is preserved.

The help_unknown_cmd() signature is updated to accept the full args
vector so the prompt can include the original arguments alongside the
corrected command name.

Add tests to t9003 covering:
- non-interactive single match: falls back to suggestion list
- non-interactive multiple matches: falls back to suggestion list
- interactive single match, 'y': corrected command runs (TTY prereq)
- interactive single match, 'n': exits cleanly (TTY prereq)

Signed-off-by: calicomills <jishnuck26@gmail.com>
---
 builtin/help.c              |  2 +-
 git.c                       |  2 +-
 help.c                      | 40 ++++++++++++++++++++++------
 help.h                      |  3 ++-
 t/t9003-help-autocorrect.sh | 53 +++++++++++++++++++++++++++++++++++++
 5 files changed, 89 insertions(+), 11 deletions(-)

diff --git a/builtin/help.c b/builtin/help.c
index a140339999..b17e61ccc8 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -618,7 +618,7 @@ static char *check_git_cmd(const char *cmd)
 	}
 
 	if (exclude_guides)
-		return help_unknown_cmd(cmd);
+		return help_unknown_cmd(cmd, NULL);
 
 	return xstrdup(cmd);
 }
diff --git a/git.c b/git.c
index 36f08891ef..d379cc85bb 100644
--- a/git.c
+++ b/git.c
@@ -994,7 +994,7 @@ int cmd_main(int argc, const char **argv)
 			exit(1);
 		}
 		if (!done_help) {
-			char *assumed = help_unknown_cmd(cmd);
+			char *assumed = help_unknown_cmd(cmd, &args);
 			strvec_replace(&args, 0, assumed);
 			free(assumed);
 			cmd = args.v[0];
diff --git a/help.c b/help.c
index 46241492ce..30f32a7206 100644
--- a/help.c
+++ b/help.c
@@ -641,7 +641,7 @@ static const char bad_interpreter_advice[] =
 	N_("'%s' appears to be a git command, but we were not\n"
 	"able to execute it. Maybe git-%s is broken?");
 
-char *help_unknown_cmd(const char *cmd)
+char *help_unknown_cmd(const char *cmd, const struct strvec *args)
 {
 	struct help_unknown_cmd_config cfg = { 0 };
 	int i, n, best_similarity = 0;
@@ -762,13 +762,37 @@ char *help_unknown_cmd(const char *cmd)
 	fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd);
 
 	if (SIMILAR_ENOUGH(best_similarity)) {
-		fprintf_ln(stderr,
-			   Q_("\nThe most similar command is",
-			      "\nThe most similar commands are",
-			   n));
-
-		for (i = 0; i < n; i++)
-			fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
+		if (n == 1 && isatty(0) && isatty(2)) {
+			char *answer;
+			struct strbuf msg = STRBUF_INIT;
+			struct strbuf full_cmd = STRBUF_INIT;
+			strbuf_addstr(&full_cmd, main_cmds.names[0]->name);
+			if (args) {
+				for (size_t j = 1; j < args->nr; j++) {
+					strbuf_addch(&full_cmd, ' ');
+					strbuf_addstr(&full_cmd, args->v[j]);
+				}
+			}
+			strbuf_addf(&msg, _("\nDid you mean 'git %s'? [y/N] "),
+				    full_cmd.buf);
+			strbuf_release(&full_cmd);
+			answer = git_prompt(msg.buf, PROMPT_ECHO);
+			strbuf_release(&msg);
+			if (starts_with(answer, "y") || starts_with(answer, "Y")) {
+				char *assumed = xstrdup(main_cmds.names[0]->name);
+				cmdnames_release(&cfg.aliases);
+				cmdnames_release(&main_cmds);
+				cmdnames_release(&other_cmds);
+				return assumed;
+			}
+		} else {
+			fprintf_ln(stderr,
+				   Q_("\nThe most similar command is",
+				      "\nThe most similar commands are",
+				   n));
+			for (i = 0; i < n; i++)
+				fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
+		}
 	}
 
 	exit(1);
diff --git a/help.h b/help.h
index c54bf0977d..a8c465b3df 100644
--- a/help.h
+++ b/help.h
@@ -32,7 +32,8 @@ void list_all_other_cmds(struct string_list *list);
 void list_cmds_by_category(struct string_list *list,
 			   const char *category);
 void list_cmds_by_config(struct string_list *list);
-char *help_unknown_cmd(const char *cmd);
+#include "strvec.h"
+char *help_unknown_cmd(const char *cmd, const struct strvec *args);
 void load_command_list(const char *prefix,
 		       struct cmdnames *main_cmds,
 		       struct cmdnames *other_cmds);
diff --git a/t/t9003-help-autocorrect.sh b/t/t9003-help-autocorrect.sh
index 8da318d2b5..6fe2da1595 100755
--- a/t/t9003-help-autocorrect.sh
+++ b/t/t9003-help-autocorrect.sh
@@ -70,4 +70,57 @@ test_expect_success 'autocorrect works in work tree created from bare repo' '
 	git -C worktree -c help.autocorrect=immediate status
 '
 
+# Default behaviour (no help.autocorrect set): when there is exactly one
+# similar command but the session is non-interactive, fall back to printing
+# the suggestion list and exiting rather than showing a prompt.
+test_expect_success 'default: single match non-interactive shows suggestion and fails' '
+	test_might_fail git config --unset help.autocorrect &&
+
+	test_must_fail git lfg 2>actual &&
+	grep "most similar command" actual &&
+	grep "lgf" actual
+'
+
+test_expect_success 'default: multiple matches non-interactive shows list and fails' '
+	test_might_fail git config --unset help.autocorrect &&
+
+	test_must_fail git com 2>actual &&
+	grep "most similar commands" actual &&
+	grep "commit" actual
+'
+
+# Interactive prompt tests require a real TTY.  On macOS the TTY prereq is
+# skipped due to IO::Pty reliability issues; these tests run on Linux CI.
+test_expect_success TTY 'default: single match interactive, answer y runs command' '
+	git config --unset help.autocorrect &&
+
+	write_script git-typotest <<-\EOF &&
+		echo typotest-ran
+	EOF
+	PATH="$PATH:." export PATH &&
+
+	# Feed "y" to /dev/tty via a wrapper that answers the prompt
+	write_script answer-prompt <<-\EOF &&
+		# Write the answer to the controlling terminal
+		printf "y\n" >/dev/tty
+		exec "$@"
+	EOF
+
+	test_terminal ./answer-prompt git typotest 2>err >out &&
+	grep "typotest-ran" out &&
+	grep "Did you mean" err
+'
+
+test_expect_success TTY 'default: single match interactive, answer n exits cleanly' '
+	git config --unset help.autocorrect &&
+
+	write_script answer-prompt-no <<-\EOF &&
+		printf "n\n" >/dev/tty
+		exec "$@"
+	EOF
+
+	test_must_fail test_terminal ./answer-prompt-no git typotest 2>err &&
+	grep "Did you mean" err
+'
+
 test_done
-- 
2.50.1



^ permalink raw reply related

* Re: [PATCH v2 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config
From: Justin Tobler @ 2026-06-18 14:15 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Jeff King
In-Reply-To: <ajOJM8EvGWWkYNuL@pks.im>

On 26/06/18 07:59AM, Patrick Steinhardt wrote:
> On Wed, Jun 17, 2026 at 01:41:40PM -0500, Justin Tobler wrote:
> > Is this really the best signal to indicate that a repository ref store
> > has not been initialized? Temporarily setting the storage format to
> > REF_STORAGE_FORMAT_UNKNOWN feels rather awkward and suggests to me that
> > `include_by_branch()` probably shouldn't be using it to begin with if
> > its not reliable.
> 
> True, but we don't really have a better signal to the best of my
> knowledge. Ideally, we'd be able to use the existence `r->refs_private`
> as signal. But that doesn't really work as the reference database is
> lazily constructed, and the recursion happens in the exact function that
> would construct it in the first place. And there indeed are cases where
> reading the configuration is the first caller of `get_main_ref_store()`.

Ok, my first thought was also whether we could use the existence of the
ref store as a signal, but I guess that won't work here.

> My first internal iteration tried to make this non-lazily constructed so
> that we can use it as a proper signal. But that led to a bunch of
> problems where we now parsed configuration way earlier than we currently
> do, and that in turn led to all kinds of errors. I was able to fix all
> of those errors except one: we expect `git config set` to work in a
> misconfigured repository so that the user can fix the misconfig without
> having to manually edit the Git configuration files. But when
> constructing the refdb eagerly we will die early in such cases.
> 
> We could again work around that issue, but that unfortunately evolved
> into a proper mess that I eventually discarded as unworkable. I think
> this is an inherent design flaw: constructing the refdb requires us to
> be able to parse the configuration, but constructing the configuration
> may require us to construct the refdb. So this awkwardness is built into
> Git's design, unfortunately.
> 
> So I'd really love to have a better signal, as I fully agree that the
> above workaround is nothing more but a hack. But I'm just not sure what
> that signal would be. And this version here does exactly what we want:
> we honor "onbranch" conditionals in all cases, except when constructing
> the main reference store. Even if it's ugly.

Could we embed an `initialized` boolean in `struct ref_store` that gets
set when the ref store is properly initialized and use that as a signal
instead? I'm not sure how complex introducing this would be though.

-Justin

^ permalink raw reply

* Re: [PATCH] gitlab-ci: migrate Windows builds away from Chocolatey
From: Justin Tobler @ 2026-06-18 14:03 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git
In-Reply-To: <ajOE2XMBzgrXxbH8@pks.im>

On 26/06/18 07:40AM, Patrick Steinhardt wrote:
> On Wed, Jun 17, 2026 at 03:03:39PM -0500, Justin Tobler wrote:
> > On 26/06/15 02:21PM, Patrick Steinhardt wrote:
> > >    before_script:
> > >      - *windows_before_script
> > > -    - choco install -y git meson ninja rust-ms
> > > -    - Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
> > > -    - refreshenv
> > > +    - ./ci/install-dependencies.ps1
> > > +    - $env:Path = "C:\Meson;C:\Rust\bin;$env:Path"
> > 
> > I assume Git is already discoverable on the path?
> 
> Good question -- in fact it's not, but in Meson we know to use the
> well-known path of "C:\Program Files\Git" automatically and that's why
> we don't have to add it here. That certainly is a bit hacky, but I'm not
> sure whether we need to change it.
> 
> Just let me know if you think so.

If it's only Meson that needs to locate Git and it is already capable of
doing that without updating the path here, this is probably fine as-is.
We could maybe explain this to future reader in a comment? But I'm not
sure it matters too much and is likely not worth a reroll IMO.

Overall this patch looks good to me.

-Justin

^ permalink raw reply

* [PATCH] zlib: properly clamp to uLong
From: Johannes Schindelin via GitGitGadget @ 2026-06-18 13:50 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

On platforms where `unsigned long` and `size_t` differ in bit size, we
want to clamp the buffers we pass to zlib to the former's size, as per
d05d666977 (git-zlib: handle data streams larger than 4GB, 2026-05-08).

The logic introduced in that commit performs a clamping to the bits,
though, which fails to do what is needed here: If too many bytes are
available in the buffers, we need to clamp to the maximum value of an
`unsigned long`. Otherwise, we ask zlib to use too small buffers, in the
worst case using 0 as the size (think: a value whose 32 lowest bits are
all zero).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
    zlib: properly clamp to uLong
    
    I re-read this logic earlier this week... and I am quite convinced that
    it needs to be fixed.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2153%2Fdscho%2Ffix-ulong-clamping-for-zlib-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2153/dscho/fix-ulong-clamping-for-zlib-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2153

 git-zlib.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/git-zlib.c b/git-zlib.c
index b91cb323ae..d21adb3bf5 100644
--- a/git-zlib.c
+++ b/git-zlib.c
@@ -38,12 +38,17 @@ static inline uInt zlib_buf_cap(unsigned long len)
 	return (ZLIB_BUF_MAX < len) ? ZLIB_BUF_MAX : len;
 }
 
+static inline uLong zlib_uLong_cap(size_t s)
+{
+	return s < ULONG_MAX_VALUE ? (uLong)s : ULONG_MAX_VALUE;
+}
+
 static void zlib_pre_call(git_zstream *s)
 {
 	s->z.next_in = s->next_in;
 	s->z.next_out = s->next_out;
-	s->z.total_in = (uLong)(s->total_in & ULONG_MAX_VALUE);
-	s->z.total_out = (uLong)(s->total_out & ULONG_MAX_VALUE);
+	s->z.total_in = zlib_uLong_cap(s->total_in);
+	s->z.total_out = zlib_uLong_cap(s->total_out);
 	s->z.avail_in = zlib_buf_cap(s->avail_in);
 	s->z.avail_out = zlib_buf_cap(s->avail_out);
 }
@@ -60,7 +65,7 @@ static void zlib_post_call(git_zstream *s, int status)
 	 * We track our own totals and verify only the low bits match.
 	 */
 	if ((s->z.total_out & ULONG_MAX_VALUE) !=
-	    ((s->total_out + bytes_produced) & ULONG_MAX_VALUE))
+	    ((zlib_uLong_cap(s->total_out) + bytes_produced) & ULONG_MAX_VALUE))
 		BUG("total_out mismatch");
 	/*
 	 * zlib does not update total_in when it returns Z_NEED_DICT,
@@ -68,7 +73,7 @@ static void zlib_post_call(git_zstream *s, int status)
 	 */
 	if (status != Z_NEED_DICT &&
 	    (s->z.total_in & ULONG_MAX_VALUE) !=
-	    ((s->total_in + bytes_consumed) & ULONG_MAX_VALUE))
+	    ((zlib_uLong_cap(s->total_in) + bytes_consumed) & ULONG_MAX_VALUE))
 		BUG("total_in mismatch");
 
 	s->total_out += bytes_produced;

base-commit: 7a094d68a27e321a99c8ab6b700909e503904bd9
-- 
gitgitgadget

^ permalink raw reply related

* Re: [PATCH v15 0/7] branch: delete-merged
From: Phillip Wood @ 2026-06-18 13:48 UTC (permalink / raw)
  To: Harald Nordgren, phillip.wood
  Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
	Johannes Sixt
In-Reply-To: <CAHwyqnWFM2jskm6soEu58tp_TgO3fmuODD-yTiK6-4Hpv8SMLQ@mail.gmail.com>

Hi Harald

On 17/06/2026 20:11, Harald Nordgren wrote:
>> Right but you sent that version a few hours after I'd posted a partial
>> review which concluded by saying I'd finish it the next day. If you send
>> a new version when you are waiting for further comments it clutters the
>> list because you know you're going to have to post another revision when
>> you get the rest of the comments. Anyone reviewing the interim version
>> is wasting their time. When you receive review comments, by all means
>> start thinking about them and updating your local copy but please don't
>> post a new version until the discussion on the previous version has
>> settled down.
> 
> That's fair. Sorry about that.
> 
> Will you let me know when your review here is finished?

I've just sent a mail with another comment but that concudes this round 
unless you have any questions about it.

> 
> I received the same feedback from Junio before, so I'm not unaware of
> this problem. I am trying to slow down. I often prepare the work as
> soon as I get some comments -- I'm on paternity leave so I have a lot
> of time when the baby is sleeping -- 

Congratulations - I hope the baby is sleeping at night as well in the day!

> then I actively hold off on
> sending to not overload the rest of you. But at the same time I think
> it's valuable to keep up a certain pace. It's a balancing act.
It is worth waiting for the discussion to settle on each round, I'll try 
and be clear when I've finished looking at each revision. I'm sure other 
folks would appreciate you looking at their patches and commenting on 
them while you're waiting for feedback on yours, especially the GSoC 
project students.

Thanks

Phillip

^ permalink raw reply

* Re: [PATCH v14 4/6] branch: add --prune-merged <branch>
From: Phillip Wood @ 2026-06-18 13:42 UTC (permalink / raw)
  To: Harald Nordgren
  Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
	Johannes Sixt
In-Reply-To: <CAHwyqnUsjpCHfS=eBphmkdDGYpQZ_LQUJi1mjrxV8ZXi+w4yhg@mail.gmail.com>

Hi Harald

On 16/06/2026 20:15, Harald Nordgren wrote:
>>> diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
>>> index 4e7deddc04..27ea1319bb 100755
>>> --- a/t/t3200-branch.sh
>>> +++ b/t/t3200-branch.sh
>>> @@ -1809,4 +1809,205 @@ test_expect_success '--forked requires a value' '
>>>        test_grep "requires a value" err
>>>    '
>>>
>>> +test_expect_success '--prune-merged: setup' '
>>> +     test_create_repo pm-upstream &&
>>
>> The rest of this test would be easier to read if we did
>>
>>          (
>>                  cd pm-upstream &&
>>                  ...
>>          )
>>
>> rather than prefixing every command with "-C pm-upstream"
> 
> I feel like the discussion to nest or not to nest has come up many
> times in other topics as well. I don't feel strongly about either way,
> but I just want to flag that if I change it now, another reviewer
> might ask me to change it back later.
> 
> Should the rules be to nest inside of setup functions (and helpers?)
> but not inside the actual tests?

I think it depends on how many commands you're running in a row in the 
same directory. In this case we're running quite a few commands so it 
seems clearer to use a subshell. In the later tests we're switching 
between repositories and running fewer commands in each one so it is 
less clear that using subshells is clearer. Also later on we're using 
test_config() which I don't think works in a subshell because it relies 
on test_when_finished().

One thing I've just thought of related to this patch is whether we want 
to protect branches that are the upstreams of branches that are not 
slated for deletion. With stacked branches it is possible that a branch 
has been merged but has other branches stacked on top of it that have 
not been merged. If we build an strset of branches that we want to 
delete, then loop over all branches and if there are any that are not in 
the to be deleted set which have their upstream in that set we'd remove 
the upstream branch from the set. Once we've done that we can convert 
the set to an strvec to pass to delete_branches()

Thanks

Phillip

> 
>>> +     test_commit -C pm-upstream base &&
>>> +     git -C pm-upstream checkout -b next &&
>>> +     test_commit -C pm-upstream one-commit &&
>>> +     test_commit -C pm-upstream two-commit &&
>>> +     git -C pm-upstream branch one HEAD~ &&
>>> +     git -C pm-upstream branch two HEAD &&
>>> +     git -C pm-upstream branch wip main &&
>>> +     git -C pm-upstream checkout main &&
>>> +     test_create_repo pm-fork
>>> +'
>>> +
>>> +test_expect_success '--prune-merged deletes branches integrated into upstream' '
>>> +     test_when_finished "rm -rf pm-merged" &&
>>> +     git clone pm-upstream pm-merged &&
>>> +     git -C pm-merged remote add fork ../pm-fork &&
>>> +     test_config -C pm-merged remote.pushDefault fork &&
>>> +     test_config -C pm-merged push.default current &&
>>
>> So we clone upstream and add fork as the default push remote. I find the
>> pm- prefixes rather distracting. It would be clearer to me if we just
>> called the repositories "upstream", "fork" and "repo"
> 
> Good point.
> 
>>> +     test_must_fail git -C pm-local rev-parse --verify refs/heads/one
>>> +'
>>> +
>>> +test_expect_success '--prune-merged warns instead of erroring on un-integrated commits' '
>>> +     test_when_finished "rm -rf pm-unmerged" &&
>>> +     git clone pm-upstream pm-unmerged &&
>>> +     git -C pm-unmerged remote add fork ../pm-fork &&
>>> +     test_config -C pm-unmerged remote.pushDefault fork &&
>>> +     test_config -C pm-unmerged push.default current &&
>>> +     git -C pm-unmerged checkout -b wip origin/wip &&
>>> +     git -C pm-unmerged branch --set-upstream-to=origin/next wip &&
>>> +     test_commit -C pm-unmerged local-only &&
>>> +     git -C pm-unmerged checkout - &&
>>> +
>>> +     git -C pm-unmerged branch --prune-merged "origin/*" 2>err &&
>>> +     test_grep "not fully merged" err &&
>>> +     test_grep ! "If you are sure you want to delete it" err &&
>>
>> I'm always suspicious of test_grep when we know what the output should
>> look like - it might be better to use test_cmp. This test does not check
>> that we also delete branches that are merged when we see one that isn't.
>>
>> I'm going to stop here - the tests I've read seem to me to be too much
>> like unit tests checking one aspect of the implementation in isolation
>> rather than checking that the whole feature works as expected.
> 
> I'll respond to the rest here: Excellent points regarding the testing
> aboce, I will take a look at doing this.
> 
> 
> Harald
> 


^ permalink raw reply

* Re: [PATCH v5 2/2] graph: indent visual root in graph
From: Junio C Hamano @ 2026-06-18 13:31 UTC (permalink / raw)
  To: Pablo Sabater
  Cc: Jeff King, git, ayu.chandekar, chandrapratap3519,
	christian.couder, jltobler, karthik.188, phillip.wood,
	siddharthasthana31
In-Reply-To: <CAN5EUNSQY2oK7BE4J9Y8APfkP6eJxta050OUu=RoJYhXOjX_OA@mail.gmail.com>

Pablo Sabater <pabloosabaterr@gmail.com> writes:

> Should I work with 'next' as a base to have dd4bc01c0a? (Sorry I've
> just worked with master).

As dd4bc01c (revision: use priority queue for non-limited streaming
walks, 2026-05-27) is already in 'master', you should be able to
work with 'master' that is no stale than 6e148f82 (Merge branch
'kk/streaming-walk-pqueue', 2026-06-16).

^ permalink raw reply

* Re: [PATCH v2 0/2] environment: move ignore_case into repo_config_values
From: Junio C Hamano @ 2026-06-18 13:14 UTC (permalink / raw)
  To: Tian Yuchen; +Cc: git, ps, phillip.wood123, johannes.schindelin, stolee
In-Reply-To: <20260618114207.605211-1-cat@malon.dev>

Tian Yuchen <cat@malon.dev> writes:

> Related materials:
>
>  [1] In this patch to migrate protect_hfs and protect_ntfs, the approach
> of introducing getters has been endorsed.
>
>  [2] Derrick Stolee's previous attempt. The reasons for the failure are
> also mentioned in [1].

[1] here refers to the starting message of the whole hfs/ntfs thing.
Do you mean that people must read the entire thread to find out what
the reasons for the failure was?  For that matter, it is not clear,
unless readers read the whole thread, where the approach of using
getters was "endorsed", either.

> [1] https://lore.kernel.org/git/20260606143412.15443-1-cat@malon.dev/
> [2] https://lore.kernel.org/git/2b4198c09cb6c04c60608d19072d419503dfe5df.1685716421.git.gitgitgadget@gmail.com/

> Changes since V1:
>
>  - s/repo_get_ignore_case()/repo_ignore_case()
>
>  - Use repo->initialized instead of repo->gitdir

I do not think I have any objections to these changes from the
previous iteration.  There may be some other things in the new
iteration but I'll have to go in and read the patches to find them
out (if they exist).

Thanks.

^ permalink raw reply

* [PATCH v14 2/2] checkout: extend --track with a "fetch" mode to refresh start-point
From: Harald Nordgren via GitGitGadget @ 2026-06-18 12:44 UTC (permalink / raw)
  To: git
  Cc: Ramsay Jones, D. Ben Knoble, Kristoffer Haugsbakk, Marc Branchaud,
	Phillip Wood, Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2281.v14.git.git.1781786652.gitgitgadget@gmail.com>

From: Harald Nordgren <haraldnordgren@gmail.com>

Add a "fetch" mode to the "--track" option of "git checkout" / "git
switch" that refreshes <start-point> before checking it out:

    git checkout -b new_branch --track=fetch origin/some-branch

is shorthand for

    git fetch origin some-branch
    git checkout -b new_branch --track origin/some-branch

Identify the remote whose configured fetch refspec maps to
<start-point> using find_tracking_remote_for_ref() (the same lookup
"--track" uses to pick which remote to record in
branch.<name>.remote), then run "git fetch <remote> <src-ref>" for
just that ref so other remote-tracking branches are left untouched.
When <start-point> is a bare <remote> (e.g. "origin"), follow
refs/remotes/<remote>/HEAD to learn which branch to refresh. If
"git fetch" fails but the remote-tracking ref already exists locally,
warn and proceed from the existing tip; otherwise abort.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 Documentation/git-checkout.adoc |  17 +-
 Documentation/git-switch.adoc   |   5 +-
 builtin/checkout.c              | 139 +++++++++++++++-
 t/t7201-co.sh                   | 276 ++++++++++++++++++++++++++++++++
 4 files changed, 430 insertions(+), 7 deletions(-)

diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index a8b3b8c2e2..20b6cae60e 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -158,11 +158,26 @@ of it").
 	resets _<branch>_ to the start point instead of failing.
 
 `-t`::
-`--track[=(direct|inherit)]`::
+`--track[=(direct|inherit|fetch)[,...]]`::
 	When creating a new branch, set up "upstream" configuration. See
 	`--track` in linkgit:git-branch[1] for details. As a convenience,
 	--track without -b implies branch creation.
 +
+The argument is a comma-separated list. `direct` (the default) and
+`inherit` select the tracking mode and are mutually exclusive. Adding
+`fetch` requests that the remote be fetched before _<start-point>_ is
+resolved, so the new branch starts from a fresh tip: when
+_<start-point>_ is in _<remote>/<branch>_ form, only that branch is
+updated; when _<start-point>_ is a bare _<remote>_ (e.g. `origin`), the
+branch named by _<remote>/HEAD_ is updated, and the checkout fails
+with a hint to configure that symref if it is not set. The checkout
+also fails if no configured remote's fetch refspec maps to
+_<start-point>_, or if more than one does (in which case the `fetch`
+cannot be unambiguously routed). If the fetch itself fails and the
+corresponding remote-tracking ref already exists, a warning is printed
+and the checkout proceeds from the existing tip; otherwise the checkout
+is aborted.
++
 If no `-b` option is given, the name of the new branch will be
 derived from the remote-tracking branch, by looking at the local part of
 the refspec configured for the corresponding remote, and then stripping
diff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc
index d6c4f229a5..a8730b1da8 100644
--- a/Documentation/git-switch.adoc
+++ b/Documentation/git-switch.adoc
@@ -155,10 +155,11 @@ variable.
 	attached to a terminal, regardless of `--quiet`.
 
 `-t`::
-`--track[ (direct|inherit)]`::
+`--track[=(direct|inherit|fetch)[,...]]`::
 	When creating a new branch, set up "upstream" configuration.
 	`-c` is implied. See `--track` in linkgit:git-branch[1] for
-	details.
+	details, and `--track` in linkgit:git-checkout[1] for the
+	`fetch` mode.
 +
 If no `-c` option is given, the name of the new branch will be derived
 from the remote-tracking branch, by looking at the local part of the
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b78b3a1d16..37caceaefd 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -25,10 +25,12 @@
 #include "preload-index.h"
 #include "read-cache.h"
 #include "refs.h"
+#include "refspec.h"
 #include "remote.h"
 #include "repo-settings.h"
 #include "resolve-undo.h"
 #include "revision.h"
+#include "run-command.h"
 #include "sequencer.h"
 #include "setup.h"
 #include "sparse-index.h"
@@ -63,6 +65,7 @@ struct checkout_opts {
 	int count_checkout_paths;
 	int overlay_mode;
 	int dwim_new_local_branch;
+	int fetch;
 	int discard_changes;
 	int accept_ref;
 	int accept_pathspec;
@@ -116,6 +119,129 @@ struct branch_info {
 	char *checkout;
 };
 
+static void fetch_remote_for_start_point(const char *arg, int quiet)
+{
+	struct strbuf dst = STRBUF_INIT;
+	struct tracking tracking;
+	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
+	struct string_list ambiguous_remotes = STRING_LIST_INIT_DUP;
+	struct child_process cmd = CHILD_PROCESS_INIT;
+	struct object_id oid;
+	struct remote *named_remote;
+	int bare_ns;
+
+	strbuf_addf(&dst, "refs/remotes/%s", arg);
+	if (check_refname_format(dst.buf, 0))
+		die(_("cannot fetch start-point '%s': not a valid "
+		      "remote-tracking name"), arg);
+
+	named_remote = remote_get(arg);
+	bare_ns = !strchr(arg, '/') ||
+		(named_remote && remote_is_configured(named_remote, 1));
+	if (bare_ns) {
+		char *head_path = xstrfmt("refs/remotes/%s/HEAD", arg);
+		const char *head_target =
+			refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+						head_path,
+						RESOLVE_REF_READING |
+						RESOLVE_REF_NO_RECURSE,
+						&oid, NULL);
+		if (head_target &&
+		    starts_with(head_target, dst.buf) &&
+		    head_target[dst.len] == '/' &&
+		    !check_refname_format(head_target, 0)) {
+			strbuf_reset(&dst);
+			strbuf_addstr(&dst, head_target);
+			bare_ns = 0;
+		}
+		free(head_path);
+	}
+
+	memset(&tracking, 0, sizeof(tracking));
+	tracking.spec.dst = dst.buf;
+	tracking.srcs = &tracking_srcs;
+	find_tracking_remote_for_ref(&tracking, &ambiguous_remotes);
+
+	if (tracking.matches > 1) {
+		int status = die_message(_("cannot fetch start-point '%s': "
+					   "fetch refspecs of multiple remotes "
+					   "map to '%s'"), arg, dst.buf);
+		advise_ambiguous_fetch_refspec(dst.buf, &ambiguous_remotes);
+		exit(status);
+	}
+
+	if (!tracking.matches) {
+		if (bare_ns && named_remote &&
+		    remote_is_configured(named_remote, 1))
+			die(_("cannot fetch start-point '%s': "
+			      "'refs/remotes/%s/HEAD' is not set; run "
+			      "'git remote set-head %s --auto' to set it"),
+			    arg, arg, arg);
+		die(_("cannot fetch start-point '%s': no configured remote's "
+		      "fetch refspec matches it"), arg);
+	}
+
+	strvec_push(&cmd.args, "fetch");
+	if (quiet)
+		strvec_push(&cmd.args, "--quiet");
+	strvec_pushl(&cmd.args, tracking.remote,
+		     tracking_srcs.items[0].string, NULL);
+	cmd.git_cmd = 1;
+	if (run_command(&cmd)) {
+		if (!refs_read_ref(get_main_ref_store(the_repository),
+				   dst.buf, &oid))
+			warning(_("failed to fetch start-point '%s'; "
+				  "using existing '%s'"), arg, dst.buf);
+		else
+			die(_("failed to fetch start-point '%s'"), arg);
+	}
+
+	string_list_clear(&tracking_srcs, 0);
+	string_list_clear(&ambiguous_remotes, 0);
+	strbuf_release(&dst);
+}
+
+static int parse_opt_checkout_track(const struct option *opt,
+				    const char *arg, int unset)
+{
+	struct checkout_opts *opts = opt->value;
+	struct string_list tokens = STRING_LIST_INIT_DUP;
+	struct string_list_item *item;
+	int saw_direct = 0;
+	int ret = 0;
+
+	opts->fetch = 0;
+	if (unset) {
+		opts->track = BRANCH_TRACK_NEVER;
+		return 0;
+	}
+	opts->track = BRANCH_TRACK_EXPLICIT;
+	if (!arg)
+		return 0;
+
+	string_list_split(&tokens, arg, ",", -1);
+	for_each_string_list_item(item, &tokens) {
+		if (!strcmp(item->string, "fetch"))
+			opts->fetch = 1;
+		else if (!strcmp(item->string, "direct"))
+			saw_direct = 1;
+		else if (!strcmp(item->string, "inherit"))
+			opts->track = BRANCH_TRACK_INHERIT;
+		else {
+			ret = error(_("option `%s' expects \"%s\", \"%s\", "
+				      "or \"%s\""),
+				    "--track", "direct", "inherit", "fetch");
+			goto out;
+		}
+	}
+	if (saw_direct && opts->track == BRANCH_TRACK_INHERIT)
+		ret = error(_("option `%s' cannot combine \"%s\" and \"%s\""),
+			    "--track", "direct", "inherit");
+out:
+	string_list_clear(&tokens, 0);
+	return ret;
+}
+
 static void branch_info_release(struct branch_info *info)
 {
 	free(info->name);
@@ -1786,10 +1912,10 @@ static struct option *add_common_switch_branch_options(
 {
 	struct option options[] = {
 		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
-		OPT_CALLBACK_F('t', "track",  &opts->track, "(direct|inherit)",
+		OPT_CALLBACK_F('t', "track",  opts, "(direct|inherit|fetch)[,...]",
 			N_("set branch tracking configuration"),
 			PARSE_OPT_OPTARG,
-			parse_opt_tracking_mode),
+			parse_opt_checkout_track),
 		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unborn branch")),
@@ -1994,8 +2120,13 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 			opts->dwim_new_local_branch &&
 			opts->track == BRANCH_TRACK_UNSPECIFIED &&
 			!opts->new_branch;
-		int n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
-					     &new_branch_info, opts, &rev);
+		int n;
+
+		if (opts->fetch)
+			fetch_remote_for_start_point(argv[0], opts->quiet);
+
+		n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
+					 &new_branch_info, opts, &rev);
 		argv += n;
 		argc -= n;
 	} else if (!opts->accept_ref && opts->from_treeish) {
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 7613b1d2a4..1e321b1512 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -870,4 +870,280 @@ test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
 	test_cmp_config "" --default "" branch.main2.merge
 '
 
+test_expect_success 'setup upstream for --track=fetch tests' '
+	git checkout main &&
+	git init fetch_upstream &&
+	test_commit -C fetch_upstream u_main &&
+	git remote add fetch_upstream fetch_upstream &&
+	git fetch fetch_upstream &&
+	git -C fetch_upstream checkout -b fetch_new &&
+	test_commit -C fetch_upstream u_new
+'
+
+test_expect_success 'checkout --track=fetch -b picks up branch created upstream after clone' '
+	git checkout main &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_new &&
+	git checkout --track=fetch -b local_new fetch_upstream/fetch_new &&
+	test_cmp_rev refs/remotes/fetch_upstream/fetch_new HEAD &&
+	test_cmp_config fetch_upstream branch.local_new.remote &&
+	test_cmp_config refs/heads/fetch_new branch.local_new.merge
+'
+
+test_expect_success 'checkout --track=fetch <remote>/<branch> leaves other tracking branches untouched' '
+	git checkout main &&
+	git -C fetch_upstream checkout -b fetch_target &&
+	test_commit -C fetch_upstream u_target_pre &&
+	git -C fetch_upstream checkout -b fetch_other &&
+	test_commit -C fetch_upstream u_other_pre &&
+	git fetch fetch_upstream &&
+	other_before=$(git rev-parse refs/remotes/fetch_upstream/fetch_other) &&
+	git -C fetch_upstream checkout fetch_target &&
+	test_commit -C fetch_upstream u_target_post &&
+	git -C fetch_upstream checkout fetch_other &&
+	test_commit -C fetch_upstream u_other_post &&
+	git checkout --track=fetch -b local_target fetch_upstream/fetch_target &&
+	test_cmp_rev refs/remotes/fetch_upstream/fetch_target HEAD &&
+	test "$(git rev-parse refs/remotes/fetch_upstream/fetch_other)" = "$other_before"
+'
+
+test_expect_success 'checkout --track=fetch with bare remote name fetches only <remote>/HEAD target' '
+	git checkout main &&
+	git -C fetch_upstream checkout main &&
+	git remote set-head fetch_upstream main &&
+	git -C fetch_upstream checkout -b fetch_unrelated &&
+	test_commit -C fetch_upstream u_unrelated_pre &&
+	git fetch fetch_upstream fetch_unrelated &&
+	unrelated_before=$(git rev-parse refs/remotes/fetch_upstream/fetch_unrelated) &&
+	git -C fetch_upstream checkout main &&
+	test_commit -C fetch_upstream u_main_post &&
+	git -C fetch_upstream checkout fetch_unrelated &&
+	test_commit -C fetch_upstream u_unrelated_post &&
+	git checkout --track=fetch -b local_from_remote fetch_upstream &&
+	test_cmp_rev refs/remotes/fetch_upstream/main HEAD &&
+	test "$(git rev-parse refs/remotes/fetch_upstream/fetch_unrelated)" = "$unrelated_before"
+'
+
+test_expect_success 'checkout --track=fetch aborts and does not create branch when no existing ref' '
+	git checkout main &&
+	test_might_fail git branch -D bogus &&
+	test_must_fail git checkout --track=fetch -b bogus fetch_upstream/does_not_exist &&
+	test_must_fail git rev-parse --verify refs/heads/bogus
+'
+
+test_expect_success 'checkout --track=fetch warns and proceeds when fetch fails but ref exists' '
+	git checkout main &&
+	git -C fetch_upstream checkout -b fetch_offline &&
+	test_commit -C fetch_upstream u_offline &&
+	git fetch fetch_upstream fetch_offline &&
+	saved_url=$(git config remote.fetch_upstream.url) &&
+	test_when_finished "git config remote.fetch_upstream.url \"$saved_url\"" &&
+	git config remote.fetch_upstream.url ./does-not-exist &&
+	git checkout --track=fetch -b local_offline fetch_upstream/fetch_offline 2>err &&
+	test_grep "failed to fetch" err &&
+	test_cmp_rev refs/remotes/fetch_upstream/fetch_offline HEAD
+'
+
+test_expect_success 'checkout --track=fetch resolves through configured fetch refspec' '
+	git checkout main &&
+	git remote add fetch_custom ./fetch_upstream &&
+	test_when_finished "git remote remove fetch_custom" &&
+	git config --replace-all remote.fetch_custom.fetch \
+		"+refs/heads/*:refs/remotes/custom-ns/*" &&
+	git -C fetch_upstream checkout -b fetch_refspec &&
+	test_commit -C fetch_upstream u_refspec &&
+	test_must_fail git rev-parse --verify refs/remotes/custom-ns/fetch_refspec &&
+	git checkout --track=fetch -b local_refspec custom-ns/fetch_refspec &&
+	test_cmp_rev refs/remotes/custom-ns/fetch_refspec HEAD
+'
+
+test_expect_success 'checkout --track=fetch on namespace bare name follows <ns>/HEAD' '
+	git checkout main &&
+	git remote add fetch_ns ./fetch_upstream &&
+	test_when_finished "git remote remove fetch_ns" &&
+	test_when_finished "git update-ref -d refs/remotes/ns_alias/HEAD" &&
+	git config --replace-all remote.fetch_ns.fetch \
+		"+refs/heads/*:refs/remotes/ns_alias/*" &&
+	git fetch fetch_ns &&
+	git symbolic-ref refs/remotes/ns_alias/HEAD refs/remotes/ns_alias/main &&
+	git -C fetch_upstream checkout main &&
+	test_commit -C fetch_upstream u_ns_post &&
+	git checkout --track=fetch -b local_ns ns_alias &&
+	test_cmp_rev refs/remotes/ns_alias/main HEAD &&
+	test_cmp_config fetch_ns branch.local_ns.remote &&
+	test_cmp_config refs/heads/main branch.local_ns.merge
+'
+
+test_expect_success '--track=fetch on bare hierarchical remote name follows <ns>/HEAD' '
+	git checkout main &&
+	git remote add nested/bare ./fetch_upstream &&
+	test_when_finished "git remote remove nested/bare" &&
+	test_when_finished "git update-ref -d refs/remotes/nested/bare/HEAD" &&
+	git fetch nested/bare &&
+	git symbolic-ref refs/remotes/nested/bare/HEAD \
+		refs/remotes/nested/bare/main &&
+	git -C fetch_upstream checkout main &&
+	test_commit -C fetch_upstream u_nested_bare_post &&
+	git checkout --track=fetch -b local_nested_bare nested/bare &&
+	test_cmp_rev refs/remotes/nested/bare/main HEAD
+'
+
+test_expect_success 'checkout --track=fetch handles hierarchical remote name' '
+	git checkout main &&
+	git remote add nested/remote ./fetch_upstream &&
+	test_when_finished "git remote remove nested/remote" &&
+	git -C fetch_upstream checkout -b fetch_hier &&
+	test_commit -C fetch_upstream u_hier &&
+	test_must_fail git rev-parse --verify refs/remotes/nested/remote/fetch_hier &&
+	git checkout --track=fetch -b local_hier nested/remote/fetch_hier &&
+	test_cmp_rev refs/remotes/nested/remote/fetch_hier HEAD
+'
+
+test_expect_success 'checkout --track=fetch dies on bare remote name with no <ns>/HEAD' '
+	git checkout main &&
+	git remote add fetch_nohead ./fetch_upstream &&
+	test_when_finished "git remote remove fetch_nohead" &&
+	test_might_fail git symbolic-ref -d refs/remotes/fetch_nohead/HEAD &&
+	test_must_fail git checkout --track=fetch -b local_nohead fetch_nohead 2>err &&
+	test_grep "refs/remotes/fetch_nohead/HEAD" err &&
+	test_grep "git remote set-head fetch_nohead --auto" err &&
+	test_must_fail git rev-parse --verify refs/heads/local_nohead
+'
+
+test_expect_success 'checkout --track=fetch on bare unknown name does not suggest set-head' '
+	git checkout main &&
+	test_must_fail git rev-parse --verify refs/remotes/no_such_ns/HEAD &&
+	test_must_fail git config --get remote.no_such_ns.url &&
+	test_must_fail git checkout --track=fetch -b local_unknown no_such_ns 2>err &&
+	test_grep "no configured remote" err &&
+	test_grep ! "set-head" err &&
+	test_must_fail git rev-parse --verify refs/heads/local_unknown
+'
+
+test_expect_success 'checkout --track=fetch rejects <ns>/HEAD pointing outside namespace' '
+	git checkout main &&
+	git remote add fetch_crossns ./fetch_upstream &&
+	test_when_finished "git remote remove fetch_crossns" &&
+	test_when_finished "git update-ref -d refs/remotes/fetch_crossns/HEAD" &&
+	git fetch fetch_crossns &&
+	git symbolic-ref refs/remotes/fetch_crossns/HEAD \
+		refs/remotes/fetch_upstream/u_main &&
+	test_must_fail git checkout --track=fetch -b local_crossns fetch_crossns 2>err &&
+	test_grep "refs/remotes/fetch_crossns/HEAD" err &&
+	test_must_fail git rev-parse --verify refs/heads/local_crossns
+'
+
+test_expect_success 'checkout --track=fetch dies on ambiguous fetch refspec match' '
+	git checkout main &&
+	git remote add fetch_ambig_a ./fetch_upstream &&
+	git remote add fetch_ambig_b ./fetch_upstream &&
+	test_when_finished "git remote remove fetch_ambig_a" &&
+	test_when_finished "git remote remove fetch_ambig_b" &&
+	git config --replace-all remote.fetch_ambig_a.fetch \
+		"+refs/heads/*:refs/remotes/ambig_ns/*" &&
+	git config --replace-all remote.fetch_ambig_b.fetch \
+		"+refs/heads/*:refs/remotes/ambig_ns/*" &&
+	git -C fetch_upstream checkout -b fetch_ambig &&
+	test_commit -C fetch_upstream u_ambig &&
+	test_must_fail git checkout --track=fetch -b local_ambig ambig_ns/fetch_ambig 2>err &&
+	test_grep "fetch_ambig_a" err &&
+	test_grep "fetch_ambig_b" err &&
+	test_grep "tracking namespaces" err &&
+	test_must_fail git rev-parse --verify refs/heads/local_ambig
+'
+
+test_expect_success 'checkout --track=fetch rejects invalid refname components' '
+	git checkout main &&
+	test_must_fail git checkout --track=fetch -b local_invalid "foo..bar" 2>err &&
+	test_grep "valid" err &&
+	test_must_fail git rev-parse --verify refs/heads/local_invalid
+'
+
+test_expect_success 'checkout --track=fetch,inherit rejects invalid refname components' '
+	git checkout main &&
+	test_must_fail git checkout --track=fetch,inherit -b local_invalid \
+		"foo..bar" 2>err &&
+	test_grep "valid" err &&
+	test_must_fail git rev-parse --verify refs/heads/local_invalid
+'
+
+test_expect_success 'checkout --track=inherit,direct is rejected' '
+	test_must_fail git checkout --track=inherit,direct -b bad fetch_upstream/fetch_new 2>err &&
+	test_grep "cannot combine" err
+'
+
+test_expect_success 'checkout --track=direct,inherit is rejected' '
+	test_must_fail git checkout --track=direct,inherit -b bad fetch_upstream/fetch_new 2>err &&
+	test_grep "cannot combine" err
+'
+
+test_expect_success 'checkout --track=fetch then --track=direct drops fetch (last-one-wins)' '
+	git checkout main &&
+	git -C fetch_upstream checkout -b fetch_lastwin &&
+	test_commit -C fetch_upstream u_lastwin &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_lastwin &&
+	test_must_fail git checkout --track=fetch --track=direct \
+		-b local_lastwin fetch_upstream/fetch_lastwin &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_lastwin
+'
+
+test_expect_success 'checkout --track=fetch then --no-track drops fetch' '
+	git checkout main &&
+	git -C fetch_upstream checkout -b fetch_notrack &&
+	test_commit -C fetch_upstream u_notrack &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_notrack &&
+	test_must_fail git checkout --track=fetch --no-track \
+		-b local_notrack fetch_upstream/fetch_notrack &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_notrack
+'
+
+test_expect_success 'checkout --track=fetch,inherit fetches remote-tracking start-point' '
+	git checkout main &&
+	git -C fetch_upstream checkout -b fetch_inherit &&
+	test_commit -C fetch_upstream u_inherit &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_inherit &&
+	git checkout --track=fetch,inherit -b local_inherit \
+		fetch_upstream/fetch_inherit &&
+	test_cmp_rev refs/remotes/fetch_upstream/fetch_inherit HEAD
+'
+
+test_expect_success 'checkout --track=fetch,inherit errors when start-point does not map to a remote' '
+	git checkout main &&
+	test_must_fail git checkout --track=fetch,inherit -b bad main 2>err &&
+	test_grep "no configured remote" err &&
+	test_must_fail git rev-parse --verify refs/heads/bad
+'
+
+test_expect_success 'checkout --track=fetch on local start-point errors' '
+	git checkout main &&
+	test_must_fail git checkout --track=fetch -b bad main 2>err &&
+	test_grep "no configured remote" err &&
+	test_must_fail git rev-parse --verify refs/heads/bad
+'
+
+test_expect_success 'checkout --track=bogus reports an error' '
+	git checkout main &&
+	test_must_fail git checkout --track=bogus -b bogus_branch fetch_upstream/fetch_new 2>err &&
+	test_grep "expects" err
+'
+
+test_expect_success 'checkout -q --track=fetch silences the fetch output' '
+	git checkout main &&
+	git -C fetch_upstream checkout -b fetch_quiet &&
+	test_commit -C fetch_upstream u_quiet &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_quiet &&
+	git checkout -q --track=fetch -b local_quiet \
+		fetch_upstream/fetch_quiet 2>err &&
+	test_grep ! "-> fetch_upstream/fetch_quiet" err &&
+	test_cmp_rev refs/remotes/fetch_upstream/fetch_quiet HEAD
+'
+
+test_expect_success 'switch --track=fetch -c picks up branch created upstream after clone' '
+	git checkout main &&
+	git -C fetch_upstream checkout -b fetch_switch &&
+	test_commit -C fetch_upstream u_switch &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_switch &&
+	git switch --track=fetch -c local_switch fetch_upstream/fetch_switch &&
+	test_cmp_rev refs/remotes/fetch_upstream/fetch_switch HEAD
+'
+
 test_done
-- 
gitgitgadget

^ permalink raw reply related

* [PATCH v14 1/2] branch: expose helpers for finding the remote owning a tracking ref
From: Harald Nordgren via GitGitGadget @ 2026-06-18 12:44 UTC (permalink / raw)
  To: git
  Cc: Ramsay Jones, D. Ben Knoble, Kristoffer Haugsbakk, Marc Branchaud,
	Phillip Wood, Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2281.v14.git.git.1781786652.gitgitgadget@gmail.com>

From: Harald Nordgren <haraldnordgren@gmail.com>

The remote-lookup that setup_tracking() does is useful outside
branch.c too; for example, deciding which remote to "git fetch"
from given a remote-tracking ref.

Move 'struct tracking' to branch.h and add two helpers backed by the
existing for_each_remote walk: find_tracking_remote_for_ref() and
advise_ambiguous_fetch_refspec(). setup_tracking() uses both. No
behavior change.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 branch.c | 96 ++++++++++++++++++++++++++++++--------------------------
 branch.h | 16 ++++++++++
 2 files changed, 68 insertions(+), 44 deletions(-)

diff --git a/branch.c b/branch.c
index 243db7d0fc..46ae7f0035 100644
--- a/branch.c
+++ b/branch.c
@@ -20,16 +20,9 @@
 #include "run-command.h"
 #include "strmap.h"
 
-struct tracking {
-	struct refspec_item spec;
-	struct string_list *srcs;
-	const char *remote;
-	int matches;
-};
-
 struct find_tracked_branch_cb {
 	struct tracking *tracking;
-	struct string_list ambiguous_remotes;
+	struct string_list *ambiguous_remotes;
 };
 
 static int find_tracked_branch(struct remote *remote, void *priv)
@@ -45,10 +38,10 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 			break;
 		case 2:
 			/* there are at least two remotes; backfill the first one */
-			string_list_append(&ftb->ambiguous_remotes, tracking->remote);
+			string_list_append(ftb->ambiguous_remotes, tracking->remote);
 			/* fall through */
 		default:
-			string_list_append(&ftb->ambiguous_remotes, remote->name);
+			string_list_append(ftb->ambiguous_remotes, remote->name);
 			free(tracking->spec.src);
 			string_list_clear(tracking->srcs, 0);
 		break;
@@ -59,6 +52,51 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 	return 0;
 }
 
+void find_tracking_remote_for_ref(struct tracking *tracking,
+				  struct string_list *ambiguous_remotes)
+{
+	struct find_tracked_branch_cb ftb_cb = {
+		.tracking = tracking,
+		.ambiguous_remotes = ambiguous_remotes,
+	};
+
+	for_each_remote(find_tracked_branch, &ftb_cb);
+}
+
+void advise_ambiguous_fetch_refspec(const char *dst,
+				    const struct string_list *ambiguous_remotes)
+{
+	struct strbuf remotes_advice = STRBUF_INIT;
+	struct string_list_item *item;
+
+	if (!advice_enabled(ADVICE_AMBIGUOUS_FETCH_REFSPEC))
+		return;
+
+	for_each_string_list_item(item, ambiguous_remotes)
+		/*
+		 * TRANSLATORS: This is a line listing a remote with duplicate
+		 * refspecs in the advice message below. For RTL languages you'll
+		 * probably want to swap the "%s" and leading "  " space around.
+		 */
+		strbuf_addf(&remotes_advice, _("  %s\n"), item->string);
+
+	/*
+	 * TRANSLATORS: The second argument is a \n-delimited list of
+	 * duplicate refspecs, composed above.
+	 */
+	advise(_("There are multiple remotes whose fetch refspecs map to the remote\n"
+		 "tracking ref '%s':\n"
+		 "%s"
+		 "\n"
+		 "This is typically a configuration error.\n"
+		 "\n"
+		 "To support setting up tracking branches, ensure that\n"
+		 "different remotes' fetch refspecs map into different\n"
+		 "tracking namespaces."), dst,
+	       remotes_advice.buf);
+	strbuf_release(&remotes_advice);
+}
+
 static int should_setup_rebase(const char *origin)
 {
 	switch (autorebase) {
@@ -254,11 +292,8 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 {
 	struct tracking tracking;
 	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
+	struct string_list ambiguous_remotes = STRING_LIST_INIT_DUP;
 	int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
-	struct find_tracked_branch_cb ftb_cb = {
-		.tracking = &tracking,
-		.ambiguous_remotes = STRING_LIST_INIT_DUP,
-	};
 
 	if (!track)
 		BUG("asked to set up tracking, but tracking is disallowed");
@@ -267,7 +302,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 	tracking.spec.dst = (char *)orig_ref;
 	tracking.srcs = &tracking_srcs;
 	if (track != BRANCH_TRACK_INHERIT)
-		for_each_remote(find_tracked_branch, &ftb_cb);
+		find_tracking_remote_for_ref(&tracking, &ambiguous_remotes);
 	else if (inherit_tracking(&tracking, orig_ref))
 		goto cleanup;
 
@@ -293,34 +328,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 	if (tracking.matches > 1) {
 		int status = die_message(_("not tracking: ambiguous information for ref '%s'"),
 					    orig_ref);
-		if (advice_enabled(ADVICE_AMBIGUOUS_FETCH_REFSPEC)) {
-			struct strbuf remotes_advice = STRBUF_INIT;
-			struct string_list_item *item;
-
-			for_each_string_list_item(item, &ftb_cb.ambiguous_remotes)
-				/*
-				 * TRANSLATORS: This is a line listing a remote with duplicate
-				 * refspecs in the advice message below. For RTL languages you'll
-				 * probably want to swap the "%s" and leading "  " space around.
-				 */
-				strbuf_addf(&remotes_advice, _("  %s\n"), item->string);
-
-			/*
-			 * TRANSLATORS: The second argument is a \n-delimited list of
-			 * duplicate refspecs, composed above.
-			 */
-			advise(_("There are multiple remotes whose fetch refspecs map to the remote\n"
-				 "tracking ref '%s':\n"
-				 "%s"
-				 "\n"
-				 "This is typically a configuration error.\n"
-				 "\n"
-				 "To support setting up tracking branches, ensure that\n"
-				 "different remotes' fetch refspecs map into different\n"
-				 "tracking namespaces."), orig_ref,
-			       remotes_advice.buf);
-			strbuf_release(&remotes_advice);
-		}
+		advise_ambiguous_fetch_refspec(orig_ref, &ambiguous_remotes);
 		exit(status);
 	}
 
@@ -347,7 +355,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 
 cleanup:
 	string_list_clear(&tracking_srcs, 0);
-	string_list_clear(&ftb_cb.ambiguous_remotes, 0);
+	string_list_clear(&ambiguous_remotes, 0);
 }
 
 int read_branch_desc(struct strbuf *buf, const char *branch_name)
diff --git a/branch.h b/branch.h
index 3dc6e2a0ff..c2e6725491 100644
--- a/branch.h
+++ b/branch.h
@@ -1,9 +1,25 @@
 #ifndef BRANCH_H
 #define BRANCH_H
 
+#include "refspec.h"
+
+struct string_list;
 struct repository;
 struct strbuf;
 
+struct tracking {
+	struct refspec_item spec;
+	struct string_list *srcs;
+	const char *remote;
+	int matches;
+};
+
+void find_tracking_remote_for_ref(struct tracking *tracking,
+				  struct string_list *ambiguous_remotes);
+
+void advise_ambiguous_fetch_refspec(const char *dst,
+				    const struct string_list *ambiguous_remotes);
+
 enum branch_track {
 	BRANCH_TRACK_UNSPECIFIED = -1,
 	BRANCH_TRACK_NEVER = 0,
-- 
gitgitgadget


^ permalink raw reply related

* [PATCH v14 0/2] checkout: --track=fetch
From: Harald Nordgren via GitGitGadget @ 2026-06-18 12:44 UTC (permalink / raw)
  To: git
  Cc: Ramsay Jones, D. Ben Knoble, Kristoffer Haugsbakk, Marc Branchaud,
	Phillip Wood, Harald Nordgren
In-Reply-To: <pull.2281.v13.git.git.1779565714.gitgitgadget@gmail.com>

Extend checkout --track with a fetch mode to refresh start-point.

Changes in v14:

 * Handle .h files in a better way.

Changes in v13:

 * Create a preparatory commit that exposes find_tracking_remote_for_ref()
   and advise_ambiguous_fetch_refspec() from branch.c, so checkout can reuse
   the same lookup git branch --track uses.
 * Use advise_ambiguous_fetch_refspec() for the "multiple remotes match"
   case, so the wording matches git branch --track.

Harald Nordgren (2):
  branch: expose helpers for finding the remote owning a tracking ref
  checkout: extend --track with a "fetch" mode to refresh start-point

 Documentation/git-checkout.adoc |  17 +-
 Documentation/git-switch.adoc   |   5 +-
 branch.c                        |  96 ++++++-----
 branch.h                        |  16 ++
 builtin/checkout.c              | 139 +++++++++++++++-
 t/t7201-co.sh                   | 276 ++++++++++++++++++++++++++++++++
 6 files changed, 498 insertions(+), 51 deletions(-)


base-commit: 4621f8ce5e9b97aa2e8d0d9ffe9d25df2471074d
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2281%2FHaraldNordgren%2Fcheckout-fetch-start-point-v14
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2281/HaraldNordgren/checkout-fetch-start-point-v14
Pull-Request: https://github.com/git/git/pull/2281

Range-diff vs v13:

 1:  2369afad24 ! 1:  f79689c23d branch: expose helpers for finding the remote owning a tracking ref
     @@ branch.h
       #define BRANCH_H
       
      +#include "refspec.h"
     -+#include "string-list.h"
      +
     ++struct string_list;
       struct repository;
       struct strbuf;
       
 2:  60adf0e67d ! 2:  8518f090b1 checkout: extend --track with a "fetch" mode to refresh start-point
     @@ builtin/checkout.c
      +#include "run-command.h"
       #include "sequencer.h"
       #include "setup.h"
     - #include "strvec.h"
     + #include "sparse-index.h"
      @@ builtin/checkout.c: struct checkout_opts {
       	int count_checkout_paths;
       	int overlay_mode;

-- 
gitgitgadget

^ permalink raw reply

* Re: [PATCH v5 2/2] graph: indent visual root in graph
From: Pablo Sabater @ 2026-06-18 12:42 UTC (permalink / raw)
  To: Jeff King
  Cc: git, ayu.chandekar, chandrapratap3519, christian.couder, gitster,
	jltobler, karthik.188, phillip.wood, siddharthasthana31
In-Reply-To: <20260617202744.GA3465855@coredump.intra.peff.net>

El mié, 17 jun 2026 a las 22:27, Jeff King (<peff@peff.net>) escribió:
>
> On Sat, Jun 13, 2026 at 09:09:16PM +0200, Pablo Sabater wrote:
>
> > +/*
> > + * Iterates the commits queue searching for the next visible commit, once found
> > + * sets visibleness and visual-root flags.
> > + * Knowing if the next commit is also a visual root avoids redundant indentations
> > + *
> > + * NEEDSWORK: The queue is actively being modified by the walker, for each commit
> > + * its parents and itself get simplified and their flags set, but for the next
> > + * unrelated commit or the grandparents they are not simplified yet, which means
> > + * that a commit whose parents are all filtered will not be marked as a visual
> > + * root candidate at the lookahead.
> > + * This causes the lookahead to fail, failing to set the cascade flag to avoid
> > + * redundant indentations.
> > + * See 'test_expect_failure' at t4218-log-graph-indentation.sh.
> > + */
> > +static void graph_peek_next_visible(struct git_graph *graph,
> > +                                 struct graph_lookahead_flags *flags)
> > +{
> > +     struct commit_list *cl;
> > +
> > +     flags->is_next_visible = 0;
> > +     flags->is_next_visual_root = 0;
> > +     flags->next_has_column = 0;
> > +
> > +     for (cl = graph->revs->commits; cl; cl = cl->next) {
> > +             if (get_commit_action(graph->revs, cl->item) != commit_show)
> > +                     continue;
> > [...]
>
> I have a feeling this may interact badly with the prio-queue introduced
> by dd4bc01c0a (revision: use priority queue for non-limited streaming
> walks, 2026-05-27). In that commit, get_revision_1() sucks all of the
> commits from revs->commits into revs->commit_queue, and then traversal
> puts the parents into that queue, not the commits list.
>
> So during the traversal, revs->commits does not hold the complete queue
> anymore. I think it does see _some_ commits, since some get placed
> directly into revs->commits and then later moved next time
> get_revision() is called. But if we instrument the code like this:
>
> diff --git a/graph.c b/graph.c
> index e0d1e2a510..8a5f17a089 100644
> --- a/graph.c
> +++ b/graph.c
> @@ -926,6 +926,10 @@ static void graph_peek_next_visible(struct git_graph *graph,
>         flags->is_next_visual_root = 0;
>         flags->next_has_column = 0;
>
> +       warning("peeking at visible commits: %d in list, %d in queue",
> +               commit_list_count(graph->revs->commits),
> +               (int)graph->revs->commit_queue.nr);
> +
>         for (cl = graph->revs->commits; cl; cl = cl->next) {
>                 if (get_commit_action(graph->revs, cl->item) != commit_show)
>                         continue;
>
> and run something like:
>
>   ./git log --graph --oneline -- Makefile
>
> we can see that we're always considering just one commit, while there
> may be dozens or hundreds in the queue.
>
> I'm not sure what the solution is. This function wants to peek ahead in
> queue order, possibly through multiple entries. But a heap-based queue
> inherently only supports peeking at the first entry.

Hi Jeff!

Yeah, I haven't read dd4bc01c0a yet but from what you say it prob
won't work anymore, I didn't know about that series, about the
lookahead I think it could still work with some tweaks, the important
part is to set the three lookahead flags.

From what I understood, we can only get the direct next commit, but no
more reliably ordered.

The flags should be fine:

- 'is_next_visible' could need to traverse multiple entries, but it
doesn't need them to be in order. We just need to know if something
will be rendered after.
- 'next_has_column' only needs the first entry.
- 'is_next_visual_root' only needs the first entry to know if it could
be a visual root, and also if it is not the last one (but we don't
need them to be ordered for this last part).

Should I work with 'next' as a base to have dd4bc01c0a? (Sorry I've
just worked with master).

I'll try to make it work but if not, the lookahead works to avoid
_redundant_ indentations, but it would still work correctly without
it.

>
> None of the tests seem to fail, but I'm not sure if that's because I'm
> way off base in my analysis, or there's a gap in the test coverage, or
> if this case is part of the expect_failure ones mentioned in the
> comment.
>
> I noticed because I have another topic which drops the revs->commits
> list entirely (and just always uses the queue), which of course doesn't
> compile when merged with this (I merge with 'jch' for my daily driver,
> which now includes this patch).
>
> -Peff

Thanks,
Pablo

^ permalink raw reply

* Re: [PATCH v6] checkout: extend --track with a "fetch" mode to refresh start-point
From: Harald Nordgren @ 2026-06-18 12:38 UTC (permalink / raw)
  To: Phillip Wood; +Cc: gitgitgadget, git
In-Reply-To: <f23eb128-958f-475f-911b-eac4f6daddff@gmail.com>

Hi Phillip!

How do you feel now, is it worth it for us to move forward with this
topic or not?


Harald

On Fri, May 8, 2026 at 3:15 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> Hi Harald
>
> On 07/05/2026 21:12, Harald Nordgren wrote:
> > Is this ready to move to next?
>
> I'm not particularly enthusiastic one way or the other about adding
> this, but so long as we only try to fetch when the user explicitly asks
> for it I don't particularly object. However having had a quick scan of
> the implementation I have a few comments
>
> * "--track=inherit,direct" is nonsense and should be rejected
>
> * currently "--track" has "last one wins" behavior so
>    "--track=inherit --track=direct" behaves like "--track=direct". We
>    should probably keep that so that "--track=fetch --track=direct"
>    behaves like "--track=direct", not "--track=fetch,direct"
>
> * if "git fetch" fails and the remote tracking ref already exists then
>    we should print a warning and carry on rather than dying which is more
>    convenient if the user or remote server are offline.
>
> * "git checkout --track=fetch origin/branch" should respect
>    remote.origin.fetch so that we fetch the ref that we're going to
>    checkout. I wonder if we can share this logic with the code that
>    sets the upstream branch.
>
> * "git checkout --track=fetch origin" should only fetch the remote
>    ref that we're going to checkout, not all the refs from origin. i.e.
>    it should read origin/HEAD to work out what to fetch.
>
> Thanks
>
> Phillip
>

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox