Git development
 help / color / mirror / Atom feed
* [RFC PATCH] switch: provide configurable detach
@ 2026-04-04 14:28 Thibaud CANALE
  2026-04-04 15:23 ` Pablo
                   ` (3 more replies)
  0 siblings, 4 replies; 9+ messages in thread
From: Thibaud CANALE @ 2026-04-04 14:28 UTC (permalink / raw)
  To: git

[-- Attachment #1: Type: text/plain, Size: 5056 bytes --]

Its purpose is to provide for git-switch(1) same detach behavior on
commit it than git-checkout(1) through configuration option
`checkout.switchDetach`.

Signed-off-by: Thibaud CANALE <thican@thican.net>
---
 Documentation/config/checkout.adoc |  4 ++++
 builtin/checkout.c                 | 18 +++++++++++++++---
 t/t2060-switch.sh                  | 27 +++++++++++++++++++++++++++
 3 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/Documentation/config/checkout.adoc b/Documentation/config/checkout.adoc
index e35d2129..3a6c1049 100644
--- a/Documentation/config/checkout.adoc
+++ b/Documentation/config/checkout.adoc
@@ -22,6 +22,10 @@ commands or functionality in the future.
 	option in `git checkout` and `git switch`. See
 	linkgit:git-switch[1] and linkgit:git-checkout[1].
 
+`checkout.switchDetach`::
+	Provides for linkgit:git-switch[1] the same detach behavior on commit id
+	than linkgit:git-checkout[1] without the explicit --detach option.
+
 `checkout.workers`::
 	The number of parallel workers to use when updating the working tree.
 	The default is one, i.e. sequential execution. If set to a value less
diff --git a/builtin/checkout.c b/builtin/checkout.c
index e031e618..1da1062e 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -53,6 +53,7 @@ struct checkout_opts {
 	int force;
 	int force_detach;
 	int implicit_detach;
+	int switch_detach;  // For checkout.switchDetach configuration
 	int writeout_stage;
 	int overwrite_ignore;
 	int ignore_skipworktree;
@@ -1005,7 +1006,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 	else
 		strbuf_insertstr(&msg, 0, reflog_msg);
 
-	if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) {
+	if (!strcmp(new_branch_info->name, "HEAD") &&
+	    !new_branch_info->path &&
+	    !opts->force_detach &&
+	    !opts->switch_detach) {
 		/* Nothing to do. */
 	} else if (opts->force_detach || !new_branch_info->path) {	/* No longer on any branch. */
 		refs_update_ref(get_main_ref_store(the_repository), msg.buf,
@@ -1014,7 +1018,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 				REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
 		if (!opts->quiet) {
 			if (old_branch_info->path &&
-			    advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)
+			    advice_enabled(ADVICE_DETACHED_HEAD) &&
+			    !opts->force_detach &&
+			    !opts->switch_detach)
 				detach_advice(new_branch_info->name);
 			describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
 		}
@@ -1541,8 +1547,11 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
 		 */
 		code = die_message(_("a branch is expected, got '%s'"), branch_info->name);
 
-	if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))
+	if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD)) {
 		advise(_("If you want to detach HEAD at the commit, try again with the --detach option."));
+		advise(_("Also you can detach by default by setting the config variable "
+		         "checkout.switchDetach to true."));
+	}
 
 	exit(code);
 }
