git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH v2] switch: mention the --detach option when dying due to lack of a branch
@ 2022-02-24  6:47 Alex Henrie
  2022-02-24  8:53 ` Ævar Arnfjörð Bjarmason
  2022-02-24 19:02 ` Junio C Hamano
  0 siblings, 2 replies; 6+ messages in thread
From: Alex Henrie @ 2022-02-24  6:47 UTC (permalink / raw)
  To: git, gitster, avarab, pclouds; +Cc: Alex Henrie

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>
---
v2:
- Rephrase advice
- Print advice after error message
- Create a new config variable to suppress the advice
- Add tests
---
 Documentation/config/advice.txt |  3 +++
 advice.c                        |  1 +
 advice.h                        |  1 +
 builtin/checkout.c              | 30 +++++++++++++++++++-----------
 t/t2060-switch.sh               | 11 +++++++++++
 5 files changed, 35 insertions(+), 11 deletions(-)

diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index adee26fbbb..c40eb09cb7 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -85,6 +85,9 @@ advice.*::
 		linkgit:git-switch[1] or linkgit:git-checkout[1]
 		to move to the detach HEAD state, to instruct how to
 		create a local branch after the fact.
+	suggestDetachingHead::
+		Advice shown when linkgit:git-switch[1] refuses to detach HEAD
+		without the explicit `--detach` option.
 	checkoutAmbiguousRemoteBranchName::
 		Advice shown when the argument to
 		linkgit:git-checkout[1] and linkgit:git-switch[1]
diff --git a/advice.c b/advice.c
index e00d30254c..2e1fd48304 100644
--- a/advice.c
+++ b/advice.c
@@ -42,6 +42,7 @@ static struct {
 	[ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] 	= { "checkoutAmbiguousRemoteBranchName", 1 },
 	[ADVICE_COMMIT_BEFORE_MERGE]			= { "commitBeforeMerge", 1 },
 	[ADVICE_DETACHED_HEAD]				= { "detachedHead", 1 },
+	[ADVICE_SUGGEST_DETACHING_HEAD]			= { "suggestDetachingHead", 1 },
 	[ADVICE_FETCH_SHOW_FORCED_UPDATES]		= { "fetchShowForcedUpdates", 1 },
 	[ADVICE_GRAFT_FILE_DEPRECATED]			= { "graftFileDeprecated", 1 },
 	[ADVICE_IGNORED_HOOK]				= { "ignoredHook", 1 },
diff --git a/advice.h b/advice.h
index a7521d6087..a3957123a1 100644
--- a/advice.h
+++ b/advice.h
@@ -20,6 +20,7 @@ struct string_list;
 	ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
 	ADVICE_COMMIT_BEFORE_MERGE,
 	ADVICE_DETACHED_HEAD,
+	ADVICE_SUGGEST_DETACHING_HEAD,
 	ADVICE_FETCH_SHOW_FORCED_UPDATES,
 	ADVICE_GRAFT_FILE_DEPRECATED,
 	ADVICE_IGNORED_HOOK,
diff --git a/builtin/checkout.c b/builtin/checkout.c
index d9b31bbb6d..9244827ca0 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1397,23 +1397,31 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
 {
 	struct object_id oid;
 	char *to_free;
+	int code;
 
 	if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free, 0) == 1) {
 		const char *ref = to_free;
 
 		if (skip_prefix(ref, "refs/tags/", &ref))
-			die(_("a branch is expected, got tag '%s'"), ref);
-		if (skip_prefix(ref, "refs/remotes/", &ref))
-			die(_("a branch is expected, got remote branch '%s'"), ref);
-		die(_("a branch is expected, got '%s'"), ref);
+			code = die_message(_("a branch is expected, got tag '%s'"), ref);
+		else if (skip_prefix(ref, "refs/remotes/", &ref))
+			code = die_message(_("a branch is expected, got remote branch '%s'"), ref);
+		else
+			code = die_message(_("a branch is expected, got '%s'"), ref);
 	}
