{"thread":{"id":"65433","subject":"[RFC PATCH] switch: provide configurable detach","startedAt":"2026-04-04T14:36:24Z","lastAt":"2026-04-07T00:50:14Z","messageCount":9,"participants":["Thibaud CANALE","Pablo","D. Ben Knoble","Junio C Hamano","Kristoffer Haugsbakk"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"540891","messageId":"njuf62lav6akkmdyqutwk7pim5yutw5cuicjidwpe5eh6qnkqr@4ir6q4uhhvht","threadId":"65433","inReplyTo":null,"subject":"[RFC PATCH] switch: provide configurable detach","fromName":"Thibaud CANALE","fromEmail":"thican@thican.net","sentAt":"2026-04-04T14:28:57Z","receivedAt":"2026-04-04T14:36:24Z","isPatch":true,"body":"Its purpose is to provide for git-switch(1) same detach behavior on\ncommit it than git-checkout(1) through configuration option\n`checkout.switchDetach`.\n\nSigned-off-by: Thibaud CANALE <thican@thican.net>\n---\n Documentation/config/checkout.adoc |  4 ++++\n builtin/checkout.c                 | 18 +++++++++++++++---\n t/t2060-switch.sh                  | 27 +++++++++++++++++++++++++++\n 3 files changed, 46 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/checkout.adoc b/Documentation/config/checkout.adoc\nindex e35d2129..3a6c1049 100644\n--- a/Documentation/config/checkout.adoc\n+++ b/Documentation/config/checkout.adoc\n@@ -22,6 +22,10 @@ commands or functionality in the future.\n \toption in `git checkout` and `git switch`. See\n \tlinkgit:git-switch[1] and linkgit:git-checkout[1].\n \n+`checkout.switchDetach`::\n+\tProvides for linkgit:git-switch[1] the same detach behavior on commit id\n+\tthan linkgit:git-checkout[1] without the explicit --detach option.\n+\n `checkout.workers`::\n \tThe number of parallel workers to use when updating the working tree.\n \tThe default is one, i.e. sequential execution. If set to a value less\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e031e618..1da1062e 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -53,6 +53,7 @@ struct checkout_opts {\n \tint force;\n \tint force_detach;\n \tint implicit_detach;\n+\tint switch_detach;  // For checkout.switchDetach configuration\n \tint writeout_stage;\n \tint overwrite_ignore;\n \tint ignore_skipworktree;\n@@ -1005,7 +1006,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \telse\n \t\tstrbuf_insertstr(&msg, 0, reflog_msg);\n \n-\tif (!strcmp(new_branch_info->name, \"HEAD\") && !new_branch_info->path && !opts->force_detach) {\n+\tif (!strcmp(new_branch_info->name, \"HEAD\") &&\n+\t    !new_branch_info->path &&\n+\t    !opts->force_detach &&\n+\t    !opts->switch_detach) {\n \t\t/* Nothing to do. */\n \t} else if (opts->force_detach || !new_branch_info->path) {\t/* No longer on any branch. */\n \t\trefs_update_ref(get_main_ref_store(the_repository), msg.buf,\n@@ -1014,7 +1018,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\tREF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path &&\n-\t\t\t    advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)\n+\t\t\t    advice_enabled(ADVICE_DETACHED_HEAD) &&\n+\t\t\t    !opts->force_detach &&\n+\t\t\t    !opts->switch_detach)\n \t\t\t\tdetach_advice(new_branch_info->name);\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n@@ -1541,8 +1547,11 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)\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+\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+\t\tadvise(_(\"Also you can detach by default by setting the config variable \"\n+\t\t         \"checkout.switchDetach to true.\"));\n+\t}\n \n \texit(code);\n }\n@@ -1660,6 +1669,7 @@ static int checkout_branch(struct checkout_opts *opts,\n \n \tif (!opts->implicit_detach &&\n \t    !opts->force_detach &&\n+\t    !opts->switch_detach &&\n \t    !opts->new_branch &&\n \t    !opts->new_branch_force &&\n \t    new_branch_info->name &&\n@@ -2119,6 +2129,8 @@ int cmd_switch(int argc,\n \toptions = add_common_options(&opts, options);\n \toptions = add_common_switch_branch_options(&opts, options);\n \n+\trepo_config_get_bool(the_repository, \"checkout.switchDetach\", &opts.switch_detach);\n+\n \tcb_option = 'c';\n \n \treturn checkout_main(argc, argv, prefix, &opts, options,\ndiff --git a/t/t2060-switch.sh b/t/t2060-switch.sh\nindex c91c4db9..3435ae98 100755\n--- a/t/t2060-switch.sh\n+++ b/t/t2060-switch.sh\n@@ -177,4 +177,31 @@ test_expect_success 'switch back when temporarily detached and checked out elsew\n \tgit -C wt2 switch --ignore-other-worktrees shared\n '\n \n+test_expect_success 'switch with configured detach behavior from configuration ' '\n+\ttest_when_finished \"\n+\t\ttest_config checkout.switchDetach false;\n+\t\tgit switch main\n+\t\" &&\n+\ttest_config checkout.switchDetach true &&\n+\tgit switch main &&\n+\tgit symbolic-ref HEAD &&\n+\tgit switch main~ &&\n+\ttest_must_fail git symbolic-ref HEAD &&\n+\tgit switch - &&\n+\tgit symbolic-ref HEAD &&\n+\tgit switch - &&\n+\ttest_must_fail git symbolic-ref HEAD\n+'\n+\n+test_expect_success 'switch without configured detach behavior from configuration ' '\n+\ttest_when_finished \"\n+\t\ttest_config checkout.switchDetach false;\n+\t\tgit switch main\n+\t\" &&\n+\ttest_config checkout.switchDetach false &&\n+\tgit switch main &&\n+\tgit symbolic-ref HEAD &&\n+\ttest_must_fail git switch main~\n+'\n+\n test_done\n\nbase-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0\n-- \nThibaud CANALE\nthican [at] thican [dot] net\nhttps://thican.net/\nGPG: rsa4096 2013-10-14 485EF628CB85CDD4CB7CFF0D52F5127650733A18\n"},{"id":"540895","messageId":"CAN5EUNR6x_HWQKrMMHcHj0hF+D_6QBiw782_QGA8E8SS7Q3pcg@mail.gmail.com","threadId":"65433","inReplyTo":"njuf62lav6akkmdyqutwk7pim5yutw5cuicjidwpe5eh6qnkqr@4ir6q4uhhvht","subject":"Re: [RFC PATCH] switch: provide configurable detach","fromName":"Pablo","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-04-04T15:23:46Z","receivedAt":"2026-04-04T15:23:59Z","isPatch":true,"body":"El sáb, 4 abr 2026 a las 16:36, Thibaud CANALE (<thican@thican.net>) escribió:\n\nHi Thibaud,\n>\n> Its purpose is to provide for git-switch(1) same detach behavior on\n> commit it than git-checkout(1) through configuration option\n> `checkout.switchDetach`.\n\nThe commit needs some work, following Documentation/SubmittingPatches:\nThe subject looks fine but the body, you should write imperatively, something\nlike \"Allow git-switch to...\" instead of \"Its purpose is to...\".\n\n>  same detach behavior on commit it than\n\nThis is confusing.\n\nAlso the body should explain WHY this change would be wanted.\n\n>\n> Signed-off-by: Thibaud CANALE <thican@thican.net>\n> ---\n>  Documentation/config/checkout.adoc |  4 ++++\n>  builtin/checkout.c                 | 18 +++++++++++++++---\n>  t/t2060-switch.sh                  | 27 +++++++++++++++++++++++++++\n>  3 files changed, 46 insertions(+), 3 deletions(-)\n>\n> diff --git a/Documentation/config/checkout.adoc b/Documentation/config/checkout.adoc\n> index e35d2129..3a6c1049 100644\n> --- a/Documentation/config/checkout.adoc\n> +++ b/Documentation/config/checkout.adoc\n> @@ -22,6 +22,10 @@ commands or functionality in the future.\n>         option in `git checkout` and `git switch`. See\n>         linkgit:git-switch[1] and linkgit:git-checkout[1].\n>\n> +`checkout.switchDetach`::\n> +       Provides for linkgit:git-switch[1] the same detach behavior on commit id\n> +       than linkgit:git-checkout[1] without the explicit --detach option.\n> +\n\nI think it would be better to describe how it would be if set rather\nthan directly\nthe what it does. e.g.:\n\"If set, detaches HEAD when...\"\n\n>  `checkout.workers`::\n>         The number of parallel workers to use when updating the working tree.\n>         The default is one, i.e. sequential execution. If set to a value less\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index e031e618..1da1062e 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -53,6 +53,7 @@ struct checkout_opts {\n>         int force;\n>         int force_detach;\n>         int implicit_detach;\n> +       int switch_detach;  // For checkout.switchDetach configuration\n\n// coments are not used in Git, if you need to add comments make them with\n/* */.\nNit I don't think a comment here is necessary, it is very self\nexplanatory already.\n\n>         int writeout_stage;\n>         int overwrite_ignore;\n>         int ignore_skipworktree;\n> @@ -1005,7 +1006,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n>         else\n>                 strbuf_insertstr(&msg, 0, reflog_msg);\n>\n> -       if (!strcmp(new_branch_info->name, \"HEAD\") && !new_branch_info->path && !opts->force_detach) {\n> +       if (!strcmp(new_branch_info->name, \"HEAD\") &&\n> +           !new_branch_info->path &&\n> +           !opts->force_detach &&\n> +           !opts->switch_detach) {\n\nseems that you're checking force_detach and switch_detach together\nevery time,they look very similar,\nsure  you need them separated?\n\n>                 /* Nothing to do. */\n>         } else if (opts->force_detach || !new_branch_info->path) {      /* No longer on any branch. */\n>                 refs_update_ref(get_main_ref_store(the_repository), msg.buf,\n> @@ -1014,7 +1018,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n>                                 REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);\n>                 if (!opts->quiet) {\n>                         if (old_branch_info->path &&\n> -                           advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)\n> +                           advice_enabled(ADVICE_DETACHED_HEAD) &&\n> +                           !opts->force_detach &&\n> +                           !opts->switch_detach)\n>                                 detach_advice(new_branch_info->name);\n>                         describe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n>                 }\n> @@ -1541,8 +1547,11 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)\n>                  */\n>                 code = die_message(_(\"a branch is expected, got '%s'\"), branch_info->name);\n>\n> -       if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))\n> +       if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD)) {\n>                 advise(_(\"If you want to detach HEAD at the commit, try again with the --detach option.\"));\n> +               advise(_(\"Also you can detach by default by setting the config variable \"\n> +                        \"checkout.switchDetach to true.\"));\n> +       }\n\nNit \"Also, you can...\" or \"You can also...\"\n\n>\n>         exit(code);\n>  }\n> @@ -1660,6 +1669,7 @@ static int checkout_branch(struct checkout_opts *opts,\n>\n>         if (!opts->implicit_detach &&\n>             !opts->force_detach &&\n> +           !opts->switch_detach &&\n\nAgain force_detach + switch_detach\n\n>             !opts->new_branch &&\n>             !opts->new_branch_force &&\n>             new_branch_info->name &&\n> @@ -2119,6 +2129,8 @@ int cmd_switch(int argc,\n>         options = add_common_options(&opts, options);\n>         options = add_common_switch_branch_options(&opts, options);\n>\n> +       repo_config_get_bool(the_repository, \"checkout.switchDetach\", &opts.switch_detach);\n> +\n>         cb_option = 'c';\n>\n>         return checkout_main(argc, argv, prefix, &opts, options,\n> diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh\n> index c91c4db9..3435ae98 100755\n> --- a/t/t2060-switch.sh\n> +++ b/t/t2060-switch.sh\n> @@ -177,4 +177,31 @@ test_expect_success 'switch back when temporarily detached and checked out elsew\n>         git -C wt2 switch --ignore-other-worktrees shared\n>  '\n>\n> +test_expect_success 'switch with configured detach behavior from configuration ' '\n\nTrailing space here at the end, also the title is kinda confusing with the\nconfigured + configuration, what about:\n\"switch detaches on commit when switchDetach is set\"\n\n> +       test_when_finished \"\n> +               test_config checkout.switchDetach false;\n> +               git switch main\n> +       \" &&\n> +       test_config checkout.switchDetach true &&\n> +       git switch main &&\n> +       git symbolic-ref HEAD &&\n> +       git switch main~ &&\n> +       test_must_fail git symbolic-ref HEAD &&\n> +       git switch - &&\n> +       git symbolic-ref HEAD &&\n> +       git switch - &&\n> +       test_must_fail git symbolic-ref HEAD\n> +'\n> +\n> +test_expect_success 'switch without configured detach behavior from configuration ' '\n\nTrailing space.\n\n> +       test_when_finished \"\n> +               test_config checkout.switchDetach false;\n\ntest_config already has test_when_finished\nI would git config --unset.\n\n> +               git switch main\n> +       \" &&\n> +       test_config checkout.switchDetach false &&\n\nThis is already false.\n\n> +       git switch main &&\n> +       git symbolic-ref HEAD &&\n> +       test_must_fail git switch main~\n> +'\n> +\n>  test_done\n>\n> base-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0\n> --\n> Thibaud CANALE\n> thican [at] thican [dot] net\n> https://thican.net/\n> GPG: rsa4096 2013-10-14 485EF628CB85CDD4CB7CFF0D52F5127650733A18\n\nOverall seems reasonable, it might be a design choice having to\nexplicitly use --detach, force_detach it's pretty clear about it but\nI dunno.\n\nFor now:\nI would rewrite the commit.\nMake sure that you need a new flag, force_detach looks very similar.\nFix the style issue.\n\nYou might find Documentation/CodingGuidelines and\nDocumentation/SubmittingPatches very helpful.\n\nPablo\n"},{"id":"540901","messageId":"CALnO6CA2_MjKENu3CK-zCfoG=edEuMm6rohBtJ_8JC9WgMudEw@mail.gmail.com","threadId":"65433","inReplyTo":"njuf62lav6akkmdyqutwk7pim5yutw5cuicjidwpe5eh6qnkqr@4ir6q4uhhvht","subject":"Re: [RFC PATCH] switch: provide configurable detach","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-04-04T16:58:48Z","receivedAt":"2026-04-04T16:59:01Z","isPatch":true,"body":"On Sat, Apr 4, 2026 at 10:36 AM Thibaud CANALE <thican@thican.net> wrote:\n>\n> Its purpose is to provide for git-switch(1) same detach behavior on\n> commit it than git-checkout(1) through configuration option\n> `checkout.switchDetach`.\n\nI considered contributing something similar at one point, though I\nwould call it \"switch.detach\" (\"<command>.<option>\").\n\nI think I eventually decided against sending a patch because I was\nconcerned that such an option might make using git-switch in scripts\nmore of a headache. If I'm using it intentionally because it will fail\nin some circumstances without --detach, now I've got to also do \"-c\nswitch.detach=false\" or something to control for the new\nconfiguration.\n\nOn the one hand, I think that argument applies mostly to plumbing\nrather than porcelain commands. On the other, a command for switching\nbranches whose behavior is reliable enough for scripts seems\nworthwhile. So, idk :)\n\n-- \nD. Ben Knoble\n"},{"id":"540931","messageId":"4gpvxpm335bvnml57ks5zbfyzd5pzp6jheebvytsobmlffpllg@6ddpoiruhgh6","threadId":"65433","inReplyTo":"CAN5EUNR6x_HWQKrMMHcHj0hF+D_6QBiw782_QGA8E8SS7Q3pcg@mail.gmail.com","subject":"Re: [RFC PATCH] switch: provide configurable detach","fromName":"Thibaud CANALE","fromEmail":"thican@thican.net","sentAt":"2026-04-04T21:10:25Z","receivedAt":"2026-04-04T21:10:27Z","isPatch":true,"body":"On Saturday 2026-04-04 17:23:46+0200, Pablo <pabloosabaterr@gmail.com> wrote:\n> El sáb, 4 abr 2026 a las 16:36, Thibaud CANALE (<thican@thican.net>) escribió:\n> \n> Hi Thibaud,\n\nHello,\n\n> > Its purpose is to provide for git-switch(1) same detach behavior on\n> > commit it than git-checkout(1) through configuration option\n> > `checkout.switchDetach`.\n> \n> The commit needs some work, following Documentation/SubmittingPatches:\n> The subject looks fine but the body, you should write imperatively, something\n> like \"Allow git-switch to...\" instead of \"Its purpose is to...\".\n\nIt was a (almost) copy of the message I wrote in the documentation, I\nwanted to avoid giving different and possibly confusing explanations\nbetween the two of them. I just noticed the typo on \"commit it\".\nBy the way, is \"commit id\" correct for constructs such as \"${branch}~x\" or\n\"${commit_id}~y\", or is there a better word for this?\n\n> >  same detach behavior on commit it than\n> \n> This is confusing.\n> \n> Also the body should explain WHY this change would be wanted.\n\nMay you tell me what you find confusing? I don’t see what else I can\nexplain without over-explaining.\n\nMaybe I can provide a real case example:\nUsing `git checkout my_branch~2` works and automatically detach on the\n2nd parent of the top commit of branch \"my_branch\", but the same\nargument with `git switch` does not.\nThe command `git switch -` also does not work if the previous step was\na detached commit while `git checkout -` can move back and forth\nbetween detached mode and branch.\n\nAbout why force_detach flag is necessary, read below.\n\nAbout why this feature, I know some users won’t change to switch/restore\ncommands and keep relying on checkout command because of this “missing”\nfeature, as its “disrupts” their workflow (humans, ’am I right?).\n\n> >\n> > Signed-off-by: Thibaud CANALE <thican@thican.net>\n> > ---\n> >  Documentation/config/checkout.adoc |  4 ++++\n> >  builtin/checkout.c                 | 18 +++++++++++++++---\n> >  t/t2060-switch.sh                  | 27 +++++++++++++++++++++++++++\n> >  3 files changed, 46 insertions(+), 3 deletions(-)\n> >\n> > diff --git a/Documentation/config/checkout.adoc b/Documentation/config/checkout.adoc\n> > index e35d2129..3a6c1049 100644\n> > --- a/Documentation/config/checkout.adoc\n> > +++ b/Documentation/config/checkout.adoc\n> > @@ -22,6 +22,10 @@ commands or functionality in the future.\n> >         option in `git checkout` and `git switch`. See\n> >         linkgit:git-switch[1] and linkgit:git-checkout[1].\n> >\n> > +`checkout.switchDetach`::\n> > +       Provides for linkgit:git-switch[1] the same detach behavior on commit id\n> > +       than linkgit:git-checkout[1] without the explicit --detach option.\n> > +\n> \n> I think it would be better to describe how it would be if set rather\n> than directly\n> the what it does. e.g.:\n> \"If set, detaches HEAD when...\"\n\nRewritten with details and example.\n\n> >  `checkout.workers`::\n> >         The number of parallel workers to use when updating the working tree.\n> >         The default is one, i.e. sequential execution. If set to a value less\n> > diff --git a/builtin/checkout.c b/builtin/checkout.c\n> > index e031e618..1da1062e 100644\n> > --- a/builtin/checkout.c\n> > +++ b/builtin/checkout.c\n> > @@ -53,6 +53,7 @@ struct checkout_opts {\n> >         int force;\n> >         int force_detach;\n> >         int implicit_detach;\n> > +       int switch_detach;  // For checkout.switchDetach configuration\n> \n> // coments are not used in Git, if you need to add comments make them with\n> /* */.\n> Nit I don't think a comment here is necessary, it is very self\n> explanatory already.\n\nI agree, I originally didn’t, I thought we could tell me to describe it\nhere to avoid looking for explanation somewhere else.\n\n> >         int writeout_stage;\n> >         int overwrite_ignore;\n> >         int ignore_skipworktree;\n> > @@ -1005,7 +1006,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n> >         else\n> >                 strbuf_insertstr(&msg, 0, reflog_msg);\n> >\n> > -       if (!strcmp(new_branch_info->name, \"HEAD\") && !new_branch_info->path && !opts->force_detach) {\n> > +       if (!strcmp(new_branch_info->name, \"HEAD\") &&\n> > +           !new_branch_info->path &&\n> > +           !opts->force_detach &&\n> > +           !opts->switch_detach) {\n> \n> seems that you're checking force_detach and switch_detach together\n> every time,they look very similar,\n> sure  you need them separated?\n\nNot every time.\nThere is cases where switch_detach is not used such as using a branch.\n\n> >                 /* Nothing to do. */\n> >         } else if (opts->force_detach || !new_branch_info->path) {      /* No longer on any branch. */\n> >                 refs_update_ref(get_main_ref_store(the_repository), msg.buf,\n> > @@ -1014,7 +1018,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n> >                                 REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);\n> >                 if (!opts->quiet) {\n> >                         if (old_branch_info->path &&\n> > -                           advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)\n> > +                           advice_enabled(ADVICE_DETACHED_HEAD) &&\n> > +                           !opts->force_detach &&\n> > +                           !opts->switch_detach)\n> >                                 detach_advice(new_branch_info->name);\n> >                         describe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n> >                 }\n> > @@ -1541,8 +1547,11 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)\n> >                  */\n> >                 code = die_message(_(\"a branch is expected, got '%s'\"), branch_info->name);\n> >\n> > -       if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))\n> > +       if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD)) {\n> >                 advise(_(\"If you want to detach HEAD at the commit, try again with the --detach option.\"));\n> > +               advise(_(\"Also you can detach by default by setting the config variable \"\n> > +                        \"checkout.switchDetach to true.\"));\n> > +       }\n> \n> Nit \"Also, you can...\" or \"You can also...\"\n\nAs you prefer.\n\n> >\n> >         exit(code);\n> >  }\n> > @@ -1660,6 +1669,7 @@ static int checkout_branch(struct checkout_opts *opts,\n> >\n> >         if (!opts->implicit_detach &&\n> >             !opts->force_detach &&\n> > +           !opts->switch_detach &&\n> \n> Again force_detach + switch_detach\n> \n> >             !opts->new_branch &&\n> >             !opts->new_branch_force &&\n> >             new_branch_info->name &&\n> > @@ -2119,6 +2129,8 @@ int cmd_switch(int argc,\n> >         options = add_common_options(&opts, options);\n> >         options = add_common_switch_branch_options(&opts, options);\n> >\n> > +       repo_config_get_bool(the_repository, \"checkout.switchDetach\", &opts.switch_detach);\n> > +\n> >         cb_option = 'c';\n> >\n> >         return checkout_main(argc, argv, prefix, &opts, options,\n> > diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh\n> > index c91c4db9..3435ae98 100755\n> > --- a/t/t2060-switch.sh\n> > +++ b/t/t2060-switch.sh\n> > @@ -177,4 +177,31 @@ test_expect_success 'switch back when temporarily detached and checked out elsew\n> >         git -C wt2 switch --ignore-other-worktrees shared\n> >  '\n> >\n> > +test_expect_success 'switch with configured detach behavior from configuration ' '\n> \n> Trailing space here at the end, also the title is kinda confusing with the\n> configured + configuration, what about:\n> \"switch detaches on commit when switchDetach is set\"\n\nFor the trailing space I was mislead by the previous test, line 149; it\nhas its trailing space, I thought it was mandatory somehow.\nThank you for the rewording.\n\n> > +       test_when_finished \"\n> > +               test_config checkout.switchDetach false;\n> > +               git switch main\n> > +       \" &&\n> > +       test_config checkout.switchDetach true &&\n> > +       git switch main &&\n> > +       git symbolic-ref HEAD &&\n> > +       git switch main~ &&\n> > +       test_must_fail git symbolic-ref HEAD &&\n> > +       git switch - &&\n> > +       git symbolic-ref HEAD &&\n> > +       git switch - &&\n> > +       test_must_fail git symbolic-ref HEAD\n> > +'\n> > +\n> > +test_expect_success 'switch without configured detach behavior from configuration ' '\n> \n> Trailing space.\n> \n> > +       test_when_finished \"\n> > +               test_config checkout.switchDetach false;\n> \n> test_config already has test_when_finished\n> I would git config --unset.\n\nACK.\n\n> > +               git switch main\n> > +       \" &&\n> > +       test_config checkout.switchDetach false &&\n> \n> This is already false.\n\nI prefer to be explicit, to test the case where the user disables this\nfeature on purpose.\n\n> > +       git switch main &&\n> > +       git symbolic-ref HEAD &&\n> > +       test_must_fail git switch main~\n> > +'\n> > +\n> >  test_done\n> >\n> > base-commit: 2855562ca6a9c6b0e7bc780b050c1e83c9fcfbd0\n> > --\n> > Thibaud CANALE\n> > thican [at] thican [dot] net\n> > https://thican.net/\n> > GPG: rsa4096 2013-10-14 485EF628CB85CDD4CB7CFF0D52F5127650733A18\n> \n> Overall seems reasonable, it might be a design choice having to\n> explicitly use --detach, force_detach it's pretty clear about it but\n> I dunno.\n> \n> For now:\n> I would rewrite the commit.\n> Make sure that you need a new flag, force_detach looks very similar.\n> Fix the style issue.\n\nIt was already sure a new flag was necessary, using force detach flag is\nuncorrect as it will not anymore allow to use back a branch for example.\nWe can see this new flag is not everywhere force_detach is.\nFor testing, I tried only force_detach, the tests are failing.\n\nV2 will follow shortly.\n\nBest regards,\n\n\n-- \nThibaud CANALE\nthican [at] thican [dot] net\nhttps://thican.net/\nGPG: rsa4096 2013-10-14 485EF628CB85CDD4CB7CFF0D52F5127650733A18\n"},{"id":"540932","messageId":"3cfgodmlybyme4ggnybudz77tkdshnsi5rn263gv4ta6ru6cq4@o7j7ohf5jlmq","threadId":"65433","inReplyTo":"njuf62lav6akkmdyqutwk7pim5yutw5cuicjidwpe5eh6qnkqr@4ir6q4uhhvht","subject":"[RFC PATCH v2] switch: provide configurable detach","fromName":"Thibaud CANALE","fromEmail":"thican@thican.net","sentAt":"2026-04-04T21:14:30Z","receivedAt":"2026-04-04T21:14:32Z","isPatch":true,"body":"Some users won’t change to switch/restore commands and keep relying on\ncheckout command because of this “missing” feature:\n\nFor git-switch(1), configurable through option `checkout.switchDetach`,\nmatch git-checkout(1)’s detach behavior on non-branch references, such\nas commit id and \"tilde\" reference (i.e \"${my_branch}~2\").\nNo need to use explicit --detach option, automatically detach on\nnon-branch reference, then reattach when going back on a branch, even\nimplicitly through `git switch -` command.\n… like git-checkout(1) uses to do.\n\nNo impact on current behavior when option is unset or set to false.\n\nSigned-off-by: Thibaud CANALE <thican@thican.net>\n---\n Documentation/config/checkout.adoc | 14 ++++++++++++++\n builtin/checkout.c                 | 19 ++++++++++++++++---\n t/t2060-switch.sh                  | 27 +++++++++++++++++++++++++++\n 3 files changed, 57 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/checkout.adoc b/Documentation/config/checkout.adoc\nindex e35d2129..5309bed7 100644\n--- a/Documentation/config/checkout.adoc\n+++ b/Documentation/config/checkout.adoc\n@@ -22,6 +22,20 @@ commands or functionality in the future.\n \toption in `git checkout` and `git switch`. See\n \tlinkgit:git-switch[1] and linkgit:git-checkout[1].\n \n+`checkout.switchDetach`::\n+\tFor linkgit:git-switch[1], matches linkgit:git-checkout[1]’s detach behavior\n+\ton non-branch references, such as commit id and \"tilde\" reference (i.e\n+\t\"${my_branch}~2\").\n++\n+No need to use explicit --detach option, automatically detach on\n+non-branch reference, then reattach when going back on a branch, even\n+implicitly through `git switch -` command.\n++\n+… like linkgit:git-checkout[1] uses to do.\n++\n+No impact when option is unset or set to false, won't continue when trying to\n+detach HEAD without explicit --detach option.\n+\n `checkout.workers`::\n \tThe number of parallel workers to use when updating the working tree.\n \tThe default is one, i.e. sequential execution. If set to a value less\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e031e618..cd7b0a60 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -53,6 +53,7 @@ struct checkout_opts {\n \tint force;\n \tint force_detach;\n \tint implicit_detach;\n+\tint switch_detach;\n \tint writeout_stage;\n \tint overwrite_ignore;\n \tint ignore_skipworktree;\n@@ -1005,7 +1006,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \telse\n \t\tstrbuf_insertstr(&msg, 0, reflog_msg);\n \n-\tif (!strcmp(new_branch_info->name, \"HEAD\") && !new_branch_info->path && !opts->force_detach) {\n+\tif (!strcmp(new_branch_info->name, \"HEAD\") &&\n+\t    !new_branch_info->path &&\n+\t    !opts->force_detach &&\n+\t    !opts->switch_detach) {\n \t\t/* Nothing to do. */\n \t} else if (opts->force_detach || !new_branch_info->path) {\t/* No longer on any branch. */\n \t\trefs_update_ref(get_main_ref_store(the_repository), msg.buf,\n@@ -1014,7 +1018,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\tREF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path &&\n-\t\t\t    advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)\n+\t\t\t    advice_enabled(ADVICE_DETACHED_HEAD) &&\n+\t\t\t    !opts->force_detach &&\n+\t\t\t    !opts->switch_detach)\n \t\t\t\tdetach_advice(new_branch_info->name);\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n@@ -1541,8 +1547,11 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)\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+\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+\t\tadvise(_(\"You can also detach by default by setting the config \"\n+\t\t         \"variable checkout.switchDetach to true.\"));\n+\t}\n \n \texit(code);\n }\n@@ -1660,6 +1669,7 @@ static int checkout_branch(struct checkout_opts *opts,\n \n \tif (!opts->implicit_detach &&\n \t    !opts->force_detach &&\n+\t    !opts->switch_detach &&\n \t    !opts->new_branch &&\n \t    !opts->new_branch_force &&\n \t    new_branch_info->name &&\n@@ -2119,6 +2129,9 @@ int cmd_switch(int argc,\n \toptions = add_common_options(&opts, options);\n \toptions = add_common_switch_branch_options(&opts, options);\n \n+\trepo_config_get_bool(the_repository, \"checkout.switchDetach\",\n+\t                     &opts.switch_detach);\n+\n \tcb_option = 'c';\n \n \treturn checkout_main(argc, argv, prefix, &opts, options,\ndiff --git a/t/t2060-switch.sh b/t/t2060-switch.sh\nindex c91c4db9..3732a826 100755\n--- a/t/t2060-switch.sh\n+++ b/t/t2060-switch.sh\n@@ -177,4 +177,31 @@ test_expect_success 'switch back when temporarily detached and checked out elsew\n \tgit -C wt2 switch --ignore-other-worktrees shared\n '\n \n+test_expect_success 'switch with configured detach behavior from configuration' '\n+\ttest_when_finished \"\n+\t\tgit config --unset checkout.switchDetach;\n+\t\tgit switch main\n+\t\" &&\n+\ttest_config checkout.switchDetach true &&\n+\tgit switch main &&\n+\tgit symbolic-ref HEAD &&\n+\tgit switch main~ &&\n+\ttest_must_fail git symbolic-ref HEAD &&\n+\tgit switch - &&\n+\tgit symbolic-ref HEAD &&\n+\tgit switch - &&\n+\ttest_must_fail git symbolic-ref HEAD\n+'\n+\n+test_expect_success 'switch without configured detach behavior from configuration' '\n+\ttest_when_finished \"\n+\t\tgit config --unset checkout.switchDetach;\n+\t\tgit switch main\n+\t\" &&\n+\ttest_config checkout.switchDetach false &&\n+\tgit switch main &&\n+\tgit symbolic-ref HEAD &&\n+\ttest_must_fail git switch main~\n+'\n+\n test_done\n\nRange-diff against v1:\n1:  d9a7ca4c ! 1:  ea7ea74c switch: provide configurable detach\n    @@ Metadata\n      ## Commit message ##\n         switch: provide configurable detach\n     \n    -    Its purpose is to provide for git-switch(1) same detach behavior on\n    -    commit it than git-checkout(1) through configuration option\n    -    `checkout.switchDetach`.\n    +    Some users won’t change to switch/restore commands and keep relying on\n    +    checkout command because of this “missing” feature:\n    +\n    +    For git-switch(1), configurable through option `checkout.switchDetach`,\n    +    match git-checkout(1)’s detach behavior on non-branch references, such\n    +    as commit id and \"tilde\" reference (i.e \"${my_branch}~2\").\n    +    No need to use explicit --detach option, automatically detach on\n    +    non-branch reference, then reattach when going back on a branch, even\n    +    implicitly through `git switch -` command.\n    +    … like git-checkout(1) uses to do.\n    +\n    +    No impact on current behavior when option is unset or set to false.\n     \n         Signed-off-by: Thibaud CANALE <thican@thican.net>\n     \n    @@ Documentation/config/checkout.adoc: commands or functionality in the future.\n      \tlinkgit:git-switch[1] and linkgit:git-checkout[1].\n      \n     +`checkout.switchDetach`::\n    -+\tProvides for linkgit:git-switch[1] the same detach behavior on commit id\n    -+\tthan linkgit:git-checkout[1] without the explicit --detach option.\n    ++\tFor linkgit:git-switch[1], matches linkgit:git-checkout[1]’s detach behavior\n    ++\ton non-branch references, such as commit id and \"tilde\" reference (i.e\n    ++\t\"${my_branch}~2\").\n    +++\n    ++No need to use explicit --detach option, automatically detach on\n    ++non-branch reference, then reattach when going back on a branch, even\n    ++implicitly through `git switch -` command.\n    +++\n    ++… like linkgit:git-checkout[1] uses to do.\n    +++\n    ++No impact when option is unset or set to false, won't continue when trying to\n    ++detach HEAD without explicit --detach option.\n     +\n      `checkout.workers`::\n      \tThe number of parallel workers to use when updating the working tree.\n    @@ builtin/checkout.c: struct checkout_opts {\n      \tint force;\n      \tint force_detach;\n      \tint implicit_detach;\n    -+\tint switch_detach;  // For checkout.switchDetach configuration\n    ++\tint switch_detach;\n      \tint writeout_stage;\n      \tint overwrite_ignore;\n      \tint ignore_skipworktree;\n    @@ builtin/checkout.c: static void die_expecting_a_branch(const struct branch_info\n     -\tif (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD))\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    -+\t\tadvise(_(\"Also you can detach by default by setting the config variable \"\n    -+\t\t         \"checkout.switchDetach to true.\"));\n    ++\t\tadvise(_(\"You can also detach by default by setting the config \"\n    ++\t\t         \"variable checkout.switchDetach to true.\"));\n     +\t}\n      \n      \texit(code);\n    @@ builtin/checkout.c: int cmd_switch(int argc,\n      \toptions = add_common_options(&opts, options);\n      \toptions = add_common_switch_branch_options(&opts, options);\n      \n    -+\trepo_config_get_bool(the_repository, \"checkout.switchDetach\", &opts.switch_detach);\n    ++\trepo_config_get_bool(the_repository, \"checkout.switchDetach\",\n    ++\t                     &opts.switch_detach);\n     +\n      \tcb_option = 'c';\n      \n    @@ t/t2060-switch.sh: test_expect_success 'switch back when temporarily detached an\n      \tgit -C wt2 switch --ignore-other-worktrees shared\n      '\n      \n    -+test_expect_success 'switch with configured detach behavior from configuration ' '\n    ++test_expect_success 'switch with configured detach behavior from configuration' '\n     +\ttest_when_finished \"\n    -+\t\ttest_config checkout.switchDetach false;\n    ++\t\tgit config --unset checkout.switchDetach;\n     +\t\tgit switch main\n     +\t\" &&\n     +\ttest_config checkout.switchDetach true &&\n    @@ t/t2060-switch.sh: test_expect_success 'switch back when temporarily detached an\n     +\ttest_must_fail git symbolic-ref HEAD\n     +'\n     +\n    -+test_expect_success 'switch without configured detach behavior from configuration ' '\n    ++test_expect_success 'switch without configured detach behavior from configuration' '\n     +\ttest_when_finished \"\n    -+\t\ttest_config checkout.switchDetach false;\n    ++\t\tgit config --unset checkout.switchDetach;\n     +\t\tgit switch main\n     +\t\" &&\n     +\ttest_config checkout.switchDetach false &&\n-- \nThibaud CANALE\nthican [at] thican [dot] net\nhttps://thican.net/\nGPG: rsa4096 2013-10-14 485EF628CB85CDD4CB7CFF0D52F5127650733A18\n"},{"id":"540933","messageId":"ebp7ebhushwhai3fms7xaseqqdwui7x43i4knwv5tkfkqyksqv@xtzmqnfgjqxc","threadId":"65433","inReplyTo":"CALnO6CA2_MjKENu3CK-zCfoG=edEuMm6rohBtJ_8JC9WgMudEw@mail.gmail.com","subject":"Re: [RFC PATCH] switch: provide configurable detach","fromName":"Thibaud CANALE","fromEmail":"thican@thican.net","sentAt":"2026-04-04T21:26:56Z","receivedAt":"2026-04-04T21:26:58Z","isPatch":true,"body":"On Saturday 2026-04-04 12:58:48-0400, D. Ben Knoble <ben.knoble@gmail.com> wrote:\n\nHello,\n\n> On Sat, Apr 4, 2026 at 10:36 AM Thibaud CANALE <thican@thican.net> wrote:\n> >\n> > Its purpose is to provide for git-switch(1) same detach behavior on\n> > commit it than git-checkout(1) through configuration option\n> > `checkout.switchDetach`.\n> \n> I considered contributing something similar at one point, though I\n> would call it \"switch.detach\" (\"<command>.<option>\").\n> \n> I think I eventually decided against sending a patch because I was\n> concerned that such an option might make using git-switch in scripts\n> more of a headache. If I'm using it intentionally because it will fail\n> in some circumstances without --detach, now I've got to also do \"-c\n> switch.detach=false\" or something to control for the new\n> configuration.\n\nI see what you mean, I could argue it’s a very niche option, only\nconcerning git-switch(1), and in case we rely on git to block further\nactions if the provided reference is not a branch or simply working on\ndetached states, it should not have any impact.\nThis is what the newly implemented test cases are designed to ensure.\n\nAs mentioned it only implements the same behavior than git-checkout(1)’s\nconcerning non-branch references.\n\nFull disclosure, I am not even concerned about this feature, as I use\nswitch/restore daily since 2017, but not everyone I know does; so here I\ntry to ease transition for them.\n\n> On the one hand, I think that argument applies mostly to plumbing\n> rather than porcelain commands. On the other, a command for switching\n> branches whose behavior is reliable enough for scripts seems\n> worthwhile. So, idk :)\n> \n> -- \n> D. Ben Knoble\n\nAlso the purpose of this request is to provide this feature for Windows\nenvironment, which is out of my abilities concerning compilation, cannot\ncompile without such environment.\n\nBest regards,\n\n\n-- \nThibaud CANALE\nthican [at] thican [dot] net\nhttps://thican.net/\nGPG: rsa4096 2013-10-14 485EF628CB85CDD4CB7CFF0D52F5127650733A18\n"},{"id":"540981","messageId":"xmqq7bqkcah8.fsf@gitster.g","threadId":"65433","inReplyTo":"njuf62lav6akkmdyqutwk7pim5yutw5cuicjidwpe5eh6qnkqr@4ir6q4uhhvht","subject":"Re: [RFC PATCH] switch: provide configurable detach","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-06T16:36:03Z","receivedAt":"2026-04-06T16:36:07Z","isPatch":true,"body":"Thibaud CANALE <thican@thican.net> writes:\n\n> Its purpose is to provide for git-switch(1) same detach behavior on\n> commit it than git-checkout(1) through configuration option\n> `checkout.switchDetach`.\n>\n> Signed-off-by: Thibaud CANALE <thican@thican.net>\n> ---\n>  Documentation/config/checkout.adoc |  4 ++++\n>  builtin/checkout.c                 | 18 +++++++++++++++---\n>  t/t2060-switch.sh                  | 27 +++++++++++++++++++++++++++\n>  3 files changed, 46 insertions(+), 3 deletions(-)\n\nSorry, but I am fairly negative on this change.\n\n\"switch\" was an attempt to give folks an improved experience over\n\"checkout\".  The implied \"--detach\" was deliberately removed from\nthe command and I do not think it was a bad move for our user base\nwho wanted to have \"switch\" that can only be used to switch branches\n(as opposed to \"checkout\" that hecks out both files and branches).\nWith fewer choices in a single command that does only a single\nthing, the hope was to make it simpler to teach.\n\nAnd it is backwards to make \"switch\" configurable in that context.\nIt defeats a major point of \"git switch\".\n\nFWIW, \"git checkout\" that knows what is and what is not a branch\nname and does not require \"--detach\" when detaching to anything that\nis not a branch name is always available and will not be going away.\n"},{"id":"540990","messageId":"f0276575-9c75-45fb-8fcc-b465619d1b97@app.fastmail.com","threadId":"65433","inReplyTo":"xmqq7bqkcah8.fsf@gitster.g","subject":"Re: [RFC PATCH] switch: provide configurable detach","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2026-04-06T17:48:49Z","receivedAt":"2026-04-06T17:49:10Z","isPatch":true,"body":"On Mon, Apr 6, 2026, at 18:36, Junio C Hamano wrote:\n> Thibaud CANALE <thican@thican.net> writes:\n>>[snip]\n>>  3 files changed, 46 insertions(+), 3 deletions(-)\n>\n> Sorry, but I am fairly negative on this change.\n>\n>[snip]\n>\n> FWIW, \"git checkout\" that knows what is and what is not a branch\n> name and does not require \"--detach\" when detaching to anything that\n> is not a branch name is always available and will not be going away.\n\nOn the one hand, I personally mostly use git-checkout(1) because I never\nuse it to check out files and I like the detach behavior. So from that\nperspective I don’t understand Git users who interject when you provide\nexamples for something and you (incidental to the example and point) use\ngit-checkout(1) because that’s what you are used to. And they interject\nbecause git-checkout(1) is “obsolete” or something and you are setting a\nbad example (or something).\n\nOn the other hand, the BreakingChanges document does call\ngit-checkout(1) “superseded”.  And in that light I do understand why\npeople want to actively avoid git-checkout(1), including implementing\nreplacements for all relevant checkout-use cases in git-switch(1).\n\n    Superseded features that will not be deprecated\n\n    [...]\n\n    • The features git-checkout(1) offers are covered by the pair of\n      commands git-restore(1) and git-switch(1). Because the use of\n      git-checkout(1) is still widespread, and it is not expected that\n      this will change anytime soon, all three commands will stay.\n\nhttps://git-scm.com/docs/BreakingChanges\n"},{"id":"541027","messageId":"xmqqh5pn8ugs.fsf@gitster.g","threadId":"65433","inReplyTo":"f0276575-9c75-45fb-8fcc-b465619d1b97@app.fastmail.com","subject":"Re: [RFC PATCH] switch: provide configurable detach","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-07T00:50:11Z","receivedAt":"2026-04-07T00:50:14Z","isPatch":true,"body":"\"Kristoffer Haugsbakk\" <kristofferhaugsbakk@fastmail.com> writes:\n\n> On the other hand, the BreakingChanges document does call\n> git-checkout(1) “superseded”.  And in that light I do understand why\n> people want to actively avoid git-checkout(1), including implementing\n> replacements for all relevant checkout-use cases in git-switch(1).\n>\n>     Superseded features that will not be deprecated\n>\n>     [...]\n>\n>     • The features git-checkout(1) offers are covered by the pair of\n>       commands git-restore(1) and git-switch(1). Because the use of\n>       git-checkout(1) is still widespread, and it is not expected that\n>       this will change anytime soon, all three commands will stay.\n\nPerhaps a good first step would be to stop calling checkout\n\"superseded\".  As they stand, all three are equally viable.  In\nother words, I would not exactly call \"switch/restore\" a failed\nexperiment, but it wasn't a clear success story, either.\n\nI think \"still\" and \"anytime soon\" in the above paragraph are doing\ndisservice by implying that somehow we want to remove checkout but\nwe cannot yet, when the reality is that we wanted to be able to\nreplace checkout with switch/restore in the distant past, but that\nwas a misguided attempt and did not work very well.\n\nIt is not like switch/restore pair did not work well or anything,\nand in that sense switch/restore themselves are by no means\nfailures, but it was a failed experiment to introduce these two as a\nway to replace and kill checkout.  For those users to whom both\n\"checking out files\" and \"checkout out a branch\" appear as clear\nconcepts, there is no reason to abandon checkout.\n"}]}