@@ -1660,6 +1669,7 @@ static int checkout_branch(struct checkout_opts *opts,
 
 	if (!opts->implicit_detach &&
 	    !opts->force_detach &&
+	    !opts->switch_detach &&
 	    !opts->new_branch &&
 	    !opts->new_branch_force &&
 	    new_branch_info->name &&
@@ -2119,6 +2129,8 @@ int cmd_switch(int argc,
 	options = add_common_options(&opts, options);
 	options = add_common_switch_branch_options(&opts, options);
 
+	repo_config_get_bool(the_repository, "checkout.switchDetach", &opts.switch_detach);
+
 	cb_option = 'c';
 
 	return checkout_main(argc, argv, prefix, &opts, options,
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
index c91c4db9..3435ae98 100755
--- a/t/t2060-switch.sh
+++ b/t/t2060-switch.sh
@@ -177,4 +177,31 @@ test_expect_success 'switch back when temporarily detached and checked out elsew
 	git -C wt2 switch --ignore-other-worktrees shared
 '
 
+test_expect_success 'switch with configured detach behavior from configuration ' '
+	test_when_finished "
+		test_config checkout.switchDetach false;
+		git switch main
+	" &&
+	test_config checkout.switchDetach true &&
+	git switch main &&
+	git symbolic-ref HEAD &&
+	git switch main~ &&
+	test_must_fail git symbolic-ref HEAD &&
+	git switch - &&
+	git symbolic-ref HEAD &&
+	git switch - &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch without configured detach behavior from configuration ' '
+	test_when_finished "
+		test_config checkout.switchDetach false;
+		git switch main
+	" &&
+	test_config checkout.switchDetach false &&
+	git switch main &&
+	git symbolic-ref HEAD &&
+	test_must_fail git switch main~
+'
+
 test_done

base-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0
-- 
Thibaud CANALE
thican [at] thican [dot] net
https://thican.net/
GPG: rsa4096 2013-10-14 485EF628CB85CDD4CB7CFF0D52F5127650733A18

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 870 bytes --]

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

* Re: [RFC PATCH] switch: provide configurable detach
  2026-04-04 14:28 [RFC PATCH] switch: provide configurable detach Thibaud CANALE
@ 2026-04-04 15:23 ` Pablo
  2026-04-04 21:10   ` Thibaud CANALE
  2026-04-04 16:58 ` D. Ben Knoble
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 9+ messages in thread
From: Pablo @ 2026-04-04 15:23 UTC (permalink / raw)
  To: Thibaud CANALE; +Cc: git

El sáb, 4 abr 2026 a las 16:36, Thibaud CANALE (<thican@thican.net>) escribió:

Hi Thibaud,
>
> Its purpose is to provide for git-switch(1) same detach behavior on
> commit it than git-checkout(1) through configuration option
> `checkout.switchDetach`.

The commit needs some work, following Documentation/SubmittingPatches:
The subject looks fine but the body, you should write imperatively, something
like "Allow git-switch to..." instead of "Its purpose is to...".

>  same detach behavior on commit it than

This is confusing.

Also the body should explain WHY this change would be wanted.

>
> Signed-off-by: Thibaud CANALE <thican@thican.net>
> ---
>  Documentation/config/checkout.adoc |  4 ++++
>  builtin/checkout.c                 | 18 +++++++++++++++---
>  t/t2060-switch.sh                  | 27 +++++++++++++++++++++++++++
>  3 files changed, 46 insertions(+), 3 deletions(-)
>
> diff --git a/Documentation/config/checkout.adoc b/Documentation/config/checkout.adoc
> index e35d2129..3a6c1049 100644
> --- a/Documentation/config/checkout.adoc
> +++ b/Documentation/config/checkout.adoc
> @@ -22,6 +22,10 @@ commands or functionality in the future.
>         option in `git checkout` and `git switch`. See
>         linkgit:git-switch[1] and linkgit:git-checkout[1].
>
> +`checkout.switchDetach`::
> +       Provides for linkgit:git-switch[1] the same detach behavior on commit id
> +       than linkgit:git-checkout[1] without the explicit --detach option.
> +

I think it would be better to describe how it would be if set rather
than directly
the what it does. e.g.:
"If set, detaches HEAD when..."

>  `checkout.workers`::
>         The number of parallel workers to use when updating the working tree.
>         The default is one, i.e. sequential execution. If set to a value less
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index e031e618..1da1062e 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -53,6 +53,7 @@ struct checkout_opts {
>         int force;
>         int force_detach;
>         int implicit_detach;
> +       int switch_detach;  // For checkout.switchDetach configuration

// coments are not used in Git, if you need to add comments make them with
/* */.
Nit I don't think a comment here is necessary, it is very self
explanatory already.

>         int writeout_stage;
>         int overwrite_ignore;
>         int ignore_skipworktree;
> @@ -1005,7 +1006,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
>         else
>                 strbuf_insertstr(&msg, 0, reflog_msg);
>
> -       if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) {
> +       if (!strcmp(new_branch_info->name, "HEAD") &&
> +           !new_branch_info->path &&
> +           !opts->force_detach &&
> +           !opts->switch_detach) {

seems that you're checking force_detach and switch_detach together
every time,they look very similar,
sure  you need them separated?

>                 /* Nothing to do. */
>         } else if (opts->force_detach || !new_branch_info->path) {      /* No longer on any branch. */
>                 refs_update_ref(get_main_ref_store(the_repository), msg.buf,
> @@ -1014,7 +1018,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
>                                 REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
>                 if (!opts->quiet) {
>                         if (old_branch_info->path &&
> -                           advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)
> +                           advice_enabled(ADVICE_DETACHED_HEAD) &&
> +                           !opts->force_detach &&
> +                           !opts->switch_detach)
>                                 detach_advice(new_branch_info->name);
>                         describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
>                 }
> @@ -1541,8 +1547,11 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
>                  */
>                 code = die_message(_("a branch is expected, got '%s'"), branch_info->name);
>
> -       if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))
> +       if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD)) {
>                 advise(_("If you want to detach HEAD at the commit, try again with the --detach option."));
> +               advise(_("Also you can detach by default by setting the config variable "
> +                        "checkout.switchDetach to true."));
> +       }

Nit "Also, you can..." or "You can also..."

>
>         exit(code);
>  }
> @@ -1660,6 +1669,7 @@ static int checkout_branch(struct checkout_opts *opts,
>
>         if (!opts->implicit_detach &&
>             !opts->force_detach &&
> +           !opts->switch_detach &&

Again force_detach + switch_detach

>             !opts->new_branch &&
>             !opts->new_branch_force &&
>             new_branch_info->name &&
> @@ -2119,6 +2129,8 @@ int cmd_switch(int argc,
>         options = add_common_options(&opts, options);
>         options = add_common_switch_branch_options(&opts, options);
>
> +       repo_config_get_bool(the_repository, "checkout.switchDetach", &opts.switch_detach);
> +
>         cb_option = 'c';
>
>         return checkout_main(argc, argv, prefix, &opts, options,
> diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
> index c91c4db9..3435ae98 100755
> --- a/t/t2060-switch.sh
> +++ b/t/t2060-switch.sh
> @@ -177,4 +177,31 @@ test_expect_success 'switch back when temporarily detached and checked out elsew
>         git -C wt2 switch --ignore-other-worktrees shared
>  '
>
> +test_expect_success 'switch with configured detach behavior from configuration ' '

Trailing space here at the end, also the title is kinda confusing with the
configured + configuration, what about:
"switch detaches on commit when switchDetach is set"

> +       test_when_finished "
> +               test_config checkout.switchDetach false;
> +               git switch main
> +       " &&
> +       test_config checkout.switchDetach true &&
> +       git switch main &&
> +       git symbolic-ref HEAD &&
> +       git switch main~ &&
> +       test_must_fail git symbolic-ref HEAD &&
> +       git switch - &&
> +       git symbolic-ref HEAD &&
> +       git switch - &&
> +       test_must_fail git symbolic-ref HEAD
> +'
> +
> +test_expect_success 'switch without configured detach behavior from configuration ' '

Trailing space.

> +       test_when_finished "
> +               test_config checkout.switchDetach false;

test_config already has test_when_finished
I would git config --unset.

> +               git switch main
> +       " &&
> +       test_config checkout.switchDetach false &&

This is already false.

> +       git switch main &&
> +       git symbolic-ref HEAD &&
> +       test_must_fail git switch main~
> +'
> +
>  test_done
>
> base-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0
> --
> Thibaud CANALE
> thican [at] thican [dot] net
> https://thican.net/
> GPG: rsa4096 2013-10-14 485EF628CB85CDD4CB7CFF0D52F5127650733A18

Overall seems reasonable, it might be a design choice having to
explicitly use --detach, force_detach it's pretty clear about it but
I dunno.

For now:
I would rewrite the commit.
Make sure that you need a new flag, force_detach looks very similar.
Fix the style issue.

You might find Documentation/CodingGuidelines and
Documentation/SubmittingPatches very helpful.

Pablo

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

* Re: [RFC PATCH] switch: provide configurable detach
  2026-04-04 14:28 [RFC PATCH] switch: provide configurable detach Thibaud CANALE
  2026-04-04 15:23 ` Pablo
@ 2026-04-04 16:58 ` D. Ben Knoble
  2026-04-04 21:26   ` Thibaud CANALE
  2026-04-04 21:14 ` [RFC PATCH v2] " Thibaud CANALE
  2026-04-06 16:36 ` [RFC PATCH] " Junio C Hamano
  3 siblings, 1 reply; 9+ messages in thread
From: D. Ben Knoble @ 2026-04-04 16:58 UTC (permalink / raw)
  To: Thibaud CANALE; +Cc: git

On Sat, Apr 4, 2026 at 10:36 AM Thibaud CANALE <thican@thican.net> wrote:
>
> Its purpose is to provide for git-switch(1) same detach behavior on
> commit it than git-checkout(1) through configuration option
> `checkout.switchDetach`.

I considered contributing something similar at one point, though I
would call it "switch.detach" ("<command>.<option>").

I think I eventually decided against sending a patch because I was
concerned that such an option might make using git-switch in scripts
more of a headache. If I'm using it intentionally because it will fail
in some circumstances without --detach, now I've got to also do "-c
switch.detach=false" or something to control for the new
configuration.

On the one hand, I think that argument applies mostly to plumbing
rather than porcelain commands. On the other, a command for switching
branches whose behavior is reliable enough for scripts seems
worthwhile. So, idk :)

-- 
D. Ben Knoble

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

* Re: [RFC PATCH] switch: provide configurable detach
  2026-04-04 15:23 ` Pablo
@ 2026-04-04 21:10   ` Thibaud CANALE
  0 siblings, 0 replies; 9+ messages in thread
From: Thibaud CANALE @ 2026-04-04 21:10 UTC (permalink / raw)
  To: Pablo; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 10163 bytes --]

On Saturday 2026-04-04 17:23:46+0200, Pablo <pabloosabaterr@gmail.com> wrote:
> El sáb, 4 abr 2026 a las 16:36, Thibaud CANALE (<thican@thican.net>) escribió:
> 
> Hi Thibaud,

Hello,

> > Its purpose is to provide for git-switch(1) same detach behavior on
> > commit it than git-checkout(1) through configuration option
> > `checkout.switchDetach`.
> 
> The commit needs some work, following Documentation/SubmittingPatches:
> The subject looks fine but the body, you should write imperatively, something
> like "Allow git-switch to..." instead of "Its purpose is to...".

It was a (almost) copy of the message I wrote in the documentation, I
wanted to avoid giving different and possibly confusing explanations
between the two of them. I just noticed the typo on "commit it".
By the way, is "commit id" correct for constructs such as "${branch}~x" or
"${commit_id}~y", or is there a better word for this?

> >  same detach behavior on commit it than
> 
> This is confusing.
> 
> Also the body should explain WHY this change would be wanted.

May you tell me what you find confusing? I don’t see what else I can
explain without over-explaining.

Maybe I can provide a real case example:
Using `git checkout my_branch~2` works and automatically detach on the
2nd parent of the top commit of branch "my_branch", but the same
argument with `git switch` does not.
The command `git switch -` also does not work if the previous step was
a detached commit while `git checkout -` can move back and forth
between detached mode and branch.

About why force_detach flag is necessary, read below.

About why this feature, I know some users won’t change to switch/restore
commands and keep relying on checkout command because of this “missing”
feature, as its “disrupts” their workflow (humans, ’am I right?).

> >
> > Signed-off-by: Thibaud CANALE <thican@thican.net>
> > ---
> >  Documentation/config/checkout.adoc |  4 ++++
> >  builtin/checkout.c                 | 18 +++++++++++++++---
> >  t/t2060-switch.sh                  | 27 +++++++++++++++++++++++++++
> >  3 files changed, 46 insertions(+), 3 deletions(-)
> >
> > diff --git a/Documentation/config/checkout.adoc b/Documentation/config/checkout.adoc
> > index e35d2129..3a6c1049 100644
> > --- a/Documentation/config/checkout.adoc
> > +++ b/Documentation/config/checkout.adoc
> > @@ -22,6 +22,10 @@ commands or functionality in the future.
> >         option in `git checkout` and `git switch`. See
> >         linkgit:git-switch[1] and linkgit:git-checkout[1].
> >
> > +`checkout.switchDetach`::
> > +       Provides for linkgit:git-switch[1] the same detach behavior on commit id
> > +       than linkgit:git-checkout[1] without the explicit --detach option.
> > +
> 
> I think it would be better to describe how it would be if set rather
> than directly
> the what it does. e.g.:
> "If set, detaches HEAD when..."

Rewritten with details and example.

> >  `checkout.workers`::
> >         The number of parallel workers to use when updating the working tree.
> >         The default is one, i.e. sequential execution. If set to a value less
> > diff --git a/builtin/checkout.c b/builtin/checkout.c
> > index e031e618..1da1062e 100644
> > --- a/builtin/checkout.c
> > +++ b/builtin/checkout.c
> > @@ -53,6 +53,7 @@ struct checkout_opts {
> >         int force;
> >         int force_detach;
> >         int implicit_detach;
> > +       int switch_detach;  // For checkout.switchDetach configuration
> 
> // coments are not used in Git, if you need to add comments make them with
> /* */.
> Nit I don't think a comment here is necessary, it is very self
> explanatory already.

I agree, I originally didn’t, I thought we could tell me to describe it
here to avoid looking for explanation somewhere else.

> >         int writeout_stage;
> >         int overwrite_ignore;
> >         int ignore_skipworktree;
> > @@ -1005,7 +1006,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
> >         else
> >                 strbuf_insertstr(&msg, 0, reflog_msg);
> >
> > -       if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) {
> > +       if (!strcmp(new_branch_info->name, "HEAD") &&
> > +           !new_branch_info->path &&
> > +           !opts->force_detach &&
> > +           !opts->switch_detach) {
> 
> seems that you're checking force_detach and switch_detach together
> every time,they look very similar,
> sure  you need them separated?

Not every time.
There is cases where switch_detach is not used such as using a branch.

> >                 /* Nothing to do. */
> >         } else if (opts->force_detach || !new_branch_info->path) {      /* No longer on any branch. */
> >                 refs_update_ref(get_main_ref_store(the_repository), msg.buf,
> > @@ -1014,7 +1018,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
> >                                 REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
> >                 if (!opts->quiet) {
> >                         if (old_branch_info->path &&
> > -                           advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)
> > +                           advice_enabled(ADVICE_DETACHED_HEAD) &&
> > +                           !opts->force_detach &&
> > +                           !opts->switch_detach)
> >                                 detach_advice(new_branch_info->name);
> >                         describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
> >                 }
> > @@ -1541,8 +1547,11 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
> >                  */
> >                 code = die_message(_("a branch is expected, got '%s'"), branch_info->name);
> >
> > -       if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))
> > +       if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD)) {
> >                 advise(_("If you want to detach HEAD at the commit, try again with the --detach option."));
> > +               advise(_("Also you can detach by default by setting the config variable "
> > +                        "checkout.switchDetach to true."));
> > +       }
> 
> Nit "Also, you can..." or "You can also..."