-	if (branch_info->commit)
-		die(_("a branch is expected, got commit '%s'"), branch_info->name);
-	/*
-	 * This case should never happen because we already die() on
-	 * non-commit, but just in case.
-	 */
-	die(_("a branch is expected, got '%s'"), branch_info->name);
+	else if (branch_info->commit)
+		code = die_message(_("a branch is expected, got commit '%s'"), branch_info->name);
+	else
+		/*
+		 * This case should never happen because we already die() on
+		 * non-commit, but just in case.
+		 */
+		code = die_message(_("a branch is expected, got '%s'"), branch_info->name);
+
+	if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))
+		advise(_("If you want to detach HEAD at the commit, try again with the --detach option."));
+
+	exit(code);
 }
 
 static void die_if_some_operation_in_progress(void)
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
index ebb961be29..f54691bac9 100755
--- a/t/t2060-switch.sh
+++ b/t/t2060-switch.sh
@@ -32,6 +32,17 @@ test_expect_success 'switch and detach' '
 	test_must_fail git symbolic-ref HEAD
 '
 
+test_expect_success 'suggestion to detach' '
+	test_must_fail git switch main^{commit} 2>stderr &&
+	test_i18ngrep "try again with the --detach option" stderr
+'
+
+test_expect_success 'suggestion to detach is suppressed with advice.suggestDetachingHead=false' '
+	test_config advice.suggestDetachingHead false &&
+	test_must_fail git switch main^{commit} 2>stderr &&
+	test_i18ngrep ! "try again with the --detach option" stderr
+'
+
 test_expect_success 'switch and detach current branch' '
 	test_when_finished git switch main &&
 	git switch main &&
-- 
2.35.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH v2] switch: mention the --detach option when dying due to lack of a branch
  2022-02-24  6:47 [PATCH v2] switch: mention the --detach option when dying due to lack of a branch Alex Henrie
@ 2022-02-24  8:53 ` Ævar Arnfjörð Bjarmason
  2022-02-24 19:02 ` Junio C Hamano
  1 sibling, 0 replies; 6+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-02-24  8:53 UTC (permalink / raw)
  To: Alex Henrie; +Cc: git, gitster, pclouds


On Wed, Feb 23 2022, Alex Henrie wrote:

> 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.

Thanks, this looks good to me! FWIW while testing this v2 I came up with
this on top, which you may or may not want (some unrelated changes):

1. We had a to_free but didn't free it, now we do. Didn't matter with
   SANITIZE=leak, but FWIW valgrind with "valgrind --leak-check=full
   --show-leak-kinds=all" is marginally happier

2. Maybe s/code = /return / with a helper is easier to read, maybe not.

3. That #2 makes the code/advice wrapper simpler (also with the to_free)

4. Used test_cmp in the test to check the actual output we got, and added
   a missing test for the "tag" case.

   In any case s/test_i18ngrep/grep/ is the right thing nowadays for new code.

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 9244827ca02..9e4d03343fa 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1393,34 +1393,39 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
 	return status;
 }
 
-static void die_expecting_a_branch(const struct branch_info *branch_info)
+static int die_expecting_a_branch_message(const struct branch_info *branch_info, char **to_free)
 {
 	struct object_id oid;
-	char *to_free;
-	int code;
 
-	if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free, 0) == 1) {
-		const char *ref = to_free;
+	if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, to_free, 0) == 1) {
+		const char *ref = *to_free;
 
 		if (skip_prefix(ref, "refs/tags/", &ref))
-			code = die_message(_("a branch is expected, got tag '%s'"), ref);
+			return die_message(_("a branch is expected, got tag '%s'"), ref);
 		else if (skip_prefix(ref, "refs/remotes/", &ref))
-			code = die_message(_("a branch is expected, got remote branch '%s'"), ref);
+			return die_message(_("a branch is expected, got remote branch '%s'"), ref);
 		else
-			code = die_message(_("a branch is expected, got '%s'"), ref);
+			return die_message(_("a branch is expected, got '%s'"), ref);
 	}
 	else if (branch_info->commit)
