switch: mention the --detach option when dying due to lack of a branch

Users who are accustomed to doing `git checkout <tag>` assume that `git switch <tag>` will do the same thing. Inform them of the --detach option so they aren't left wondering why `git switch` doesn't work but `git checkout` does. Signed-off-by: Alex Henrie <alexhenrie24@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Alex Henrie committed Feb 25, 2022 at 23:12 UTC 808213ba36ea72408cc45117a825ad9a714535ba
5 files changed +35 -11
Documentation/config/advice.txt
+3
@@ -85,6 +85,9 @@ advice.*::
85 linkgit:git-switch[1] or linkgit:git-checkout[1]
86 to move to the detach HEAD state, to instruct how to
87 create a local branch after the fact.
88 + suggestDetachingHead::
89 + Advice shown when linkgit:git-switch[1] refuses to detach HEAD
90 + without the explicit `--detach` option.
91 checkoutAmbiguousRemoteBranchName::
92 Advice shown when the argument to
93 linkgit:git-checkout[1] and linkgit:git-switch[1]
advice.c
+1
@@ -42,6 +42,7 @@ static struct {
42 [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName", 1 },
43 [ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge", 1 },
44 [ADVICE_DETACHED_HEAD] = { "detachedHead", 1 },
45 + [ADVICE_SUGGEST_DETACHING_HEAD] = { "suggestDetachingHead", 1 },
46 [ADVICE_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates", 1 },
47 [ADVICE_GRAFT_FILE_DEPRECATED] = { "graftFileDeprecated", 1 },
48 [ADVICE_IGNORED_HOOK] = { "ignoredHook", 1 },
advice.h
+1
@@ -20,6 +20,7 @@ struct string_list;
20 ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
21 ADVICE_COMMIT_BEFORE_MERGE,
22 ADVICE_DETACHED_HEAD,
23 + ADVICE_SUGGEST_DETACHING_HEAD,
24 ADVICE_FETCH_SHOW_FORCED_UPDATES,
25 ADVICE_GRAFT_FILE_DEPRECATED,
26 ADVICE_IGNORED_HOOK,
builtin/checkout.c
+19 -11
@@ -1397,23 +1397,31 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
1397 {
1398 struct object_id oid;
1399 char *to_free;
1400 + int code;
1401
1402 if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free, 0) == 1) {
1403 const char *ref = to_free;
1404
1405 if (skip_prefix(ref, "refs/tags/", &ref))
1405 - die(_("a branch is expected, got tag '%s'"), ref);
1406 - if (skip_prefix(ref, "refs/remotes/", &ref))
1407 - die(_("a branch is expected, got remote branch '%s'"), ref);
1408 - die(_("a branch is expected, got '%s'"), ref);
1406 + code = die_message(_("a branch is expected, got tag '%s'"), ref);
1407 + else if (skip_prefix(ref, "refs/remotes/", &ref))
1408 + code = die_message(_("a branch is expected, got remote branch '%s'"), ref);
1409 + else
1410 + code = die_message(_("a branch is expected, got '%s'"), ref);
1411 }
1410 - if (branch_info->commit)
1411 - die(_("a branch is expected, got commit '%s'"), branch_info->name);
1412 - /*
1413 - * This case should never happen because we already die() on
1414 - * non-commit, but just in case.
1415 - */
1416 - die(_("a branch is expected, got '%s'"), branch_info->name);
1412 + else if (branch_info->commit)
1413 + code = die_message(_("a branch is expected, got commit '%s'"), branch_info->name);
1414 + else
1415 + /*
1416 + * This case should never happen because we already die() on
1417 + * non-commit, but just in case.
1418 + */
1419 + code = die_message(_("a branch is expected, got '%s'"), branch_info->name);
1420 +
1421 + if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))
1422 + advise(_("If you want to detach HEAD at the commit, try again with the --detach option."));
1423 +
1424 + exit(code);
1425 }
1426
1427 static void die_if_some_operation_in_progress(void)
t/t2060-switch.sh
+11
@@ -32,6 +32,17 @@ test_expect_success 'switch and detach' '
32 test_must_fail git symbolic-ref HEAD
33 '
34
35 +test_expect_success 'suggestion to detach' '
36 + test_must_fail git switch main^{commit} 2>stderr &&
37 + grep "try again with the --detach option" stderr
38 +'
39 +
40 +test_expect_success 'suggestion to detach is suppressed with advice.suggestDetachingHead=false' '
41 + test_config advice.suggestDetachingHead false &&
42 + test_must_fail git switch main^{commit} 2>stderr &&
43 + ! grep "try again with the --detach option" stderr
44 +'
45 +
46 test_expect_success 'switch and detach current branch' '
47 test_when_finished git switch main &&
48 git switch main &&