diff mbox series

[v3] tracking branches: add advice to ambiguous refspec error

Message ID pull.1183.v3.git.1648450268285.gitgitgadget@gmail.com (mailing list archive)
State Superseded
Headers show
Series [v3] tracking branches: add advice to ambiguous refspec error | expand

Commit Message

Tao Klerks March 28, 2022, 6:51 a.m. UTC
From: Tao Klerks <tao@klerks.biz>

The error "not tracking: ambiguous information for ref" is raised
when we are evaluating what tracking information to set on a branch,
and find that the ref to be added as tracking branch is mapped
under multiple remotes' fetch refspecs.

This can easily happen when a user copy-pastes a remote definition
in their git config, and forgets to change the tracking path.

Add advice in this situation, explicitly highlighting which remotes
are involved and suggesting how to correct the situation.

Signed-off-by: Tao Klerks <tao@klerks.biz>
---
    tracking branches: add advice to ambiguous refspec error
    
    I believe this third version incorporates all Ævar's suggestions, and
    might be usable. Removed "RFC" prefix.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1183%2FTaoK%2Fadvise-ambiguous-tracking-refspec-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1183/TaoK/advise-ambiguous-tracking-refspec-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/1183

Range-diff vs v2:

 1:  6c1cd885dda ! 1:  22ffe81ac26 RFC: tracking branches: add advice to ambiguous refspec error
     @@ Metadata
      Author: Tao Klerks <tao@klerks.biz>
      
       ## Commit message ##
     -    RFC: tracking branches: add advice to ambiguous refspec error
     +    tracking branches: add advice to ambiguous refspec error
      
          The error "not tracking: ambiguous information for ref" is raised
          when we are evaluating what tracking information to set on a branch,
     @@ Commit message
      
       ## Documentation/config/advice.txt ##
      @@ Documentation/config/advice.txt: advice.*::
     - 		Advice shown when either linkgit:git-add[1] or linkgit:git-rm[1]
     - 		is asked to update index entries outside the current sparse
     - 		checkout.
     + 	can tell Git that you do not need help by setting these to 'false':
     + +
     + --
      +	ambiguousFetchRefspec::
      +		Advice shown when branch tracking relationship setup fails due
      +		to multiple remotes' refspecs mapping to the same remote
      +		tracking namespace in the repo.
     - --
     + 	fetchShowForcedUpdates::
     + 		Advice shown when linkgit:git-fetch[1] takes a long time
     + 		to calculate forced updates after ref updates, or to warn
      
       ## advice.c ##
      @@ advice.c: static struct {
     @@ branch.c: static int find_tracked_branch(struct remote *remote, void *priv)
       			free(tracking->spec.src);
       			string_list_clear(tracking->srcs, 0);
       		}
     -+		strbuf_addf(&ftb->remotes_advice, "  %s\n", remote->name);
     ++		/*
     ++		 * TRANSLATORS: This is a line listing a remote with duplicate
     ++		 * refspecs, to be later included in advice message
     ++		 * ambiguousFetchRefspec. For RTL languages you'll probably want
     ++		 * to swap the "%s" and leading "  " space around.
     ++		 */
     ++		strbuf_addf(&ftb->remotes_advice, _("  %s\n"), remote->name);
       		tracking->spec.src = NULL;
       	}
       
     @@ branch.c: static int inherit_tracking(struct tracking *tracking, const char *ori
       	return 0;
       }
       
     -+
     -+static const char ambiguous_refspec_advice_pre[] =
     -+N_("\n"
     -+"There are multiple remotes whose fetch refspecs map to the remote\n"
     -+"tracking ref";)
     -+static const char ambiguous_refspec_advice_post[] =
     -+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.\n");
      +
       /*
     -  * This is called when new_ref is branched off of orig_ref, and tries
     -  * to infer the settings for branch.<new_ref>.{remote,merge} from the
     +  * Used internally to set the branch.<new_ref>.{remote,merge} config
     +  * settings so that branch 'new_ref' tracks 'orig_ref'. Unlike
      @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
       	struct tracking tracking;
       	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
     @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
      -		for_each_remote(find_tracked_branch, &tracking);
      +		for_each_remote(find_tracked_branch, &ftb_cb);
       	else if (inherit_tracking(&tracking, orig_ref))
     - 		return;
     + 		goto cleanup;
       
      @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
     - 			return;
     + 			goto cleanup;
       		}
       
      -	if (tracking.matches > 1)
     @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
      +		int status = die_message(_("not tracking: ambiguous information for ref %s"),
      +					    orig_ref);
      +		if (advice_enabled(ADVICE_AMBIGUOUS_FETCH_REFSPEC))
     -+			advise("%s %s:\n%s\n%s",
     -+			       _(ambiguous_refspec_advice_pre),
     ++			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,
     -+			       ftb_cb.remotes_advice.buf,
     -+			       _(ambiguous_refspec_advice_post)
     ++			       ftb_cb.remotes_advice.buf
      +			       );
      +		exit(status);
      +	}
       
       	if (tracking.srcs->nr < 1)
       		string_list_append(tracking.srcs, orig_ref);
     +@@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
     + 		exit(-1);
     + 
     + cleanup:
     ++	strbuf_release(&ftb_cb.remotes_advice);
     + 	string_list_clear(&tracking_srcs, 0);
     + }
     + 


 Documentation/config/advice.txt |  4 +++
 advice.c                        |  1 +
 advice.h                        |  1 +
 branch.c                        | 44 +++++++++++++++++++++++++++++----
 4 files changed, 45 insertions(+), 5 deletions(-)


base-commit: abf474a5dd901f28013c52155411a48fd4c09922

Comments

Junio C Hamano March 28, 2022, 4:23 p.m. UTC | #1
"Tao Klerks via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Tao Klerks <tao@klerks.biz>
>
> The error "not tracking: ambiguous information for ref" is raised
> when we are evaluating what tracking information to set on a branch,
> and find that the ref to be added as tracking branch is mapped
> under multiple remotes' fetch refspecs.

OK.

>  Documentation/config/advice.txt |  4 +++
>  advice.c                        |  1 +
>  advice.h                        |  1 +
>  branch.c                        | 44 +++++++++++++++++++++++++++++----
>  4 files changed, 45 insertions(+), 5 deletions(-)
>
> diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
> index c40eb09cb7e..90f7dbd03aa 100644
> --- a/Documentation/config/advice.txt
> +++ b/Documentation/config/advice.txt
> @@ -4,6 +4,10 @@ advice.*::
>  	can tell Git that you do not need help by setting these to 'false':
>  +
>  --
> +	ambiguousFetchRefspec::
> +		Advice shown when branch tracking relationship setup fails due
> +		to multiple remotes' refspecs mapping to the same remote
> +		tracking namespace in the repo.

	Advice shown when fetch refspec for multiple remotes map to
	the same remote-tracking branch namespace and causes branch
	tracking set-up to fail.

"remote-tracking" should be spelled as a single word.  There are
some existing mistakes in the code, but let's not make it worse.

> diff --git a/branch.c b/branch.c
> index 6b31df539a5..5c28d432103 100644
> --- a/branch.c
> +++ b/branch.c
> @@ -18,9 +18,15 @@ struct tracking {
>  	int matches;
>  };
>  
> +struct find_tracked_branch_cb {
> +	struct tracking *tracking;
> +	struct strbuf remotes_advice;
> +};
> +
>  static int find_tracked_branch(struct remote *remote, void *priv)
>  {
> -	struct tracking *tracking = priv;
> +	struct find_tracked_branch_cb *ftb = priv;
> +	struct tracking *tracking = ftb->tracking;
>  
>  	if (!remote_find_tracking(remote, &tracking->spec)) {
>  		if (++tracking->matches == 1) {
>  			string_list_append(tracking->srcs, tracking->spec.src);
>  			tracking->remote = remote->name;
>  		} else {
>  			free(tracking->spec.src);
>  			string_list_clear(tracking->srcs, 0);
>  		}
> +		/*
> +		 * TRANSLATORS: This is a line listing a remote with duplicate
> +		 * refspecs, to be later included in advice message
> +		 * ambiguousFetchRefspec. For RTL languages you'll probably want
> +		 * to swap the "%s" and leading "  " space around.
> +		 */
> +		strbuf_addf(&ftb->remotes_advice, _("  %s\n"), remote->name);
>  		tracking->spec.src = NULL;
>  	}

