{"thread":{"id":"57063","subject":"[PATCH 0/2] checkout: introduce \"--to-branch\" option","startedAt":"2021-12-10T06:22:14Z","lastAt":"2021-12-13T21:52:41Z","messageCount":16,"participants":["ZheNing Hu via GitGitGadget","Ævar Arnfjörð Bjarmason","Christian Couder","Bagas Sanjaya","Junio C Hamano","ZheNing Hu"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"443744","messageId":"pull.1095.git.1639117329.gitgitgadget@gmail.com","threadId":"57063","inReplyTo":null,"subject":"[PATCH 0/2] checkout: introduce \"--to-branch\" option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-10T06:22:07Z","receivedAt":"2021-12-10T06:22:14Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Sometimes people expect to checkout to a commit (or a commit which tag\nreference to) and develop on its corresponding branch. This may take some\nsteps:\n\n$ git checkout v1.1\n$ git log  # search for the branch dev1\n$ git checkout dev1\n\n\ncollapse the above steps into one step:\n\n$ git checkout --to-branch v1.1\nSwitched to branch 'refs/heads/dev1'\n\n\nThis will help users switch to the unqiue branch quickly.\n\nThanks.\n\nZheNing Hu (2):\n  checkout: handling branch_info memory leak\n  checkout: introduce \"--to-branch\" option\n\n Documentation/git-checkout.txt |  8 +++-\n builtin/checkout.c             | 49 +++++++++++++++++++-\n t/t2018-checkout-branch.sh     | 85 ++++++++++++++++++++++++++++++++++\n t/t9902-completion.sh          |  1 +\n 4 files changed, 140 insertions(+), 3 deletions(-)\n\n\nbase-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1095%2Fadlternative%2Fcheckout-w-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1095/adlternative/checkout-w-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1095\n-- \ngitgitgadget\n"},{"id":"443745","messageId":"e9f6be692db40978b835e7298c1a1c664566ec84.1639117329.git.gitgitgadget@gmail.com","threadId":"57063","inReplyTo":"pull.1095.git.1639117329.gitgitgadget@gmail.com","subject":"[PATCH 1/2] checkout: handling branch_info memory leak","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-10T06:22:08Z","receivedAt":"2021-12-10T06:22:14Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nOn the checkout code path, we dynamically allocate\nmemory for `refname` and `path` in `struct branch_info`,\nbut did not free their memory at the end. So introduce\nbranch_info_clear() which can free branch_info memory.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n builtin/checkout.c | 16 ++++++++++++++--\n 1 file changed, 14 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex cbf73b8c9f6..3eeac3147f2 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -103,6 +103,15 @@ struct branch_info {\n \tchar *checkout;\n };\n \n+static void branch_info_clear(struct branch_info *branch_info) {\n+\tif (branch_info->path) {\n+\t\tfree((char *)branch_info->path);\n+\t\tbranch_info->path = NULL;\n+\t}\n+\tif (branch_info->refname)\n+\t\tFREE_AND_NULL(branch_info->refname);\n+}\n+\n static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,\n \t\t\t      int changed)\n {\n@@ -1578,6 +1587,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n {\n \tstruct branch_info new_branch_info;\n \tint parseopt_flags = 0;\n+\tint ret = 0;\n \n \tmemset(&new_branch_info, 0, sizeof(new_branch_info));\n \topts->overwrite_ignore = 1;\n@@ -1768,9 +1778,11 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \n \tUNLEAK(opts);\n \tif (opts->patch_mode || opts->pathspec.nr)\n-\t\treturn checkout_paths(opts, &new_branch_info);\n+\t\tret = checkout_paths(opts, &new_branch_info);\n \telse\n-\t\treturn checkout_branch(opts, &new_branch_info);\n+\t\tret = checkout_branch(opts, &new_branch_info);\n+\tbranch_info_clear(&new_branch_info);\n+\treturn ret;\n }\n \n int cmd_checkout(int argc, const char **argv, const char *prefix)\n-- \ngitgitgadget\n\n"},{"id":"443746","messageId":"254b352e31029d8151eb6a974fdf8c127340cf79.1639117329.git.gitgitgadget@gmail.com","threadId":"57063","inReplyTo":"pull.1095.git.1639117329.gitgitgadget@gmail.com","subject":"[PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-10T06:22:09Z","receivedAt":"2021-12-10T06:22:16Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nWhen we want checkout to a branch (e.g. dev1) which reference\nto a commit, but sometimes we only remember the tag (e.g. v1.1)\non it, we will use `git checkout v1.1` to find the commit first,\ngit will be in the state of deatching HEAD, so we have to search the\nbranches on the commit and checkout the branch we perfer. This will\nbe a bit cumbersome.\n\nIntroduce \"--to-branch\" option, `git checkout --to-branch <tag>`\nand `git checkout --to-branch <commit>` will search all branches\nand find a unique branch reference to the commit (or the commit which\nthe tag reference to) and checkout to it. If the commit have more\nthan one branches, it will report error \"here are more than one\nbranch on commit\".\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-checkout.txt |  8 +++-\n builtin/checkout.c             | 33 +++++++++++++\n t/t2018-checkout-branch.sh     | 85 ++++++++++++++++++++++++++++++++++\n t/t9902-completion.sh          |  1 +\n 4 files changed, 126 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex d473c9bf387..2a240699fd9 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n [verse]\n 'git checkout' [-q] [-f] [-m] [<branch>]\n 'git checkout' [-q] [-f] [-m] --detach [<branch>]\n-'git checkout' [-q] [-f] [-m] [--detach] <commit>\n+'git checkout' [-q] [-f] [-m] [--detach] [-w|--to-branch] <commit>\n 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]\n@@ -210,6 +210,12 @@ variable.\n \t`<commit>` is not a branch name.  See the \"DETACHED HEAD\" section\n \tbelow for details.\n \n+-w::\n+--to-branch::\n+\tRather than checking out a commit to work on it, checkout out\n+\tto the unique branch on it. If there are multiple branches on\n+\tthe commit, the checkout will fail.\n+\n --orphan <new_branch>::\n \tCreate a new 'orphan' branch, named `<new_branch>`, started from\n \t`<start_point>` and switch to it.  The first commit made on this\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 3eeac3147f2..696248b05c0 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -28,6 +28,7 @@\n #include \"xdiff-interface.h\"\n #include \"entry.h\"\n #include \"parallel-checkout.h\"\n+#include \"../ref-filter.h\"\n \n static const char * const checkout_usage[] = {\n \tN_(\"git checkout [<options>] <branch>\"),\n@@ -70,6 +71,7 @@ struct checkout_opts {\n \tint empty_pathspec_ok;\n \tint checkout_index;\n \tint checkout_worktree;\n+\tint to_branch;\n \tconst char *ignore_unmerged_opt;\n \tint ignore_unmerged;\n \tint pathspec_file_nul;\n@@ -1512,6 +1514,35 @@ static int checkout_branch(struct checkout_opts *opts,\n \t\t    (flag & REF_ISSYMREF) && is_null_oid(&rev))\n \t\t\treturn switch_unborn_to_new_branch(opts);\n \t}\n+\tif (opts->to_branch) {\n+\t\tstruct ref_filter filter;\n+\t\tstruct ref_array array;\n+\t\tint i;\n+\t\tint count = 0;\n+\t\tconst char *unused_pattern = NULL;\n+\n+\t\tmemset(&array, 0, sizeof(array));\n+\t\tmemset(&filter, 0, sizeof(filter));\n+\t\tfilter.kind = FILTER_REFS_BRANCHES;\n+\t\tfilter.name_patterns = &unused_pattern;\n+\t\tfilter_refs(&array, &filter, filter.kind);\n+\t\tfor (i = 0; i < array.nr; i++) {\n+\t\t\tif (oideq(&array.items[i]->objectname, &new_branch_info->oid)) {\n+\t\t\t\tif (count)\n+\t\t\t\t\tdie(_(\"here are more than one branch on commit %s\"), oid_to_hex(&new_branch_info->oid));\n+\t\t\t\tcount++;\n+\t\t\t\tif (new_branch_info->refname)\n+\t\t\t\t\tfree((char *)new_branch_info->refname);\n+\t\t\t\tnew_branch_info->refname = xstrdup(array.items[i]->refname);\n+\t\t\t\tif (new_branch_info->path)\n+\t\t\t\t\tfree((char *)new_branch_info->path);\n+\t\t\t\tnew_branch_info->path = xstrdup(array.items[i]->refname);\n+\t\t\t\tnew_branch_info->name = new_branch_info->path;\n+\t\t\t}\n+\t\t}\n+\t\tref_array_clear(&array);\n+\t}\n+\n \treturn switch_branches(opts, new_branch_info);\n }\n \n@@ -1797,6 +1828,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL('l', NULL, &opts.new_branch_log, N_(\"create reflog for new branch\")),\n \t\tOPT_BOOL(0, \"guess\", &opts.dwim_new_local_branch,\n \t\t\t N_(\"second guess 'git checkout <no-such-branch>' (default)\")),\n+\t\tOPT_BOOL('w', \"to-branch\", &opts.to_branch,\n+\t\t\t N_(\"checkout to a branch from a commit or a tag\")),\n \t\tOPT_BOOL(0, \"overlay\", &opts.overlay_mode, N_(\"use overlay mode (default)\")),\n \t\tOPT_END()\n \t};\ndiff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh\nindex 93be1c0eae5..53e45cfe7fd 100755\n--- a/t/t2018-checkout-branch.sh\n+++ b/t/t2018-checkout-branch.sh\n@@ -270,4 +270,89 @@ test_expect_success 'checkout -b rejects an extra path argument' '\n \ttest_i18ngrep \"Cannot update paths and switch to branch\" err\n '\n \n+test_expect_success 'checkout -w with oid' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tgit branch -M main &&\n+\t\ttest_commit initial file1 &&\n+\t\tHEAD1=$(git rev-parse --verify HEAD) &&\n+\t\tgit switch -c dev &&\n+\t\ttest_commit step2 file2 &&\n+\t\tHEAD2=$(git rev-parse --verify HEAD) &&\n+\n+\t\tgit checkout -w $HEAD1 &&\n+\n+\t\techo \"refs/heads/main\" >ref.expect &&\n+\t\tgit rev-parse --symbolic-full-name HEAD >ref.actual &&\n+\t\ttest_cmp ref.expect ref.actual &&\n+\n+\t\techo \"$HEAD1\" >oid.expect &&\n+\t\tgit rev-parse --verify HEAD >oid.actual &&\n+\t\ttest_cmp oid.expect oid.actual &&\n+\n+\t\tgit checkout -w $HEAD2 &&\n+\n+\t\techo \"refs/heads/dev\" >ref.expect &&\n+\t\tgit rev-parse --symbolic-full-name HEAD >ref.actual &&\n+\t\ttest_cmp ref.expect ref.actual &&\n+\n+\t\techo \"$HEAD2\" >oid.expect &&\n+\t\tgit rev-parse --verify HEAD >oid.actual &&\n+\t\ttest_cmp oid.expect oid.actual\n+\t)\n+'\n+\n+test_expect_success 'checkout -w with tag' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tgit branch -M main &&\n+\t\ttest_commit initial file1 &&\n+\t\tgit tag v1 &&\n+\t\tHEAD1=$(git rev-parse --verify HEAD) &&\n+\t\tgit switch -c dev &&\n+\t\ttest_commit step2 file2 &&\n+\t\tgit tag v2 &&\n+\t\tHEAD2=$(git rev-parse --verify HEAD) &&\n+\n+\t\tgit checkout -w v1 &&\n+\n+\t\techo \"refs/heads/main\" >ref.expect &&\n+\t\tgit rev-parse --symbolic-full-name HEAD >ref.actual &&\n+\t\ttest_cmp ref.expect ref.actual &&\n+\n+\t\techo \"$HEAD1\" >oid.expect &&\n+\t\tgit rev-parse --verify HEAD >oid.actual &&\n+\t\ttest_cmp oid.expect oid.actual &&\n+\n+\t\tgit checkout -w v2 &&\n+\n+\t\techo \"refs/heads/dev\" >ref.expect &&\n+\t\tgit rev-parse --symbolic-full-name HEAD >ref.actual &&\n+\t\ttest_cmp ref.expect ref.actual &&\n+\n+\t\techo \"$HEAD2\" >oid.expect &&\n+\t\tgit rev-parse --verify HEAD >oid.actual &&\n+\t\ttest_cmp oid.expect oid.actual\n+\t)\n+'\n+\n+test_expect_success 'checkout -w with branches' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tgit branch -M main &&\n+\t\ttest_commit initial file1 &&\n+\t\tHEAD1=$(git rev-parse --verify HEAD) &&\n+\t\tgit tag v1 &&\n+\t\tgit branch dev &&\n+\t\ttest_must_fail git checkout -w $HEAD1 &&\n+\t\ttest_must_fail git checkout -w dev\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 518203fbe07..4ec134338c2 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2021,6 +2021,7 @@ test_expect_success 'double dash \"git checkout\"' '\n \t--orphan=Z\n \t--ours Z\n \t--theirs Z\n+\t--to-branch Z\n \t--merge Z\n \t--conflict=Z\n \t--patch Z\n-- \ngitgitgadget\n"},{"id":"443748","messageId":"211210.865yrxdj95.gmgdl@evledraar.gmail.com","threadId":"57063","inReplyTo":"e9f6be692db40978b835e7298c1a1c664566ec84.1639117329.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/2] checkout: handling branch_info memory leak","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-10T07:13:23Z","receivedAt":"2021-12-10T07:14:19Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Dec 10 2021, ZheNing Hu via GitGitGadget wrote:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> On the checkout code path, we dynamically allocate\n> memory for `refname` and `path` in `struct branch_info`,\n> but did not free their memory at the end. So introduce\n> branch_info_clear() which can free branch_info memory.\n\nThis (partially) duplicates my 9081a421a6d (checkout: fix \"branch info\"\nmemory leaks, 2021-11-16) which is in \"next\" already.\n"},{"id":"443751","messageId":"CAP8UFD0MK++3QdMAvJ6Az0LZyXUWXRJcRQPG1THKw4CFW0yRLg@mail.gmail.com","threadId":"57063","inReplyTo":"254b352e31029d8151eb6a974fdf8c127340cf79.1639117329.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2021-12-10T08:34:43Z","receivedAt":"2021-12-10T08:34:56Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Dec 10, 2021 at 7:22 AM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> When we want checkout to a branch (e.g. dev1) which reference\n> to a commit, but sometimes we only remember the tag (e.g. v1.1)\n> on it, we will use `git checkout v1.1` to find the commit first,\n> git will be in the state of deatching HEAD, so we have to search the\n\ns/deatching/detached/\n\nIt's possible, and likely faster, to use something like `git log -1\nv1.1` or `git show <sha>` to see which branches point to the tag or\ncommit, as the branches are added as decoration to the output.\n\n> branches on the commit and checkout the branch we perfer. This will\n\ns/perfer/prefer/\n\n> be a bit cumbersome.\n>\n> Introduce \"--to-branch\" option, `git checkout --to-branch <tag>`\n> and `git checkout --to-branch <commit>` will search all branches\n> and find a unique branch reference to the commit (or the commit which\n> the tag reference to) and checkout to it. If the commit have more\n\ns/have/has/\n\n> than one branches, it will report error \"here are more than one\n\ns/branches/branch/\n\n> branch on commit\".\n\nMaybe reporting something like \"more than one branch point to commit\nXXX\" would be a bit better.\n\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  Documentation/git-checkout.txt |  8 +++-\n>  builtin/checkout.c             | 33 +++++++++++++\n>  t/t2018-checkout-branch.sh     | 85 ++++++++++++++++++++++++++++++++++\n>  t/t9902-completion.sh          |  1 +\n>  4 files changed, 126 insertions(+), 1 deletion(-)\n>\n> diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\n> index d473c9bf387..2a240699fd9 100644\n> --- a/Documentation/git-checkout.txt\n> +++ b/Documentation/git-checkout.txt\n> @@ -10,7 +10,7 @@ SYNOPSIS\n>  [verse]\n>  'git checkout' [-q] [-f] [-m] [<branch>]\n>  'git checkout' [-q] [-f] [-m] --detach [<branch>]\n> -'git checkout' [-q] [-f] [-m] [--detach] <commit>\n> +'git checkout' [-q] [-f] [-m] [--detach] [-w|--to-branch] <commit>\n\nIt's a bit strange that --detach can be used along with the new\noption, as the purpose of the new option is to not detach. It makes\none wonder what happens when both --detach and --to-branch are used.\n\nI wonder if all the following lines:\n\n      git checkout [-q] [-f] [-m] [<branch>]\n      git checkout [-q] [-f] [-m] --detach [<branch>]\n      git checkout [-q] [-f] [-m] [--detach] <commit>\n\ncould be replaced with just:\n\n      git checkout [-q] [-f] [-m] [--detach|--to-branch] <commitish>\n\n>  'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n>  'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...\n>  'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]\n> @@ -210,6 +210,12 @@ variable.\n>         `<commit>` is not a branch name.  See the \"DETACHED HEAD\" section\n>         below for details.\n>\n> +-w::\n> +--to-branch::\n\nUsing a short option name like \"-w\" might not be a good idea at this\npoint. Maybe if many people use the long option a lot, they will want\na short option name, but we can add it then instead of using one of\nthe few left right now.\n\n> +       Rather than checking out a commit to work on it, checkout out\n\n\"checking out a commit to work on it\" might not describe well that it\nworks when we pass a tag too and that we checkout the underlying\ncommit in the detached HEAD mode by default.\n\n> +       to the unique branch on it. If there are multiple branches on\n> +       the commit, the checkout will fail.\n\nIt might be a bit better to say that a branch \"points to\" a commit,\nrather than that it is \"on\" a commit.\n\n>  --orphan <new_branch>::\n>         Create a new 'orphan' branch, named `<new_branch>`, started from\n>         `<start_point>` and switch to it.  The first commit made on this\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index 3eeac3147f2..696248b05c0 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -28,6 +28,7 @@\n>  #include \"xdiff-interface.h\"\n>  #include \"entry.h\"\n>  #include \"parallel-checkout.h\"\n> +#include \"../ref-filter.h\"\n\nAre you sure that \"../\" is needed above?\n\n>  static const char * const checkout_usage[] = {\n>         N_(\"git checkout [<options>] <branch>\"),\n> @@ -70,6 +71,7 @@ struct checkout_opts {\n>         int empty_pathspec_ok;\n>         int checkout_index;\n>         int checkout_worktree;\n> +       int to_branch;\n>         const char *ignore_unmerged_opt;\n>         int ignore_unmerged;\n>         int pathspec_file_nul;\n> @@ -1512,6 +1514,35 @@ static int checkout_branch(struct checkout_opts *opts,\n>                     (flag & REF_ISSYMREF) && is_null_oid(&rev))\n>                         return switch_unborn_to_new_branch(opts);\n>         }\n> +       if (opts->to_branch) {\n> +               struct ref_filter filter;\n> +               struct ref_array array;\n> +               int i;\n> +               int count = 0;\n> +               const char *unused_pattern = NULL;\n> +\n> +               memset(&array, 0, sizeof(array));\n> +               memset(&filter, 0, sizeof(filter));\n> +               filter.kind = FILTER_REFS_BRANCHES;\n> +               filter.name_patterns = &unused_pattern;\n> +               filter_refs(&array, &filter, filter.kind);\n> +               for (i = 0; i < array.nr; i++) {\n> +                       if (oideq(&array.items[i]->objectname, &new_branch_info->oid)) {\n> +                               if (count)\n> +                                       die(_(\"here are more than one branch on commit %s\"), oid_to_hex(&new_branch_info->oid));\n> +                               count++;\n> +                               if (new_branch_info->refname)\n> +                                       free((char *)new_branch_info->refname);\n> +                               new_branch_info->refname = xstrdup(array.items[i]->refname);\n> +                               if (new_branch_info->path)\n> +                                       free((char *)new_branch_info->path);\n> +                               new_branch_info->path = xstrdup(array.items[i]->refname);\n> +                               new_branch_info->name = new_branch_info->path;\n> +                       }\n> +               }\n> +               ref_array_clear(&array);\n\nIt might be my personal taste, but I would find it cleaner and easier\nto understand if a separate function to find the branch we are looking\nfor was called, instead of adding all the code here.\n\n> +       }\n> +\n>         return switch_branches(opts, new_branch_info);\n>  }\n>\n> @@ -1797,6 +1828,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n>                 OPT_BOOL('l', NULL, &opts.new_branch_log, N_(\"create reflog for new branch\")),\n>                 OPT_BOOL(0, \"guess\", &opts.dwim_new_local_branch,\n>                          N_(\"second guess 'git checkout <no-such-branch>' (default)\")),\n> +               OPT_BOOL('w', \"to-branch\", &opts.to_branch,\n> +                        N_(\"checkout to a branch from a commit or a tag\")),\n>                 OPT_BOOL(0, \"overlay\", &opts.overlay_mode, N_(\"use overlay mode (default)\")),\n>                 OPT_END()\n>         };\n> diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh\n> index 93be1c0eae5..53e45cfe7fd 100755\n> --- a/t/t2018-checkout-branch.sh\n> +++ b/t/t2018-checkout-branch.sh\n\nI plan to look at the tests after we decide how the new option relates\nto --detach.\n\nThanks!\n"},{"id":"443761","messageId":"211210.86y24sdd7k.gmgdl@evledraar.gmail.com","threadId":"57063","inReplyTo":"254b352e31029d8151eb6a974fdf8c127340cf79.1639117329.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-10T08:59:43Z","receivedAt":"2021-12-10T09:24:51Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Dec 10 2021, ZheNing Hu via GitGitGadget wrote:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> When we want checkout to a branch (e.g. dev1) which reference\n> to a commit, but sometimes we only remember the tag (e.g. v1.1)\n> on it, we will use `git checkout v1.1` to find the commit first,\n> git will be in the state of deatching HEAD, so we have to search the\n> branches on the commit and checkout the branch we perfer. This will\n> be a bit cumbersome.\n>\n> Introduce \"--to-branch\" option, `git checkout --to-branch <tag>`\n> and `git checkout --to-branch <commit>` will search all branches\n> and find a unique branch reference to the commit (or the commit which\n> the tag reference to) and checkout to it. If the commit have more\n> than one branches, it will report error \"here are more than one\n> branch on commit\".\n>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  Documentation/git-checkout.txt |  8 +++-\n>  builtin/checkout.c             | 33 +++++++++++++\n>  t/t2018-checkout-branch.sh     | 85 ++++++++++++++++++++++++++++++++++\n>  t/t9902-completion.sh          |  1 +\n>  4 files changed, 126 insertions(+), 1 deletion(-)\n>\n> diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\n> index d473c9bf387..2a240699fd9 100644\n> --- a/Documentation/git-checkout.txt\n> +++ b/Documentation/git-checkout.txt\n> @@ -10,7 +10,7 @@ SYNOPSIS\n>  [verse]\n>  'git checkout' [-q] [-f] [-m] [<branch>]\n>  'git checkout' [-q] [-f] [-m] --detach [<branch>]\n> -'git checkout' [-q] [-f] [-m] [--detach] <commit>\n> +'git checkout' [-q] [-f] [-m] [--detach] [-w|--to-branch] <commit>\n>  'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n>  'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...\n>  'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]\n> @@ -210,6 +210,12 @@ variable.\n>  \t`<commit>` is not a branch name.  See the \"DETACHED HEAD\" section\n>  \tbelow for details.\n>  \n> +-w::\n> +--to-branch::\n> +\tRather than checking out a commit to work on it, checkout out\n> +\tto the unique branch on it. If there are multiple branches on\n> +\tthe commit, the checkout will fail.\n> +\n\nSo basically what this option implements is something that could be done\nas a shellscript of:\n\t\n\tgit_checkout_branch_from_oid () {\n\t\trev=$1\n\t\tgit for-each-ref --format='%(refname:strip=2)' --points-at $rev >/tmp/found\n\t\tif test $(wc -l </tmp/found) -ne 1\n\t        then\n\t\t\techo \"Goldilocks error in finding $rev: $(cat /tmp/found)\"\n\t                return 1\n\t\tfi\n\t        git checkout $found\n\t}\n\nWhich is not to say that it isn't useful, but that I think adding this\nto \"git checkout\" specifically is adding this to the wrong level. Isn't\nthis useful to most things that parse revisions? I.e. wouldn't a better\ninterface be via the peel syntax?\n\n    oid=$(git rev-parse HEAD)\n    git checkout $oid^{tobranch}\n\nDoing it that way would allow any arbitrary command that takes revisions\nnow access to that, and we could have e.g. \"^{tobranches}\" too, so you\ncould do:\n\n    git for-each-ref --format='%(refname:strip=2)' $oid^{tobranches}\n\nOr:\n\n    git log $oid^{tobranches}\n\nI think implementing that is a bit harder. It's peel_onion() in\nobject-name.c. I think parse_branchname_arg() via get_oid_mb() is now\nonly capable of filling in an OID for a given name, and then checking\nout that name comes as a separate step, and you can't just return\ne.g. \"master\".\n\nBut I don't think anything stops us from adjusting those functions a bit\nso that get_oid_with_context(() and friends could pass down say an\noptional \"struct string_list *\", and the \"peel\" could then be expanded\nto that.\n\nSimilar to how we have \"git chekout -\", and the \"-\" is understood by\nsome commands, but not all (via some opt-in whose location I forget...).\n"},{"id":"443793","messageId":"7a8206d3-f06f-07b0-c60b-dea549af7731@gmail.com","threadId":"57063","inReplyTo":"254b352e31029d8151eb6a974fdf8c127340cf79.1639117329.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"Bagas Sanjaya","fromEmail":"bagasdotme@gmail.com","sentAt":"2021-12-10T11:51:34Z","receivedAt":"2021-12-10T11:51:41Z","isPatch":true,"sender":{"key":"bagasdotme@gmail.com","avatar":"https://avatars.githubusercontent.com/u/40219486?v=4"},"body":"On 10/12/21 13.22, ZheNing Hu via GitGitGadget wrote:\n> +-w::\n> +--to-branch::\n> +\tRather than checking out a commit to work on it, checkout out\n> +\tto the unique branch on it. If there are multiple branches on\n> +\tthe commit, the checkout will fail.\n> +\n\nDid you mean unique branch that contains the commit?\n\n-- \nAn old man doll... just what I always wanted! - Clara\n"},{"id":"443842","messageId":"xmqq5yrwm7km.fsf@gitster.g","threadId":"57063","inReplyTo":"254b352e31029d8151eb6a974fdf8c127340cf79.1639117329.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-10T22:14:01Z","receivedAt":"2021-12-10T22:14:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> When we want checkout to a branch (e.g. dev1) which reference\n> to a commit, but sometimes we only remember the tag (e.g. v1.1)\n> on it, we will use `git checkout v1.1` to find the commit first,\n> git will be in the state of deatching HEAD, so we have to search the\n> branches on the commit and checkout the branch we perfer. This will\n> be a bit cumbersome.\n>\n> Introduce \"--to-branch\" option, `git checkout --to-branch <tag>`\n> and `git checkout --to-branch <commit>` will search all branches\n> and find a unique branch reference to the commit (or the commit which\n> the tag reference to) and checkout to it. If the commit have more\n> than one branches, it will report error \"here are more than one\n> branch on commit\".\n\nSorry, but the above explanation does not make any sense to me.  \n\nIt is unclear if you mean \"dev1\" exactly point at the commit tagged\nas v1.1, or you want the branch \"dev1\" that is a descedanant of\nv1.1.  Without telling that to the reader, the above explanation is\nuseless.\n\nAnd whether you meant the former or the latter, neither use case does\nnot make much sense.\n\nFirst, suppose you meant \"checkout --to-branch v1.1\" to find a\nbranch whose tip exactly points at v1.1.  You instead check out\n\"dev1\" branch, and work on it to advance its history.  When you are\ndone, you may go to another branch and work on something else.\n\nBut then what?  When you need another topic that also needs to be\nlater merge-able to v1.1, \"checkout --to-branch v1.1\" no longer will\nbe able to find \"dev1\", because, well, you have already used it to\nbuild something else.\n\nSo, \"--to-branch v1.1\" that finds and checks out a branch whose tip\nexactly points at v1.1 would be pretty useless.\n\nSo let's correct the unwritten assumption and say \"--to-branch v1.1\"\nfinds a branch that is descendant of the tag.  It is like I have\nmaint-2.33 branch to prepare for v2.33.1, v2.33.2,... maintenance\nreleases and being able to find maint-2.33 by saying v2.33.2 (or\nv2.33.1) _might_ be convenient.\n\nBut that would only be true if there is only one single branch per\nfamily of tags (in the above example, v2.33.* tags).  You cannot use\nthe workflow where many topic branches run in parallel, and get\nmerged to the integration branch(es) only after they are ready,\nbecause you need bugfix-1-for-v2.33, bugfix-2-for-v2.33,... branches\nall forked from v2.33.0 (or a commit with a later tag in the v2.33.*\nfamily) to cook these independent fixes that are destined for the\nmaint-2.33 integration branch, so you cannot uniquely find maint-2.33\nby saying v2.33.0 or v2.33.1 or whatever.\n\nI also sense that the first paragraph of the proposed log message\nfor this commit hints that the user needs a bit more studying of\nexisting tools.  When we know v1.1 but do not know if we already\nhave branches that are based on it, we DO NOT do \"git checkout v1.1\".\nInstead the first thing we would do is \"git branch --contains v1.1\"\n(add \"--no-merged main\" to exclude the branches that have already\ngraduated to 'main').\n\nSo, for this partcular topic, what I would recommend is *not* jump\nin and add a new feature, but to study what's available and build a\nworkflow around the existing features.\n\n\n\n\n"},{"id":"443866","messageId":"CAOLTT8SLQXbH_GGF1sArVSsZ1b=poqMPhdSWOkWPS0f_SGoONw@mail.gmail.com","threadId":"57063","inReplyTo":"CAP8UFD0MK++3QdMAvJ6Az0LZyXUWXRJcRQPG1THKw4CFW0yRLg@mail.gmail.com","subject":"Re: [PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-12-11T06:34:46Z","receivedAt":"2021-12-11T06:35:01Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Christian Couder <christian.couder@gmail.com> 于2021年12月10日周五 16:34写道：\n\n> >  [verse]\n> >  'git checkout' [-q] [-f] [-m] [<branch>]\n> >  'git checkout' [-q] [-f] [-m] --detach [<branch>]\n> > -'git checkout' [-q] [-f] [-m] [--detach] <commit>\n> > +'git checkout' [-q] [-f] [-m] [--detach] [-w|--to-branch] <commit>\n>\n> It's a bit strange that --detach can be used along with the new\n> option, as the purpose of the new option is to not detach. It makes\n> one wonder what happens when both --detach and --to-branch are used.\n>\n\nWhen both --detach and --to-branch are used, --detach will work...\nOf course, it should be reasonable to prevent them from being used at the\nsame time.\n\n> I wonder if all the following lines:\n>\n>       git checkout [-q] [-f] [-m] [<branch>]\n>       git checkout [-q] [-f] [-m] --detach [<branch>]\n>       git checkout [-q] [-f] [-m] [--detach] <commit>\n>\n> could be replaced with just:\n>\n>       git checkout [-q] [-f] [-m] [--detach|--to-branch] <commitish>\n>\n\nWell, it will be much more concise.\n\n> >  'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n> >  'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>...\n> >  'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul]\n> > @@ -210,6 +210,12 @@ variable.\n> >         `<commit>` is not a branch name.  See the \"DETACHED HEAD\" section\n> >         below for details.\n> >\n> > +-w::\n> > +--to-branch::\n>\n> Using a short option name like \"-w\" might not be a good idea at this\n> point. Maybe if many people use the long option a lot, they will want\n> a short option name, but we can add it then instead of using one of\n> the few left right now.\n>\n\nMakes sense.\n\n> > +       Rather than checking out a commit to work on it, checkout out\n>\n> \"checking out a commit to work on it\" might not describe well that it\n> works when we pass a tag too and that we checkout the underlying\n> commit in the detached HEAD mode by default.\n>\n> > +       to the unique branch on it. If there are multiple branches on\n> > +       the commit, the checkout will fail.\n>\n> It might be a bit better to say that a branch \"points to\" a commit,\n> rather than that it is \"on\" a commit.\n>\n\nOK.\n\n> >  static const char * const checkout_usage[] = {\n> >         N_(\"git checkout [<options>] <branch>\"),\n> > @@ -70,6 +71,7 @@ struct checkout_opts {\n> >         int empty_pathspec_ok;\n> >         int checkout_index;\n> >         int checkout_worktree;\n> > +       int to_branch;\n> >         const char *ignore_unmerged_opt;\n> >         int ignore_unmerged;\n> >         int pathspec_file_nul;\n> > @@ -1512,6 +1514,35 @@ static int checkout_branch(struct checkout_opts *opts,\n> >                     (flag & REF_ISSYMREF) && is_null_oid(&rev))\n> >                         return switch_unborn_to_new_branch(opts);\n> >         }\n> > +       if (opts->to_branch) {\n> > +               struct ref_filter filter;\n> > +               struct ref_array array;\n> > +               int i;\n> > +               int count = 0;\n> > +               const char *unused_pattern = NULL;\n> > +\n> > +               memset(&array, 0, sizeof(array));\n> > +               memset(&filter, 0, sizeof(filter));\n> > +               filter.kind = FILTER_REFS_BRANCHES;\n> > +               filter.name_patterns = &unused_pattern;\n> > +               filter_refs(&array, &filter, filter.kind);\n> > +               for (i = 0; i < array.nr; i++) {\n> > +                       if (oideq(&array.items[i]->objectname, &new_branch_info->oid)) {\n> > +                               if (count)\n> > +                                       die(_(\"here are more than one branch on commit %s\"), oid_to_hex(&new_branch_info->oid));\n> > +                               count++;\n> > +                               if (new_branch_info->refname)\n> > +                                       free((char *)new_branch_info->refname);\n> > +                               new_branch_info->refname = xstrdup(array.items[i]->refname);\n> > +                               if (new_branch_info->path)\n> > +                                       free((char *)new_branch_info->path);\n> > +                               new_branch_info->path = xstrdup(array.items[i]->refname);\n> > +                               new_branch_info->name = new_branch_info->path;\n> > +                       }\n> > +               }\n> > +               ref_array_clear(&array);\n>\n> It might be my personal taste, but I would find it cleaner and easier\n> to understand if a separate function to find the branch we are looking\n> for was called, instead of adding all the code here.\n>\n\nYou are right.\n\n> > +       }\n> > +\n> >         return switch_branches(opts, new_branch_info);\n> >  }\n> >\n> > @@ -1797,6 +1828,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n> >                 OPT_BOOL('l', NULL, &opts.new_branch_log, N_(\"create reflog for new branch\")),\n> >                 OPT_BOOL(0, \"guess\", &opts.dwim_new_local_branch,\n> >                          N_(\"second guess 'git checkout <no-such-branch>' (default)\")),\n> > +               OPT_BOOL('w', \"to-branch\", &opts.to_branch,\n> > +                        N_(\"checkout to a branch from a commit or a tag\")),\n> >                 OPT_BOOL(0, \"overlay\", &opts.overlay_mode, N_(\"use overlay mode (default)\")),\n> >                 OPT_END()\n> >         };\n> > diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh\n> > index 93be1c0eae5..53e45cfe7fd 100755\n> > --- a/t/t2018-checkout-branch.sh\n> > +++ b/t/t2018-checkout-branch.sh\n>\n> I plan to look at the tests after we decide how the new option relates\n> to --detach.\n>\n> Thanks!\n\nThanks.\n--\nZheNing Hu\n"},{"id":"443869","messageId":"CAOLTT8Qt57C41fiq0C+8szj9OMwUexJq_pV2QNwy8AsgoZscDg@mail.gmail.com","threadId":"57063","inReplyTo":"211210.86y24sdd7k.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-12-11T07:11:24Z","receivedAt":"2021-12-11T07:11:39Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> 于2021年12月10日周五 17:24写道：\n> > +-w::\n> > +--to-branch::\n> > +     Rather than checking out a commit to work on it, checkout out\n> > +     to the unique branch on it. If there are multiple branches on\n> > +     the commit, the checkout will fail.\n> > +\n>\n> So basically what this option implements is something that could be done\n> as a shellscript of:\n>\n>         git_checkout_branch_from_oid () {\n>                 rev=$1\n>                 git for-each-ref --format='%(refname:strip=2)' --points-at $rev >/tmp/found\n>                 if test $(wc -l </tmp/found) -ne 1\n>                 then\n>                         echo \"Goldilocks error in finding $rev: $(cat /tmp/found)\"\n>                         return 1\n>                 fi\n>                 git checkout $found\n>         }\n>\n\nYes, this is the effect I expect, and it can indeed be done through\nthe shellscript.\n\n> Which is not to say that it isn't useful, but that I think adding this\n> to \"git checkout\" specifically is adding this to the wrong level. Isn't\n> this useful to most things that parse revisions? I.e. wouldn't a better\n> interface be via the peel syntax?\n>\n>     oid=$(git rev-parse HEAD)\n>     git checkout $oid^{tobranch}\n>\n> Doing it that way would allow any arbitrary command that takes revisions\n> now access to that, and we could have e.g. \"^{tobranches}\" too, so you\n> could do:\n>\n>     git for-each-ref --format='%(refname:strip=2)' $oid^{tobranches}\n>\n> Or:\n>\n>     git log $oid^{tobranches}\n>\n\nVery good inspiration, putting \"oid -> branches\" in peel will be more elegant\nand useful.\n\n> I think implementing that is a bit harder. It's peel_onion() in\n> object-name.c. I think parse_branchname_arg() via get_oid_mb() is now\n> only capable of filling in an OID for a given name, and then checking\n> out that name comes as a separate step, and you can't just return\n> e.g. \"master\".\n>\n> But I don't think anything stops us from adjusting those functions a bit\n> so that get_oid_with_context(() and friends could pass down say an\n> optional \"struct string_list *\", and the \"peel\" could then be expanded\n> to that.\n>\n\nI agree.\n\n> Similar to how we have \"git chekout -\", and the \"-\" is understood by\n> some commands, but not all (via some opt-in whose location I forget...).\n\n\"-\" is parsed as \"@{-1}\".\n\nThanks.\n--\nZheNing Hu\n"},{"id":"443870","messageId":"CAOLTT8RG0T1o5nEoHPrm7g-+Z8yw=GSy1U+5A7nxvzk4dBQ6sw@mail.gmail.com","threadId":"57063","inReplyTo":"7a8206d3-f06f-07b0-c60b-dea549af7731@gmail.com","subject":"Re: [PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-12-11T07:12:48Z","receivedAt":"2021-12-11T07:13:02Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Bagas Sanjaya <bagasdotme@gmail.com> 于2021年12月10日周五 19:51写道：\n>\n> On 10/12/21 13.22, ZheNing Hu via GitGitGadget wrote:\n> > +-w::\n> > +--to-branch::\n> > +     Rather than checking out a commit to work on it, checkout out\n> > +     to the unique branch on it. If there are multiple branches on\n> > +     the commit, the checkout will fail.\n> > +\n>\n> Did you mean unique branch that contains the commit?\n>\n\nI mean the unique branch which points to the commit.\n\n> --\n> An old man doll... just what I always wanted! - Clara\n\n--\nZheNing Hu\n"},{"id":"443875","messageId":"CAOLTT8Rx9M9=a5M8UeDrJqMayTXo=dvdanVDLi7QLdPX8W_Tzw@mail.gmail.com","threadId":"57063","inReplyTo":"xmqq5yrwm7km.fsf@gitster.g","subject":"Re: [PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-12-11T07:51:01Z","receivedAt":"2021-12-11T07:51:14Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年12月11日周六 06:14写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > When we want checkout to a branch (e.g. dev1) which reference\n> > to a commit, but sometimes we only remember the tag (e.g. v1.1)\n> > on it, we will use `git checkout v1.1` to find the commit first,\n> > git will be in the state of deatching HEAD, so we have to search the\n> > branches on the commit and checkout the branch we perfer. This will\n> > be a bit cumbersome.\n> >\n> > Introduce \"--to-branch\" option, `git checkout --to-branch <tag>`\n> > and `git checkout --to-branch <commit>` will search all branches\n> > and find a unique branch reference to the commit (or the commit which\n> > the tag reference to) and checkout to it. If the commit have more\n> > than one branches, it will report error \"here are more than one\n> > branch on commit\".\n>\n> Sorry, but the above explanation does not make any sense to me.\n>\n> It is unclear if you mean \"dev1\" exactly point at the commit tagged\n> as v1.1, or you want the branch \"dev1\" that is a descedanant of\n> v1.1.  Without telling that to the reader, the above explanation is\n> useless.\n>\n\nI meant the former.\n\n> And whether you meant the former or the latter, neither use case does\n> not make much sense.\n>\n> First, suppose you meant \"checkout --to-branch v1.1\" to find a\n> branch whose tip exactly points at v1.1.  You instead check out\n> \"dev1\" branch, and work on it to advance its history.  When you are\n> done, you may go to another branch and work on something else.\n>\n> But then what?  When you need another topic that also needs to be\n> later merge-able to v1.1, \"checkout --to-branch v1.1\" no longer will\n> be able to find \"dev1\", because, well, you have already used it to\n> build something else.\n>\n> So, \"--to-branch v1.1\" that finds and checks out a branch whose tip\n> exactly points at v1.1 would be pretty useless.\n>\n\nWell, I didn't consider what you said before. I just want to find a shortcut\nfor \"oid -> branches\" and \"tag -> branches\". And I can quickly use it to\nswitch branches.\n\n> So let's correct the unwritten assumption and say \"--to-branch v1.1\"\n> finds a branch that is descendant of the tag.  It is like I have\n> maint-2.33 branch to prepare for v2.33.1, v2.33.2,... maintenance\n> releases and being able to find maint-2.33 by saying v2.33.2 (or\n> v2.33.1) _might_ be convenient.\n>\n> But that would only be true if there is only one single branch per\n> family of tags (in the above example, v2.33.* tags).  You cannot use\n> the workflow where many topic branches run in parallel, and get\n> merged to the integration branch(es) only after they are ready,\n> because you need bugfix-1-for-v2.33, bugfix-2-for-v2.33,... branches\n> all forked from v2.33.0 (or a commit with a later tag in the v2.33.*\n> family) to cook these independent fixes that are destined for the\n> maint-2.33 integration branch, so you cannot uniquely find maint-2.33\n> by saying v2.33.0 or v2.33.1 or whatever.\n>\n\nWell, in the case of many branches pointing to one commit, this \"--to-branch\"\nis not very useful.\n\n> I also sense that the first paragraph of the proposed log message\n> for this commit hints that the user needs a bit more studying of\n> existing tools.  When we know v1.1 but do not know if we already\n> have branches that are based on it, we DO NOT do \"git checkout v1.1\".\n> Instead the first thing we would do is \"git branch --contains v1.1\"\n> (add \"--no-merged main\" to exclude the branches that have already\n> graduated to 'main').\n>\n\n\"git branch --contains v1.1\" can find all branches whose history contains the\n commit tagged as v1.1. So what if \"git checkout --contains v1.1\"?\n\nIf there is only one branch, checkout to it; if there are multiple branches,\nit will degenerate into \"git branch --contains v1.1\" to show these branches to\nthe user. Of course this feature is not very consistent with my\noriginal intention...\n\n> So, for this partcular topic, what I would recommend is *not* jump\n> in and add a new feature, but to study what's available and build a\n> workflow around the existing features.\n>\n>\n\nThanks.\n--\nZheNing Hu\n"},{"id":"443905","messageId":"xmqqy24pk6f4.fsf@gitster.g","threadId":"57063","inReplyTo":"CAOLTT8Rx9M9=a5M8UeDrJqMayTXo=dvdanVDLi7QLdPX8W_Tzw@mail.gmail.com","subject":"Re: [PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-12T18:46:23Z","receivedAt":"2021-12-12T18:46:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n>> It is unclear if you mean \"dev1\" exactly point at the commit tagged\n>> as v1.1, or you want the branch \"dev1\" that is a descedanant of\n>> v1.1.  Without telling that to the reader, the above explanation is\n>> useless.\n>>\n>\n> I meant the former.\n>\n>> And whether you meant the former or the latter, neither use case does\n>> not make much sense.\n>> ...\n>> So, \"--to-branch v1.1\" that finds and checks out a branch whose tip\n>> exactly points at v1.1 would be pretty useless.\n> ...\n> \"git branch --contains v1.1\" can find all branches whose history contains the\n>  commit tagged as v1.1. So what if \"git checkout --contains v1.1\"?\n\nI already said, whether you meant \"the only branch that points\nexactly at\" or \"the only branch that contains\", the feature does not\nmake sense.  Forcing users to keep only a single branch that either\npoints at a given tag is simply impossible and also useless.  Once\nthe branch gains even a single commit, it will no loger be pointing\nat the tag, so \"let's prepare a branch pointing at v1.1 just in case\nwhen I want to start working from there\" would not be a good\nworkflow to begin with.  Forcing users to keep only a single branch\nthat contains a given tag would encourage even a worse workflow to\nthrow in unrelated things, whose only commonality is that they all\nwant to fork from a single tag, into a single branch.\n\nIOW, there is nothing we want to add to \"git checkout/switch\" for\nthis topic.  \"git checkout --contains $tag\" smells like a solution\nlooking for a problem.\n\n\n"},{"id":"444026","messageId":"211213.86r1ag6ztx.gmgdl@evledraar.gmail.com","threadId":"57063","inReplyTo":"xmqqy24pk6f4.fsf@gitster.g","subject":"Re: [PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-13T19:55:00Z","receivedAt":"2021-12-13T19:59:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sun, Dec 12 2021, Junio C Hamano wrote:\n\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n>>> It is unclear if you mean \"dev1\" exactly point at the commit tagged\n>>> as v1.1, or you want the branch \"dev1\" that is a descedanant of\n>>> v1.1.  Without telling that to the reader, the above explanation is\n>>> useless.\n>>>\n>>\n>> I meant the former.\n>>\n>>> And whether you meant the former or the latter, neither use case does\n>>> not make much sense.\n>>> ...\n>>> So, \"--to-branch v1.1\" that finds and checks out a branch whose tip\n>>> exactly points at v1.1 would be pretty useless.\n>> ...\n>> \"git branch --contains v1.1\" can find all branches whose history contains the\n>>  commit tagged as v1.1. So what if \"git checkout --contains v1.1\"?\n>\n> I already said, whether you meant \"the only branch that points\n> exactly at\" or \"the only branch that contains\", the feature does not\n> make sense.  Forcing users to keep only a single branch that either\n> points at a given tag is simply impossible and also useless.  Once\n> the branch gains even a single commit, it will no loger be pointing\n> at the tag, so \"let's prepare a branch pointing at v1.1 just in case\n> when I want to start working from there\" would not be a good\n> workflow to begin with.  Forcing users to keep only a single branch\n> that contains a given tag would encourage even a worse workflow to\n> throw in unrelated things, whose only commonality is that they all\n> want to fork from a single tag, into a single branch.\n>\n> IOW, there is nothing we want to add to \"git checkout/switch\" for\n> this topic.  \"git checkout --contains $tag\" smells like a solution\n> looking for a problem.\n\nI don't see how it's fundimentally different than the DWIM logic of\ntaking \"<name>\" and seeing that there's only one \"refs/heads/<name>\",\nand giving up in other cases where we get ambiguous reference names that\nwe can't resolve.\n\nI.e. I think this is probably useful for some, it's a common workflow to\nhave a 1=1 relationship like this, and if it's 1=many we can just handle\nit as we do with ambiguous ref, or ambiguous short OIDs for that matter.\n\nBut as I noted upthread I really don't see this making sense as a\nper-command thing, as opposed to some extension of the \"peel\"\nsyntax. I.e. we should (or at least expose it as such to the user)\ninterpret that sort of argument/newx syntax before \"git checkout\" et al\nget to it, so from a UX perspective you could feed a ^{oid2ref} (or\nwhatever it would be called) to rev-parse, checkout etc. etc.\n"},{"id":"444030","messageId":"xmqqr1agb313.fsf@gitster.g","threadId":"57063","inReplyTo":"211213.86r1ag6ztx.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-13T21:36:40Z","receivedAt":"2021-12-13T21:36:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> I don't see how it's fundimentally different than the DWIM logic of\n> taking \"<name>\" and seeing that there's only one \"refs/heads/<name>\",\n> and giving up in other cases where we get ambiguous reference names that\n> we can't resolve.\n\nIn that example, once you obtained a local branch whose name is\n<name>, it is obvious how you would work with that.  Your next \"git\ncheckout <name>\" would work on the local one, and only the initial\none does something magical.\n\nWhich makes quite a lot of sense.\n\nThere is no similarity in it with \"--to-branch <tag>\" that is being\ndiscussed here.\n\n\n\n"},{"id":"444033","messageId":"xmqqh7bcb2am.fsf@gitster.g","threadId":"57063","inReplyTo":"xmqqr1agb313.fsf@gitster.g","subject":"Re: [PATCH 2/2] checkout: introduce \"--to-branch\" option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-13T21:52:33Z","receivedAt":"2021-12-13T21:52:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>\n>> I don't see how it's fundimentally different than the DWIM logic of\n>> taking \"<name>\" and seeing that there's only one \"refs/heads/<name>\",\n>> and giving up in other cases where we get ambiguous reference names that\n>> we can't resolve.\n>\n> In that example, once you obtained a local branch whose name is\n> <name>, it is obvious how you would work with that.  Your next \"git\n> checkout <name>\" would work on the local one, and only the initial\n> one does something magical.\n>\n> Which makes quite a lot of sense.\n>\n> There is no similarity in it with \"--to-branch <tag>\" that is being\n> discussed here.\n\nAnother thing you seem to be ignoring is that it requires you to\nprepare in advance and keep one (and only one) dormant branch for\nthe <tag> you will use the \"--to-branch <tag>\" with, because it\nfinds the branch whose tip exactly points at the tag.\n\nYou can use the command exactly once for the <tag> and check out\nthat branch, but then what?  Once you start working on that branch,\nyou would by definition no longer have a suitable branch that can be\nused to with \"--to-branch <tag>\", because (1) it was the only one\nthat pointed at <tag> or you'd have gotten an error, and (2) you\nhave moved the tip of the branch so it no longer points at the tag.\nAnd you'd expect the user to say \"git branch <next-branch> <tag>\" in\npreparation for the next time you might want to use \"--to-branch\n<tag>\" and keep that pristine?\n\nI do not think of any similar counterpart in \"teach checkout <name>\nto dwim and fork from a remote-tracking branch from a remote, if\nthere is no other remote with a branch with that name\" DWIMming to\nthese nonsense.\n\n"}]}