{"thread":{"id":"57479","subject":"[PATCH v3] switch: mention the --detach option when dying due to lack of a branch","startedAt":"2022-02-26T06:15:31Z","lastAt":"2022-02-26T06:15:31Z","messageCount":1,"participants":["Alex Henrie"],"isPatch":true,"patchVersion":3,"patchTotal":null},"messages":[{"id":"449658","messageId":"20220226061213.1590341-1-alexhenrie24@gmail.com","threadId":"57479","inReplyTo":null,"subject":"[PATCH v3] switch: mention the --detach option when dying due to lack of a branch","fromName":"Alex Henrie","fromEmail":"alexhenrie24@gmail.com","sentAt":"2022-02-26T06:12:13Z","receivedAt":"2022-02-26T06:15:31Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"Users who are accustomed to doing `git checkout <tag>` assume that\n`git switch <tag>` will do the same thing. Inform them of the --detach\noption so they aren't left wondering why `git switch` doesn't work but\n`git checkout` does.\n\nSigned-off-by: Alex Henrie <alexhenrie24@gmail.com>\n---\nv3: Use plain grep instead of test_i18ngrep\n---\n Documentation/config/advice.txt |  3 +++\n advice.c                        |  1 +\n advice.h                        |  1 +\n builtin/checkout.c              | 30 +++++++++++++++++++-----------\n t/t2060-switch.sh               | 11 +++++++++++\n 5 files changed, 35 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\nindex adee26fbbb..c40eb09cb7 100644\n--- a/Documentation/config/advice.txt\n+++ b/Documentation/config/advice.txt\n@@ -85,6 +85,9 @@ advice.*::\n \t\tlinkgit:git-switch[1] or linkgit:git-checkout[1]\n \t\tto move to the detach HEAD state, to instruct how to\n \t\tcreate a local branch after the fact.\n+\tsuggestDetachingHead::\n+\t\tAdvice shown when linkgit:git-switch[1] refuses to detach HEAD\n+\t\twithout the explicit `--detach` option.\n \tcheckoutAmbiguousRemoteBranchName::\n \t\tAdvice shown when the argument to\n \t\tlinkgit:git-checkout[1] and linkgit:git-switch[1]\ndiff --git a/advice.c b/advice.c\nindex e00d30254c..2e1fd48304 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -42,6 +42,7 @@ static struct {\n \t[ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] \t= { \"checkoutAmbiguousRemoteBranchName\", 1 },\n \t[ADVICE_COMMIT_BEFORE_MERGE]\t\t\t= { \"commitBeforeMerge\", 1 },\n \t[ADVICE_DETACHED_HEAD]\t\t\t\t= { \"detachedHead\", 1 },\n+\t[ADVICE_SUGGEST_DETACHING_HEAD]\t\t\t= { \"suggestDetachingHead\", 1 },\n \t[ADVICE_FETCH_SHOW_FORCED_UPDATES]\t\t= { \"fetchShowForcedUpdates\", 1 },\n \t[ADVICE_GRAFT_FILE_DEPRECATED]\t\t\t= { \"graftFileDeprecated\", 1 },\n \t[ADVICE_IGNORED_HOOK]\t\t\t\t= { \"ignoredHook\", 1 },\ndiff --git a/advice.h b/advice.h\nindex a7521d6087..a3957123a1 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -20,6 +20,7 @@ struct string_list;\n \tADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,\n \tADVICE_COMMIT_BEFORE_MERGE,\n \tADVICE_DETACHED_HEAD,\n+\tADVICE_SUGGEST_DETACHING_HEAD,\n \tADVICE_FETCH_SHOW_FORCED_UPDATES,\n \tADVICE_GRAFT_FILE_DEPRECATED,\n \tADVICE_IGNORED_HOOK,\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex d9b31bbb6d..9244827ca0 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1397,23 +1397,31 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)\n {\n \tstruct object_id oid;\n \tchar *to_free;\n+\tint code;\n \n \tif (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free, 0) == 1) {\n \t\tconst char *ref = to_free;\n \n \t\tif (skip_prefix(ref, \"refs/tags/\", &ref))\n-\t\t\tdie(_(\"a branch is expected, got tag '%s'\"), ref);\n-\t\tif (skip_prefix(ref, \"refs/remotes/\", &ref))\n-\t\t\tdie(_(\"a branch is expected, got remote branch '%s'\"), ref);\n-\t\tdie(_(\"a branch is expected, got '%s'\"), ref);\n+\t\t\tcode = die_message(_(\"a branch is expected, got tag '%s'\"), ref);\n+\t\telse if (skip_prefix(ref, \"refs/remotes/\", &ref))\n+\t\t\tcode = die_message(_(\"a branch is expected, got remote branch '%s'\"), ref);\n+\t\telse\n+\t\t\tcode = die_message(_(\"a branch is expected, got '%s'\"), ref);\n \t}\n-\tif (branch_info->commit)\n-\t\tdie(_(\"a branch is expected, got commit '%s'\"), branch_info->name);\n-\t/*\n-\t * This case should never happen because we already die() on\n-\t * non-commit, but just in case.\n-\t */\n-\tdie(_(\"a branch is expected, got '%s'\"), branch_info->name);\n+\telse if (branch_info->commit)\n+\t\tcode = die_message(_(\"a branch is expected, got commit '%s'\"), branch_info->name);\n+\telse\n+\t\t/*\n+\t\t * This case should never happen because we already die() on\n+\t\t * non-commit, but just in case.\n+\t\t */\n+\t\tcode = die_message(_(\"a branch is expected, got '%s'\"), branch_info->name);\n+\n+\tif (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))\n+\t\tadvise(_(\"If you want to detach HEAD at the commit, try again with the --detach option.\"));\n+\n+\texit(code);\n }\n \n static void die_if_some_operation_in_progress(void)\ndiff --git a/t/t2060-switch.sh b/t/t2060-switch.sh\nindex ebb961be29..5a7caf958c 100755\n--- a/t/t2060-switch.sh\n+++ b/t/t2060-switch.sh\n@@ -32,6 +32,17 @@ test_expect_success 'switch and detach' '\n \ttest_must_fail git symbolic-ref HEAD\n '\n \n+test_expect_success 'suggestion to detach' '\n+\ttest_must_fail git switch main^{commit} 2>stderr &&\n+\tgrep \"try again with the --detach option\" stderr\n+'\n+\n+test_expect_success 'suggestion to detach is suppressed with advice.suggestDetachingHead=false' '\n+\ttest_config advice.suggestDetachingHead false &&\n+\ttest_must_fail git switch main^{commit} 2>stderr &&\n+\t! grep \"try again with the --detach option\" stderr\n+'\n+\n test_expect_success 'switch and detach current branch' '\n \ttest_when_finished git switch main &&\n \tgit switch main &&\n-- \n2.35.1\n\n"}]}