This is wasteful.  remotes_advice does not have to be filled when we
are not going to give advice, i.e. there is only one matching remote
and no second or subsequent ones, which should be the majority case.
And we should not make majority of users who do not make a mistake
that needs the advice message pay the cost of giving advice to those
who screw up, if we can avoid it.

Instead, only when we are looking at the second one, we can stuff
tracking->remote (i.e. the first one) to remotes_advice, and then
append remote->name there.  Perhaps we can do it like so?

	struct strbuf *names = &ftb->remotes_advice;
	const char *namefmt = N_("  %s\n");

	switch (++tracking->matches) {
	case 1:
		string_list_append(tracking->srcs, tracking->spec.src);
		tracking->remote = remote->name;
		break;
	case 2:
		strbuf_addf(names, _(namefmt), tracking->remote);
		/* fall through */
	default:
		strbuf_addf(names, _(namefmt), remote->name);
                free(tracking->spec.src);
                string_list_clear(tracking->srcs, 0);
                break;
	}
	tracking->spec.src = NULL;

For a bonus point, remotes_advice member can be left empty without
paying the cost to strbuf_addf() when the advice configuration says
we are not going to show the message.

Thanks.
Glen Choo March 28, 2022, 5:12 p.m. UTC | #2
Junio C Hamano <gitster@pobox.com> writes:

>> diff --git a/branch.c b/branch.c
>> index 6b31df539a5..5c28d432103 100644
>> --- a/branch.c
>> +++ b/branch.c
>> @@ -18,9 +18,15 @@ struct tracking {
>>  	int matches;
>>  };
>>  
>> +struct find_tracked_branch_cb {
>> +	struct tracking *tracking;
>> +	struct strbuf remotes_advice;
>> +};
>> +
>>  static int find_tracked_branch(struct remote *remote, void *priv)
>>  {
>> -	struct tracking *tracking = priv;
>> +	struct find_tracked_branch_cb *ftb = priv;
>> +	struct tracking *tracking = ftb->tracking;
>>  
>>  	if (!remote_find_tracking(remote, &tracking->spec)) {
>>  		if (++tracking->matches == 1) {
>>  			string_list_append(tracking->srcs, tracking->spec.src);
>>  			tracking->remote = remote->name;
>>  		} else {
>>  			free(tracking->spec.src);
>>  			string_list_clear(tracking->srcs, 0);
>>  		}
>> +		/*
>> +		 * TRANSLATORS: This is a line listing a remote with duplicate
>> +		 * refspecs, to be later included in advice message
>> +		 * ambiguousFetchRefspec. For RTL languages you'll probably want
>> +		 * to swap the "%s" and leading "  " space around.
>> +		 */
>> +		strbuf_addf(&ftb->remotes_advice, _("  %s\n"), remote->name);
>>  		tracking->spec.src = NULL;
>>  	}
>
> This is wasteful.  remotes_advice does not have to be filled when we
> are not going to give advice, i.e. there is only one matching remote
> and no second or subsequent ones, which should be the majority case.
> And we should not make majority of users who do not make a mistake
> that needs the advice message pay the cost of giving advice to those
> who screw up, if we can avoid it.
>
> Instead, only when we are looking at the second one, we can stuff
> tracking->remote (i.e. the first one) to remotes_advice, and then
> append remote->name there.  Perhaps we can do it like so?
>
> 	struct strbuf *names = &ftb->remotes_advice;
> 	const char *namefmt = N_("  %s\n");
>
> 	switch (++tracking->matches) {
> 	case 1:
> 		string_list_append(tracking->srcs, tracking->spec.src);
> 		tracking->remote = remote->name;
> 		break;
> 	case 2:
> 		strbuf_addf(names, _(namefmt), tracking->remote);
> 		/* fall through */
> 	default:
> 		strbuf_addf(names, _(namefmt), remote->name);
>                 free(tracking->spec.src);
>                 string_list_clear(tracking->srcs, 0);
>                 break;
> 	}
> 	tracking->spec.src = NULL;
>
> For a bonus point, remotes_advice member can be left empty without
> paying the cost to strbuf_addf() when the advice configuration says
> we are not going to show the message.
>
> Thanks.

Hm, what do you think of an alternate approach of storing of the
matching remotes in a string_list, something like:

  struct find_tracked_branch_cb {
  	struct tracking *tracking;
  	struct string_list matching_remotes;
  };
  
  static int find_tracked_branch(struct remote *remote, void *priv)
  {
  	struct tracking *tracking = priv;
  	struct find_tracked_branch_cb *ftb = priv;
  	struct tracking *tracking = ftb->tracking;
  
  	if (!remote_find_tracking(remote, &tracking->spec)) {
  		if (++tracking->matches == 1) {
  			string_list_append(tracking->srcs, tracking->spec.src);
  			tracking->remote = remote->name;
  		} else {
  			free(tracking->spec.src);
  			string_list_clear(tracking->srcs, 0);
  		}
  		string_list_append(&ftb->matching_remotes, remote->name);
  		tracking->spec.src = NULL;
  	}

then construct the advice message in setup_tracking()? To my untrained
eye, "case 2" requires a bit of extra work to understand.
Junio C Hamano March 28, 2022, 5:23 p.m. UTC | #3
Glen Choo <chooglen@google.com> writes:

> Hm, what do you think of an alternate approach of storing of the
> matching remotes in a string_list, something like:
>
>   struct find_tracked_branch_cb {
>   	struct tracking *tracking;
>   	struct string_list matching_remotes;
>   };
>   
>   static int find_tracked_branch(struct remote *remote, void *priv)
>   {
>   	struct tracking *tracking = priv;
>   	struct find_tracked_branch_cb *ftb = priv;
>   	struct tracking *tracking = ftb->tracking;
>   
>   	if (!remote_find_tracking(remote, &tracking->spec)) {
>   		if (++tracking->matches == 1) {
>   			string_list_append(tracking->srcs, tracking->spec.src);
>   			tracking->remote = remote->name;
>   		} else {
>   			free(tracking->spec.src);
>   			string_list_clear(tracking->srcs, 0);
>   		}
>   		string_list_append(&ftb->matching_remotes, remote->name);
>   		tracking->spec.src = NULL;
>   	}
>
> then construct the advice message in setup_tracking()? To my untrained
> eye, "case 2" requires a bit of extra work to understand.

If I were writing this code from scratch, I would have probably
collected the names first in this callback, and assembled them at
the end into a single string when we need a single string to show.

Having said that, as long as you do that lazily not to penalize
those who have sane setting without the need for advice/error to
trigger, I do not particularly care how the list of matching remote
names are kept.  Having string_list_append() unconditionally like
the above patch has, even for folks with just a single match without
need for the advice/error message is suboptimal, I would think.
Tao Klerks March 28, 2022, 6:02 p.m. UTC | #4
On Mon, Mar 28, 2022 at 7:23 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Glen Choo <chooglen@google.com> writes:
>
> > Hm, what do you think of an alternate approach of storing of the
> > matching remotes in a string_list, something like:
[...]
> > then construct the advice message in setup_tracking()? To my untrained
> > eye, "case 2" requires a bit of extra work to understand.

Interestingly, that was what I had in the original RFC. I started using
the strbuf later, after Ævar confirmed that a single "advise()" call is
the way to go. I understood building the string as we go to lead to
simpler code, as it meant one less loop. On the other hand I
understand Junio is more concerned about performance than the
existence of a second loop that we should almost never hit.

I'm very happy to switch from strbuf-building to string_list-appending,
but I'm curious to understand how/why the performance of
strbuf_addf() would be notably worse than that of
string_list_append().

Is there public doc about this somewhere?

> Having said that, as long as you do that lazily not to penalize
> those who have sane setting without the need for advice/error to
> trigger, I do not particularly care how the list of matching remote
> names are kept.  Having string_list_append() unconditionally like
> the above patch has, even for folks with just a single match without
> need for the advice/error message is suboptimal, I would think.

Again, I'm new here, and not a great coder to start with, but I'm
having a hard time understanding why the single extra/gratuitous
strbuf_addf() or string_list_append() call that we stand to optimize
(I haven't understood whether there is a significant difference
between them) would ever be noticeable in the context of creating
a branch.

I of course completely understand optimizing anything that will
end up looping, but this is a max of 1 iteration's savings; I would
have thought that at these levels, readability/maintainability (and
succinctness) of the code would trump any marginal performance
savings.

To that end, I'd understand going back to string_list_append() as
Glen proposes, and building a formatted string in a single place
(setup_tracking()) only when required - both for readability, and
in case some aspect of strbuf_addf() is non-trivially expensive -
but is the "only append to the string_list() on the rare second
pass" optimization really worth the increase in amount of code?

Is "performance over succinctness" a general principle that
could or should be noted somewhere?
Ævar Arnfjörð Bjarmason March 28, 2022, 6:50 p.m. UTC | #5
On Mon, Mar 28 2022, Tao Klerks wrote:

> On Mon, Mar 28, 2022 at 7:23 PM Junio C Hamano <gitster@pobox.com> wrote:
>>
>> Glen Choo <chooglen@google.com> writes:
>>
>> > Hm, what do you think of an alternate approach of storing of the
>> > matching remotes in a string_list, something like:
> [...]
>> > then construct the advice message in setup_tracking()? To my untrained
>> > eye, "case 2" requires a bit of extra work to understand.
>
> Interestingly, that was what I had in the original RFC. I started using
> the strbuf later, after Ævar confirmed that a single "advise()" call is
> the way to go. I understood building the string as we go to lead to
> simpler code, as it meant one less loop. On the other hand I
> understand Junio is more concerned about performance than the
> existence of a second loop that we should almost never hit.
>
> I'm very happy to switch from strbuf-building to string_list-appending,
> but I'm curious to understand how/why the performance of
> strbuf_addf() would be notably worse than that of
> string_list_append().
>
> Is there public doc about this somewhere?

We could do a string_list as in your v1 and concat it as we're
formatting it, but pushing new strings to a string_list v.s. appending
to a strbuf is actually more efficient in favor of the strbuf.

But as to not penalizing those who don't have the advice enabled,
something like this (untested)?:

diff --git a/branch.c b/branch.c
index 5c28d432103..83545456c36 100644
--- a/branch.c
+++ b/branch.c
@@ -20,6 +20,7 @@ struct tracking {
 
 struct find_tracked_branch_cb {
 	struct tracking *tracking;
+	unsigned int do_advice:1;
 	struct strbuf remotes_advice;
 };
 
@@ -36,6 +37,9 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 			free(tracking->spec.src);
 			string_list_clear(tracking->srcs, 0);
 		}
+		tracking->spec.src = NULL;
+		if (!ftb->do_advice)
+			return 0;
 		/*
 		 * TRANSLATORS: This is a line listing a remote with duplicate
 		 * refspecs, to be later included in advice message
@@ -43,7 +47,6 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 		 * to swap the "%s" and leading "  " space around.
 		 */
 		strbuf_addf(&ftb->remotes_advice, _("  %s\n"), remote->name);
-		tracking->spec.src = NULL;
 	}
 
 	return 0;
@@ -249,6 +252,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 	struct find_tracked_branch_cb ftb_cb = {
 		.tracking = &tracking,
 		.remotes_advice = STRBUF_INIT,
+		.do_advice = advice_enabled(ADVICE_AMBIGUOUS_FETCH_REFSPEC),
 	};
 
 	memset(&tracking, 0, sizeof(tracking));
@@ -273,7 +277,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))
+		if (ftb_cb.do_advice)
 			advise(_("There are multiple remotes whose fetch refspecs map to the remote\n"
 				 "tracking ref %s:\n"
 				 "%s"
Junio C Hamano March 28, 2022, 8:27 p.m. UTC | #6
Tao Klerks <tao@klerks.biz> writes:

>> Having said that, as long as you do that lazily not to penalize
>> those who have sane setting without the need for advice/error to
>> trigger, I do not particularly care how the list of matching remote
>> names are kept.  Having string_list_append() unconditionally like
>> the above patch has, even for folks with just a single match without
>> need for the advice/error message is suboptimal, I would think.
>
> Again, I'm new here, and not a great coder to start with, but I'm
> having a hard time understanding why the single extra/gratuitous
> strbuf_addf() or string_list_append() call that we stand to optimize
> (I haven't understood whether there is a significant difference
> between them) would ever be noticeable in the context of creating
> a branch.

Small things accumulate.

Unless you have to bend over backwards to do so, avoid computing
unconditionally what you only need in an error case.  People do not
make mistake all the time, and they shouldn't be forced to pay for
the possibility that other people may make mistakes and may want to
get help.

When you see that you are wasting cycles "just in case I may see an
error", you may stop, take a deep breath, and think if you can push
the extra work to error code path with equally simple and easy code.
And in this case, I think it is just as equally easy and simple as
the unconditional code in your patch to optimize for the case where
there is *no* duplicate.

It is a good discipline to learn, especially if you are new, as it
is something that stays with you even after you move on to different
projects.

> I of course completely understand optimizing anything that will
> end up looping, but this is a max of 1 iteration's savings; I would

When there is no error, you do not even need to keep the "names that
will become necessary for advice" at all, so you are comparing 0
with 1.  And if you read the no-error case of the suggested rewrite,
you'd realize that it is much easier to reason about.  You do not
have to wonder what the remotes_advice strbuf (or string_list) is
doing in the no-error code path at all.  This is not about
performance, it is about clarity of the code (not doing unnecessary
thing means doing only essential thing, after all).

Between strbuf appending and string_list appending, I do not
personally care.  As long as the code can produce the same output,
it is OK.  Using string_list _may_ have an advantage that string
formatting logic will be concentrated in a single place, as opposed
to strbuf appending, where part of formatting decisions need to be
made in the callback and other part is left in the advise-string.
And because this should all happen only in error code path, the
performance between two APIs would not matter at all (and for that
to truly hold, you need to stick to "do not unconditionally compute 
what you need only in an error case" discipline).
Junio C Hamano March 28, 2022, 8:32 p.m. UTC | #7
Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> But as to not penalizing those who don't have the advice enabled,
> something like this (untested)?:

Unconditionally computing what you need only in the error path is
the primary sin in the patch, and that should be addressed first.

If we need to do new things (like adding the do_advice member), it
becomes questionable if it is worth doing.  In this case, I think
the update is simple enough and the control flow makes it clear what
is and what isn't needed only for advice-message generation, so it
might be an overall win.

Thanks.
Tao Klerks March 29, 2022, 11:26 a.m. UTC | #8
OK, I

On Mon, Mar 28, 2022 at 10:27 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Small things accumulate.
>

OK. I've tried combining Junio's "only do something when needed" case
statement with Glen's "co-locate advice-related string manipulations"
proposal, and I believe this disqualifies and obviates the need for passing
around any new "are we going to be issuing advice" flag.
diff mbox series

Patch

diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index c40eb09cb7e..90f7dbd03aa 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -4,6 +4,10 @@  advice.*::
 	can tell Git that you do not need help by setting these to 'false':
 +
 --
+	ambiguousFetchRefspec::
+		Advice shown when branch tracking relationship setup fails due
+		to multiple remotes' refspecs mapping to the same remote
+		tracking namespace in the repo.
 	fetchShowForcedUpdates::
 		Advice shown when linkgit:git-fetch[1] takes a long time
 		to calculate forced updates after ref updates, or to warn
diff --git a/advice.c b/advice.c
index 2e1fd483040..18ac8739519 100644
--- a/advice.c
+++ b/advice.c
@@ -39,6 +39,7 @@  static struct {
 	[ADVICE_ADD_EMPTY_PATHSPEC]			= { "addEmptyPathspec", 1 },
 	[ADVICE_ADD_IGNORED_FILE]			= { "addIgnoredFile", 1 },
 	[ADVICE_AM_WORK_DIR] 				= { "amWorkDir", 1 },
+	[ADVICE_AMBIGUOUS_FETCH_REFSPEC]		= { "ambiguousFetchRefspec", 1 },
 	[ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] 	= { "checkoutAmbiguousRemoteBranchName", 1 },
 	[ADVICE_COMMIT_BEFORE_MERGE]			= { "commitBeforeMerge", 1 },
 	[ADVICE_DETACHED_HEAD]				= { "detachedHead", 1 },
diff --git a/advice.h b/advice.h
index a3957123a16..2d4c94f38eb 100644
--- a/advice.h
+++ b/advice.h
@@ -17,6 +17,7 @@  struct string_list;
 	ADVICE_ADD_EMPTY_PATHSPEC,
 	ADVICE_ADD_IGNORED_FILE,
 	ADVICE_AM_WORK_DIR,
+	ADVICE_AMBIGUOUS_FETCH_REFSPEC,
 	ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
 	ADVICE_COMMIT_BEFORE_MERGE,
 	ADVICE_DETACHED_HEAD,
diff --git a/branch.c b/branch.c
index 6b31df539a5..5c28d432103 100644
--- a/branch.c
+++ b/branch.c
@@ -18,9 +18,15 @@  struct tracking {
 	int matches;
 };
 
+struct find_tracked_branch_cb {
+	struct tracking *tracking;
+	struct strbuf remotes_advice;
+};
+
 static int find_tracked_branch(struct remote *remote, void *priv)
 {
-	struct tracking *tracking = priv;
+	struct find_tracked_branch_cb *ftb = priv;
+	struct tracking *tracking = ftb->tracking;
 
 	if (!remote_find_tracking(remote, &tracking->spec)) {
 		if (++tracking->matches == 1) {
@@ -30,6 +36,13 @@  static int find_tracked_branch(struct remote *remote, void *priv)
 			free(tracking->spec.src);
 			string_list_clear(tracking->srcs, 0);
 		}
+		/*
+		 * TRANSLATORS: This is a line listing a remote with duplicate
+		 * refspecs, to be later included in advice message
+		 * ambiguousFetchRefspec. For RTL languages you'll probably want
+		 * to swap the "%s" and leading "  " space around.
+		 */
+		strbuf_addf(&ftb->remotes_advice, _("  %s\n"), remote->name);
 		tracking->spec.src = NULL;
 	}
 
@@ -219,6 +232,7 @@  static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
 	return 0;
 }
 
+
 /*
  * Used internally to set the branch.<new_ref>.{remote,merge} config
  * settings so that branch 'new_ref' tracks 'orig_ref'. Unlike
@@ -232,12 +246,16 @@  static void setup_tracking(const char *new_ref, const char *orig_ref,
 	struct tracking tracking;
 	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
 	int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
+	struct find_tracked_branch_cb ftb_cb = {
+		.tracking = &tracking,
+		.remotes_advice = STRBUF_INIT,
+	};
 
 	memset(&tracking, 0, sizeof(tracking));
 	tracking.spec.dst = (char *)orig_ref;
 	tracking.srcs = &tracking_srcs;
 	if (track != BRANCH_TRACK_INHERIT)
-		for_each_remote(find_tracked_branch, &tracking);
+		for_each_remote(find_tracked_branch, &ftb_cb);
 	else if (inherit_tracking(&tracking, orig_ref))
 		goto cleanup;
 
@@ -252,9 +270,24 @@  static void setup_tracking(const char *new_ref, const char *orig_ref,
 			goto cleanup;
 		}
 
-	if (tracking.matches > 1)
-		die(_("not tracking: ambiguous information for ref %s"),
-		    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))
+			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,
+			       ftb_cb.remotes_advice.buf
+			       );
+		exit(status);
+	}
 
 	if (tracking.srcs->nr < 1)
 		string_list_append(tracking.srcs, orig_ref);
@@ -263,6 +296,7 @@  static void setup_tracking(const char *new_ref, const char *orig_ref,
 		exit(-1);
 
 cleanup:
+	strbuf_release(&ftb_cb.remotes_advice);
 	string_list_clear(&tracking_srcs, 0);
 }