As you prefer.

> >
> >         exit(code);
> >  }
> > @@ -1660,6 +1669,7 @@ static int checkout_branch(struct checkout_opts *opts,
> >
> >         if (!opts->implicit_detach &&
> >             !opts->force_detach &&
> > +           !opts->switch_detach &&
> 
> Again force_detach + switch_detach
> 
> >             !opts->new_branch &&
> >             !opts->new_branch_force &&
> >             new_branch_info->name &&
> > @@ -2119,6 +2129,8 @@ int cmd_switch(int argc,
> >         options = add_common_options(&opts, options);
> >         options = add_common_switch_branch_options(&opts, options);
> >
> > +       repo_config_get_bool(the_repository, "checkout.switchDetach", &opts.switch_detach);
> > +
> >         cb_option = 'c';
> >
> >         return checkout_main(argc, argv, prefix, &opts, options,
> > diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
> > index c91c4db9..3435ae98 100755
> > --- a/t/t2060-switch.sh
> > +++ b/t/t2060-switch.sh
> > @@ -177,4 +177,31 @@ test_expect_success 'switch back when temporarily detached and checked out elsew
> >         git -C wt2 switch --ignore-other-worktrees shared
> >  '
> >
> > +test_expect_success 'switch with configured detach behavior from configuration ' '
> 
> Trailing space here at the end, also the title is kinda confusing with the
> configured + configuration, what about:
> "switch detaches on commit when switchDetach is set"

For the trailing space I was mislead by the previous test, line 149; it
has its trailing space, I thought it was mandatory somehow.
Thank you for the rewording.

> > +       test_when_finished "
> > +               test_config checkout.switchDetach false;
> > +               git switch main
> > +       " &&
> > +       test_config checkout.switchDetach true &&
> > +       git switch main &&
> > +       git symbolic-ref HEAD &&
> > +       git switch main~ &&
> > +       test_must_fail git symbolic-ref HEAD &&
> > +       git switch - &&
> > +       git symbolic-ref HEAD &&
> > +       git switch - &&
> > +       test_must_fail git symbolic-ref HEAD
> > +'
> > +
> > +test_expect_success 'switch without configured detach behavior from configuration ' '
> 
> Trailing space.
> 
> > +       test_when_finished "
> > +               test_config checkout.switchDetach false;
> 
> test_config already has test_when_finished
> I would git config --unset.

ACK.

> > +               git switch main
> > +       " &&
> > +       test_config checkout.switchDetach false &&
> 
> This is already false.

I prefer to be explicit, to test the case where the user disables this
feature on purpose.

> > +       git switch main &&
> > +       git symbolic-ref HEAD &&
> > +       test_must_fail git switch main~
> > +'
> > +
> >  test_done
> >
> > base-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0
> > --
> > Thibaud CANALE
> > thican [at] thican [dot] net
> > https://thican.net/
> > GPG: rsa4096 2013-10-14 485EF628CB85CDD4CB7CFF0D52F5127650733A18
> 
> Overall seems reasonable, it might be a design choice having to
> explicitly use --detach, force_detach it's pretty clear about it but
> I dunno.
> 
> For now:
> I would rewrite the commit.
> Make sure that you need a new flag, force_detach looks very similar.
> Fix the style issue.

It was already sure a new flag was necessary, using force detach flag is
uncorrect as it will not anymore allow to use back a branch for example.
We can see this new flag is not everywhere force_detach is.
For testing, I tried only force_detach, the tests are failing.

V2 will follow shortly.

Best regards,


-- 
Thibaud CANALE
thican [at] thican [dot] net
https://thican.net/
GPG: rsa4096 2013-10-14 485EF628CB85CDD4CB7CFF0D52F5127650733A18

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 870 bytes --]

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

* [RFC PATCH v2] switch: provide configurable detach
  2026-04-04 14:28 [RFC PATCH] switch: provide configurable detach Thibaud CANALE
  2026-04-04 15:23 ` Pablo
  2026-04-04 16:58 ` D. Ben Knoble
@ 2026-04-04 21:14 ` Thibaud CANALE
  2026-04-06 16:36 ` [RFC PATCH] " Junio C Hamano
  3 siblings, 0 replies; 9+ messages in thread
From: Thibaud CANALE @ 2026-04-04 21:14 UTC (permalink / raw)
  To: git

[-- Attachment #1: Type: text/plain, Size: 10595 bytes --]

Some users won’t change to switch/restore commands and keep relying on
checkout command because of this “missing” feature:

For git-switch(1), configurable through option `checkout.switchDetach`,
match git-checkout(1)’s detach behavior on non-branch references, such
as commit id and "tilde" reference (i.e "${my_branch}~2").
No need to use explicit --detach option, automatically detach on
non-branch reference, then reattach when going back on a branch, even
implicitly through `git switch -` command.
… like git-checkout(1) uses to do.

No impact on current behavior when option is unset or set to false.

Signed-off-by: Thibaud CANALE <thican@thican.net>
---
 Documentation/config/checkout.adoc | 14 ++++++++++++++
 builtin/checkout.c                 | 19 ++++++++++++++++---
 t/t2060-switch.sh                  | 27 +++++++++++++++++++++++++++
 3 files changed, 57 insertions(+), 3 deletions(-)

diff --git a/Documentation/config/checkout.adoc b/Documentation/config/checkout.adoc
index e35d2129..5309bed7 100644
--- a/Documentation/config/checkout.adoc
+++ b/Documentation/config/checkout.adoc
@@ -22,6 +22,20 @@ commands or functionality in the future.
 	option in `git checkout` and `git switch`. See
 	linkgit:git-switch[1] and linkgit:git-checkout[1].
 
+`checkout.switchDetach`::
+	For linkgit:git-switch[1], matches linkgit:git-checkout[1]’s detach behavior
+	on non-branch references, such as commit id and "tilde" reference (i.e
+	"${my_branch}~2").
++
+No need to use explicit --detach option, automatically detach on
+non-branch reference, then reattach when going back on a branch, even
+implicitly through `git switch -` command.
++
+… like linkgit:git-checkout[1] uses to do.
++
+No impact when option is unset or set to false, won't continue when trying to
+detach HEAD without explicit --detach option.
+
 `checkout.workers`::
 	The number of parallel workers to use when updating the working tree.
 	The default is one, i.e. sequential execution. If set to a value less
diff --git a/builtin/checkout.c b/builtin/checkout.c
index e031e618..cd7b0a60 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -53,6 +53,7 @@ struct checkout_opts {
 	int force;
 	int force_detach;
 	int implicit_detach;
+	int switch_detach;
 	int writeout_stage;
 	int overwrite_ignore;
 	int ignore_skipworktree;
@@ -1005,7 +1006,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 	else
 		strbuf_insertstr(&msg, 0, reflog_msg);
 
-	if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) {
+	if (!strcmp(new_branch_info->name, "HEAD") &&
+	    !new_branch_info->path &&
+	    !opts->force_detach &&
+	    !opts->switch_detach) {
 		/* Nothing to do. */
 	} else if (opts->force_detach || !new_branch_info->path) {	/* No longer on any branch. */
 		refs_update_ref(get_main_ref_store(the_repository), msg.buf,
@@ -1014,7 +1018,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 				REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
 		if (!opts->quiet) {
 			if (old_branch_info->path &&
-			    advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)
+			    advice_enabled(ADVICE_DETACHED_HEAD) &&
+			    !opts->force_detach &&
+			    !opts->switch_detach)
 				detach_advice(new_branch_info->name);
 			describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
 		}
@@ -1541,8 +1547,11 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
 		 */
 		code = die_message(_("a branch is expected, got '%s'"), branch_info->name);
 
-	if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))
+	if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD)) {
 		advise(_("If you want to detach HEAD at the commit, try again with the --detach option."));
+		advise(_("You can also detach by default by setting the config "
+		         "variable checkout.switchDetach to true."));
+	}
 
 	exit(code);
 }
@@ -1660,6 +1669,7 @@ static int checkout_branch(struct checkout_opts *opts,
 
 	if (!opts->implicit_detach &&
 	    !opts->force_detach &&
+	    !opts->switch_detach &&
 	    !opts->new_branch &&
 	    !opts->new_branch_force &&
 	    new_branch_info->name &&
@@ -2119,6 +2129,9 @@ int cmd_switch(int argc,
 	options = add_common_options(&opts, options);
 	options = add_common_switch_branch_options(&opts, options);
 
+	repo_config_get_bool(the_repository, "checkout.switchDetach",
+	                     &opts.switch_detach);
+
 	cb_option = 'c';
 
 	return checkout_main(argc, argv, prefix, &opts, options,
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
index c91c4db9..3732a826 100755
--- a/t/t2060-switch.sh
+++ b/t/t2060-switch.sh
@@ -177,4 +177,31 @@ test_expect_success 'switch back when temporarily detached and checked out elsew
 	git -C wt2 switch --ignore-other-worktrees shared
 '
 
+test_expect_success 'switch with configured detach behavior from configuration' '
+	test_when_finished "
+		git config --unset checkout.switchDetach;
+		git switch main
+	" &&
+	test_config checkout.switchDetach true &&
+	git switch main &&
+	git symbolic-ref HEAD &&
+	git switch main~ &&
+	test_must_fail git symbolic-ref HEAD &&
+	git switch - &&
+	git symbolic-ref HEAD &&
+	git switch - &&
+	test_must_fail git symbolic-ref HEAD
+'
+
+test_expect_success 'switch without configured detach behavior from configuration' '
+	test_when_finished "
+		git config --unset checkout.switchDetach;
+		git switch main
+	" &&
+	test_config checkout.switchDetach false &&
+	git switch main &&
+	git symbolic-ref HEAD &&
+	test_must_fail git switch main~
+'
+
 test_done

Range-diff against v1:
1:  d9a7ca4c ! 1:  ea7ea74c switch: provide configurable detach
    @@ Metadata
      ## Commit message ##
         switch: provide configurable detach
     
    -    Its purpose is to provide for git-switch(1) same detach behavior on
    -    commit it than git-checkout(1) through configuration option
    -    `checkout.switchDetach`.
    +    Some users won’t change to switch/restore commands and keep relying on
    +    checkout command because of this “missing” feature:
    +
    +    For git-switch(1), configurable through option `checkout.switchDetach`,
    +    match git-checkout(1)’s detach behavior on non-branch references, such
    +    as commit id and "tilde" reference (i.e "${my_branch}~2").
    +    No need to use explicit --detach option, automatically detach on
    +    non-branch reference, then reattach when going back on a branch, even
    +    implicitly through `git switch -` command.
    +    … like git-checkout(1) uses to do.
    +
    +    No impact on current behavior when option is unset or set to false.
     
         Signed-off-by: Thibaud CANALE <thican@thican.net>
     
    @@ Documentation/config/checkout.adoc: commands or functionality in the future.
      	linkgit:git-switch[1] and linkgit:git-checkout[1].
      
     +`checkout.switchDetach`::
    -+	Provides for linkgit:git-switch[1] the same detach behavior on commit id
    -+	than linkgit:git-checkout[1] without the explicit --detach option.
    ++	For linkgit:git-switch[1], matches linkgit:git-checkout[1]’s detach behavior
    ++	on non-branch references, such as commit id and "tilde" reference (i.e
    ++	"${my_branch}~2").
    +++
    ++No need to use explicit --detach option, automatically detach on
    ++non-branch reference, then reattach when going back on a branch, even
    ++implicitly through `git switch -` command.
    +++
    ++… like linkgit:git-checkout[1] uses to do.
    +++
    ++No impact when option is unset or set to false, won't continue when trying to
    ++detach HEAD without explicit --detach option.
     +
      `checkout.workers`::
      	The number of parallel workers to use when updating the working tree.
    @@ builtin/checkout.c: struct checkout_opts {
      	int force;
      	int force_detach;
      	int implicit_detach;
    -+	int switch_detach;  // For checkout.switchDetach configuration
    ++	int switch_detach;
      	int writeout_stage;
      	int overwrite_ignore;
      	int ignore_skipworktree;
    @@ builtin/checkout.c: static void die_expecting_a_branch(const struct branch_info
     -	if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))
     +	if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD)) {
      		advise(_("If you want to detach HEAD at the commit, try again with the --detach option."));
    -+		advise(_("Also you can detach by default by setting the config variable "
    -+		         "checkout.switchDetach to true."));
    ++		advise(_("You can also detach by default by setting the config "
    ++		         "variable checkout.switchDetach to true."));
     +	}
      
      	exit(code);
    @@ builtin/checkout.c: int cmd_switch(int argc,
      	options = add_common_options(&opts, options);
      	options = add_common_switch_branch_options(&opts, options);
      
    -+	repo_config_get_bool(the_repository, "checkout.switchDetach", &opts.switch_detach);
    ++	repo_config_get_bool(the_repository, "checkout.switchDetach",
    ++	                     &opts.switch_detach);
     +
      	cb_option = 'c';
      
    @@ t/t2060-switch.sh: test_expect_success 'switch back when temporarily detached an
      	git -C wt2 switch --ignore-other-worktrees shared
      '
      
    -+test_expect_success 'switch with configured detach behavior from configuration ' '
    ++test_expect_success 'switch with configured detach behavior from configuration' '
     +	test_when_finished "
    -+		test_config checkout.switchDetach false;
    ++		git config --unset checkout.switchDetach;
     +		git switch main
     +	" &&
     +	test_config checkout.switchDetach true &&
    @@ t/t2060-switch.sh: test_expect_success 'switch back when temporarily detached an
     +	test_must_fail git symbolic-ref HEAD
     +'
     +
    -+test_expect_success 'switch without configured detach behavior from configuration ' '
    ++test_expect_success 'switch without configured detach behavior from configuration' '
     +	test_when_finished "
    -+		test_config checkout.switchDetach false;
    ++		git config --unset checkout.switchDetach;
     +		git switch main
     +	" &&
     +	test_config checkout.switchDetach false &&
-- 
Thibaud CANALE
thican [at] thican [dot] net
https://thican.net/
GPG: rsa4096 2013-10-14 485EF628CB85CDD4CB7CFF0D52F5127650733A18

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 870 bytes --]

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

* Re: [RFC PATCH] switch: provide configurable detach
  2026-04-04 16:58 ` D. Ben Knoble
@ 2026-04-04 21:26   ` Thibaud CANALE
  0 siblings, 0 replies; 9+ messages in thread
From: Thibaud CANALE @ 2026-04-04 21:26 UTC (permalink / raw)
  To: D. Ben Knoble; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 2080 bytes --]

On Saturday 2026-04-04 12:58:48-0400, D. Ben Knoble <ben.knoble@gmail.com> wrote:

Hello,

> On Sat, Apr 4, 2026 at 10:36 AM Thibaud CANALE <thican@thican.net> wrote:
> >
> > Its purpose is to provide for git-switch(1) same detach behavior on
> > commit it than git-checkout(1) through configuration option
> > `checkout.switchDetach`.
> 
> I considered contributing something similar at one point, though I
> would call it "switch.detach" ("<command>.<option>").
> 
> I think I eventually decided against sending a patch because I was
> concerned that such an option might make using git-switch in scripts
> more of a headache. If I'm using it intentionally because it will fail
> in some circumstances without --detach, now I've got to also do "-c
> switch.detach=false" or something to control for the new
> configuration.

I see what you mean, I could argue it’s a very niche option, only
concerning git-switch(1), and in case we rely on git to block further
actions if the provided reference is not a branch or simply working on
detached states, it should not have any impact.
This is what the newly implemented test cases are designed to ensure.

As mentioned it only implements the same behavior than git-checkout(1)’s
concerning non-branch references.

Full disclosure, I am not even concerned about this feature, as I use
switch/restore daily since 2017, but not everyone I know does; so here I
try to ease transition for them.

> On the one hand, I think that argument applies mostly to plumbing
> rather than porcelain commands. On the other, a command for switching
> branches whose behavior is reliable enough for scripts seems
> worthwhile. So, idk :)
> 
> -- 
> D. Ben Knoble

Also the purpose of this request is to provide this feature for Windows
environment, which is out of my abilities concerning compilation, cannot
compile without such environment.

Best regards,


-- 
Thibaud CANALE
thican [at] thican [dot] net
https://thican.net/
GPG: rsa4096 2013-10-14 485EF628CB85CDD4CB7CFF0D52F5127650733A18

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 870 bytes --]

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

