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, 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. Also update a test to explicitly expect that advice. Signed-off-by: Tao Klerks <tao@klerks.biz> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Tao Klerks committed Apr 1, 2022 at 06:05 UTC e4921d877ab3487fbc0bde8b3e59b757d274783c
5 files changed +78 -9
Documentation/config/advice.txt
+4
@@ -4,6 +4,10 @@ advice.*::
4 can tell Git that you do not need help by setting these to 'false':
5 +
6 --
7 + ambiguousFetchRefspec::
8 + Advice shown when fetch refspec for multiple remotes map to
9 + the same remote-tracking branch namespace and causes branch
10 + tracking set-up to fail.
11 fetchShowForcedUpdates::
12 Advice shown when linkgit:git-fetch[1] takes a long time
13 to calculate forced updates after ref updates, or to warn
advice.c
+1
@@ -39,6 +39,7 @@ static struct {
39 [ADVICE_ADD_EMPTY_PATHSPEC] = { "addEmptyPathspec", 1 },
40 [ADVICE_ADD_IGNORED_FILE] = { "addIgnoredFile", 1 },
41 [ADVICE_AM_WORK_DIR] = { "amWorkDir", 1 },
42 + [ADVICE_AMBIGUOUS_FETCH_REFSPEC] = { "ambiguousFetchRefspec", 1 },
43 [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName", 1 },
44 [ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge", 1 },
45 [ADVICE_DETACHED_HEAD] = { "detachedHead", 1 },
advice.h
+1
@@ -17,6 +17,7 @@ struct string_list;
17 ADVICE_ADD_EMPTY_PATHSPEC,
18 ADVICE_ADD_IGNORED_FILE,
19 ADVICE_AM_WORK_DIR,
20 + ADVICE_AMBIGUOUS_FETCH_REFSPEC,
21 ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
22 ADVICE_COMMIT_BEFORE_MERGE,
23 ADVICE_DETACHED_HEAD,
branch.c
+56 -7
@@ -18,17 +18,31 @@ struct tracking {
18 int matches;
19 };
20
21 +struct find_tracked_branch_cb {
22 + struct tracking *tracking;
23 + struct string_list ambiguous_remotes;
24 +};
25 +
26 static int find_tracked_branch(struct remote *remote, void *priv)
27 {
23 - struct tracking *tracking = priv;
28 + struct find_tracked_branch_cb *ftb = priv;
29 + struct tracking *tracking = ftb->tracking;
30
31 if (!remote_find_tracking(remote, &tracking->spec)) {
26 - if (++tracking->matches == 1) {
32 + switch (++tracking->matches) {
33 + case 1:
34 string_list_append(tracking->srcs, tracking->spec.src);
35 tracking->remote = remote->name;
29 - } else {
36 + break;
37 + case 2:
38 + /* there are at least two remotes; backfill the first one */
39 + string_list_append(&ftb->ambiguous_remotes, tracking->remote);
40 + /* fall through */
41 + default:
42 + string_list_append(&ftb->ambiguous_remotes, remote->name);
43 free(tracking->spec.src);
44 string_list_clear(tracking->srcs, 0);
45 + break;
46 }
47 tracking->spec.src = NULL;
48 }
@@ -232,12 +246,16 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
246 struct tracking tracking;
247 struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
248 int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
249 + struct find_tracked_branch_cb ftb_cb = {
250 + .tracking = &tracking,
251 + .ambiguous_remotes = STRING_LIST_INIT_DUP,
252 + };
253
254 memset(&tracking, 0, sizeof(tracking));
255 tracking.spec.dst = (char *)orig_ref;
256 tracking.srcs = &tracking_srcs;
257 if (track != BRANCH_TRACK_INHERIT)
240 - for_each_remote(find_tracked_branch, &tracking);
258 + for_each_remote(find_tracked_branch, &ftb_cb);
259 else if (inherit_tracking(&tracking, orig_ref))
260 goto cleanup;
261
@@ -252,9 +270,39 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
270 goto cleanup;
271 }
272
255 - if (tracking.matches > 1)
256 - die(_("not tracking: ambiguous information for ref %s"),
257 - orig_ref);
273 + if (tracking.matches > 1) {
274 + int status = die_message(_("not tracking: ambiguous information for ref '%s'"),
275 + orig_ref);
276 + if (advice_enabled(ADVICE_AMBIGUOUS_FETCH_REFSPEC)) {
277 + struct strbuf remotes_advice = STRBUF_INIT;
278 + struct string_list_item *item;
279 +
280 + for_each_string_list_item(item, &ftb_cb.ambiguous_remotes)
281 + /*
282 + * TRANSLATORS: This is a line listing a remote with duplicate
283 + * refspecs in the advice message below. For RTL languages you'll
284 + * probably want to swap the "%s" and leading " " space around.
285 + */
286 + strbuf_addf(&remotes_advice, _(" %s\n"), item->string);
287 +
288 + /*
289 + * TRANSLATORS: The second argument is a \n-delimited list of
290 + * duplicate refspecs, composed above.
291 + */
292 + advise(_("There are multiple remotes whose fetch refspecs map to the remote\n"
293 + "tracking ref '%s':\n"
294 + "%s"
295 + "\n"
296 + "This is typically a configuration error.\n"
297 + "\n"
298 + "To support setting up tracking branches, ensure that\n"
299 + "different remotes' fetch refspecs map into different\n"
300 + "tracking namespaces."), orig_ref,
301 + remotes_advice.buf);
302 + strbuf_release(&remotes_advice);
303 + }
304 + exit(status);
305 + }
306
307 if (tracking.srcs->nr < 1)
308 string_list_append(tracking.srcs, orig_ref);
@@ -264,6 +312,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
312
313 cleanup:
314 string_list_clear(&tracking_srcs, 0);
315 + string_list_clear(&ftb_cb.ambiguous_remotes, 0);
316 }
317
318 int read_branch_desc(struct strbuf *buf, const char *branch_name)
t/t3200-branch.sh
+16 -2
@@ -1039,13 +1039,27 @@ test_expect_success 'checkout -b with -l makes reflog when core.logAllRefUpdates
1039 git rev-parse --verify gamma@{0}
1040 '
1041
1042 -test_expect_success 'avoid ambiguous track' '
1042 +test_expect_success 'avoid ambiguous track and advise' '
1043 git config branch.autosetupmerge true &&
1044 git config remote.ambi1.url lalala &&
1045 git config remote.ambi1.fetch refs/heads/lalala:refs/heads/main &&
1046 git config remote.ambi2.url lilili &&
1047 git config remote.ambi2.fetch refs/heads/lilili:refs/heads/main &&
1048 - test_must_fail git branch all1 main &&
1048 + cat <<-EOF >expected &&
1049 + fatal: not tracking: ambiguous information for ref '\''refs/heads/main'\''
1050 + hint: There are multiple remotes whose fetch refspecs map to the remote
1051 + hint: tracking ref '\''refs/heads/main'\'':
1052 + hint: ambi1
1053 + hint: ambi2
1054 + hint: ''
1055 + hint: This is typically a configuration error.
1056 + hint: ''
1057 + hint: To support setting up tracking branches, ensure that
1058 + hint: different remotes'\'' fetch refspecs map into different
1059 + hint: tracking namespaces.
1060 + EOF
1061 + test_must_fail git branch all1 main 2>actual &&
1062 + test_cmp expected actual &&
1063 test -z "$(git config branch.all1.merge)"
1064 '
1065