-		code = die_message(_("a branch is expected, got commit '%s'"), branch_info->name);
+		return die_message(_("a branch is expected, got commit '%s'"), branch_info->name);
 	else
 		/*
 		 * This case should never happen because we already die() on
 		 * non-commit, but just in case.
 		 */
-		code = die_message(_("a branch is expected, got '%s'"), branch_info->name);
+		return die_message(_("a branch is expected, got '%s'"), branch_info->name);
+}
+
+static void die_expecting_a_branch(const struct branch_info *branch_info)
+{
+	char *to_free = NULL;
+	int code = die_expecting_a_branch_message(branch_info, &to_free);
 
 	if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))
 		advise(_("If you want to detach HEAD at the commit, try again with the --detach option."));
 
+	free(to_free);
 	exit(code);
 }
 
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
index f54691bac9f..0708359ee24 100755
--- a/t/t2060-switch.sh
+++ b/t/t2060-switch.sh
@@ -32,15 +32,31 @@ test_expect_success 'switch and detach' '
 	test_must_fail git symbolic-ref HEAD
 '
 
-test_expect_success 'suggestion to detach' '
-	test_must_fail git switch main^{commit} 2>stderr &&
-	test_i18ngrep "try again with the --detach option" stderr
+test_expect_success 'suggestion to detach commit' '
+	test_must_fail git switch main^{commit} 2>actual &&
+	cat >expect <<-\EOF &&
+	fatal: a branch is expected, got commit '\''main^{commit}'\''
+	hint: If you want to detach HEAD at the commit, try again with the --detach option.
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'suggestion to detach tag' '
+	test_must_fail git switch first 2>actual &&
+	cat >expect <<-\EOF &&
+	fatal: a branch is expected, got tag '\''first'\''
+	hint: If you want to detach HEAD at the commit, try again with the --detach option.
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'suggestion to detach is suppressed with advice.suggestDetachingHead=false' '
 	test_config advice.suggestDetachingHead false &&
-	test_must_fail git switch main^{commit} 2>stderr &&
-	test_i18ngrep ! "try again with the --detach option" stderr
+	test_must_fail git switch main^{commit} 2>actual &&
+	cat >expect <<-\EOF &&
+	fatal: a branch is expected, got commit '\''main^{commit}'\''
+	EOF
+	test_cmp expect actual
 '
 
 test_expect_success 'switch and detach current branch' '

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH v2] switch: mention the --detach option when dying due to lack of a branch
  2022-02-24  6:47 [PATCH v2] switch: mention the --detach option when dying due to lack of a branch Alex Henrie
  2022-02-24  8:53 ` Ævar Arnfjörð Bjarmason
@ 2022-02-24 19:02 ` Junio C Hamano
  2022-02-25 11:57   ` Ævar Arnfjörð Bjarmason
  1 sibling, 1 reply; 6+ messages in thread
From: Junio C Hamano @ 2022-02-24 19:02 UTC (permalink / raw)
  To: Alex Henrie; +Cc: git, avarab, pclouds

Alex Henrie <alexhenrie24@gmail.com> writes:

> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index d9b31bbb6d..9244827ca0 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -1397,23 +1397,31 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
>  {
>  	struct object_id oid;
>  	char *to_free;
> +	int code;
>  
>  	if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free, 0) == 1) {
>  		const char *ref = to_free;
>  
>  		if (skip_prefix(ref, "refs/tags/", &ref))
> -			die(_("a branch is expected, got tag '%s'"), ref);
> -		if (skip_prefix(ref, "refs/remotes/", &ref))
> -			die(_("a branch is expected, got remote branch '%s'"), ref);
> -		die(_("a branch is expected, got '%s'"), ref);
> +			code = die_message(_("a branch is expected, got tag '%s'"), ref);
> +		else if (skip_prefix(ref, "refs/remotes/", &ref))
> +			code = die_message(_("a branch is expected, got remote branch '%s'"), ref);
> +		else
> +			code = die_message(_("a branch is expected, got '%s'"), ref);
>  	}
> -	if (branch_info->commit)
> -		die(_("a branch is expected, got commit '%s'"), branch_info->name);

In the original code, when dwim_ref() said there is a unique hit, we
died with varying messages.  So it was OK to have this check not as
a part of if/elseif/... cascade.

> -	/*
> -	 * This case should never happen because we already die() on
> -	 * non-commit, but just in case.
> -	 */
> -	die(_("a branch is expected, got '%s'"), branch_info->name);
> +	else if (branch_info->commit)

But now, new code only sets code without dying, so we avoid
overwriting the code (and calling die_message() twice) by turning it
"else if".  OK.

> +		code = die_message(_("a branch is expected, got commit '%s'"), branch_info->name);
> +	else
> +		/*
> +		 * This case should never happen because we already die() on
> +		 * non-commit, but just in case.
> +		 */
> +		code = die_message(_("a branch is expected, got '%s'"), branch_info->name);

OK.  So "code" gets assigned exactly once in the above
if/elseif/... cascade.  Not defining the variable with
initialization at the beginning of this function is correct.

> +	if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))
> +		advise(_("If you want to detach HEAD at the commit, try again with the --detach option."));
> +
> +	exit(code);
>  }

OK.

> diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
> index ebb961be29..f54691bac9 100755
> --- a/t/t2060-switch.sh
> +++ b/t/t2060-switch.sh
> @@ -32,6 +32,17 @@ test_expect_success 'switch and detach' '
>  	test_must_fail git symbolic-ref HEAD
>  '
>  
> +test_expect_success 'suggestion to detach' '
> +	test_must_fail git switch main^{commit} 2>stderr &&
> +	test_i18ngrep "try again with the --detach option" stderr
> +'
> +
> +test_expect_success 'suggestion to detach is suppressed with advice.suggestDetachingHead=false' '
> +	test_config advice.suggestDetachingHead false &&
> +	test_must_fail git switch main^{commit} 2>stderr &&
> +	test_i18ngrep ! "try again with the --detach option" stderr
> +'

OK, we try to be consistent with other tests in the file, and leave
s/test_i18n// to a file-wide clean-up outside the topic.

>  test_expect_success 'switch and detach current branch' '
>  	test_when_finished git switch main &&
>  	git switch main &&

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v2] switch: mention the --detach option when dying due to lack of a branch
  2022-02-24 19:02 ` Junio C Hamano
@ 2022-02-25 11:57   ` Ævar Arnfjörð Bjarmason
  2022-02-25 17:20     ` Alex Henrie
  2022-02-25 17:26     ` Junio C Hamano
  0 siblings, 2 replies; 6+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-02-25 11:57 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Alex Henrie, git, pclouds


On Thu, Feb 24 2022, Junio C Hamano wrote:

> Alex Henrie <alexhenrie24@gmail.com> writes:
>> diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
>> index ebb961be29..f54691bac9 100755
>> --- a/t/t2060-switch.sh
>> +++ b/t/t2060-switch.sh
>> @@ -32,6 +32,17 @@ test_expect_success 'switch and detach' '
>>  	test_must_fail git symbolic-ref HEAD
>>  '
>>  
>> +test_expect_success 'suggestion to detach' '
>> +	test_must_fail git switch main^{commit} 2>stderr &&
>> +	test_i18ngrep "try again with the --detach option" stderr
>> +'
>> +
>> +test_expect_success 'suggestion to detach is suppressed with advice.suggestDetachingHead=false' '
>> +	test_config advice.suggestDetachingHead false &&
>> +	test_must_fail git switch main^{commit} 2>stderr &&
>> +	test_i18ngrep ! "try again with the --detach option" stderr
>> +'
>
> OK, we try to be consistent with other tests in the file, and leave
> s/test_i18n// to a file-wide clean-up outside the topic.

FWIW that's not the case here. This is the first use of test_i18ngrep in
this file.

But better to use test_cmp as noted in
<220224.86sfs8abj6.gmgdl@evledraar.gmail.com> in the sid-thread.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v2] switch: mention the --detach option when dying due to lack of a branch
  2022-02-25 11:57   ` Ævar Arnfjörð Bjarmason
@ 2022-02-25 17:20     ` Alex Henrie
  2022-02-25 17:26     ` Junio C Hamano
  1 sibling, 0 replies; 6+ messages in thread
From: Alex Henrie @ 2022-02-25 17:20 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Junio C Hamano, Git mailing list,
	Nguyễn Thái Ngọc Duy

On Fri, Feb 25, 2022 at 4:57 AM Ævar Arnfjörð Bjarmason
<avarab@gmail.com> wrote:
>
> On Thu, Feb 24 2022, Junio C Hamano wrote:
>
> > Alex Henrie <alexhenrie24@gmail.com> writes:
> >> diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
> >> index ebb961be29..f54691bac9 100755
> >> --- a/t/t2060-switch.sh
> >> +++ b/t/t2060-switch.sh
> >> @@ -32,6 +32,17 @@ test_expect_success 'switch and detach' '
> >>      test_must_fail git symbolic-ref HEAD
> >>  '
> >>
> >> +test_expect_success 'suggestion to detach' '
> >> +    test_must_fail git switch main^{commit} 2>stderr &&
> >> +    test_i18ngrep "try again with the --detach option" stderr
> >> +'
> >> +
> >> +test_expect_success 'suggestion to detach is suppressed with advice.suggestDetachingHead=false' '
> >> +    test_config advice.suggestDetachingHead false &&
> >> +    test_must_fail git switch main^{commit} 2>stderr &&
> >> +    test_i18ngrep ! "try again with the --detach option" stderr
> >> +'
> >
> > OK, we try to be consistent with other tests in the file, and leave
> > s/test_i18n// to a file-wide clean-up outside the topic.
>
> FWIW that's not the case here. This is the first use of test_i18ngrep in
> this file.
>
> But better to use test_cmp as noted in
> <220224.86sfs8abj6.gmgdl@evledraar.gmail.com> in the sid-thread.

Why is test_cmp preferable to grep in tests like this?

-Alex

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v2] switch: mention the --detach option when dying due to lack of a branch
  2022-02-25 11:57   ` Ævar Arnfjörð Bjarmason
  2022-02-25 17:20     ` Alex Henrie
@ 2022-02-25 17:26     ` Junio C Hamano
  1 sibling, 0 replies; 6+ messages in thread
From: Junio C Hamano @ 2022-02-25 17:26 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: Alex Henrie, git, pclouds

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

>>> +	test_i18ngrep ! "try again with the --detach option" stderr
>>> +'
>>
>> OK, we try to be consistent with other tests in the file, and leave
>> s/test_i18n// to a file-wide clean-up outside the topic.
>
> FWIW that's not the case here. This is the first use of test_i18ngrep in
> this file.

Oh, thanks for pointing it out---I remembered that somebody gave a
similar suggestion and blindly assumed that there are other
instances already.  If so, picking, between grep and test_cmp,
whichever requires the least amount of boilerplate code is fine by
me.

Thanks.

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2022-02-25 17:26 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-02-24  6:47 [PATCH v2] switch: mention the --detach option when dying due to lack of a branch Alex Henrie
2022-02-24  8:53 ` Ævar Arnfjörð Bjarmason
2022-02-24 19:02 ` Junio C Hamano
2022-02-25 11:57   ` Ævar Arnfjörð Bjarmason
2022-02-25 17:20     ` Alex Henrie
2022-02-25 17:26     ` Junio C Hamano

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).