* Re: [RFC PATCH] switch: provide configurable detach
  2026-04-04 14:28 [RFC PATCH] switch: provide configurable detach Thibaud CANALE
                   ` (2 preceding siblings ...)
  2026-04-04 21:14 ` [RFC PATCH v2] " Thibaud CANALE
@ 2026-04-06 16:36 ` Junio C Hamano
  2026-04-06 17:48   ` Kristoffer Haugsbakk
  3 siblings, 1 reply; 9+ messages in thread
From: Junio C Hamano @ 2026-04-06 16:36 UTC (permalink / raw)
  To: Thibaud CANALE; +Cc: git

Thibaud CANALE <thican@thican.net> writes:

> Its purpose is to provide for git-switch(1) same detach behavior on
> commit it than git-checkout(1) through configuration option
> `checkout.switchDetach`.
>
> Signed-off-by: Thibaud CANALE <thican@thican.net>
> ---
>  Documentation/config/checkout.adoc |  4 ++++
>  builtin/checkout.c                 | 18 +++++++++++++++---
>  t/t2060-switch.sh                  | 27 +++++++++++++++++++++++++++
>  3 files changed, 46 insertions(+), 3 deletions(-)

Sorry, but I am fairly negative on this change.

"switch" was an attempt to give folks an improved experience over
"checkout".  The implied "--detach" was deliberately removed from
the command and I do not think it was a bad move for our user base
who wanted to have "switch" that can only be used to switch branches
(as opposed to "checkout" that hecks out both files and branches).
With fewer choices in a single command that does only a single
thing, the hope was to make it simpler to teach.

