{"thread":{"id":"64874","subject":"[PATCH 0/2] Improving advise messages from \"switch\"","startedAt":"2026-01-27T19:29:39Z","lastAt":"2026-02-19T22:21:59Z","messageCount":11,"participants":["Junio C Hamano","Kristoffer Haugsbakk","Patrick Steinhardt"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"534721","messageId":"20260127192936.904719-1-gitster@pobox.com","threadId":"64874","inReplyTo":null,"subject":"[PATCH 0/2] Improving advise messages from \"switch\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-27T19:29:34Z","receivedAt":"2026-01-27T19:29:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Simon Cheng noticed [*] that \"git switch <name>\", in an attempt to\ncreate a local branch <name> after a remote tracking branch of the\nsame name, fails correctly when multiple remotes have branches with\nthat name, but gives an advise message to disambiguate using \"git\ncheckout\".\n\nObviously, the user wanted to use \"git switch\", and \"git switch\" is\nequipped to do so, just like \"git checkout\" can, so it makes more\nsense to give hint based on \"git switch\".\n\nThe first patch of this series restructures the call chain of\ncheckout, switch, and restore commands a bit, so that a helper deep\nin the callchain can find out what end-user command it is working\nfor.  The second patch takes advantage of the new structure to tweak\nthe advise message in question, given from a helper function in a\nfairly deep part of the call chain.\n\n[Reference]\n\n * https://lore.kernel.org/git/CA+itcS0iyqNyzOP0cueLg7B3yadoEr_VWJ-QoL+YPFUPJiE2RQ@mail.gmail.com\n\nJunio C Hamano (2):\n  checkout: pass program-readable token to unified \"main\"\n  checkout: tell \"parse_remote_branch\" which command is calling it\n\n builtin/checkout.c        | 92 ++++++++++++++++++++++++++++-----------\n t/t2027-checkout-track.sh | 21 +++++++++\n 2 files changed, 88 insertions(+), 25 deletions(-)\n\n-- \n2.53.0-rc2-135-gb1217c0133\n\n"},{"id":"534722","messageId":"20260127192936.904719-2-gitster@pobox.com","threadId":"64874","inReplyTo":"20260127192936.904719-1-gitster@pobox.com","subject":"[PATCH 1/2] checkout: pass program-readable token to unified \"main\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-27T19:29:35Z","receivedAt":"2026-01-27T19:29:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The \"git checkout\", \"git switch\", and \"git restore\" commands share a\nsingle implementation, checkout_main(), which switches error message\nit gives using the usage string passed by each of these three\nfront-ends.\n\nIn order to be able to tweak behaviours of the commands based on\nwhich one we are executing, invent an enum that denotes which one of\nthese three commands is currently executing, and pass that to\ncheckout_main() instead.  With this step, there is no externally\nvisible behaviour change, as this enum parameter is only used to\nchoose among the three usage strings.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/checkout.c | 63 +++++++++++++++++++++++++++++++---------------\n 1 file changed, 43 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex f9453473fe..4f189fde48 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -43,22 +43,6 @@\n #include \"parallel-checkout.h\"\n #include \"add-interactive.h\"\n \n-static const char * const checkout_usage[] = {\n-\tN_(\"git checkout [<options>] <branch>\"),\n-\tN_(\"git checkout [<options>] [<branch>] -- <file>...\"),\n-\tNULL,\n-};\n-\n-static const char * const switch_branch_usage[] = {\n-\tN_(\"git switch [<options>] [<branch>]\"),\n-\tNULL,\n-};\n-\n-static const char * const restore_usage[] = {\n-\tN_(\"git restore [<options>] [--source=<branch>] <file>...\"),\n-\tNULL,\n-};\n-\n struct checkout_opts {\n \tint patch_mode;\n \tint patch_context;\n@@ -1293,6 +1277,13 @@ static void setup_new_branch_info_and_source_tree(\n \t}\n }\n \n+\n+enum checkout_command {\n+\tCHECKOUT_CHECKOUT = 1,\n+\tCHECKOUT_SWITCH = 2,\n+\tCHECKOUT_RESTORE = 3,\n+};\n+\n static char *parse_remote_branch(const char *arg,\n \t\t\t\t struct object_id *rev,\n \t\t\t\t int could_be_checkout_paths)\n@@ -1767,12 +1758,44 @@ static char cb_option = 'b';\n \n static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\t struct checkout_opts *opts, struct option *options,\n-\t\t\t const char * const usagestr[])\n+\t\t\t enum checkout_command which_command)\n {\n \tint parseopt_flags = 0;\n \tstruct branch_info new_branch_info = { 0 };\n \tint ret;\n \n+\tstatic const char * const checkout_usage[] = {\n+\t\tN_(\"git checkout [<options>] <branch>\"),\n+\t\tN_(\"git checkout [<options>] [<branch>] -- <file>...\"),\n+\t\tNULL,\n+\t};\n+\n+\tstatic const char * const switch_branch_usage[] = {\n+\t\tN_(\"git switch [<options>] [<branch>]\"),\n+\t\tNULL,\n+\t};\n+\n+\tstatic const char * const restore_usage[] = {\n+\t\tN_(\"git restore [<options>] [--source=<branch>] <file>...\"),\n+\t\tNULL,\n+\t};\n+\n+\tconst char * const *usagestr;\n+\n+\tswitch (which_command) {\n+\tcase CHECKOUT_CHECKOUT:\n+\t\tusagestr = checkout_usage;\n+\t\tbreak;\n+\tcase CHECKOUT_SWITCH:\n+\t\tusagestr = switch_branch_usage;\n+\t\tbreak;\n+\tcase CHECKOUT_RESTORE:\n+\t\tusagestr = restore_usage;\n+\t\tbreak;\n+\tdefault:\n+\t\tBUG(\"No such checkout variant %d\", which_command);\n+\t}\n+\n \topts->overwrite_ignore = 1;\n \topts->prefix = prefix;\n \topts->show_progress = -1;\n@@ -2032,7 +2055,7 @@ int cmd_checkout(int argc,\n \toptions = add_checkout_path_options(&opts, options);\n \n \treturn checkout_main(argc, argv, prefix, &opts, options,\n-\t\t\t     checkout_usage);\n+\t\t\t     CHECKOUT_CHECKOUT);\n }\n \n int cmd_switch(int argc,\n@@ -2071,7 +2094,7 @@ int cmd_switch(int argc,\n \tcb_option = 'c';\n \n \treturn checkout_main(argc, argv, prefix, &opts, options,\n-\t\t\t     switch_branch_usage);\n+\t\t\t     CHECKOUT_SWITCH);\n }\n \n int cmd_restore(int argc,\n@@ -2107,5 +2130,5 @@ int cmd_restore(int argc,\n \toptions = add_checkout_path_options(&opts, options);\n \n \treturn checkout_main(argc, argv, prefix, &opts, options,\n-\t\t\t     restore_usage);\n+\t\t\t     CHECKOUT_RESTORE);\n }\n-- \n2.53.0-rc2-135-gb1217c0133\n\n"},{"id":"534723","messageId":"20260127192936.904719-3-gitster@pobox.com","threadId":"64874","inReplyTo":"20260127192936.904719-1-gitster@pobox.com","subject":"[PATCH 2/2] checkout: tell \"parse_remote_branch\" which command is calling it","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-27T19:29:36Z","receivedAt":"2026-01-27T19:29:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"When \"git checkout <dwim>\" and \"git switch <dwim>\" need to error out\ndue to ambiguity of the branch name <dwim>, these command give an\nadvise message that tells a sample command to show how to\ndisambiguate from the parse_remote_branch() function.  The sample\ncommand hardcodes \"git checkout\", since this feature predates \"git\nswitch\" by a large margin.  To a user who said \"git switch <dwim>\"\nand got this message, it is confusing.\n\nPass the \"enum checkout_command\", which was invented in the previous\nstep for this exact purpose, down the call chain leading to\nparse_remote_branch() function to change the sample command shown to\nthe user in this advise message.\n\nReported-by: Simon Cheng <cyqsimon@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/checkout.c        | 29 ++++++++++++++++++++++++-----\n t/t2027-checkout-track.sh | 21 +++++++++++++++++++++\n 2 files changed, 45 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 4f189fde48..17f31c30b2 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1286,7 +1286,8 @@ enum checkout_command {\n \n static char *parse_remote_branch(const char *arg,\n \t\t\t\t struct object_id *rev,\n-\t\t\t\t int could_be_checkout_paths)\n+\t\t\t\t int could_be_checkout_paths,\n+\t\t\t\t enum checkout_command which_command)\n {\n \tint num_matches = 0;\n \tchar *remote = unique_tracking_name(arg, rev, &num_matches);\n@@ -1299,14 +1300,30 @@ static char *parse_remote_branch(const char *arg,\n \n \tif (!remote && num_matches > 1) {\n \t    if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) {\n+\t\t    const char *cmdname;\n+\n+\t\t    switch (which_command) {\n+\t\t    case CHECKOUT_CHECKOUT:\n+\t\t\t    cmdname = \"checkout\";\n+\t\t\t    break;\n+\t\t    case CHECKOUT_SWITCH:\n+\t\t\t    cmdname = \"switch\";\n+\t\t\t    break;\n+\t\t    default:\n+\t\t\t    BUG(\"command <%d> should not reach parse_remote_branch\",\n+\t\t\t\twhich_command);\n+\t\t\t    break;\n+\t\t    }\n+\n \t\t    advise(_(\"If you meant to check out a remote tracking branch on, e.g. 'origin',\\n\"\n \t\t\t     \"you can do so by fully qualifying the name with the --track option:\\n\"\n \t\t\t     \"\\n\"\n-\t\t\t     \"    git checkout --track origin/<name>\\n\"\n+\t\t\t     \"    git %s --track origin/<name>\\n\"\n \t\t\t     \"\\n\"\n \t\t\t     \"If you'd like to always have checkouts of an ambiguous <name> prefer\\n\"\n \t\t\t     \"one remote, e.g. the 'origin' remote, consider setting\\n\"\n-\t\t\t     \"checkout.defaultRemote=origin in your config.\"));\n+\t\t\t     \"checkout.defaultRemote=origin in your config.\"),\n+\t\t\t   cmdname);\n \t    }\n \n \t    die(_(\"'%s' matched multiple (%d) remote tracking branches\"),\n@@ -1318,6 +1335,7 @@ static char *parse_remote_branch(const char *arg,\n \n static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tint dwim_new_local_branch_ok,\n+\t\t\t\tenum checkout_command which_command,\n \t\t\t\tstruct branch_info *new_branch_info,\n \t\t\t\tstruct checkout_opts *opts,\n \t\t\t\tstruct object_id *rev)\n@@ -1427,7 +1445,8 @@ static int parse_branchname_arg(int argc, const char **argv,\n \n \t\tif (recover_with_dwim) {\n \t\t\tremote = parse_remote_branch(arg, rev,\n-\t\t\t\t\t\t     could_be_checkout_paths);\n+\t\t\t\t\t\t     could_be_checkout_paths,\n+\t\t\t\t\t\t     which_command);\n \t\t\tif (remote) {\n \t\t\t\t*new_branch = arg;\n \t\t\t\targ = remote;\n@@ -1916,7 +1935,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\topts->dwim_new_local_branch &&\n \t\t\topts->track == BRANCH_TRACK_UNSPECIFIED &&\n \t\t\t!opts->new_branch;\n-\t\tint n = parse_branchname_arg(argc, argv, dwim_ok,\n+\t\tint n = parse_branchname_arg(argc, argv, dwim_ok, which_command,\n \t\t\t\t\t     &new_branch_info, opts, &rev);\n \t\targv += n;\n \t\targc -= n;\ndiff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh\nindex a397790df5..e9f8d8ec48 100755\n--- a/t/t2027-checkout-track.sh\n+++ b/t/t2027-checkout-track.sh\n@@ -47,4 +47,25 @@ test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n \ttest_cmp_config refs/heads/main branch.b4.merge\n '\n \n+test_expect_success 'ambiguous tracking info' '\n+\t# Set up a few remote repositories\n+\tgit init --bare --initial-branch=trunk src1 &&\n+\tgit init --bare --initial-branch=trunk src2 &&\n+\tgit push src1 one:refs/heads/trunk &&\n+\tgit push src2 two:refs/heads/trunk &&\n+\n+\tgit remote add -f src1 \"file://$PWD/src1\" &&\n+\tgit remote add -f src2 \"file://$PWD/src2\" &&\n+\n+\t# DWIM\n+\ttest_must_fail git checkout trunk 2>hint &&\n+\ttest_grep \"hint: *git checkout --track\" hint &&\n+\ttest_grep ! \"hint: *git switch --track\" hint &&\n+\n+\t{ git update-ref -d refs/heads/trunk || :; } &&\n+\ttest_must_fail git switch trunk 2>hint &&\n+\ttest_grep ! \"hint: *git checkout --track\" hint &&\n+\ttest_grep \"hint: *git switch --track\" hint\n+'\n+\n test_done\n-- \n2.53.0-rc2-135-gb1217c0133\n\n"},{"id":"534727","messageId":"fa7f1648-3cf6-4e5f-bee9-fb5e8700d01d@app.fastmail.com","threadId":"64874","inReplyTo":"20260127192936.904719-3-gitster@pobox.com","subject":"Re: [PATCH 2/2] checkout: tell \"parse_remote_branch\" which command is calling it","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2026-01-27T20:35:43Z","receivedAt":"2026-01-27T20:36:05Z","isPatch":true,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"On Tue, Jan 27, 2026, at 20:29, Junio C Hamano wrote:\n> When \"git checkout <dwim>\" and \"git switch <dwim>\" need to error out\n> due to ambiguity of the branch name <dwim>, these command give an\n\ns/these command/these two commands/ or something.\n\n> advise message that tells a sample command to show how to\n> disambiguate from the parse_remote_branch() function.  The sample\n> command hardcodes \"git checkout\", since this feature predates \"git\n> switch\" by a large margin.  To a user who said \"git switch <dwim>\"\n> and got this message, it is confusing.\n>\n> Pass the \"enum checkout_command\", which was invented in the previous\n> step for this exact purpose, down the call chain leading to\n> parse_remote_branch() function to change the sample command shown to\n> the user in this advise message.\n\nThe commit message could also say that it adds a regression test for\nthis advice (for the first time).\n\n>\n> Reported-by: Simon Cheng <cyqsimon@gmail.com>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  builtin/checkout.c        | 29 ++++++++++++++++++++++++-----\n>  t/t2027-checkout-track.sh | 21 +++++++++++++++++++++\n>  2 files changed, 45 insertions(+), 5 deletions(-)\n>[snip]\n> diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh\n> index a397790df5..e9f8d8ec48 100755\n> --- a/t/t2027-checkout-track.sh\n> +++ b/t/t2027-checkout-track.sh\n> @@ -47,4 +47,25 @@ test_expect_success 'checkout --track -b overrides\n> autoSetupMerge=inherit' '\n>  \ttest_cmp_config refs/heads/main branch.b4.merge\n>  '\n>\n> +test_expect_success 'ambiguous tracking info' '\n> +\t# Set up a few remote repositories\n> +\tgit init --bare --initial-branch=trunk src1 &&\n> +\tgit init --bare --initial-branch=trunk src2 &&\n> +\tgit push src1 one:refs/heads/trunk &&\n> +\tgit push src2 two:refs/heads/trunk &&\n> +\n> +\tgit remote add -f src1 \"file://$PWD/src1\" &&\n> +\tgit remote add -f src2 \"file://$PWD/src2\" &&\n> +\n> +\t# DWIM\n> +\ttest_must_fail git checkout trunk 2>hint &&\n> +\ttest_grep \"hint: *git checkout --track\" hint &&\n> +\ttest_grep ! \"hint: *git switch --track\" hint &&\n> +\n> +\t{ git update-ref -d refs/heads/trunk || :; } &&\n\nI don’t understand what the purpose of this is after `git checkout` but\nbefore `git switch`. I can delete it and the test still passes. Is it\npost-test cleanup?\n\n> +\ttest_must_fail git switch trunk 2>hint &&\n> +\ttest_grep ! \"hint: *git checkout --track\" hint &&\n> +\ttest_grep \"hint: *git switch --track\" hint\n> +'\n\nMaybe just the positive greps are enough. I read these a few times\nbecause I thought the order was wrong, i.e. that `hint` was overwritten\nbefore it got tested. The regression that they test are unlikely and\nthese negative greps might not make immediate sense for future\nreaders. I dunno.\n\nI was about to suggest parameterizing the command but that makes it\nharder to grep for that part of the advice.\n\n    checkout_with_cmd () {\n            cmd=\"$1\"\n            test_must_fail git $cmd trunk 2>hint &&\n            test_grep --fixed-string \\\n                      \"hint:     git $cmd --track origin/<name>\" hint\n    }\n\n\n> +\n>  test_done\n> --\n> 2.53.0-rc2-135-gb1217c0133\n"},{"id":"534729","messageId":"xmqq343qg38n.fsf@gitster.g","threadId":"64874","inReplyTo":"fa7f1648-3cf6-4e5f-bee9-fb5e8700d01d@app.fastmail.com","subject":"Re: [PATCH 2/2] checkout: tell \"parse_remote_branch\" which command is calling it","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-27T21:22:32Z","receivedAt":"2026-01-27T21:22:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Kristoffer Haugsbakk\" <kristofferhaugsbakk@fastmail.com> writes:\n\n>> +\t# DWIM\n>> +\ttest_must_fail git checkout trunk 2>hint &&\n>> +\ttest_grep \"hint: *git checkout --track\" hint &&\n>> +\ttest_grep ! \"hint: *git switch --track\" hint &&\n>> +\n>> +\t{ git update-ref -d refs/heads/trunk || :; } &&\n>\n> I don’t understand what the purpose of this is after `git checkout` but\n> before `git switch`. I can delete it and the test still passes. Is it\n> post-test cleanup?\n\nJust in case \"git checkout trunk\" that was expected to fail still\ncreates the 'trunk' branch by a bug.  I do not want the failure of\nthe next \"git switch trunk\" to be due to \"hey, you already have a\nlocal branch of that name\", and make sure the failure is from \"you\nhave two remotes with trunk, and I cannot tell which one you meant\".\n\n>> +\ttest_must_fail git switch trunk 2>hint &&\n>> +\ttest_grep ! \"hint: *git checkout --track\" hint &&\n>> +\ttest_grep \"hint: *git switch --track\" hint\n>> +'\n>\n> Maybe just the positive greps are enough. I read these a few times\n> because I thought the order was wrong, i.e. that `hint` was overwritten\n> before it got tested. The regression that they test are unlikely and\n> these negative greps might not make immediate sense for future\n> readers. I dunno.\n\nPossibly.  These tests to expect concrete strings in the output are\nalready familiar with how these output strings are built, so they\nshould know that when 'git checkout --track' appears, it is very\nunlikely that 'git switch --track' would appear there, for example.\n\nThanks.\n"},{"id":"534827","messageId":"20260129190616.645471-1-gitster@pobox.com","threadId":"64874","inReplyTo":"20260127192936.904719-1-gitster@pobox.com","subject":"[PATCH v2 0/2] Improving advise messages from \"switch\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-29T19:06:14Z","receivedAt":"2026-01-29T19:06:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Simon Cheng noticed [*] that \"git switch <name>\", in an attempt to\ncreate a local branch <name> after a remote tracking branch of the\nsame name, fails correctly when multiple remotes have branches with\nthat name, but gives an advise message to disambiguate using \"git\ncheckout\".\n\nObviously, the user wanted to use \"git switch\", and \"git switch\" is\nequipped to do so, just like \"git checkout\" can, so it makes more\nsense to give hint based on \"git switch\".\n\nThe first patch of this series restructures the call chain of\ncheckout, switch, and restore commands a bit, so that a helper deep\nin the callchain can find out what end-user command it is working\nfor.  The second patch takes advantage of the new structure to tweak\nthe advise message in question, given from a helper function in a\nfairly deep part of the call chain.\n\n[Reference]\n\n * https://lore.kernel.org/git/CA+itcS0iyqNyzOP0cueLg7B3yadoEr_VWJ-QoL+YPFUPJiE2RQ@mail.gmail.com\n\nJunio C Hamano (2):\n  checkout: pass program-readable token to unified \"main\"\n  checkout: tell \"parse_remote_branch\" which command is calling it\n\n builtin/checkout.c        | 92 ++++++++++++++++++++++++++++-----------\n t/t2027-checkout-track.sh | 18 ++++++++\n 2 files changed, 85 insertions(+), 25 deletions(-)\n\n---\n\nThis second iteration simplifies the tests in the second patch\nsomewhat, and rewords its proposed log message, as suggested by\nKristoffer Haugsbakk.\n\n"},{"id":"534828","messageId":"20260129190616.645471-2-gitster@pobox.com","threadId":"64874","inReplyTo":"20260129190616.645471-1-gitster@pobox.com","subject":"[PATCH v2 1/2] checkout: pass program-readable token to unified \"main\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-29T19:06:15Z","receivedAt":"2026-01-29T19:06:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The \"git checkout\", \"git switch\", and \"git restore\" commands share a\nsingle implementation, checkout_main(), which switches error message\nit gives using the usage string passed by each of these three\nfront-ends.\n\nIn order to be able to tweak behaviours of the commands based on\nwhich one we are executing, invent an enum that denotes which one of\nthese three commands is currently executing, and pass that to\ncheckout_main() instead.  With this step, there is no externally\nvisible behaviour change, as this enum parameter is only used to\nchoose among the three usage strings.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * No change relative to v1\n\n builtin/checkout.c | 63 +++++++++++++++++++++++++++++++---------------\n 1 file changed, 43 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex f9453473fe..4f189fde48 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -43,22 +43,6 @@\n #include \"parallel-checkout.h\"\n #include \"add-interactive.h\"\n \n-static const char * const checkout_usage[] = {\n-\tN_(\"git checkout [<options>] <branch>\"),\n-\tN_(\"git checkout [<options>] [<branch>] -- <file>...\"),\n-\tNULL,\n-};\n-\n-static const char * const switch_branch_usage[] = {\n-\tN_(\"git switch [<options>] [<branch>]\"),\n-\tNULL,\n-};\n-\n-static const char * const restore_usage[] = {\n-\tN_(\"git restore [<options>] [--source=<branch>] <file>...\"),\n-\tNULL,\n-};\n-\n struct checkout_opts {\n \tint patch_mode;\n \tint patch_context;\n@@ -1293,6 +1277,13 @@ static void setup_new_branch_info_and_source_tree(\n \t}\n }\n \n+\n+enum checkout_command {\n+\tCHECKOUT_CHECKOUT = 1,\n+\tCHECKOUT_SWITCH = 2,\n+\tCHECKOUT_RESTORE = 3,\n+};\n+\n static char *parse_remote_branch(const char *arg,\n \t\t\t\t struct object_id *rev,\n \t\t\t\t int could_be_checkout_paths)\n@@ -1767,12 +1758,44 @@ static char cb_option = 'b';\n \n static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\t struct checkout_opts *opts, struct option *options,\n-\t\t\t const char * const usagestr[])\n+\t\t\t enum checkout_command which_command)\n {\n \tint parseopt_flags = 0;\n \tstruct branch_info new_branch_info = { 0 };\n \tint ret;\n \n+\tstatic const char * const checkout_usage[] = {\n+\t\tN_(\"git checkout [<options>] <branch>\"),\n+\t\tN_(\"git checkout [<options>] [<branch>] -- <file>...\"),\n+\t\tNULL,\n+\t};\n+\n+\tstatic const char * const switch_branch_usage[] = {\n+\t\tN_(\"git switch [<options>] [<branch>]\"),\n+\t\tNULL,\n+\t};\n+\n+\tstatic const char * const restore_usage[] = {\n+\t\tN_(\"git restore [<options>] [--source=<branch>] <file>...\"),\n+\t\tNULL,\n+\t};\n+\n+\tconst char * const *usagestr;\n+\n+\tswitch (which_command) {\n+\tcase CHECKOUT_CHECKOUT:\n+\t\tusagestr = checkout_usage;\n+\t\tbreak;\n+\tcase CHECKOUT_SWITCH:\n+\t\tusagestr = switch_branch_usage;\n+\t\tbreak;\n+\tcase CHECKOUT_RESTORE:\n+\t\tusagestr = restore_usage;\n+\t\tbreak;\n+\tdefault:\n+\t\tBUG(\"No such checkout variant %d\", which_command);\n+\t}\n+\n \topts->overwrite_ignore = 1;\n \topts->prefix = prefix;\n \topts->show_progress = -1;\n@@ -2032,7 +2055,7 @@ int cmd_checkout(int argc,\n \toptions = add_checkout_path_options(&opts, options);\n \n \treturn checkout_main(argc, argv, prefix, &opts, options,\n-\t\t\t     checkout_usage);\n+\t\t\t     CHECKOUT_CHECKOUT);\n }\n \n int cmd_switch(int argc,\n@@ -2071,7 +2094,7 @@ int cmd_switch(int argc,\n \tcb_option = 'c';\n \n \treturn checkout_main(argc, argv, prefix, &opts, options,\n-\t\t\t     switch_branch_usage);\n+\t\t\t     CHECKOUT_SWITCH);\n }\n \n int cmd_restore(int argc,\n@@ -2107,5 +2130,5 @@ int cmd_restore(int argc,\n \toptions = add_checkout_path_options(&opts, options);\n \n \treturn checkout_main(argc, argv, prefix, &opts, options,\n-\t\t\t     restore_usage);\n+\t\t\t     CHECKOUT_RESTORE);\n }\n-- \n2.53.0-rc2-135-gb1217c0133\n\n"},{"id":"534829","messageId":"20260129190616.645471-3-gitster@pobox.com","threadId":"64874","inReplyTo":"20260129190616.645471-1-gitster@pobox.com","subject":"[PATCH v2 2/2] checkout: tell \"parse_remote_branch\" which command is calling it","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-29T19:06:16Z","receivedAt":"2026-01-29T19:06:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"When \"git checkout <dwim>\" and \"git switch <dwim>\" need to error out\ndue to ambiguity of the branch name <dwim>, these two commands give\nan advise message with a sample command that tells the user how to\ndisambiguate from the parse_remote_branch() function.  The sample\ncommand hardcodes \"git checkout\", since this feature predates \"git\nswitch\" by a large margin.  To a user who said \"git switch <dwim>\"\nand got this message, it is confusing.\n\nPass the \"enum checkout_command\", which was invented in the previous\nstep for this exact purpose, down the call chain leading to\nparse_remote_branch() function to change the sample command shown to\nthe user in this advise message.\n\nAlso add a bit more test coverage for this \"fail to DWIM under\nambiguity\" that we lack, as well as the message we produce when we\nfail.\n\nReported-by: Simon Cheng <cyqsimon@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/checkout.c        | 29 ++++++++++++++++++++++++-----\n t/t2027-checkout-track.sh | 18 ++++++++++++++++++\n 2 files changed, 42 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 4f189fde48..17f31c30b2 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1286,7 +1286,8 @@ enum checkout_command {\n \n static char *parse_remote_branch(const char *arg,\n \t\t\t\t struct object_id *rev,\n-\t\t\t\t int could_be_checkout_paths)\n+\t\t\t\t int could_be_checkout_paths,\n+\t\t\t\t enum checkout_command which_command)\n {\n \tint num_matches = 0;\n \tchar *remote = unique_tracking_name(arg, rev, &num_matches);\n@@ -1299,14 +1300,30 @@ static char *parse_remote_branch(const char *arg,\n \n \tif (!remote && num_matches > 1) {\n \t    if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) {\n+\t\t    const char *cmdname;\n+\n+\t\t    switch (which_command) {\n+\t\t    case CHECKOUT_CHECKOUT:\n+\t\t\t    cmdname = \"checkout\";\n+\t\t\t    break;\n+\t\t    case CHECKOUT_SWITCH:\n+\t\t\t    cmdname = \"switch\";\n+\t\t\t    break;\n+\t\t    default:\n+\t\t\t    BUG(\"command <%d> should not reach parse_remote_branch\",\n+\t\t\t\twhich_command);\n+\t\t\t    break;\n+\t\t    }\n+\n \t\t    advise(_(\"If you meant to check out a remote tracking branch on, e.g. 'origin',\\n\"\n \t\t\t     \"you can do so by fully qualifying the name with the --track option:\\n\"\n \t\t\t     \"\\n\"\n-\t\t\t     \"    git checkout --track origin/<name>\\n\"\n+\t\t\t     \"    git %s --track origin/<name>\\n\"\n \t\t\t     \"\\n\"\n \t\t\t     \"If you'd like to always have checkouts of an ambiguous <name> prefer\\n\"\n \t\t\t     \"one remote, e.g. the 'origin' remote, consider setting\\n\"\n-\t\t\t     \"checkout.defaultRemote=origin in your config.\"));\n+\t\t\t     \"checkout.defaultRemote=origin in your config.\"),\n+\t\t\t   cmdname);\n \t    }\n \n \t    die(_(\"'%s' matched multiple (%d) remote tracking branches\"),\n@@ -1318,6 +1335,7 @@ static char *parse_remote_branch(const char *arg,\n \n static int parse_branchname_arg(int argc, const char **argv,\n \t\t\t\tint dwim_new_local_branch_ok,\n+\t\t\t\tenum checkout_command which_command,\n \t\t\t\tstruct branch_info *new_branch_info,\n \t\t\t\tstruct checkout_opts *opts,\n \t\t\t\tstruct object_id *rev)\n@@ -1427,7 +1445,8 @@ static int parse_branchname_arg(int argc, const char **argv,\n \n \t\tif (recover_with_dwim) {\n \t\t\tremote = parse_remote_branch(arg, rev,\n-\t\t\t\t\t\t     could_be_checkout_paths);\n+\t\t\t\t\t\t     could_be_checkout_paths,\n+\t\t\t\t\t\t     which_command);\n \t\t\tif (remote) {\n \t\t\t\t*new_branch = arg;\n \t\t\t\targ = remote;\n@@ -1916,7 +1935,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t\t\topts->dwim_new_local_branch &&\n \t\t\topts->track == BRANCH_TRACK_UNSPECIFIED &&\n \t\t\t!opts->new_branch;\n-\t\tint n = parse_branchname_arg(argc, argv, dwim_ok,\n+\t\tint n = parse_branchname_arg(argc, argv, dwim_ok, which_command,\n \t\t\t\t\t     &new_branch_info, opts, &rev);\n \t\targv += n;\n \t\targc -= n;\ndiff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh\nindex a397790df5..c01f1cd617 100755\n--- a/t/t2027-checkout-track.sh\n+++ b/t/t2027-checkout-track.sh\n@@ -47,4 +47,22 @@ test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n \ttest_cmp_config refs/heads/main branch.b4.merge\n '\n \n+test_expect_success 'ambiguous tracking info' '\n+\t# Set up a few remote repositories\n+\tgit init --bare --initial-branch=trunk src1 &&\n+\tgit init --bare --initial-branch=trunk src2 &&\n+\tgit push src1 one:refs/heads/trunk &&\n+\tgit push src2 two:refs/heads/trunk &&\n+\n+\tgit remote add -f src1 \"file://$PWD/src1\" &&\n+\tgit remote add -f src2 \"file://$PWD/src2\" &&\n+\n+\t# DWIM\n+\ttest_must_fail git checkout trunk 2>hint.checkout &&\n+\ttest_grep \"hint: *git checkout --track\" hint.checkout &&\n+\n+\ttest_must_fail git switch trunk 2>hint.switch &&\n+\ttest_grep \"hint: *git switch --track\" hint.switch\n+'\n+\n test_done\n-- \n2.53.0-rc2-135-gb1217c0133\n\n"},{"id":"535359","messageId":"aYYROwDxrHU-H3n_@pks.im","threadId":"64874","inReplyTo":"20260129190616.645471-2-gitster@pobox.com","subject":"Re: [PATCH v2 1/2] checkout: pass program-readable token to unified \"main\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-06T16:05:15Z","receivedAt":"2026-02-06T16:05:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 29, 2026 at 11:06:15AM -0800, Junio C Hamano wrote:\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index f9453473fe..4f189fde48 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n[snip]\n> +\tswitch (which_command) {\n> +\tcase CHECKOUT_CHECKOUT:\n> +\t\tusagestr = checkout_usage;\n> +\t\tbreak;\n> +\tcase CHECKOUT_SWITCH:\n> +\t\tusagestr = switch_branch_usage;\n> +\t\tbreak;\n> +\tcase CHECKOUT_RESTORE:\n> +\t\tusagestr = restore_usage;\n> +\t\tbreak;\n> +\tdefault:\n> +\t\tBUG(\"No such checkout variant %d\", which_command);\n> +\t}\n\nTiniest nit, really not worth addressing on its own: BUG messages\ntypically start with a lower-case letter.\n\nOther than that I like that we have less global constants with this\nchange.\n\nPatrick\n"},{"id":"535360","messageId":"aYYRRBdCrbWmy1Gj@pks.im","threadId":"64874","inReplyTo":"20260129190616.645471-3-gitster@pobox.com","subject":"Re: [PATCH v2 2/2] checkout: tell \"parse_remote_branch\" which command is calling it","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-06T16:05:24Z","receivedAt":"2026-02-06T16:05:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jan 29, 2026 at 11:06:16AM -0800, Junio C Hamano wrote:\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index 4f189fde48..17f31c30b2 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -1299,14 +1300,30 @@ static char *parse_remote_branch(const char *arg,\n>  \n>  \tif (!remote && num_matches > 1) {\n>  \t    if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) {\n> +\t\t    const char *cmdname;\n> +\n> +\t\t    switch (which_command) {\n> +\t\t    case CHECKOUT_CHECKOUT:\n> +\t\t\t    cmdname = \"checkout\";\n> +\t\t\t    break;\n> +\t\t    case CHECKOUT_SWITCH:\n> +\t\t\t    cmdname = \"switch\";\n> +\t\t\t    break;\n> +\t\t    default:\n> +\t\t\t    BUG(\"command <%d> should not reach parse_remote_branch\",\n> +\t\t\t\twhich_command);\n> +\t\t\t    break;\n> +\t\t    }\n\n`parse_remote_branch()` is only called by `parse_branchname_arg()`,\nwhich in turn is only called if `opts->accept_ref` is truish. And as\nthat value only gets set to 1 in git-checkout(1) and git-switch(1) we\nknow that we indeed don't have to care about git-restore(1).\n\n>  \t\t    advise(_(\"If you meant to check out a remote tracking branch on, e.g. 'origin',\\n\"\n>  \t\t\t     \"you can do so by fully qualifying the name with the --track option:\\n\"\n>  \t\t\t     \"\\n\"\n> -\t\t\t     \"    git checkout --track origin/<name>\\n\"\n> +\t\t\t     \"    git %s --track origin/<name>\\n\"\n>  \t\t\t     \"\\n\"\n>  \t\t\t     \"If you'd like to always have checkouts of an ambiguous <name> prefer\\n\"\n>  \t\t\t     \"one remote, e.g. the 'origin' remote, consider setting\\n\"\n> -\t\t\t     \"checkout.defaultRemote=origin in your config.\"));\n> +\t\t\t     \"checkout.defaultRemote=origin in your config.\"),\n> +\t\t\t   cmdname);\n>  \t    }\n>  \n>  \t    die(_(\"'%s' matched multiple (%d) remote tracking branches\"),\n\nYup, makes sense.\n\nOverall this patch series looks good to me. There's been the one\nmicronit in the preceding commit, but I really don't think it warrants\na new version.\n\nThanks!\n\nPatrick\n"},{"id":"536446","messageId":"xmqqtsvc1iij.fsf@gitster.g","threadId":"64874","inReplyTo":"aYYROwDxrHU-H3n_@pks.im","subject":"Re: [PATCH v2 1/2] checkout: pass program-readable token to unified \"main\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-19T22:21:56Z","receivedAt":"2026-02-19T22:21:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Thu, Jan 29, 2026 at 11:06:15AM -0800, Junio C Hamano wrote:\n>> diff --git a/builtin/checkout.c b/builtin/checkout.c\n>> index f9453473fe..4f189fde48 100644\n>> --- a/builtin/checkout.c\n>> +++ b/builtin/checkout.c\n> [snip]\n>> +\tswitch (which_command) {\n>> +\tcase CHECKOUT_CHECKOUT:\n>> +\t\tusagestr = checkout_usage;\n>> +\t\tbreak;\n>> +\tcase CHECKOUT_SWITCH:\n>> +\t\tusagestr = switch_branch_usage;\n>> +\t\tbreak;\n>> +\tcase CHECKOUT_RESTORE:\n>> +\t\tusagestr = restore_usage;\n>> +\t\tbreak;\n>> +\tdefault:\n>> +\t\tBUG(\"No such checkout variant %d\", which_command);\n>> +\t}\n>\n> Tiniest nit, really not worth addressing on its own: BUG messages\n> typically start with a lower-case letter.\n>\n> Other than that I like that we have less global constants with this\n> change.\n\nThanks.\n\nAs I locally amended the nit away while queuing, I guess I should\nmerge this down to 'next'.\n\n"}]}