And it is backwards to make "switch" configurable in that context.
It defeats a major point of "git switch".

FWIW, "git checkout" that knows what is and what is not a branch
name and does not require "--detach" when detaching to anything that
is not a branch name is always available and will not be going away.

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

* Re: [RFC PATCH] switch: provide configurable detach
  2026-04-06 16:36 ` [RFC PATCH] " Junio C Hamano
@ 2026-04-06 17:48   ` Kristoffer Haugsbakk
  2026-04-07  0:50     ` Junio C Hamano
  0 siblings, 1 reply; 9+ messages in thread
From: Kristoffer Haugsbakk @ 2026-04-06 17:48 UTC (permalink / raw)
  To: Junio C Hamano, Thibaud CANALE; +Cc: git

On Mon, Apr 6, 2026, at 18:36, Junio C Hamano wrote:
> Thibaud CANALE <thican@thican.net> writes:
>>[snip]
>>  3 files changed, 46 insertions(+), 3 deletions(-)
>
> Sorry, but I am fairly negative on this change.
>
>[snip]
>
> FWIW, "git checkout" that knows what is and what is not a branch
> name and does not require "--detach" when detaching to anything that
> is not a branch name is always available and will not be going away.

On the one hand, I personally mostly use git-checkout(1) because I never
use it to check out files and I like the detach behavior. So from that
perspective I don’t understand Git users who interject when you provide
examples for something and you (incidental to the example and point) use
git-checkout(1) because that’s what you are used to. And they interject
because git-checkout(1) is “obsolete” or something and you are setting a
bad example (or something).

On the other hand, the BreakingChanges document does call
git-checkout(1) “superseded”.  And in that light I do understand why
people want to actively avoid git-checkout(1), including implementing
replacements for all relevant checkout-use cases in git-switch(1).

    Superseded features that will not be deprecated

    [...]

    • The features git-checkout(1) offers are covered by the pair of
      commands git-restore(1) and git-switch(1). Because the use of
      git-checkout(1) is still widespread, and it is not expected that
      this will change anytime soon, all three commands will stay.

https://git-scm.com/docs/BreakingChanges

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

* Re: [RFC PATCH] switch: provide configurable detach
  2026-04-06 17:48   ` Kristoffer Haugsbakk
@ 2026-04-07  0:50     ` Junio C Hamano
  0 siblings, 0 replies; 9+ messages in thread
From: Junio C Hamano @ 2026-04-07  0:50 UTC (permalink / raw)
  To: Kristoffer Haugsbakk; +Cc: Thibaud CANALE, git

"Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:

> On the other hand, the BreakingChanges document does call
> git-checkout(1) “superseded”.  And in that light I do understand why
> people want to actively avoid git-checkout(1), including implementing
> replacements for all relevant checkout-use cases in git-switch(1).
>
>     Superseded features that will not be deprecated
>
>     [...]
>
>     • The features git-checkout(1) offers are covered by the pair of
>       commands git-restore(1) and git-switch(1). Because the use of
>       git-checkout(1) is still widespread, and it is not expected that
>       this will change anytime soon, all three commands will stay.

Perhaps a good first step would be to stop calling checkout
"superseded".  As they stand, all three are equally viable.  In
other words, I would not exactly call "switch/restore" a failed
experiment, but it wasn't a clear success story, either.

I think "still" and "anytime soon" in the above paragraph are doing
disservice by implying that somehow we want to remove checkout but
we cannot yet, when the reality is that we wanted to be able to
replace checkout with switch/restore in the distant past, but that
was a misguided attempt and did not work very well.

It is not like switch/restore pair did not work well or anything,
and in that sense switch/restore themselves are by no means
failures, but it was a failed experiment to introduce these two as a
way to replace and kill checkout.  For those users to whom both
"checking out files" and "checkout out a branch" appear as clear
concepts, there is no reason to abandon checkout.

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

end of thread, other threads:[~2026-04-07  0:50 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-04 14:28 [RFC PATCH] switch: provide configurable detach Thibaud CANALE
2026-04-04 15:23 ` Pablo
2026-04-04 21:10   ` Thibaud CANALE
2026-04-04 16:58 ` D. Ben Knoble
2026-04-04 21:26   ` Thibaud CANALE
2026-04-04 21:14 ` [RFC PATCH v2] " Thibaud CANALE
2026-04-06 16:36 ` [RFC PATCH] " Junio C Hamano
2026-04-06 17:48   ` Kristoffer Haugsbakk
2026-04-07  0:50     ` Junio C Hamano

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox