{"thread":{"id":"59607","subject":"[PATCH v9 2/8] t2400: print captured git output when finished","startedAt":"2023-04-17T09:35:38Z","lastAt":"2023-08-11T17:43:46Z","messageCount":34,"participants":["Jacob Abel","Junio C Hamano","Teng Long"],"isPatch":true,"patchVersion":9,"patchTotal":8},"messages":[{"id":"475507","messageId":"20230417093255.31079-2-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230417093255.31079-1-jacobabel@nullpo.dev","subject":"[PATCH v9 1/8] worktree add: include -B in usage docs","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-17T09:33:35Z","receivedAt":"2023-04-17T09:35:38Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Document `-B` next to where `-b` is already documented to bring the\nusage docs in line with other commands such as git checkout.\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n Documentation/git-worktree.txt | 2 +-\n builtin/worktree.c             | 2 +-\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 063d6eeb99..b9c12779f1 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]]\n-\t\t   [-b <new-branch>] <path> [<commit-ish>]\n+\t\t   [(-b | -B) <new-branch>] <path> [<commit-ish>]\n 'git worktree list' [-v | --porcelain [-z]]\n 'git worktree lock' [--reason <string>] <worktree>\n 'git worktree move' <worktree> <new-path>\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 39e9e5c9ce..d1b4b53f2c 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -22,7 +22,7 @@\n\n #define BUILTIN_WORKTREE_ADD_USAGE \\\n \tN_(\"git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]\\n\" \\\n-\t   \"                 [-b <new-branch>] <path> [<commit-ish>]\")\n+\t   \"                 [(-b | -B) <new-branch>] <path> [<commit-ish>]\")\n #define BUILTIN_WORKTREE_LIST_USAGE \\\n \tN_(\"git worktree list [-v | --porcelain [-z]]\")\n #define BUILTIN_WORKTREE_LOCK_USAGE \\\n--\n2.39.2\n\n\n"},{"id":"475506","messageId":"20230417093255.31079-3-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230417093255.31079-1-jacobabel@nullpo.dev","subject":"[PATCH v9 2/8] t2400: print captured git output when finished","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-17T09:33:44Z","receivedAt":"2023-04-17T09:35:39Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Update tests that capture stderr so that at the end of the test they\nprint the captured text back out to stderr. This simplifies debugging\nwhen inspecting test logs after executing with `-x`.\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n t/t2400-worktree-add.sh | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex d587e0b20d..9bc3db20e4 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -326,6 +326,8 @@ test_expect_success 'add -B' '\n '\n\n test_expect_success 'add --quiet' '\n+\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n+\ttest_when_finished cat actual >&2 &&\n \tgit worktree add --quiet another-worktree main 2>actual &&\n \ttest_must_be_empty actual\n '\n--\n2.39.2\n\n\n"},{"id":"475508","messageId":"20230417093255.31079-5-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230417093255.31079-1-jacobabel@nullpo.dev","subject":"[PATCH v9 4/8] t2400: add tests to verify --quiet","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-17T09:34:01Z","receivedAt":"2023-04-17T09:36:02Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Add tests to verify that the command performs operations the same with\n`--quiet` as without it. Additionally verifies that all non-fatal output\nis suppressed.\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n t/t2400-worktree-add.sh | 38 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 38 insertions(+)\n\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex 6822be4666..18831c4d93 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -336,6 +336,14 @@ test_expect_success 'add --quiet' '\n \ttest_must_be_empty actual\n '\n\n+test_expect_success 'add --quiet -b' '\n+\ttest_when_finished \"git branch -D quietnewbranch\" &&\n+\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n+\ttest_when_finished cat actual >&2 &&\n+\tgit worktree add --quiet -b quietnewbranch another-worktree 2>actual &&\n+\ttest_must_be_empty actual\n+'\n+\n test_expect_success 'local clone from linked checkout' '\n \tgit clone --local here here-clone &&\n \t( cd here-clone && git fsck )\n@@ -534,6 +542,36 @@ test_expect_success 'git worktree add --guess-remote sets up tracking' '\n \t\ttest_cmp_rev refs/remotes/repo_a/foo refs/heads/foo\n \t)\n '\n+test_expect_success 'git worktree add --guess-remote sets up tracking (quiet)' '\n+\ttest_when_finished rm -rf repo_a repo_b foo &&\n+\ttest_when_finished cat repo_b/actual >&2 &&\n+\tsetup_remote_repo repo_a repo_b &&\n+\t(\n+\t\tcd repo_b &&\n+\t\tgit worktree add --quiet --guess-remote ../foo 2>actual &&\n+\t\ttest_must_be_empty actual\n+\t) &&\n+\t(\n+\t\tcd foo &&\n+\t\ttest_branch_upstream foo repo_a foo &&\n+\t\ttest_cmp_rev refs/remotes/repo_a/foo refs/heads/foo\n+\t)\n+'\n+\n+test_expect_success 'git worktree --no-guess-remote (quiet)' '\n+\ttest_when_finished rm -rf repo_a repo_b foo &&\n+\tsetup_remote_repo repo_a repo_b &&\n+\t(\n+\t\tcd repo_b &&\n+\t\tgit worktree add --quiet --no-guess-remote ../foo\n+\t) &&\n+\t(\n+\t\tcd foo &&\n+\t\ttest_must_fail git config \"branch.foo.remote\" &&\n+\t\ttest_must_fail git config \"branch.foo.merge\" &&\n+\t\ttest_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo\n+\t)\n+'\n\n test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' '\n \ttest_when_finished rm -rf repo_a repo_b foo &&\n--\n2.39.2\n\n\n"},{"id":"475509","messageId":"20230417093255.31079-6-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230417093255.31079-1-jacobabel@nullpo.dev","subject":"[PATCH v9 5/8] worktree add: add --orphan flag","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-17T09:34:10Z","receivedAt":"2023-04-17T09:36:12Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Add support for creating an orphan branch when adding a new worktree.\nThe functionality of this flag is equivalent to git switch's --orphan\noption.\n\nCurrent Behavior:\n% git -C foo.git --no-pager branch -l\n+ main\n% git -C foo.git worktree add main/\nPreparing worktree (new branch 'main')\nHEAD is now at 6c93a75 a commit\n%\n\n% git init bar.git\nInitialized empty Git repository in /path/to/bar.git/\n% git -C bar.git --no-pager branch -l\n\n% git -C bar.git worktree add main/\nPreparing worktree (new branch 'main')\nfatal: not a valid object name: 'HEAD'\n%\n\nNew Behavior:\n\n% git -C foo.git --no-pager branch -l\n+ main\n% git -C foo.git worktree add main/\nPreparing worktree (new branch 'main')\nHEAD is now at 6c93a75 a commit\n%\n\n% git init --bare bar.git\nInitialized empty Git repository in /path/to/bar.git/\n% git -C bar.git --no-pager branch -l\n\n% git -C bar.git worktree add main/\nPreparing worktree (new branch 'main')\nfatal: invalid reference: HEAD\n% git -C bar.git worktree add --orphan -b main/\nPreparing worktree (new branch 'main')\n% git -C bar.git worktree add --orphan -b newbranch worktreedir/\nPreparing worktree (new branch 'newbranch')\n%\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n Documentation/git-worktree.txt |  6 ++-\n builtin/worktree.c             | 67 +++++++++++++++++++++++++++------\n t/t2400-worktree-add.sh        | 69 ++++++++++++++++++++++++++++++++++\n 3 files changed, 130 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex b9c12779f1..485d865eb2 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]]\n-\t\t   [(-b | -B) <new-branch>] <path> [<commit-ish>]\n+\t\t   [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]\n 'git worktree list' [-v | --porcelain [-z]]\n 'git worktree lock' [--reason <string>] <worktree>\n 'git worktree move' <worktree> <new-path>\n@@ -222,6 +222,10 @@ This can also be set up as the default behaviour by using the\n \tWith `prune`, do not remove anything; just report what it would\n \tremove.\n\n+--orphan::\n+\tWith `add`, make the new worktree and index empty, associating\n+\tthe worktree with a new orphan/unborn branch named `<new-branch>`.\n+\n --porcelain::\n \tWith `list`, output in an easy-to-parse format for scripts.\n \tThis format will remain stable across Git versions and regardless of user\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex d1b4b53f2c..48de7fc3b0 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -22,7 +22,8 @@\n\n #define BUILTIN_WORKTREE_ADD_USAGE \\\n \tN_(\"git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]\\n\" \\\n-\t   \"                 [(-b | -B) <new-branch>] <path> [<commit-ish>]\")\n+\t   \"                 [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]\")\n+\n #define BUILTIN_WORKTREE_LIST_USAGE \\\n \tN_(\"git worktree list [-v | --porcelain [-z]]\")\n #define BUILTIN_WORKTREE_LOCK_USAGE \\\n@@ -95,6 +96,7 @@ struct add_opts {\n \tint detach;\n \tint quiet;\n \tint checkout;\n+\tint orphan;\n \tconst char *keep_locked;\n };\n\n@@ -368,6 +370,22 @@ static int checkout_worktree(const struct add_opts *opts,\n \treturn run_command(&cp);\n }\n\n+static int make_worktree_orphan(const char * ref, const struct add_opts *opts,\n+\t\t\t\tstruct strvec *child_env)\n+{\n+\tstruct strbuf symref = STRBUF_INIT;\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\tvalidate_new_branchname(ref, &symref, 0);\n+\tstrvec_pushl(&cp.args, \"symbolic-ref\", \"HEAD\", symref.buf, NULL);\n+\tif (opts->quiet)\n+\t\tstrvec_push(&cp.args, \"--quiet\");\n+\tstrvec_pushv(&cp.env, child_env->v);\n+\tstrbuf_release(&symref);\n+\tcp.git_cmd = 1;\n+\treturn run_command(&cp);\n+}\n+\n static int add_worktree(const char *path, const char *refname,\n \t\t\tconst struct add_opts *opts)\n {\n@@ -397,7 +415,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tdie_if_checked_out(symref.buf, 0);\n \t}\n \tcommit = lookup_commit_reference_by_name(refname);\n-\tif (!commit)\n+\tif (!commit && !opts->orphan)\n \t\tdie(_(\"invalid reference: %s\"), refname);\n\n \tname = worktree_basename(path, &len);\n@@ -486,10 +504,10 @@ static int add_worktree(const char *path, const char *refname,\n \tstrvec_pushf(&child_env, \"%s=%s\", GIT_WORK_TREE_ENVIRONMENT, path);\n \tcp.git_cmd = 1;\n\n-\tif (!is_branch)\n+\tif (!is_branch && commit) {\n \t\tstrvec_pushl(&cp.args, \"update-ref\", \"HEAD\",\n \t\t\t     oid_to_hex(&commit->object.oid), NULL);\n-\telse {\n+\t} else {\n \t\tstrvec_pushl(&cp.args, \"symbolic-ref\", \"HEAD\",\n \t\t\t     symref.buf, NULL);\n \t\tif (opts->quiet)\n@@ -501,6 +519,10 @@ static int add_worktree(const char *path, const char *refname,\n \tif (ret)\n \t\tgoto done;\n\n+\tif (opts->orphan &&\n+\t    (ret = make_worktree_orphan(refname, opts, &child_env)))\n+\t\tgoto done;\n+\n \tif (opts->checkout &&\n \t    (ret = checkout_worktree(opts, &child_env)))\n \t\tgoto done;\n@@ -520,7 +542,7 @@ static int add_worktree(const char *path, const char *refname,\n \t * Hook failure does not warrant worktree deletion, so run hook after\n \t * is_junk is cleared, but do return appropriate code when hook fails.\n \t */\n-\tif (!ret && opts->checkout) {\n+\tif (!ret && opts->checkout && !opts->orphan) {\n \t\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n\n \t\tstrvec_pushl(&opt.env, \"GIT_DIR\", \"GIT_WORK_TREE\", NULL);\n@@ -568,7 +590,7 @@ static void print_preparing_worktree_line(int detach,\n \t\telse {\n \t\t\tstruct commit *commit = lookup_commit_reference_by_name(branch);\n \t\t\tif (!commit)\n-\t\t\t\tdie(_(\"invalid reference: %s\"), branch);\n+\t\t\t\tBUG(_(\"unreachable: invalid reference: %s\"), branch);\n \t\t\tfprintf_ln(stderr, _(\"Preparing worktree (detached HEAD %s)\"),\n \t\t\t\t  repo_find_unique_abbrev(the_repository, &commit->object.oid, DEFAULT_ABBREV));\n \t\t}\n@@ -620,6 +642,7 @@ static int add(int ac, const char **av, const char *prefix)\n \t\t\t   N_(\"create a new branch\")),\n \t\tOPT_STRING('B', NULL, &new_branch_force, N_(\"branch\"),\n \t\t\t   N_(\"create or reset a branch\")),\n+\t\tOPT_BOOL(0, \"orphan\", &opts.orphan, N_(\"create unborn/orphaned branch\")),\n \t\tOPT_BOOL('d', \"detach\", &opts.detach, N_(\"detach HEAD at named commit\")),\n \t\tOPT_BOOL(0, \"checkout\", &opts.checkout, N_(\"populate the new working tree\")),\n \t\tOPT_BOOL(0, \"lock\", &keep_locked, N_(\"keep the new working tree locked\")),\n@@ -640,6 +663,17 @@ static int add(int ac, const char **av, const char *prefix)\n \tac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0);\n \tif (!!opts.detach + !!new_branch + !!new_branch_force > 1)\n \t\tdie(_(\"options '%s', '%s', and '%s' cannot be used together\"), \"-b\", \"-B\", \"--detach\");\n+\tif (opts.detach && opts.orphan)\n+\t\tdie(_(\"options '%s', and '%s' cannot be used together\"),\n+\t\t    \"--orphan\", \"--detach\");\n+\tif (opts.orphan && opt_track)\n+\t\tdie(_(\"'%s' and '%s' cannot be used together\"), \"--orphan\", \"--track\");\n+\tif (opts.orphan && !opts.checkout)\n+\t\tdie(_(\"'%s' and '%s' cannot be used together\"), \"--orphan\",\n+\t\t    \"--no-checkout\");\n+\tif (opts.orphan && ac == 2)\n+\t\tdie(_(\"'%s' and '%s' cannot be used together\"), \"--orphan\",\n+\t\t    _(\"<commit-ish>\"));\n \tif (lock_reason && !keep_locked)\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--reason\", \"--lock\");\n \tif (lock_reason)\n@@ -668,13 +702,17 @@ static int add(int ac, const char **av, const char *prefix)\n \t\tstrbuf_release(&symref);\n \t}\n\n-\tif (ac < 2 && !new_branch && !opts.detach) {\n+\tif (opts.orphan && !new_branch) {\n+\t\tint n;\n+\t\tconst char *s = worktree_basename(path, &n);\n+\t\tnew_branch = xstrndup(s, n);\n+\t} else if (new_branch || opts.detach || opts.orphan) {\n+\t\t// No-op\n+\t} else if (ac < 2) {\n \t\tconst char *s = dwim_branch(path, &new_branch);\n \t\tif (s)\n \t\t\tbranch = s;\n-\t}\n-\n-\tif (ac == 2 && !new_branch && !opts.detach) {\n+\t} else if (ac == 2) {\n \t\tstruct object_id oid;\n \t\tstruct commit *commit;\n \t\tconst char *remote;\n@@ -688,10 +726,17 @@ static int add(int ac, const char **av, const char *prefix)\n \t\t\t}\n \t\t}\n \t}\n+\n+\tif (!opts.orphan && !lookup_commit_reference_by_name(branch)) {\n+\t\tdie(_(\"invalid reference: %s\"), branch);\n+\t}\n+\n \tif (!opts.quiet)\n \t\tprint_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force);\n\n-\tif (new_branch) {\n+\tif (opts.orphan) {\n+\t\tbranch = new_branch;\n+\t} else if (new_branch) {\n \t\tstruct child_process cp = CHILD_PROCESS_INIT;\n \t\tcp.git_cmd = 1;\n \t\tstrvec_push(&cp.args, \"branch\");\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex 18831c4d93..2ea4342867 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -313,6 +313,10 @@ test_wt_add_excl () {\n test_wt_add_excl -b poodle -B poodle bamboo main\n test_wt_add_excl -b poodle --detach bamboo main\n test_wt_add_excl -B poodle --detach bamboo main\n+test_wt_add_excl --orphan --detach bamboo\n+test_wt_add_excl --orphan --no-checkout bamboo\n+test_wt_add_excl --orphan bamboo main\n+test_wt_add_excl --orphan -b bamboo wtdir/ main\n\n test_expect_success '\"add -B\" fails if the branch is checked out' '\n \tgit rev-parse newmain >before &&\n@@ -344,6 +348,63 @@ test_expect_success 'add --quiet -b' '\n \ttest_must_be_empty actual\n '\n\n+test_expect_success '\"add --orphan\"' '\n+\ttest_when_finished \"git worktree remove -f -f orphandir\" &&\n+\tgit worktree add --orphan -b neworphan orphandir &&\n+\techo refs/heads/neworphan >expected &&\n+\tgit -C orphandir symbolic-ref HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '\"add --orphan (no -b)\"' '\n+\ttest_when_finished \"git worktree remove -f -f neworphan\" &&\n+\tgit worktree add --orphan neworphan &&\n+\techo refs/heads/neworphan >expected &&\n+\tgit -C neworphan symbolic-ref HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '\"add --orphan --quiet\"' '\n+\ttest_when_finished \"git worktree remove -f -f orphandir\" &&\n+\ttest_when_finished cat log.actual >&2 &&\n+\tgit worktree add --quiet --orphan -b neworphan orphandir 2>log.actual &&\n+\ttest_must_be_empty log.actual &&\n+\techo refs/heads/neworphan >expected &&\n+\tgit -C orphandir symbolic-ref HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '\"add --orphan\" fails if the branch already exists' '\n+\ttest_when_finished \"git branch -D existingbranch\" &&\n+\tgit worktree add -b existingbranch orphandir main &&\n+\tgit worktree remove orphandir &&\n+\ttest_must_fail git worktree add --orphan -b existingbranch orphandir\n+'\n+\n+test_expect_success '\"add --orphan\" with empty repository' '\n+\ttest_when_finished \"rm -rf empty_repo\" &&\n+\techo refs/heads/newbranch >expected &&\n+\tGIT_DIR=\"empty_repo\" git init --bare &&\n+\tgit -C empty_repo  worktree add --orphan -b newbranch worktreedir &&\n+\tgit -C empty_repo/worktreedir symbolic-ref HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '\"add\" worktree with orphan branch and lock' '\n+\tgit worktree add --lock --orphan -b orphanbr orphan-with-lock &&\n+\ttest_when_finished \"git worktree unlock orphan-with-lock || :\" &&\n+\ttest -f .git/worktrees/orphan-with-lock/locked\n+'\n+\n+test_expect_success '\"add\" worktree with orphan branch, lock, and reason' '\n+\tlock_reason=\"why not\" &&\n+\tgit worktree add --detach --lock --reason \"$lock_reason\" orphan-with-lock-reason main &&\n+\ttest_when_finished \"git worktree unlock orphan-with-lock-reason || :\" &&\n+\ttest -f .git/worktrees/orphan-with-lock-reason/locked &&\n+\techo \"$lock_reason\" >expect &&\n+\ttest_cmp expect .git/worktrees/orphan-with-lock-reason/locked\n+'\n+\n test_expect_success 'local clone from linked checkout' '\n \tgit clone --local here here-clone &&\n \t( cd here-clone && git fsck )\n@@ -460,6 +521,14 @@ setup_remote_repo () {\n \t)\n }\n\n+test_expect_success '\"add\" <path> <remote/branch> w/ no HEAD' '\n+\ttest_when_finished rm -rf repo_upstream repo_local foo &&\n+\tsetup_remote_repo repo_upstream repo_local &&\n+\tgit -C repo_local config --bool core.bare true &&\n+\tgit -C repo_local branch -D main &&\n+\tgit -C repo_local worktree add ./foo repo_upstream/foo\n+'\n+\n test_expect_success '--no-track avoids setting up tracking' '\n \ttest_when_finished rm -rf repo_upstream repo_local foo &&\n \tsetup_remote_repo repo_upstream repo_local &&\n--\n2.39.2\n\n\n"},{"id":"475510","messageId":"20230417093255.31079-8-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230417093255.31079-1-jacobabel@nullpo.dev","subject":"[PATCH v9 7/8] worktree add: extend DWIM to infer --orphan","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-17T09:34:27Z","receivedAt":"2023-04-17T09:36:23Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Extend DWIM to try to infer `--orphan` when in an empty repository. i.e.\na repository with an invalid/unborn HEAD, no local branches, and if\n`--guess-remote` is used then no remote branches.\n\nThis behavior is equivalent to `git switch -c` or `git checkout -b` in\nan empty repository.\n\nAlso warn the user (overriden with `-f`/`--force`) when they likely\nintend to checkout a remote branch to the worktree but have not yet\nfetched from the remote. i.e. when using `--guess-remote` and there is a\nremote but no local or remote refs.\n\nCurrent Behavior:\n% git --no-pager branch --list --remotes\n% git remote\norigin\n% git workree add ../main\nhint: If you meant to create a worktree containing a new orphan branch\n[...]\nhint: Disable this message with \"git config advice.worktreeAddOrphan false\"\nfatal: invalid reference: HEAD\n% git workree add --guess-remote ../main\nhint: If you meant to create a worktree containing a new orphan branch\n[...]\nhint: Disable this message with \"git config advice.worktreeAddOrphan false\"\nfatal: invalid reference: HEAD\n% git fetch --quiet\n% git --no-pager branch --list --remotes\norigin/HEAD -> origin/main\norigin/main\n% git workree add --guess-remote ../main\nPreparing worktree (new branch 'main')\nbranch 'main' set up to track 'origin/main'.\nHEAD is now at dadc8e6dac commit message\n%\n\nNew Behavior:\n% git --no-pager branch --list --remotes\n% git remote\norigin\n% git workree add ../main\nNo possible source branch, inferring '--orphan'\nPreparing worktree (new branch 'main')\n% git worktree remove ../main\n% git workree add --guess-remote ../main\nfatal: No local or remote refs exist despite at least one remote\npresent, stopping; use 'add -f' to overide or fetch a remote first\n% git workree add --guess-remote -f ../main\nNo possible source branch, inferring '--orphan'\nPreparing worktree (new branch 'main')\n% git worktree remove ../main\n% git fetch --quiet\n% git --no-pager branch --list --remotes\norigin/HEAD -> origin/main\norigin/main\n% git workree add --guess-remote ../main\nPreparing worktree (new branch 'main')\nbranch 'main' set up to track 'origin/main'.\nHEAD is now at dadc8e6dac commit message\n%\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n Documentation/git-worktree.txt |  10 +\n builtin/worktree.c             | 114 ++++++++++-\n t/t2400-worktree-add.sh        | 332 +++++++++++++++++++++++++++++++++\n 3 files changed, 455 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 485d865eb2..a4fbf5e838 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -95,6 +95,16 @@ exist, a new branch based on `HEAD` is automatically created as if\n `-b <branch>` was given.  If `<branch>` does exist, it will be checked out\n in the new worktree, if it's not checked out anywhere else, otherwise the\n command will refuse to create the worktree (unless `--force` is used).\n++\n+If `<commit-ish>` is omitted, neither `--detach`, or `--orphan` is\n+used, and there are no valid local branches (or remote branches if\n+`--guess-remote` is specified) then, as a convenience, the new worktree is\n+associated with a new orphan branch named `<branch>` (after\n+`$(basename <path>)` if neither `-b` or `-B` is used) as if `--orphan` was\n+passed to the command. In the event the repository has a remote and\n+`--guess-remote` is used, but no remote or local branches exist, then the\n+command fails with a warning reminding the user to fetch from their remote\n+first (or override by using `-f/--force`).\n\n list::\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 12348d3d16..95b5bbb1d2 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -11,6 +11,7 @@\n #include \"strvec.h\"\n #include \"branch.h\"\n #include \"refs.h\"\n+#include \"remote.h\"\n #include \"run-command.h\"\n #include \"hook.h\"\n #include \"sigchain.h\"\n@@ -39,6 +40,9 @@\n #define BUILTIN_WORKTREE_UNLOCK_USAGE \\\n \tN_(\"git worktree unlock <worktree>\")\n\n+#define WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT \\\n+\t_(\"No possible source branch, inferring '--orphan'\")\n+\n #define WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT \\\n \t_(\"If you meant to create a worktree containing a new orphan branch\\n\" \\\n \t\"(branch with no commits) for this repository, you can do so\\n\" \\\n@@ -612,6 +616,107 @@ static void print_preparing_worktree_line(int detach,\n \t}\n }\n\n+/**\n+ * Callback to short circuit iteration over refs on the first reference\n+ * corresponding to a valid oid.\n+ *\n+ * Returns 0 on failure and non-zero on success.\n+ */\n+static int first_valid_ref(const char *refname,\n+\t\t\t   const struct object_id *oid,\n+\t\t\t   int flags,\n+\t\t\t   void *cb_data)\n+{\n+\treturn 1;\n+}\n+\n+/**\n+ * Verifies HEAD and determines whether there exist any valid local references.\n+ *\n+ * - Checks whether HEAD points to a valid reference.\n+ *\n+ * - Checks whether any valid local branches exist.\n+ *\n+ * Returns 1 if any of the previous checks are true, otherwise returns 0.\n+ */\n+static int can_use_local_refs(const struct add_opts *opts)\n+{\n+\tif (head_ref(first_valid_ref, NULL)) {\n+\t\treturn 1;\n+\t} else if (for_each_branch_ref(first_valid_ref, NULL)) {\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+/**\n+ * Reports whether the necessary flags were set and whether the repository has\n+ * remote references to attempt DWIM tracking of upstream branches.\n+ *\n+ * 1. Checks that `--guess-remote` was used or `worktree.guessRemote = true`.\n+ *\n+ * 2. Checks whether any valid remote branches exist.\n+ *\n+ * 3. Checks that there exists at least one remote and emits a warning/error\n+ *    if both checks 1. and 2. are false (can be bypassed with `--force`).\n+ *\n+ * Returns 1 if checks 1. and 2. are true, otherwise 0.\n+ */\n+static int can_use_remote_refs(const struct add_opts *opts)\n+{\n+\tif (!guess_remote) {\n+\t\tif (!opts->quiet)\n+\t\t\tfprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);\n+\t\treturn 0;\n+\t} else if (for_each_remote_ref(first_valid_ref, NULL)) {\n+\t\treturn 1;\n+\t} else if (!opts->force && remote_get(NULL)) {\n+\t\tdie(_(\"No local or remote refs exist despite at least one remote\\n\"\n+\t\t      \"present, stopping; use 'add -f' to overide or fetch a remote first\"));\n+\t} else if (!opts->quiet) {\n+\t\tfprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);\n+\t}\n+\treturn 0;\n+}\n+\n+/**\n+ * Determines whether `--orphan` should be inferred in the evaluation of\n+ * `worktree add path/` or `worktree add -b branch path/` and emits an error\n+ * if the supplied arguments would produce an illegal combination  when the\n+ * `--orphan` flag is included.\n+ *\n+ * `opts` and `opt_track` contain the other options & flags supplied to the\n+ * command.\n+ *\n+ * remote determines whether to check `can_use_remote_refs()` or not. This\n+ * is primarily to differentiate between the basic `add` DWIM and `add -b`.\n+ *\n+ * Returns 1 when inferring `--orphan`, 0 otherwise, and emits an error when\n+ * `--orphan` is inferred but doing so produces an illegal combination of\n+ * options and flags. Additionally produces an error when remote refs are\n+ * checked and the repo is in a state that looks like the user added a remote\n+ * but forgot to fetch (and did not override the warning with -f).\n+ */\n+static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote)\n+{\n+\tif (can_use_local_refs(opts)) {\n+\t\treturn 0;\n+\t} else if (remote && can_use_remote_refs(opts)) {\n+\t\treturn 0;\n+\t} else if (!opts->quiet) {\n+\t\tfprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);\n+\t}\n+\n+\tif (opt_track) {\n+\t\tdie(_(\"'%s' and '%s' cannot be used together\"), \"--orphan\",\n+\t\t    \"--track\");\n+\t} else if (!opts->checkout) {\n+\t\tdie(_(\"'%s' and '%s' cannot be used together\"), \"--orphan\",\n+\t\t    \"--no-checkout\");\n+\t}\n+\treturn 1;\n+}\n+\n static const char *dwim_branch(const char *path, const char **new_branch)\n {\n \tint n;\n@@ -722,12 +827,19 @@ static int add(int ac, const char **av, const char *prefix)\n \t\tint n;\n \t\tconst char *s = worktree_basename(path, &n);\n \t\tnew_branch = xstrndup(s, n);\n-\t} else if (new_branch || opts.detach || opts.orphan) {\n+\t} else if (opts.orphan || opts.detach) {\n \t\t// No-op\n+\t} else if (ac < 2 && new_branch) {\n+\t\t// DWIM: Infer --orphan when repo has no refs.\n+\t\topts.orphan = dwim_orphan(&opts, !!opt_track, 0);\n \t} else if (ac < 2) {\n+\t\t// DWIM: Guess branch name from path.\n \t\tconst char *s = dwim_branch(path, &new_branch);\n \t\tif (s)\n \t\t\tbranch = s;\n+\n+\t\t// DWIM: Infer --orphan when repo has no refs.\n+\t\topts.orphan = (!s) && dwim_orphan(&opts, !!opt_track, 1);\n \t} else if (ac == 2) {\n \t\tstruct object_id oid;\n \t\tstruct commit *commit;\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex 7ea56ef7c1..e5cca1d11b 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -712,6 +712,338 @@ test_expect_success 'git worktree --no-guess-remote option overrides config' '\n \t)\n '\n\n+test_dwim_orphan () {\n+\tlocal info_text=\"No possible source branch, inferring '--orphan'\" &&\n+\tlocal fetch_error_text=\"fatal: No local or remote refs exist despite at least one remote\" &&\n+\tlocal orphan_hint=\"hint: If you meant to create a worktree containing a new orphan branch\" &&\n+\tlocal invalid_ref_regex=\"^fatal: invalid reference:\\s\\+.*\" &&\n+\tlocal bad_combo_regex=\"^fatal: '[a-z-]\\+' and '[a-z-]\\+' cannot be used together\" &&\n+\n+\tlocal git_ns=\"repo\" &&\n+\tlocal dashc_args=\"-C $git_ns\" &&\n+\tlocal use_cd=0 &&\n+\n+\tlocal bad_head=0 &&\n+\tlocal empty_repo=1 &&\n+\tlocal local_ref=0 &&\n+\tlocal use_quiet=0 &&\n+\tlocal remote=0 &&\n+\tlocal remote_ref=0 &&\n+\tlocal use_new_branch=0 &&\n+\n+\tlocal outcome=\"$1\" &&\n+\tlocal outcome_text &&\n+\tlocal success &&\n+\tshift &&\n+\tlocal args=\"\" &&\n+\tlocal context=\"\" &&\n+\tcase \"$outcome\" in\n+\t\"infer\")\n+\t\tsuccess=1 &&\n+\t\toutcome_text='\"add\" DWIM infer --orphan'\n+\t\t;;\n+\t\"no_infer\")\n+\t\tsuccess=1 &&\n+\t\toutcome_text='\"add\" DWIM doesnt infer --orphan'\n+\t\t;;\n+\t\"fetch_error\")\n+\t\tsuccess=0 &&\n+\t\toutcome_text='\"add\" error need fetch'\n+\t\t;;\n+\t\"fatal_orphan_bad_combo\")\n+\t\tsuccess=0 &&\n+\t\toutcome_text='\"add\" error inferred \"--orphan\" gives illegal opts combo'\n+\t\t;;\n+\t*)\n+\t\techo \"test_dwim_orphan(): invalid outcome: '$outcome'\" >&2 &&\n+\t\treturn 1\n+\t\t;;\n+\tesac &&\n+\twhile [ $# -gt 0 ]\n+\tdo\n+\t\tcase \"$1\" in\n+\t\t# How and from where to create the worktree\n+\t\t\"-C_repo\")\n+\t\t\tuse_cd=0 &&\n+\t\t\tgit_ns=\"repo\" &&\n+\t\t\tdashc_args=\"-C $git_ns\" &&\n+\t\t\tcontext=\"$context, 'git -C repo'\"\n+\t\t\t;;\n+\t\t\"-C_wt\")\n+\t\t\tuse_cd=0 &&\n+\t\t\tgit_ns=\"wt\" &&\n+\t\t\tdashc_args=\"-C $git_ns\" &&\n+\t\t\tcontext=\"$context, 'git -C wt'\"\n+\t\t\t;;\n+\t\t\"cd_repo\")\n+\t\t\tuse_cd=1 &&\n+\t\t\tgit_ns=\"repo\" &&\n+\t\t\tdashc_args=\"\" &&\n+\t\t\tcontext=\"$context, 'cd repo && git'\"\n+\t\t\t;;\n+\t\t\"cd_wt\")\n+\t\t\tuse_cd=1 &&\n+\t\t\tgit_ns=\"wt\" &&\n+\t\t\tdashc_args=\"\" &&\n+\t\t\tcontext=\"$context, 'cd wt && git'\"\n+\t\t\t;;\n+\n+\t\t# Bypass the \"pull first\" warning\n+\t\t\"force\")\n+\t\t\targs=\"$args --force\" &&\n+\t\t\tcontext=\"$context, --force\"\n+\t\t\t;;\n+\n+\t\t# Try to use remote refs when DWIM\n+\t\t\"guess_remote\")\n+\t\t\targs=\"$args --guess-remote\" &&\n+\t\t\tcontext=\"$context, --guess-remote\"\n+\t\t\t;;\n+\t\t\"no_guess_remote\")\n+\t\t\targs=\"$args --no-guess-remote\" &&\n+\t\t\tcontext=\"$context, --no-guess-remote\"\n+\t\t\t;;\n+\n+\t\t# Whether there is at least one local branch present\n+\t\t\"local_ref\")\n+\t\t\tempty_repo=0 &&\n+\t\t\tlocal_ref=1 &&\n+\t\t\tcontext=\"$context, >=1 local branches\"\n+\t\t\t;;\n+\t\t\"no_local_ref\")\n+\t\t\tempty_repo=0 &&\n+\t\t\tcontext=\"$context, 0 local branches\"\n+\t\t\t;;\n+\n+\t\t# Whether the HEAD points at a valid ref (skip this opt when no refs)\n+\t\t\"good_head\")\n+\t\t\t# requires: local_ref\n+\t\t\tcontext=\"$context, valid HEAD\"\n+\t\t\t;;\n+\t\t\"bad_head\")\n+\t\t\tbad_head=1 &&\n+\t\t\tcontext=\"$context, invalid (or orphan) HEAD\"\n+\t\t\t;;\n+\n+\t\t# Whether the code path is tested with the base add command or -b\n+\t\t\"no_-b\")\n+\t\t\tuse_new_branch=0 &&\n+\t\t\tcontext=\"$context, no --branch\"\n+\t\t\t;;\n+\t\t\"-b\")\n+\t\t\tuse_new_branch=1 &&\n+\t\t\tcontext=\"$context, --branch\"\n+\t\t\t;;\n+\n+\t\t# Whether to check that all output is suppressed (except errors)\n+\t\t# or that the output is as expected\n+\t\t\"quiet\")\n+\t\t\tuse_quiet=1 &&\n+\t\t\targs=\"$args --quiet\" &&\n+\t\t\tcontext=\"$context, --quiet\"\n+\t\t\t;;\n+\t\t\"no_quiet\")\n+\t\t\tuse_quiet=0 &&\n+\t\t\tcontext=\"$context, no --quiet (expect output)\"\n+\t\t\t;;\n+\n+\t\t# Whether there is at least one remote attached to the repo\n+\t\t\"remote\")\n+\t\t\tempty_repo=0 &&\n+\t\t\tremote=1 &&\n+\t\t\tcontext=\"$context, >=1 remotes\"\n+\t\t\t;;\n+\t\t\"no_remote\")\n+\t\t\tempty_repo=0 &&\n+\t\t\tremote=0 &&\n+\t\t\tcontext=\"$context, 0 remotes\"\n+\t\t\t;;\n+\n+\t\t# Whether there is at least one valid remote ref\n+\t\t\"remote_ref\")\n+\t\t\t# requires: remote\n+\t\t\tempty_repo=0 &&\n+\t\t\tremote_ref=1 &&\n+\t\t\tcontext=\"$context, >=1 fetched remote branches\"\n+\t\t\t;;\n+\t\t\"no_remote_ref\")\n+\t\t\tempty_repo=0 &&\n+\t\t\tremote_ref=0 &&\n+\t\t\tcontext=\"$context, 0 fetched remote branches\"\n+\t\t\t;;\n+\n+\t\t# Options or flags that become illegal when --orphan is inferred\n+\t\t\"no_checkout\")\n+\t\t\targs=\"$args --no-checkout\" &&\n+\t\t\tcontext=\"$context, --no-checkout\"\n+\t\t\t;;\n+\t\t\"track\")\n+\t\t\targs=\"$args --track\" &&\n+\t\t\tcontext=\"$context, --track\"\n+\t\t\t;;\n+\n+\t\t# All other options are illegal\n+\t\t*)\n+\t\t\techo \"test_dwim_orphan(): invalid arg: '$1'\" >&2 &&\n+\t\t\treturn 1\n+\t\t\t;;\n+\t\tesac &&\n+\t\tshift\n+\tdone &&\n+\tcontext=\"${context#', '}\" &&\n+\tif [ $use_new_branch -eq 1 ]\n+\tthen\n+\t\targs=\"$args -b foo\"\n+\telse\n+\t\tcontext=\"DWIM (no --branch), $context\"\n+\tfi &&\n+\tif [ $empty_repo -eq 1 ]\n+\tthen\n+\t\tcontext=\"empty repo, $context\"\n+\tfi &&\n+\targs=\"$args ../foo\" &&\n+\tcontext=\"${context%', '}\" &&\n+\ttest_expect_success \"$outcome_text w/ $context\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\tif [ $local_ref -eq 1 ] && [ \"$git_ns\" = \"repo\" ]\n+\t\tthen\n+\t\t\t(cd repo && test_commit commit) &&\n+\t\t\tif [ $bad_head -eq 1 ]\n+\t\t\tthen\n+\t\t\t\tgit -C repo symbolic-ref HEAD refs/heads/badbranch\n+\t\t\tfi\n+\t\telif [ $local_ref -eq 1 ] && [ \"$git_ns\" = \"wt\" ]\n+\t\tthen\n+\t\t\ttest_when_finished \"git -C repo worktree remove -f ../wt\" &&\n+\t\t\tgit -C repo worktree add --orphan -b main ../wt &&\n+\t\t\t(cd wt && test_commit commit) &&\n+\t\t\tif [ $bad_head -eq 1 ]\n+\t\t\tthen\n+\t\t\t\tgit -C wt symbolic-ref HEAD refs/heads/badbranch\n+\t\t\tfi\n+\t\telif [ $local_ref -eq 0 ] && [ \"$git_ns\" = \"wt\" ]\n+\t\tthen\n+\t\t\ttest_when_finished \"git -C repo worktree remove -f ../wt\" &&\n+\t\t\tgit -C repo worktree add --orphan -b orphanbranch ../wt\n+\t\tfi &&\n+\n+\t\tif [ $remote -eq 1 ]\n+\t\tthen\n+\t\t\ttest_when_finished \"rm -rf upstream\" &&\n+\t\t\tgit init upstream &&\n+\t\t\t(cd upstream && test_commit commit) &&\n+\t\t\tgit -C upstream switch -c foo &&\n+\t\t\tgit -C repo remote add upstream ../upstream\n+\t\tfi &&\n+\n+\t\tif [ $remote_ref -eq 1 ]\n+\t\tthen\n+\t\t\tgit -C repo fetch\n+\t\tfi &&\n+\t\tif [ $success -eq 1 ]\n+\t\tthen\n+\t\t\ttest_when_finished git -C repo worktree remove ../foo\n+\t\tfi &&\n+\t\tif [ $use_cd -eq 1 ]\n+\t\tthen\n+\t\t\ttest_when_finished cat \"$git_ns/actual\" >&2\n+\t\telse\n+\t\t\ttest_when_finished cat actual >&2\n+\t\tfi &&\n+\t\t(\n+\t\t\tif [ $use_cd -eq 1 ]\n+\t\t\tthen\n+\t\t\t\tcd $git_ns\n+\t\t\tfi &&\n+\t\t\tif [ \"$outcome\" = \"infer\" ]\n+\t\t\tthen\n+\t\t\t\tgit $dashc_args worktree add $args 2>actual &&\n+\t\t\t\tif [ $use_quiet -eq 1 ]\n+\t\t\t\tthen\n+\t\t\t\t\ttest_must_be_empty actual\n+\t\t\t\telse\n+\t\t\t\t\tgrep \"$info_text\" actual\n+\t\t\t\tfi\n+\t\t\telif [ \"$outcome\" = \"no_infer\" ]\n+\t\t\tthen\n+\t\t\t\tgit $dashc_args worktree add $args 2>actual &&\n+\t\t\t\tif [ $use_quiet -eq 1 ]\n+\t\t\t\tthen\n+\t\t\t\t\ttest_must_be_empty actual\n+\t\t\t\telse\n+\t\t\t\t\t! grep \"$info_text\" actual\n+\t\t\t\tfi\n+\t\t\telif [ \"$outcome\" = \"fetch_error\" ]\n+\t\t\tthen\n+\t\t\t\ttest_must_fail git $dashc_args worktree add $args 2>actual &&\n+\t\t\t\tgrep \"$fetch_error_text\" actual\n+\t\t\telif [ \"$outcome\" = \"fatal_orphan_bad_combo\" ]\n+\t\t\tthen\n+\t\t\t\ttest_must_fail git $dashc_args worktree add $args 2>actual &&\n+\t\t\t\tif [ $use_quiet -eq 1 ]\n+\t\t\t\tthen\n+\t\t\t\t\t! grep \"$info_text\" actual\n+\t\t\t\telse\n+\t\t\t\t\tgrep \"$info_text\" actual\n+\t\t\t\tfi &&\n+\t\t\t\tgrep \"$bad_combo_regex\" actual\n+\t\t\telif [ \"$outcome\" = \"warn_bad_head\" ]\n+\t\t\tthen\n+\t\t\t\ttest_must_fail git $dashc_args worktree add $args 2>actual &&\n+\t\t\t\tif [ $use_quiet -eq 1 ]\n+\t\t\t\tthen\n+\t\t\t\t\tgrep \"$invalid_ref_regex\" actual &&\n+\t\t\t\t\t! grep \"$orphan_hint\" actual\n+\t\t\t\telse\n+\t\t\t\t\theadpath=$(git $dashc_args rev-parse --sq --path-format=absolute --git-path HEAD) &&\n+\t\t\t\t\theadcontents=$(cat \"$headpath\") &&\n+\t\t\t\t\tgrep \"HEAD points to an invalid (or orphaned) reference\" actual &&\n+\t\t\t\t\tgrep \"HEAD path:\\s*.$headpath.\" actual &&\n+\t\t\t\t\tgrep \"HEAD contents:\\s*.$headcontents.\" actual &&\n+\t\t\t\t\tgrep \"$orphan_hint\" actual &&\n+\t\t\t\t\t! grep \"$info_text\" actual\n+\t\t\t\tfi &&\n+\t\t\t\tgrep \"$invalid_ref_regex\" actual\n+\t\t\telse\n+\t\t\t\t# Unreachable\n+\t\t\t\tfalse\n+\t\t\tfi\n+\t\t) &&\n+\t\tif [ $success -ne 1 ]\n+\t\tthen\n+\t\t\ttest_path_is_missing foo\n+\t\tfi\n+\t'\n+}\n+\n+for quiet_mode in \"no_quiet\" \"quiet\"\n+do\n+\tfor changedir_type in \"cd_repo\" \"cd_wt\" \"-C_repo\" \"-C_wt\"\n+\tdo\n+\t\tdwim_test_args=\"$quiet_mode $changedir_type\"\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args no_-b\n+\t\ttest_dwim_orphan 'no_infer' $dwim_test_args no_-b local_ref good_head\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref no_remote no_remote_ref no_guess_remote\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref no_guess_remote\n+\t\ttest_dwim_orphan 'fetch_error' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote force\n+\t\ttest_dwim_orphan 'no_infer' $dwim_test_args no_-b no_local_ref remote remote_ref guess_remote\n+\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args -b\n+\t\ttest_dwim_orphan 'no_infer' $dwim_test_args -b local_ref good_head\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref no_remote no_remote_ref no_guess_remote\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref no_guess_remote\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref guess_remote\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote remote_ref guess_remote\n+\tdone\n+\n+\ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b no_checkout\n+\ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b track\n+\ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode -b no_checkout\n+\ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode -b track\n+done\n+\n post_checkout_hook () {\n \ttest_when_finished \"rm -rf .git/hooks\" &&\n \tmkdir .git/hooks &&\n--\n2.39.2\n\n\n"},{"id":"475511","messageId":"20230417093255.31079-1-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":null,"subject":"[PATCH v9 0/8] worktree: Support `--orphan` when creating new worktrees","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-17T09:33:22Z","receivedAt":"2023-04-17T09:36:24Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"This patchset introduces the ability to create new worktrees from orphan/unborn\nbranches and introduces DWIM behavior to create worktrees from an orphan branch\nwhen no valid refs exists locally in the repository (as is typical in newly\ninitialized repositories) or on a remote (when `--guess-remote` is used).\n\nThis addresses the issue of `git worktree add` failing when attempting to create\na worktree from a newly initialized repository (which can be seen in this SO\nquestion [1]).\n\nNote: The last 2 patches in this patchset were initially intended to be part of\na \"part 2\" patchset but given that they are so closely tied to this patchset I\nhave been developing the two together. I'm fine with either splitting the\npatchset and resending it or leaving them rolled together, whichever is\neasier/more convenient for everyone.\n\nThis patchset has eight parts:\n  * adding `-B` to the usage docs (noticed during dev and it seemed too small\n    to justify a separate submission)\n  * updating test cases to still show the output of git commands when the test\n    script is run with `-x` to aid in debugging.\n  * adding a helper fn to simplify testing for mutual exclusion of options\n    in `t/t2400-worktree-add.sh`\n  * adding additional test cases to verify both that behavior doesn't change\n    when using `--quiet` and that the extraneous output is properly suppressed.\n  * adding the ability to create a worktree from an unborn/orphan branch\n    to `git-worktree-add`\n  * adding an advise for using --orphan when `git worktree add` fails due to\n    a bad ref.\n  * adding functionality to DWIM when there are no existing branches and the\n    user likely intends to create an orphan branch.\n  * updating worktree add to emit a warning (containing debug information\n    about the current HEAD) when trying to use a HEAD that points to a\n    non-existant (or unborn) reference and there exist other valid branches.\n\nChanges from v8 (patches 1/8 - 6/8):\n  * Rebase to a newer commit on main (from c03801e19c to 9857273be0) to bypass\n    build failures caused by curl deprecation compile warnings & to eliminate\n    merge conflicts. The below range diff is made against a v8 rebased against\n    the same point\n  * Touched up commit messages.\n  * Shortened title for patch 6/8 to fit in 50 character limit.\n  * Updated tests to print stderr on test exit for tests which capture stderr\n    to improve debugging of individual test failures.\n  * Added tests to verify `--quiet` is actually quiet and doesn't otherwise\n    change command behavior.\n  * Changed `--orphan` from an option to a flag that can be used either on its\n    own or with `-b`/`-B` as requested in [2].\n  * Pulled a conditional and `die()` out of `print_preparing_worktree_line()`\n    so that it'd always be checked regardless of `--quiet`. This change was\n    made because a bug was introduced in an early revision of v9 that caused\n    behavior to differ depending on whether `--quiet` was supplied to the\n    command. To limit the changes made, the original `die()` was left as a\n    `BUG()`.\n  * Moved `!lookup_commit_reference_by_name(branch)` check and `--orphan` hint\n    to the line before the call to `print_preparing_worktree_line()` to combine\n    with the conditional from the above/previous change.\n  * Wrapped `--orphan` advice/hint in conditional to suppress display when\n    using `--quiet`.\n  * Updated `--orphan` advice/hint to match the `add -b branch dir/` vs\n    `add dir/` syntax initially supplied by the user.\n  * Updated `--orphan` hint tests to check presence on bad HEAD instead of\n    empty repo.\n\nChanges from v8 (patches 7/8 & 8/8):\n  * Extended DWIM to infer `--orphan` when no other branches exist in the repo\n    (or remotely when using `--guess-remote` while not using `-b`) [3][4].\n  * Added checks to warn/die when inferring `--orphan` causes the set of\n    supplied options & flags to produce an illegal combination.\n  * Added extensive tests to verify new DWIM behavior.\n  * Added a failure/warning when the user likely forgot to fetch from an\n    upstream repo (i.e. when there is a remote, guess_remote is enabled, and\n    there aren't any local or remote branches in the repo). Can be bypassed\n    with `--force`/`-f` [3].\n  * Updated documentation for worktree-add to mention `--orphan` when\n    discussing situations where DWIM behavior does or does not occur.\n  * Added a warning when the current namespace's HEAD points to an invalid\n    or non-existant reference and the user is trying to create a new worktree\n    from that HEAD.\n\n1. https://stackoverflow.com/a/68717229/15064705/\n2. https://lore.kernel.org/git/e5aadd5d-9b85-4dc9-e9f7-117892b4b283@dunelm.org.uk/\n3. https://lore.kernel.org/git/20230119222003.qcdrhcsvjlyab6af@phi/\n4. https://lore.kernel.org/git/20230118224020.vrytmeyt3vbanoh2@phi/\n\nJacob Abel (8):\n  worktree add: include -B in usage docs\n  t2400: print captured git output when finished\n  t2400: refactor \"worktree add\" opt exclusion tests\n  t2400: add tests to verify --quiet\n  worktree add: add --orphan flag\n  worktree add: introduce \"try --orphan\" hint\n  worktree add: extend DWIM to infer --orphan\n  worktree add: emit warn when there is a bad HEAD\n\n Documentation/config/advice.txt |   4 +\n Documentation/git-worktree.txt  |  16 +-\n advice.c                        |   1 +\n advice.h                        |   1 +\n builtin/worktree.c              | 226 +++++++++++++-\n t/t2400-worktree-add.sh         | 520 +++++++++++++++++++++++++++++++-\n 6 files changed, 747 insertions(+), 21 deletions(-)\n\nRange-diff against v8:\n1:  cbda416378 = 1:  91153fdb4c worktree add: include -B in usage docs\n-:  ---------- > 2:  8cfbc89dd5 t2400: print captured git output when finished\n2:  5f83015779 ! 3:  ab03d92c3a worktree add: refactor opt exclusion tests\n    @@ Metadata\n     Author: Jacob Abel <jacobabel@nullpo.dev>\n\n      ## Commit message ##\n    -    worktree add: refactor opt exclusion tests\n    +    t2400: refactor \"worktree add\" opt exclusion tests\n\n         Pull duplicate test code into a function so that additional opt\n         combinations can be tested succinctly.\n    @@ t/t2400-worktree-add.sh: test_expect_success '\"add\" no auto-vivify with --detach\n     +test_wt_add_excl () {\n     +\tlocal opts=\"$*\" &&\n     +\ttest_expect_success \"'worktree add' with '$opts' has mutually exclusive options\" '\n    ++\t\ttest_when_finished cat actual >&2 &&\n     +\t\ttest_must_fail git worktree add $opts 2>actual &&\n     +\t\tgrep -E \"fatal:( options)? .* cannot be used together\" actual\n     +\t'\n-:  ---------- > 4:  d9a3468c93 t2400: add tests to verify --quiet\n3:  6ac19eeeae ! 5:  8ef9587deb worktree add: add --orphan flag\n    @@ Metadata\n      ## Commit message ##\n         worktree add: add --orphan flag\n\n    -    Adds support for creating an orphan branch when adding a new worktree.\n    -    This functionality is equivalent to git switch's --orphan flag.\n    -\n    -    The original reason this feature was implemented was to allow a user\n    -    to initialise a new repository using solely the worktree oriented\n    -    workflow.\n    +    Add support for creating an orphan branch when adding a new worktree.\n    +    The functionality of this flag is equivalent to git switch's --orphan\n    +    option.\n\n         Current Behavior:\n         % git -C foo.git --no-pager branch -l\n    @@ Commit message\n         % git -C bar.git worktree add main/\n         Preparing worktree (new branch 'main')\n         fatal: invalid reference: HEAD\n    -    % git -C bar.git worktree add --orphan main main/\n    +    % git -C bar.git worktree add --orphan -b main/\n         Preparing worktree (new branch 'main')\n    +    % git -C bar.git worktree add --orphan -b newbranch worktreedir/\n    +    Preparing worktree (new branch 'newbranch')\n         %\n\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n    @@ Commit message\n\n      ## Documentation/git-worktree.txt ##\n     @@ Documentation/git-worktree.txt: SYNOPSIS\n    + --------\n      [verse]\n      'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]]\n    - \t\t   [(-b | -B) <new-branch>] <path> [<commit-ish>]\n    -+'git worktree add' [-f] [--lock [--reason <string>]]\n    -+\t\t   --orphan <new-branch> <path>\n    +-\t\t   [(-b | -B) <new-branch>] <path> [<commit-ish>]\n    ++\t\t   [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]\n      'git worktree list' [-v | --porcelain [-z]]\n      'git worktree lock' [--reason <string>] <worktree>\n      'git worktree move' <worktree> <new-path>\n    -@@ Documentation/git-worktree.txt: exist, a new branch based on `HEAD` is automatically created as if\n    - `-b <branch>` was given.  If `<branch>` does exist, it will be checked out\n    - in the new worktree, if it's not checked out anywhere else, otherwise the\n    - command will refuse to create the worktree (unless `--force` is used).\n    -++\n    -+------------\n    -+$ git worktree add --orphan <branch> <path>\n    -+------------\n    -++\n    -+Create a worktree containing no files, with an empty index, and associated\n    -+with a new orphan branch named `<branch>`. The first commit made on this new\n    -+branch will have no parents and will be the root of a new history disconnected\n    -+from any other branches.\n    -\n    - list::\n    -\n     @@ Documentation/git-worktree.txt: This can also be set up as the default behaviour by using the\n      \tWith `prune`, do not remove anything; just report what it would\n      \tremove.\n\n    -+--orphan <new-branch>::\n    ++--orphan::\n     +\tWith `add`, make the new worktree and index empty, associating\n    -+\tthe worktree with a new orphan branch named `<new-branch>`.\n    ++\tthe worktree with a new orphan/unborn branch named `<new-branch>`.\n     +\n      --porcelain::\n      \tWith `list`, output in an easy-to-parse format for scripts.\n    @@ builtin/worktree.c\n      #define BUILTIN_WORKTREE_ADD_USAGE \\\n      \tN_(\"git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]\\n\" \\\n     -\t   \"                 [(-b | -B) <new-branch>] <path> [<commit-ish>]\")\n    -+\t   \"                 [(-b | -B) <new-branch>] <path> [<commit-ish>]\"), \\\n    -+\tN_(\"git worktree add [-f] [--lock [--reason <string>]]\\n\" \\\n    -+\t   \"                 --orphan <new-branch> <path>\")\n    ++\t   \"                 [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]\")\n     +\n      #define BUILTIN_WORKTREE_LIST_USAGE \\\n      \tN_(\"git worktree list [-v | --porcelain [-z]]\")\n    @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam\n      \t\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n\n      \t\tstrvec_pushl(&opt.env, \"GIT_DIR\", \"GIT_WORK_TREE\", NULL);\n    -@@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)\n    - \tchar *path;\n    - \tconst char *branch;\n    - \tconst char *new_branch = NULL;\n    -+\tconst char *orphan_branch = NULL;\n    - \tconst char *opt_track = NULL;\n    - \tconst char *lock_reason = NULL;\n    - \tint keep_locked = 0;\n    +@@ builtin/worktree.c: static void print_preparing_worktree_line(int detach,\n    + \t\telse {\n    + \t\t\tstruct commit *commit = lookup_commit_reference_by_name(branch);\n    + \t\t\tif (!commit)\n    +-\t\t\t\tdie(_(\"invalid reference: %s\"), branch);\n    ++\t\t\t\tBUG(_(\"unreachable: invalid reference: %s\"), branch);\n    + \t\t\tfprintf_ln(stderr, _(\"Preparing worktree (detached HEAD %s)\"),\n    + \t\t\t\t  repo_find_unique_abbrev(the_repository, &commit->object.oid, DEFAULT_ABBREV));\n    + \t\t}\n     @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)\n      \t\t\t   N_(\"create a new branch\")),\n      \t\tOPT_STRING('B', NULL, &new_branch_force, N_(\"branch\"),\n      \t\t\t   N_(\"create or reset a branch\")),\n    -+\t\tOPT_STRING(0, \"orphan\", &orphan_branch, N_(\"branch\"),\n    -+\t\t\t   N_(\"new unparented branch\")),\n    ++\t\tOPT_BOOL(0, \"orphan\", &opts.orphan, N_(\"create unborn/orphaned branch\")),\n      \t\tOPT_BOOL('d', \"detach\", &opts.detach, N_(\"detach HEAD at named commit\")),\n      \t\tOPT_BOOL(0, \"checkout\", &opts.checkout, N_(\"populate the new working tree\")),\n      \t\tOPT_BOOL(0, \"lock\", &keep_locked, N_(\"keep the new working tree locked\")),\n     @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)\n    - \tmemset(&opts, 0, sizeof(opts));\n    - \topts.checkout = 1;\n      \tac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0);\n    -+\topts.orphan = !!orphan_branch;\n      \tif (!!opts.detach + !!new_branch + !!new_branch_force > 1)\n      \t\tdie(_(\"options '%s', '%s', and '%s' cannot be used together\"), \"-b\", \"-B\", \"--detach\");\n    -+\tif (!!opts.detach + !!opts.orphan + !!new_branch + !!new_branch_force > 1)\n    -+\t\tdie(_(\"options '%s', '%s', '%s', and '%s' cannot be used together\"),\n    -+\t\t    \"-b\", \"-B\", \"--orphan\", \"--detach\");\n    ++\tif (opts.detach && opts.orphan)\n    ++\t\tdie(_(\"options '%s', and '%s' cannot be used together\"),\n    ++\t\t    \"--orphan\", \"--detach\");\n     +\tif (opts.orphan && opt_track)\n     +\t\tdie(_(\"'%s' and '%s' cannot be used together\"), \"--orphan\", \"--track\");\n     +\tif (opts.orphan && !opts.checkout)\n    @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)\n      \t}\n\n     -\tif (ac < 2 && !new_branch && !opts.detach) {\n    -+\tif (opts.orphan) {\n    -+\t\tnew_branch = orphan_branch;\n    -+\t} else if (ac < 2 && !new_branch && !opts.detach) {\n    ++\tif (opts.orphan && !new_branch) {\n    ++\t\tint n;\n    ++\t\tconst char *s = worktree_basename(path, &n);\n    ++\t\tnew_branch = xstrndup(s, n);\n    ++\t} else if (new_branch || opts.detach || opts.orphan) {\n    ++\t\t// No-op\n    ++\t} else if (ac < 2) {\n      \t\tconst char *s = dwim_branch(path, &new_branch);\n      \t\tif (s)\n      \t\t\tbranch = s;\n    +-\t}\n    +-\n    +-\tif (ac == 2 && !new_branch && !opts.detach) {\n    ++\t} else if (ac == 2) {\n    + \t\tstruct object_id oid;\n    + \t\tstruct commit *commit;\n    + \t\tconst char *remote;\n     @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)\n    + \t\t\t}\n    + \t\t}\n    + \t}\n    ++\n    ++\tif (!opts.orphan && !lookup_commit_reference_by_name(branch)) {\n    ++\t\tdie(_(\"invalid reference: %s\"), branch);\n    ++\t}\n    ++\n      \tif (!opts.quiet)\n      \t\tprint_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force);\n\n     -\tif (new_branch) {\n     +\tif (opts.orphan) {\n     +\t\tbranch = new_branch;\n    -+\t} else if (!lookup_commit_reference_by_name(branch)) {\n    -+\t\tdie(_(\"invalid reference: %s\"), branch);\n     +\t} else if (new_branch) {\n      \t\tstruct child_process cp = CHILD_PROCESS_INIT;\n      \t\tcp.git_cmd = 1;\n    @@ t/t2400-worktree-add.sh: test_wt_add_excl () {\n      test_wt_add_excl -b poodle -B poodle bamboo main\n      test_wt_add_excl -b poodle --detach bamboo main\n      test_wt_add_excl -B poodle --detach bamboo main\n    -+test_wt_add_excl -B poodle --orphan poodle bamboo\n    -+test_wt_add_excl -b poodle --orphan poodle bamboo\n    -+test_wt_add_excl --orphan poodle --detach bamboo\n    -+test_wt_add_excl --orphan poodle --no-checkout bamboo\n    -+test_wt_add_excl --orphan poodle bamboo main\n    ++test_wt_add_excl --orphan --detach bamboo\n    ++test_wt_add_excl --orphan --no-checkout bamboo\n    ++test_wt_add_excl --orphan bamboo main\n    ++test_wt_add_excl --orphan -b bamboo wtdir/ main\n\n      test_expect_success '\"add -B\" fails if the branch is checked out' '\n      \tgit rev-parse newmain >before &&\n    -@@ t/t2400-worktree-add.sh: test_expect_success 'add --quiet' '\n    +@@ t/t2400-worktree-add.sh: test_expect_success 'add --quiet -b' '\n      \ttest_must_be_empty actual\n      '\n\n     +test_expect_success '\"add --orphan\"' '\n     +\ttest_when_finished \"git worktree remove -f -f orphandir\" &&\n    -+\tgit worktree add --orphan neworphan orphandir &&\n    ++\tgit worktree add --orphan -b neworphan orphandir &&\n    ++\techo refs/heads/neworphan >expected &&\n    ++\tgit -C orphandir symbolic-ref HEAD >actual &&\n    ++\ttest_cmp expected actual\n    ++'\n    ++\n    ++test_expect_success '\"add --orphan (no -b)\"' '\n    ++\ttest_when_finished \"git worktree remove -f -f neworphan\" &&\n    ++\tgit worktree add --orphan neworphan &&\n    ++\techo refs/heads/neworphan >expected &&\n    ++\tgit -C neworphan symbolic-ref HEAD >actual &&\n    ++\ttest_cmp expected actual\n    ++'\n    ++\n    ++test_expect_success '\"add --orphan --quiet\"' '\n    ++\ttest_when_finished \"git worktree remove -f -f orphandir\" &&\n    ++\ttest_when_finished cat log.actual >&2 &&\n    ++\tgit worktree add --quiet --orphan -b neworphan orphandir 2>log.actual &&\n    ++\ttest_must_be_empty log.actual &&\n     +\techo refs/heads/neworphan >expected &&\n     +\tgit -C orphandir symbolic-ref HEAD >actual &&\n     +\ttest_cmp expected actual\n    @@ t/t2400-worktree-add.sh: test_expect_success 'add --quiet' '\n     +\n     +test_expect_success '\"add --orphan\" fails if the branch already exists' '\n     +\ttest_when_finished \"git branch -D existingbranch\" &&\n    -+\ttest_when_finished \"git worktree remove -f -f orphandir\" &&\n     +\tgit worktree add -b existingbranch orphandir main &&\n    -+\ttest_must_fail git worktree add --orphan existingbranch orphandir2 &&\n    -+\ttest_path_is_missing orphandir2\n    ++\tgit worktree remove orphandir &&\n    ++\ttest_must_fail git worktree add --orphan -b existingbranch orphandir\n     +'\n     +\n     +test_expect_success '\"add --orphan\" with empty repository' '\n     +\ttest_when_finished \"rm -rf empty_repo\" &&\n     +\techo refs/heads/newbranch >expected &&\n     +\tGIT_DIR=\"empty_repo\" git init --bare &&\n    -+\tgit -C empty_repo  worktree add --orphan newbranch worktreedir &&\n    ++\tgit -C empty_repo  worktree add --orphan -b newbranch worktreedir &&\n     +\tgit -C empty_repo/worktreedir symbolic-ref HEAD >actual &&\n     +\ttest_cmp expected actual\n     +'\n     +\n     +test_expect_success '\"add\" worktree with orphan branch and lock' '\n    -+\tgit worktree add --lock --orphan orphanbr orphan-with-lock &&\n    ++\tgit worktree add --lock --orphan -b orphanbr orphan-with-lock &&\n     +\ttest_when_finished \"git worktree unlock orphan-with-lock || :\" &&\n     +\ttest -f .git/worktrees/orphan-with-lock/locked\n     +'\n4:  3d76a5b6b8 ! 6:  d2800266f9 worktree add: add hint to direct users towards --orphan\n    @@ Metadata\n     Author: Jacob Abel <jacobabel@nullpo.dev>\n\n      ## Commit message ##\n    -    worktree add: add hint to direct users towards --orphan\n    +    worktree add: introduce \"try --orphan\" hint\n\n    -    Adds a new advice/hint in `git worktree add` for when the user\n    +    Add a new advice/hint in `git worktree add` for when the user\n         tries to create a new worktree from a reference that doesn't exist.\n\n         Current Behavior:\n\n    -    % git init --bare foo.git\n    -    Initialized empty Git repository in /path/to/foo.git/\n    -    % git -C foo.git worktree add main/\n    -    Preparing worktree (new branch 'main')\n    +    % git init foo\n    +    Initialized empty Git repository in /path/to/foo/\n    +    % touch file\n    +    % git -C foo commit -q -a -m \"test commit\"\n    +    % git -C foo switch --orphan norefbranch\n    +    % git -C foo worktree add newbranch/\n    +    Preparing worktree (new branch 'newbranch')\n         fatal: invalid reference: HEAD\n         %\n\n         New Behavior:\n\n    -    % git init --bare foo.git\n    -    Initialized empty Git repository in /path/to/foo.git/\n    -    % git -C foo.git worktree add main/\n    -    Preparing worktree (new branch 'main')\n    +    % git init --bare foo\n    +    Initialized empty Git repository in /path/to/foo/\n    +    % touch file\n    +    % git -C foo commit -q -a -m \"test commit\"\n    +    % git -C foo switch --orphan norefbranch\n    +    % git -C foo worktree add newbranch/\n    +    Preparing worktree (new branch 'newbranch')\n         hint: If you meant to create a worktree containing a new orphan branch\n         hint: (branch with no commits) for this repository, you can do so\n         hint: using the --orphan option:\n         hint:\n    -    hint:   git worktree add --orphan main ./main\n    +    hint:   git worktree add --orphan newbranch/\n    +    hint:\n    +    hint: Disable this message with \"git config advice.worktreeAddOrphan false\"\n    +    fatal: invalid reference: HEAD\n    +    % git -C foo worktree add -b newbranch2 new_wt/\n    +    Preparing worktree (new branch 'newbranch')\n    +    hint: If you meant to create a worktree containing a new orphan branch\n    +    hint: (branch with no commits) for this repository, you can do so\n    +    hint: using the --orphan option:\n    +    hint:\n    +    hint:   git worktree add --orphan -b newbranch2 new_wt/\n         hint:\n         hint: Disable this message with \"git config advice.worktreeAddOrphan false\"\n         fatal: invalid reference: HEAD\n    @@ advice.h: struct string_list;\n      int git_default_advice_config(const char *var, const char *value);\n\n      ## builtin/worktree.c ##\n    +@@\n    + #define BUILTIN_WORKTREE_UNLOCK_USAGE \\\n    + \tN_(\"git worktree unlock <worktree>\")\n    +\n    ++#define WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT \\\n    ++\t_(\"If you meant to create a worktree containing a new orphan branch\\n\" \\\n    ++\t\"(branch with no commits) for this repository, you can do so\\n\" \\\n    ++\t\"using the --orphan flag:\\n\" \\\n    ++\t\"\\n\" \\\n    ++\t\"\tgit worktree add --orphan -b %s %s\\n\")\n    ++\n    ++#define WORKTREE_ADD_ORPHAN_NO_DASH_B_HINT_TEXT \\\n    ++\t_(\"If you meant to create a worktree containing a new orphan branch\\n\" \\\n    ++\t\"(branch with no commits) for this repository, you can do so\\n\" \\\n    ++\t\"using the --orphan flag:\\n\" \\\n    ++\t\"\\n\" \\\n    ++\t\"\tgit worktree add --orphan %s\\n\")\n    ++\n    + static const char * const git_worktree_usage[] = {\n    + \tBUILTIN_WORKTREE_ADD_USAGE,\n    + \tBUILTIN_WORKTREE_LIST_USAGE,\n    +@@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)\n    + \tconst char *opt_track = NULL;\n    + \tconst char *lock_reason = NULL;\n    + \tint keep_locked = 0;\n    ++\tint used_new_branch_options;\n    + \tstruct option options[] = {\n    + \t\tOPT__FORCE(&opts.force,\n    + \t\t\t   N_(\"checkout <branch> even if already checked out in other worktree\"),\n    +@@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)\n    +\n    + \tpath = prefix_filename(prefix, av[0]);\n    + \tbranch = ac < 2 ? \"HEAD\" : av[1];\n    ++\tused_new_branch_options = new_branch || new_branch_force;\n    +\n    + \tif (!strcmp(branch, \"-\"))\n    + \t\tbranch = \"@{-1}\";\n     @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)\n    - \tif (opts.orphan) {\n    - \t\tbranch = new_branch;\n    - \t} else if (!lookup_commit_reference_by_name(branch)) {\n    -+\t\tadvise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN,\n    -+\t\t\t_(\"If you meant to create a worktree containing a new orphan branch\\n\"\n    -+\t\t\t\"(branch with no commits) for this repository, you can do so\\n\"\n    -+\t\t\t\"using the --orphan option:\\n\"\n    -+\t\t\t\"\\n\"\n    -+\t\t\t\"\tgit worktree add --orphan %s %s\\n\"), new_branch, path);\n    + \t}\n    +\n    + \tif (!opts.orphan && !lookup_commit_reference_by_name(branch)) {\n    ++\t\tint attempt_hint = !opts.quiet && (ac < 2);\n    ++\t\tif (attempt_hint && used_new_branch_options) {\n    ++\t\t\tadvise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN,\n    ++\t\t\t\tWORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT,\n    ++\t\t\t\tnew_branch, path);\n    ++\t\t} else if (attempt_hint) {\n    ++\t\t\tadvise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN,\n    ++\t\t\t\tWORKTREE_ADD_ORPHAN_NO_DASH_B_HINT_TEXT, path);\n    ++\t\t}\n      \t\tdie(_(\"invalid reference: %s\"), branch);\n    - \t} else if (new_branch) {\n    - \t\tstruct child_process cp = CHILD_PROCESS_INIT;\n    + \t}\n    +\n\n      ## t/t2400-worktree-add.sh ##\n     @@ t/t2400-worktree-add.sh: test_expect_success '\"add\" worktree with orphan branch, lock, and reason' '\n    @@ t/t2400-worktree-add.sh: test_expect_success '\"add\" worktree with orphan branch,\n      '\n\n     +# Note: Quoted arguments containing spaces are not supported.\n    -+test_wt_add_empty_repo_orphan_hint () {\n    ++test_wt_add_orphan_hint () {\n     +\tlocal context=\"$1\" &&\n    -+\tshift &&\n    ++\tlocal use_branch=$2 &&\n    ++\tshift 2 &&\n     +\tlocal opts=\"$*\" &&\n    -+\ttest_expect_success \"'worktree add' show orphan hint in empty repo w/ $context\" '\n    -+\t\ttest_when_finished \"rm -rf empty_repo\" &&\n    -+\t\tGIT_DIR=\"empty_repo\" git init --bare &&\n    -+\t\ttest_must_fail git -C empty_repo worktree add $opts foobar/ 2>actual &&\n    ++\ttest_expect_success \"'worktree add' show orphan hint in bad/orphan HEAD w/ $context\" '\n    ++\t\ttest_when_finished \"rm -rf repo\" &&\n    ++\t\tgit init repo &&\n    ++\t\t(cd repo && test_commit commit) &&\n    ++\t\tgit -C repo switch --orphan noref &&\n    ++\t\ttest_when_finished cat actual >&2 &&\n    ++\t\ttest_must_fail git -C repo worktree add $opts foobar/ 2>actual &&\n     +\t\t! grep \"error: unknown switch\" actual &&\n    -+\t\tgrep \"hint: If you meant to create a worktree containing a new orphan branch\" actual\n    ++\t\tgrep \"hint: If you meant to create a worktree containing a new orphan branch\" actual &&\n    ++\t\tif [ $use_branch -eq 1 ]\n    ++\t\tthen\n    ++\t\t\tgrep -E \"^hint:\\s+git worktree add --orphan -b \\S+ \\S+\\s*$\" actual\n    ++\t\telse\n    ++\t\t\tgrep -E \"^hint:\\s+git worktree add --orphan \\S+\\s*$\" actual\n    ++\t\tfi\n    ++\n     +\t'\n     +}\n     +\n    -+test_wt_add_empty_repo_orphan_hint 'DWIM'\n    -+test_wt_add_empty_repo_orphan_hint '-b' -b foobar_branch\n    -+test_wt_add_empty_repo_orphan_hint '-B' -B foobar_branch\n    ++test_wt_add_orphan_hint 'no opts' 0\n    ++test_wt_add_orphan_hint '-b' 1 -b foobar_branch\n    ++test_wt_add_orphan_hint '-B' 1 -B foobar_branch\n    ++\n    ++test_expect_success \"'worktree add' doesn't show orphan hint in bad/orphan HEAD w/ --quiet\" '\n    ++\ttest_when_finished \"rm -rf repo\" &&\n    ++\tgit init repo &&\n    ++\t(cd repo && test_commit commit) &&\n    ++\ttest_when_finished cat actual >&2 &&\n    ++\ttest_must_fail git -C repo worktree add --quiet foobar_branch foobar/ 2>actual &&\n    ++\t! grep \"error: unknown switch\" actual &&\n    ++\t! grep \"hint: If you meant to create a worktree containing a new orphan branch\" actual\n    ++'\n     +\n      test_expect_success 'local clone from linked checkout' '\n      \tgit clone --local here here-clone &&\n-:  ---------- > 7:  e5e139766c worktree add: extend DWIM to infer --orphan\n-:  ---------- > 8:  296226ffd5 worktree add: emit warn when there is a bad HEAD\n--\n2.39.2\n\n\n"},{"id":"475512","messageId":"20230417093255.31079-4-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230417093255.31079-1-jacobabel@nullpo.dev","subject":"[PATCH v9 3/8] t2400: refactor \"worktree add\" opt exclusion tests","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-17T09:33:52Z","receivedAt":"2023-04-17T09:36:54Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Pull duplicate test code into a function so that additional opt\ncombinations can be tested succinctly.\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n t/t2400-worktree-add.sh | 24 ++++++++++++++----------\n 1 file changed, 14 insertions(+), 10 deletions(-)\n\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex 9bc3db20e4..6822be4666 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -298,17 +298,21 @@ test_expect_success '\"add\" no auto-vivify with --detach and <branch> omitted' '\n \ttest_must_fail git -C mish/mash symbolic-ref HEAD\n '\n\n-test_expect_success '\"add\" -b/-B mutually exclusive' '\n-\ttest_must_fail git worktree add -b poodle -B poodle bamboo main\n-'\n-\n-test_expect_success '\"add\" -b/--detach mutually exclusive' '\n-\ttest_must_fail git worktree add -b poodle --detach bamboo main\n-'\n+# Helper function to test mutually exclusive options.\n+#\n+# Note: Quoted arguments containing spaces are not supported.\n+test_wt_add_excl () {\n+\tlocal opts=\"$*\" &&\n+\ttest_expect_success \"'worktree add' with '$opts' has mutually exclusive options\" '\n+\t\ttest_when_finished cat actual >&2 &&\n+\t\ttest_must_fail git worktree add $opts 2>actual &&\n+\t\tgrep -E \"fatal:( options)? .* cannot be used together\" actual\n+\t'\n+}\n\n-test_expect_success '\"add\" -B/--detach mutually exclusive' '\n-\ttest_must_fail git worktree add -B poodle --detach bamboo main\n-'\n+test_wt_add_excl -b poodle -B poodle bamboo main\n+test_wt_add_excl -b poodle --detach bamboo main\n+test_wt_add_excl -B poodle --detach bamboo main\n\n test_expect_success '\"add -B\" fails if the branch is checked out' '\n \tgit rev-parse newmain >before &&\n--\n2.39.2\n\n\n"},{"id":"475513","messageId":"20230417093255.31079-7-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230417093255.31079-1-jacobabel@nullpo.dev","subject":"[PATCH v9 6/8] worktree add: introduce \"try --orphan\" hint","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-17T09:34:19Z","receivedAt":"2023-04-17T09:37:24Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Add a new advice/hint in `git worktree add` for when the user\ntries to create a new worktree from a reference that doesn't exist.\n\nCurrent Behavior:\n\n% git init foo\nInitialized empty Git repository in /path/to/foo/\n% touch file\n% git -C foo commit -q -a -m \"test commit\"\n% git -C foo switch --orphan norefbranch\n% git -C foo worktree add newbranch/\nPreparing worktree (new branch 'newbranch')\nfatal: invalid reference: HEAD\n%\n\nNew Behavior:\n\n% git init --bare foo\nInitialized empty Git repository in /path/to/foo/\n% touch file\n% git -C foo commit -q -a -m \"test commit\"\n% git -C foo switch --orphan norefbranch\n% git -C foo worktree add newbranch/\nPreparing worktree (new branch 'newbranch')\nhint: If you meant to create a worktree containing a new orphan branch\nhint: (branch with no commits) for this repository, you can do so\nhint: using the --orphan option:\nhint:\nhint:   git worktree add --orphan newbranch/\nhint:\nhint: Disable this message with \"git config advice.worktreeAddOrphan false\"\nfatal: invalid reference: HEAD\n% git -C foo worktree add -b newbranch2 new_wt/\nPreparing worktree (new branch 'newbranch')\nhint: If you meant to create a worktree containing a new orphan branch\nhint: (branch with no commits) for this repository, you can do so\nhint: using the --orphan option:\nhint:\nhint:   git worktree add --orphan -b newbranch2 new_wt/\nhint:\nhint: Disable this message with \"git config advice.worktreeAddOrphan false\"\nfatal: invalid reference: HEAD\n%\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n Documentation/config/advice.txt |  4 ++++\n advice.c                        |  1 +\n advice.h                        |  1 +\n builtin/worktree.c              | 25 +++++++++++++++++++++\n t/t2400-worktree-add.sh         | 39 +++++++++++++++++++++++++++++++++\n 5 files changed, 70 insertions(+)\n\ndiff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\nindex c96b5b2e5d..c548a91e67 100644\n--- a/Documentation/config/advice.txt\n+++ b/Documentation/config/advice.txt\n@@ -138,4 +138,8 @@ advice.*::\n \t\tcheckout.\n \tdiverging::\n \t\tAdvice shown when a fast-forward is not possible.\n+\tworktreeAddOrphan::\n+\t\tAdvice shown when a user tries to create a worktree from an\n+\t\tinvalid reference, to instruct how to create a new orphan\n+\t\tbranch instead.\n --\ndiff --git a/advice.c b/advice.c\nindex d6232439c3..e5a9bb9b44 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -78,6 +78,7 @@ static struct {\n \t[ADVICE_SUBMODULES_NOT_UPDATED] \t\t= { \"submodulesNotUpdated\", 1 },\n \t[ADVICE_UPDATE_SPARSE_PATH]\t\t\t= { \"updateSparsePath\", 1 },\n \t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n+\t[ADVICE_WORKTREE_ADD_ORPHAN]\t\t\t= { \"worktreeAddOrphan\", 1 },\n };\n\n static const char turn_off_instructions[] =\ndiff --git a/advice.h b/advice.h\nindex 0f584163f5..2affbe1426 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -49,6 +49,7 @@ struct string_list;\n \tADVICE_UPDATE_SPARSE_PATH,\n \tADVICE_WAITING_FOR_EDITOR,\n \tADVICE_SKIPPED_CHERRY_PICKS,\n+\tADVICE_WORKTREE_ADD_ORPHAN,\n };\n\n int git_default_advice_config(const char *var, const char *value);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 48de7fc3b0..12348d3d16 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -39,6 +39,20 @@\n #define BUILTIN_WORKTREE_UNLOCK_USAGE \\\n \tN_(\"git worktree unlock <worktree>\")\n\n+#define WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT \\\n+\t_(\"If you meant to create a worktree containing a new orphan branch\\n\" \\\n+\t\"(branch with no commits) for this repository, you can do so\\n\" \\\n+\t\"using the --orphan flag:\\n\" \\\n+\t\"\\n\" \\\n+\t\"\tgit worktree add --orphan -b %s %s\\n\")\n+\n+#define WORKTREE_ADD_ORPHAN_NO_DASH_B_HINT_TEXT \\\n+\t_(\"If you meant to create a worktree containing a new orphan branch\\n\" \\\n+\t\"(branch with no commits) for this repository, you can do so\\n\" \\\n+\t\"using the --orphan flag:\\n\" \\\n+\t\"\\n\" \\\n+\t\"\tgit worktree add --orphan %s\\n\")\n+\n static const char * const git_worktree_usage[] = {\n \tBUILTIN_WORKTREE_ADD_USAGE,\n \tBUILTIN_WORKTREE_LIST_USAGE,\n@@ -634,6 +648,7 @@ static int add(int ac, const char **av, const char *prefix)\n \tconst char *opt_track = NULL;\n \tconst char *lock_reason = NULL;\n \tint keep_locked = 0;\n+\tint used_new_branch_options;\n \tstruct option options[] = {\n \t\tOPT__FORCE(&opts.force,\n \t\t\t   N_(\"checkout <branch> even if already checked out in other worktree\"),\n@@ -686,6 +701,7 @@ static int add(int ac, const char **av, const char *prefix)\n\n \tpath = prefix_filename(prefix, av[0]);\n \tbranch = ac < 2 ? \"HEAD\" : av[1];\n+\tused_new_branch_options = new_branch || new_branch_force;\n\n \tif (!strcmp(branch, \"-\"))\n \t\tbranch = \"@{-1}\";\n@@ -728,6 +744,15 @@ static int add(int ac, const char **av, const char *prefix)\n \t}\n\n \tif (!opts.orphan && !lookup_commit_reference_by_name(branch)) {\n+\t\tint attempt_hint = !opts.quiet && (ac < 2);\n+\t\tif (attempt_hint && used_new_branch_options) {\n+\t\t\tadvise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN,\n+\t\t\t\tWORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT,\n+\t\t\t\tnew_branch, path);\n+\t\t} else if (attempt_hint) {\n+\t\t\tadvise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN,\n+\t\t\t\tWORKTREE_ADD_ORPHAN_NO_DASH_B_HINT_TEXT, path);\n+\t\t}\n \t\tdie(_(\"invalid reference: %s\"), branch);\n \t}\n\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex 2ea4342867..7ea56ef7c1 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -405,6 +405,45 @@ test_expect_success '\"add\" worktree with orphan branch, lock, and reason' '\n \ttest_cmp expect .git/worktrees/orphan-with-lock-reason/locked\n '\n\n+# Note: Quoted arguments containing spaces are not supported.\n+test_wt_add_orphan_hint () {\n+\tlocal context=\"$1\" &&\n+\tlocal use_branch=$2 &&\n+\tshift 2 &&\n+\tlocal opts=\"$*\" &&\n+\ttest_expect_success \"'worktree add' show orphan hint in bad/orphan HEAD w/ $context\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(cd repo && test_commit commit) &&\n+\t\tgit -C repo switch --orphan noref &&\n+\t\ttest_when_finished cat actual >&2 &&\n+\t\ttest_must_fail git -C repo worktree add $opts foobar/ 2>actual &&\n+\t\t! grep \"error: unknown switch\" actual &&\n+\t\tgrep \"hint: If you meant to create a worktree containing a new orphan branch\" actual &&\n+\t\tif [ $use_branch -eq 1 ]\n+\t\tthen\n+\t\t\tgrep -E \"^hint:\\s+git worktree add --orphan -b \\S+ \\S+\\s*$\" actual\n+\t\telse\n+\t\t\tgrep -E \"^hint:\\s+git worktree add --orphan \\S+\\s*$\" actual\n+\t\tfi\n+\n+\t'\n+}\n+\n+test_wt_add_orphan_hint 'no opts' 0\n+test_wt_add_orphan_hint '-b' 1 -b foobar_branch\n+test_wt_add_orphan_hint '-B' 1 -B foobar_branch\n+\n+test_expect_success \"'worktree add' doesn't show orphan hint in bad/orphan HEAD w/ --quiet\" '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(cd repo && test_commit commit) &&\n+\ttest_when_finished cat actual >&2 &&\n+\ttest_must_fail git -C repo worktree add --quiet foobar_branch foobar/ 2>actual &&\n+\t! grep \"error: unknown switch\" actual &&\n+\t! grep \"hint: If you meant to create a worktree containing a new orphan branch\" actual\n+'\n+\n test_expect_success 'local clone from linked checkout' '\n \tgit clone --local here here-clone &&\n \t( cd here-clone && git fsck )\n--\n2.39.2\n\n\n"},{"id":"475514","messageId":"20230417093255.31079-9-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230417093255.31079-1-jacobabel@nullpo.dev","subject":"[PATCH v9 8/8] worktree add: emit warn when there is a bad HEAD","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-17T09:34:34Z","receivedAt":"2023-04-17T09:37:28Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Add a warning to `worktree add` when the command tries to reference\nHEAD, there exist valid local branches, and the HEAD points to a\nnon-existent reference.\n\nCurrent Behavior:\n% git -C foo worktree list\n/path/to/repo/foo     dadc8e6dac [main]\n/path/to/repo/foo_wt  0000000000 [badref]\n% git -C foo worktree add ../wt1\nPreparing worktree (new branch 'wt1')\nHEAD is now at dadc8e6dac dummy commit\n% git -C foo_wt worktree add ../wt2\nhint: If you meant to create a worktree containing a new orphan branch\n[...]\nhint: Disable this message with \"git config advice.worktreeAddOrphan false\"\nfatal: invalid reference: HEAD\n%\n\nNew Behavior:\n% git -C foo worktree list\n/path/to/repo/foo     dadc8e6dac [main]\n/path/to/repo/foo_wt  0000000000 [badref]\n% git -C foo worktree add ../wt1\nPreparing worktree (new branch 'wt1')\nHEAD is now at dadc8e6dac dummy commit\n% git -C foo_wt worktree add ../wt2\nwarning: HEAD points to an invalid (or orphaned) reference.\nHEAD path: '/path/to/repo/foo/.git/worktrees/foo_wt/HEAD'\nHEAD contents: 'ref: refs/heads/badref'\nhint: If you meant to create a worktree containing a new orphan branch\n[...]\nhint: Disable this message with \"git config advice.worktreeAddOrphan false\"\nfatal: invalid reference: HEAD\n%\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n builtin/worktree.c      | 34 +++++++++++++++++++++++++++++-----\n t/t2400-worktree-add.sh | 18 +++++++++++++++++-\n 2 files changed, 46 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 95b5bbb1d2..0fba4cfdf8 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -637,6 +637,9 @@ static int first_valid_ref(const char *refname,\n  *\n  * - Checks whether any valid local branches exist.\n  *\n+ * - Emits a warning if there exist any valid branches but HEAD does not point\n+ *   to a valid reference.\n+ *\n  * Returns 1 if any of the previous checks are true, otherwise returns 0.\n  */\n static int can_use_local_refs(const struct add_opts *opts)\n@@ -644,6 +647,23 @@ static int can_use_local_refs(const struct add_opts *opts)\n \tif (head_ref(first_valid_ref, NULL)) {\n \t\treturn 1;\n \t} else if (for_each_branch_ref(first_valid_ref, NULL)) {\n+\t\tif (!opts->quiet) {\n+\t\t\tstruct strbuf path = STRBUF_INIT;\n+\t\t\tstruct strbuf contents = STRBUF_INIT;\n+\n+\t\t\tstrbuf_add_real_path(&path, get_worktree_git_dir(NULL));\n+\t\t\tstrbuf_addstr(&path, \"/HEAD\");\n+\t\t\tstrbuf_read_file(&contents, path.buf, 64);\n+\t\t\tstrbuf_stripspace(&contents, 0);\n+\t\t\tstrbuf_strip_suffix(&contents, \"\\n\");\n+\n+\t\t\twarning(_(\"HEAD points to an invalid (or orphaned) reference.\\n\"\n+\t\t\t\t  \"HEAD path: '%s'\\n\"\n+\t\t\t\t  \"HEAD contents: '%s'\"),\n+\t\t\t\t  path.buf, contents.buf);\n+\t\t\tstrbuf_release(&path);\n+\t\t\tstrbuf_release(&contents);\n+\t\t}\n \t\treturn 1;\n \t}\n \treturn 0;\n@@ -665,16 +685,12 @@ static int can_use_local_refs(const struct add_opts *opts)\n static int can_use_remote_refs(const struct add_opts *opts)\n {\n \tif (!guess_remote) {\n-\t\tif (!opts->quiet)\n-\t\t\tfprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);\n \t\treturn 0;\n \t} else if (for_each_remote_ref(first_valid_ref, NULL)) {\n \t\treturn 1;\n \t} else if (!opts->force && remote_get(NULL)) {\n \t\tdie(_(\"No local or remote refs exist despite at least one remote\\n\"\n \t\t      \"present, stopping; use 'add -f' to overide or fetch a remote first\"));\n-\t} else if (!opts->quiet) {\n-\t\tfprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);\n \t}\n \treturn 0;\n }\n@@ -827,8 +843,12 @@ static int add(int ac, const char **av, const char *prefix)\n \t\tint n;\n \t\tconst char *s = worktree_basename(path, &n);\n \t\tnew_branch = xstrndup(s, n);\n-\t} else if (opts.orphan || opts.detach) {\n+\t} else if (opts.orphan) {\n \t\t// No-op\n+\t} else if (opts.detach) {\n+\t\t// Check HEAD\n+\t\tif (!strcmp(branch, \"HEAD\"))\n+\t\t\tcan_use_local_refs(&opts);\n \t} else if (ac < 2 && new_branch) {\n \t\t// DWIM: Infer --orphan when repo has no refs.\n \t\topts.orphan = dwim_orphan(&opts, !!opt_track, 0);\n@@ -853,6 +873,10 @@ static int add(int ac, const char **av, const char *prefix)\n \t\t\t\tbranch = remote;\n \t\t\t}\n \t\t}\n+\n+\t\tif (!strcmp(branch, \"HEAD\"))\n+\t\t\tcan_use_local_refs(&opts);\n+\n \t}\n\n \tif (!opts.orphan && !lookup_commit_reference_by_name(branch)) {\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex e5cca1d11b..09bf506155 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -729,6 +729,7 @@ test_dwim_orphan () {\n \tlocal use_quiet=0 &&\n \tlocal remote=0 &&\n \tlocal remote_ref=0 &&\n+\tlocal use_detach=0 &&\n \tlocal use_new_branch=0 &&\n\n \tlocal outcome=\"$1\" &&\n@@ -754,6 +755,10 @@ test_dwim_orphan () {\n \t\tsuccess=0 &&\n \t\toutcome_text='\"add\" error inferred \"--orphan\" gives illegal opts combo'\n \t\t;;\n+\t\"warn_bad_head\")\n+\t\tsuccess=0 &&\n+\t\toutcome_text='\"add\" error, warn on bad HEAD, hint use orphan'\n+\t\t;;\n \t*)\n \t\techo \"test_dwim_orphan(): invalid outcome: '$outcome'\" >&2 &&\n \t\treturn 1\n@@ -825,7 +830,7 @@ test_dwim_orphan () {\n \t\t\tcontext=\"$context, invalid (or orphan) HEAD\"\n \t\t\t;;\n\n-\t\t# Whether the code path is tested with the base add command or -b\n+\t\t# Whether the code path is tested with the base add command, -b, or --detach\n \t\t\"no_-b\")\n \t\t\tuse_new_branch=0 &&\n \t\t\tcontext=\"$context, no --branch\"\n@@ -834,6 +839,10 @@ test_dwim_orphan () {\n \t\t\tuse_new_branch=1 &&\n \t\t\tcontext=\"$context, --branch\"\n \t\t\t;;\n+\t\t\"detach\")\n+\t\t\tuse_detach=1 &&\n+\t\t\tcontext=\"$context, --detach\"\n+\t\t\t;;\n\n \t\t# Whether to check that all output is suppressed (except errors)\n \t\t# or that the output is as expected\n@@ -894,6 +903,9 @@ test_dwim_orphan () {\n \tif [ $use_new_branch -eq 1 ]\n \tthen\n \t\targs=\"$args -b foo\"\n+\telif [ $use_detach -eq 1 ]\n+\tthen\n+\t\targs=\"$args --detach\"\n \telse\n \t\tcontext=\"DWIM (no --branch), $context\"\n \tfi &&\n@@ -1036,6 +1048,10 @@ do\n \t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref no_guess_remote\n \t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref guess_remote\n \t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote remote_ref guess_remote\n+\n+\t\ttest_dwim_orphan 'warn_bad_head' $dwim_test_args no_-b local_ref bad_head\n+\t\ttest_dwim_orphan 'warn_bad_head' $dwim_test_args -b local_ref bad_head\n+\t\ttest_dwim_orphan 'warn_bad_head' $dwim_test_args detach local_ref bad_head\n \tdone\n\n \ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b no_checkout\n--\n2.39.2\n\n\n"},{"id":"475560","messageId":"xmqq8reqkyfz.fsf@gitster.g","threadId":"59607","inReplyTo":"20230417093255.31079-3-jacobabel@nullpo.dev","subject":"Re: [PATCH v9 2/8] t2400: print captured git output when finished","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-17T21:09:52Z","receivedAt":"2023-04-17T21:10:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Abel <jacobabel@nullpo.dev> writes:\n\n>  test_expect_success 'add --quiet' '\n> +\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n> +\ttest_when_finished cat actual >&2 &&\n\nI doubt that this redirection does anything you expect it do.\nDoesn't it redirect the standard output that is emitted by the\ntest_when_finished shell function when it registers another\ntest_cleanup scriptlet to the standard error, and when test_cleanup\nis indeed run, wouldn't \"cat actual\" send its output to the standard\noutput?\n\nNo, I am not suggesting to write the line as:\n\n\ttest_when_finished \"cat >&2 actual\" &&\n\n>  \tgit worktree add --quiet another-worktree main 2>actual &&\n>  \ttest_must_be_empty actual\n\nThe reason why I do not suggest \"fixing\" the above is because\ntest_must_be_empty, when fails, does this:\n\n        test_must_be_empty () {\n                test \"$#\" -ne 1 && BUG \"1 param\"\n                test_path_is_file \"$1\" &&\n                if test -s \"$1\"\n                then\n                        echo \"'$1' is not empty, it contains:\"\n                        cat \"$1\"\n                        return 1\n                fi\n        }\n\ni.e. it sends the contents of \"actual\" to the standard output\nalready.  When it succeeds, of course \"actual\" is empty, and there\nis no point in showing its contents.\n\nSo \"sh t2400-*.sh -x -i\" already shows \"cat actual\" output.  Try\nthe attached patch on top of this one and running it would show\nthe above message shown by test_must_be_empty and the contents of\nthe file 'actual'.  \"git worktree remove\" fails and your \"cat\" in\nthe test_cleanup does not even trigger, by the way.\n\nThere may be cases where having something like this might help, but\nrunning the test with \"-x\" is not it---that case is already covered\nby what test_must_be_empty gives us, I think.\n\n t/t2400-worktree-add.sh | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git c/t/t2400-worktree-add.sh w/t/t2400-worktree-add.sh\nindex 9bc3db20e4..814642c8ae 100755\n--- c/t/t2400-worktree-add.sh\n+++ w/t/t2400-worktree-add.sh\n@@ -329,9 +329,12 @@ test_expect_success 'add --quiet' '\n \ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n \ttest_when_finished cat actual >&2 &&\n \tgit worktree add --quiet another-worktree main 2>actual &&\n+echo foo >>actual &&\n \ttest_must_be_empty actual\n '\n \n+exit\n+\n test_expect_success 'local clone from linked checkout' '\n \tgit clone --local here here-clone &&\n \t( cd here-clone && git fsck )\n\n"},{"id":"475561","messageId":"xmqqmt36jixr.fsf@gitster.g","threadId":"59607","inReplyTo":"20230417093255.31079-4-jacobabel@nullpo.dev","subject":"Re: [PATCH v9 3/8] t2400: refactor \"worktree add\" opt exclusion tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-17T21:30:08Z","receivedAt":"2023-04-17T21:30:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Abel <jacobabel@nullpo.dev> writes:\n\n> +# Helper function to test mutually exclusive options.\n> +#\n> +# Note: Quoted arguments containing spaces are not supported.\n\nGood to have this note here.\n\n> +test_wt_add_excl () {\n> +\tlocal opts=\"$*\" &&\n> +\ttest_expect_success \"'worktree add' with '$opts' has mutually exclusive options\" '\n> +\t\ttest_when_finished cat actual >&2 &&\n\nAgain, I do not think this sends output to the standard output at\nthe end of this test piece.\n\n> +\t\ttest_must_fail git worktree add $opts 2>actual &&\n> +\t\tgrep -E \"fatal:( options)? .* cannot be used together\" actual\n> +\t'\n> +}\n\nI do not think this patch is needed (I'd rather see people learn the\ntrick of running with \"-i\" and rely on the fact that the trash\ndirectory is left intact to be inspected), but if you must, it may\nmake more sense to add test_must_contain to make a failed 'grep\n\"$@\"' easier to see, similar to the way that test_must_be_empty\nhelps a failing 'test !  -s \"$1\"', something along the lines of ...\n\n\ttest_must_contain () {\n\t\tif ! grep \"$@\"\n\t\tthen\n\t\t\techo \"'grep $*' fails; the file contains\"\n\t\t\twhile test $# != 1\n\t\t\tdo\n\t\t\t\tshift\n\t\t\tdone\n                        cat \"$1\"\n\t\t\treturn 1\n\t\tfi\n\t}\n\nThanks.\n"},{"id":"475562","messageId":"xmqqfs8yjisl.fsf@gitster.g","threadId":"59607","inReplyTo":"20230417093255.31079-5-jacobabel@nullpo.dev","subject":"Re: [PATCH v9 4/8] t2400: add tests to verify --quiet","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-17T21:33:14Z","receivedAt":"2023-04-17T21:33:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Abel <jacobabel@nullpo.dev> writes:\n\n> +test_expect_success 'add --quiet -b' '\n> +\ttest_when_finished \"git branch -D quietnewbranch\" &&\n> +\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n> +\ttest_when_finished cat actual >&2 &&\n> +\tgit worktree add --quiet -b quietnewbranch another-worktree 2>actual &&\n> +\ttest_must_be_empty actual\n> +'\n\nIt is good to test the --quiet option.  It is not good to have the\nineffective \"cat actual\" when test_must_be_empty is already used.\nProbably the same comment applies to the rest of the patch.\n\nThanks.\n"},{"id":"475581","messageId":"olztmib77r35mx33a655obqpxui6coj74hfxoxfvcudnkpbqns@ixerneqaai45","threadId":"59607","inReplyTo":"xmqq8reqkyfz.fsf@gitster.g","subject":"Re: [PATCH v9 2/8] t2400: print captured git output when finished","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-18T03:53:55Z","receivedAt":"2023-04-18T03:54:15Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"On 23/04/17 02:09PM, Junio C Hamano wrote:\n> Jacob Abel <jacobabel@nullpo.dev> writes:\n>\n> >  test_expect_success 'add --quiet' '\n> > +\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n> > +\ttest_when_finished cat actual >&2 &&\n>\n> I doubt that this redirection does anything you expect it do.\n> Doesn't it redirect the standard output that is emitted by the\n> test_when_finished shell function when it registers another\n> test_cleanup scriptlet to the standard error, and when test_cleanup\n> is indeed run, wouldn't \"cat actual\" send its output to the standard\n> output?\n\nYes that's correct. I figured \"grab from stderr, cat to stderr\" but yes\nthis isn't necessarily what we want here. Dropping the `>&2` causes it to\nwork as expected.\n\n>\n> No, I am not suggesting to write the line as:\n>\n> \ttest_when_finished \"cat >&2 actual\" &&\n>\n> >  \tgit worktree add --quiet another-worktree main 2>actual &&\n> >  \ttest_must_be_empty actual\n>\n> The reason why I do not suggest \"fixing\" the above is because\n> test_must_be_empty, when fails, does this:\n>\n>         test_must_be_empty () {\n>                 test \"$#\" -ne 1 && BUG \"1 param\"\n>                 test_path_is_file \"$1\" &&\n>                 if test -s \"$1\"\n>                 then\n>                         echo \"'$1' is not empty, it contains:\"\n>                         cat \"$1\"\n>                         return 1\n>                 fi\n>         }\n>\n> i.e. it sends the contents of \"actual\" to the standard output\n> already.  When it succeeds, of course \"actual\" is empty, and there\n> is no point in showing its contents.\n>\n> So \"sh t2400-*.sh -x -i\" already shows \"cat actual\" output.  Try\n> the attached patch on top of this one and running it would show\n> the above message shown by test_must_be_empty and the contents of\n> the file 'actual'.  \"git worktree remove\" fails and your \"cat\" in\n> the test_cleanup does not even trigger, by the way.\n\nThat should not be the case. From what I've seen, the test cleanup is\nexecuted in reverse order from the order they are declared with\n`test_when_finished`. So as long as `cat` is the last command added to test\ncleanup it should always execute immediately after the first command in the\nscript fails. And as long as the `cat` is added immediately before the\n`git worktree add`, that means it should be the most recently added in the\nevent that command fails.\n\n>\n> There may be cases where having something like this might help, but\n> running the test with \"-x\" is not it---that case is already covered\n> by what test_must_be_empty gives us, I think.\n>\n> [...]\n\nI attached an example below to try to illustrate the issue I was attempting\nto solve. If `git worktree add ... 2>actual` fails, redirecting stderr to\nactual eats the output that would normally show w/ `-x`. Then because a\ncommand fails, it never reaches the `test_must_be_empty`.\n\nTest results of running `sh t2400-*.sh -x` for this test when\n`git worktree add` fails (caused in this case by adding `--bad-arg` to the\ncommand):\n\n    expecting success of 2400.37 'add --quiet':\n            test_when_finished \"git worktree remove -f -f another-worktree\" &&\n            test_when_finished cat actual >&2 &&\n            git worktree add --quiet --bad-arg another-worktree main 2>actual &&\n            test_must_be_empty actual\n\n    ++ test_when_finished 'git worktree remove -f -f another-worktree'\n    ++ test 0 = 0\n    ++ test_cleanup='{ git worktree remove -f -f another-worktree\n                    } && (exit \"$eval_ret\"); eval_ret=$?; :'\n    ++ test_when_finished cat actual\n    ++ test 0 = 0\n    ++ test_cleanup='{ cat actual\n                    } && (exit \"$eval_ret\"); eval_ret=$?; { git worktree remove -f -f another-worktree\n                    } && (exit \"$eval_ret\"); eval_ret=$?; :'\n    ++ git worktree add --quiet --bad-arg another-worktree main\n    error: last command exited with $?=129\n    ++ cat actual\n    error: unknown option `bad-arg'\n    usage: git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]\n                            [(-b | -B) <new-branch>] <path> [<commit-ish>]\n\n        -f, --force           checkout <branch> even if already checked out in other worktree\n        -b <branch>           create a new branch\n        -B <branch>           create or reset a branch\n        -d, --detach          detach HEAD at named commit\n        --checkout            populate the new working tree\n        --lock                keep the new working tree locked\n        --reason <string>     reason for locking\n        -q, --quiet           suppress progress reporting\n        --track               set up tracking mode (see git-branch(1))\n        --guess-remote        try to match the new branch name with a remote-tracking branch\n\n    ++ exit 129\n    ++ eval_ret=129\n    ++ git worktree remove -f -f another-worktree\n    fatal: 'another-worktree' is not a working tree\n    ++ eval_ret=128\n    ++ :\n    not ok 37 - add --quiet\n\nThe same test but with the `test_when_finished cat actual` removed:\n\n    expecting success of 2400.37 'add --quiet':\n            test_when_finished \"git worktree remove -f -f another-worktree\" &&\n            git worktree add --quiet --bad-arg another-worktree main 2>actual &&\n            test_must_be_empty actual\n\n    ++ test_when_finished 'git worktree remove -f -f another-worktree'\n    ++ test 0 = 0\n    ++ test_cleanup='{ git worktree remove -f -f another-worktree\n                    } && (exit \"$eval_ret\"); eval_ret=$?; :'\n    ++ git worktree add --quiet --bad-arg another-worktree main\n    error: last command exited with $?=129\n    ++ git worktree remove -f -f another-worktree\n    fatal: 'another-worktree' is not a working tree\n    ++ eval_ret=128\n    ++ :\n    not ok 37 - add --quiet\n\n\n"},{"id":"475631","messageId":"xmqqsfcxdu9k.fsf@gitster.g","threadId":"59607","inReplyTo":"olztmib77r35mx33a655obqpxui6coj74hfxoxfvcudnkpbqns@ixerneqaai45","subject":"Re: [PATCH v9 2/8] t2400: print captured git output when finished","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-18T16:34:15Z","receivedAt":"2023-04-18T16:34:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Abel <jacobabel@nullpo.dev> writes:\n\n>> So \"sh t2400-*.sh -x -i\" already shows \"cat actual\" output.  Try\n>> the attached patch on top of this one and running it would show\n>> the above message shown by test_must_be_empty and the contents of\n>> the file 'actual'.\n>\n> That should not be the case.\n\nHere is how it fails and test_must_be_empty complains that the\n\"actual\" file is not empty, if you run the script with \"-x -i\" after\napplying the patch in the message you are responding to on top of\nthis step.\n\n    expecting success of 2400.37 'add --quiet':\n            test_when_finished \"git worktree remove -f -f another-worktree\" &&\n            test_when_finished cat actual >&2 &&\n            git worktree add --quiet another-worktree main 2>actual &&\n    echo foo >>actual &&\n            test_must_be_empty actual\n\n    ++ test_when_finished 'git worktree remove -f -f another-worktree'\n    ++ test 0 = 0\n    ++ test_cleanup='{ git worktree remove -f -f another-worktree\n                    } && (exit \"$eval_ret\"); eval_ret=$?; :'\n    ++ test_when_finished cat actual\n    ++ test 0 = 0\n    ++ test_cleanup='{ cat actual\n                    } && (exit \"$eval_ret\"); eval_ret=$?; { git worktree remove -f -f another-worktree\n                    } && (exit \"$eval_ret\"); eval_ret=$?; :'\n    ++ git worktree add --quiet another-worktree main\n    ++ echo foo\n    ++ test_must_be_empty actual\n    ++ test 1 -ne 1\n    ++ test_path_is_file actual\n    ++ test 1 -ne 1\n    ++ test -f actual\n    ++ test -s actual\n    ++ echo ''\\''actual'\\'' is not empty, it contains:'\n    'actual' is not empty, it contains:\n    ++ cat actual\n    foo\n    ++ return 1\n    error: last command exited with $?=1\n    not ok 37 - add --quiet\n\nObserve what test_must_be_empty does in the last part of the\ntranscript above.  If you run it without \"-i\", then the trace will\nshow \"cat actual\" twice (one from test_must_be_empty above, then a\nredundant one from the test_when_finished).\n\nAnother reason why we shouldn't add this to test_when_finished is\nbecause it would not help those who run the tests with \"-i\" option.\nThe test_cleanup handlers are meant to be \"clean-up\" routines, and\nthey are not run when the user uses \"-i\", intending to go into the\ntest directory after seeing the test fail and inspect what is left\nthere.\n"},{"id":"475693","messageId":"44hu2yj5d3e2pabrm65vmm32fxe4qesiohii5mbouqu4dupecp@r6nhf3nar4b5","threadId":"59607","inReplyTo":"xmqqsfcxdu9k.fsf@gitster.g","subject":"Re: [PATCH v9 2/8] t2400: print captured git output when finished","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-19T13:23:32Z","receivedAt":"2023-04-19T13:25:03Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"On 23/04/18 09:34AM, Junio C Hamano wrote:\n> Jacob Abel <jacobabel@nullpo.dev> writes:\n>\n> > [...]\n>\n> Here is how it fails and test_must_be_empty complains that the\n> \"actual\" file is not empty, if you run the script with \"-x -i\" after\n> applying the patch in the message you are responding to on top of\n> this step.\n>\n> [...]\n>\n> Observe what test_must_be_empty does in the last part of the\n> transcript above.  If you run it without \"-i\", then the trace will\n> show \"cat actual\" twice (one from test_must_be_empty above, then a\n> redundant one from the test_when_finished).\n>\n> Another reason why we shouldn't add this to test_when_finished is\n> because it would not help those who run the tests with \"-i\" option.\n> The test_cleanup handlers are meant to be \"clean-up\" routines, and\n> they are not run when the user uses \"-i\", intending to go into the\n> test directory after seeing the test fail and inspect what is left\n> there.\n\nAh ok I understand what you mean now.\n\nWould the following work? Since all we care about in `git worktree add` is\n`stderr`, can't we just duplicate `stderr` to `stdout` while redirecting\nstderr to `actual` so that in the event the git command fails, it's still\ndisplayed in the output of `sh t2400-*.sh -x`?\n\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex 82091cbb1f..a8f734b1c3 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -327,8 +327,7 @@ test_expect_success 'add -B' '\n\n test_expect_success 'add --quiet' '\n \ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n-\ttest_when_finished \"cat actual\" &&\n-\tgit worktree add --quiet another-worktree main 2>actual &&\n+\tgit worktree add --quiet another-worktree main 2>actual 2>&1 &&\n \ttest_must_be_empty actual\n '\n\n\n"},{"id":"475694","messageId":"zyuwfbx5e57ly4ybfbnapbzbkw4ib5xbreu3nawkd4f5srhgfe@pwp4gw3hbgap","threadId":"59607","inReplyTo":"xmqqsfcxdu9k.fsf@gitster.g","subject":"Re: [PATCH v9 2/8] t2400: print captured git output when finished","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-19T13:36:56Z","receivedAt":"2023-04-19T13:37:52Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"On 23/04/19 09:23AM, Jacob Abel wrote:\n> On 23/04/18 09:34AM, Junio C Hamano wrote:\n> > [...]\n>\n> Ah ok I understand what you mean now.\n>\n> Would the following work? Since all we care about in `git worktree add` is\n> `stderr`, can't we just duplicate `stderr` to `stdout` while redirecting\n> stderr to `actual` so that in the event the git command fails, it's still\n> displayed in the output of `sh t2400-*.sh -x`?\n>\n> diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\n> index 82091cbb1f..a8f734b1c3 100755\n> --- a/t/t2400-worktree-add.sh\n> +++ b/t/t2400-worktree-add.sh\n> @@ -327,8 +327,7 @@ test_expect_success 'add -B' '\n>\n>  test_expect_success 'add --quiet' '\n>  \ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n> -\ttest_when_finished \"cat actual\" &&\n> -\tgit worktree add --quiet another-worktree main 2>actual &&\n> +\tgit worktree add --quiet another-worktree main 2>actual 2>&1 &&\n>  \ttest_must_be_empty actual\n>  '\n>\n\nOk scratch that. I tried checking this a bit more and it doesn't work quite\nas expected. I'll remove the `cat actual`s from the tests and if I can\nthink of a better alternative, I'll report back.\n\n"},{"id":"475700","messageId":"xmqqmt33angr.fsf@gitster.g","threadId":"59607","inReplyTo":"zyuwfbx5e57ly4ybfbnapbzbkw4ib5xbreu3nawkd4f5srhgfe@pwp4gw3hbgap","subject":"Re: [PATCH v9 2/8] t2400: print captured git output when finished","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-19T15:41:40Z","receivedAt":"2023-04-19T15:41:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Abel <jacobabel@nullpo.dev> writes:\n\n> On 23/04/19 09:23AM, Jacob Abel wrote:\n>> On 23/04/18 09:34AM, Junio C Hamano wrote:\n>> > [...]\n>>\n>> Ah ok I understand what you mean now.\n>>\n>> Would the following work? Since all we care about in `git worktree add` is\n>> `stderr`, can't we just duplicate `stderr` to `stdout` while redirecting\n>> stderr to `actual` so that in the event the git command fails, it's still\n>> displayed in the output of `sh t2400-*.sh -x`?\n>>\n>> diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\n>> index 82091cbb1f..a8f734b1c3 100755\n>> --- a/t/t2400-worktree-add.sh\n>> +++ b/t/t2400-worktree-add.sh\n>> @@ -327,8 +327,7 @@ test_expect_success 'add -B' '\n>>\n>>  test_expect_success 'add --quiet' '\n>>  \ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n>> -\ttest_when_finished \"cat actual\" &&\n>> -\tgit worktree add --quiet another-worktree main 2>actual &&\n\n\n>> +\tgit worktree add --quiet another-worktree main 2>actual 2>&1 &&\n>>  \ttest_must_be_empty actual\n>>  '\n>>\n>\n> Ok scratch that. I tried checking this a bit more and it doesn't work quite\n> as expected. I'll remove the `cat actual`s from the tests and if I can\n> think of a better alternative, I'll report back.\n\nWhile I do not understand what you are trying to achieve here, if\nyou expect both the standard output and error streams are quiet,\nthen\n\n\tcmd >output 2>&1\n\nwould store both to 'output' and test_must_be_empty can check what\nis in there.\n\nOn the other hand,\n\n\tcmd 2>&1 >output\n\nwould store the standard output of 'cmd' in 'output' while sending\nthe standard error of 'cmd' to the standard output of the whole\nthing.\n"},{"id":"475705","messageId":"hhuzqzxm7zjijrocghio3mwh7hyacsgz6d5adpxg2gy6on5vf2@u3h2hcuvfl5z","threadId":"59607","inReplyTo":"xmqqmt33angr.fsf@gitster.g","subject":"Re: [PATCH v9 2/8] t2400: print captured git output when finished","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-19T16:50:36Z","receivedAt":"2023-04-19T16:50:50Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"On 23/04/19 08:41AM, Junio C Hamano wrote:\n> Jacob Abel <jacobabel@nullpo.dev> writes:\n>\n> > [...]\n>\n> While I do not understand what you are trying to achieve here, if\n> you expect both the standard output and error streams are quiet,\n> then\n>\n> \tcmd >output 2>&1\n>\n> would store both to 'output' and test_must_be_empty can check what\n> is in there.\n>\n> On the other hand,\n>\n> \tcmd 2>&1 >output\n>\n> would store the standard output of 'cmd' in 'output' while sending\n> the standard error of 'cmd' to the standard output of the whole\n> thing.\n\nI thought it was redirecting to both actual and stdout (like a tee would)\nand jumped the gun (replying to your message) before verifying that it\nworked as I assumed.\n\nThe goal was to be able to capture stderr without suppressing it in the\noutput of `sh t2400-*.sh -x`. The original solution I had did it's job\nwhile I was debugging an issue for the later patches in this set and I\nsuppose I could have always used `-i` and looked inside `actual` myself\nafter the git command failed.\n\nSince the issues with the patchset I was having were resolved and there is\nan alternate way to debug the tests in the event they fail, I'm just going\nto drop these changes (and drop the `cat actual` changes).\n\n"},{"id":"475720","messageId":"dsbjgeppeusvddeqn466xwnaa6eif4uoofbcju4sdzhotp5ctq@hh6unkszo5k3","threadId":"59607","inReplyTo":"xmqqmt36jixr.fsf@gitster.g","subject":"Re: [PATCH v9 3/8] t2400: refactor \"worktree add\" opt exclusion tests","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-20T02:46:59Z","receivedAt":"2023-04-20T02:47:28Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"On 23/04/17 02:30PM, Junio C Hamano wrote:\n> Jacob Abel <jacobabel@nullpo.dev> writes:\n>\n> [...]\n>\n> > [....]\n>\n> Again, I do not think this sends output to the standard output at\n> the end of this test piece.\n>\n> > +\t\ttest_must_fail git worktree add $opts 2>actual &&\n> > +\t\tgrep -E \"fatal:( options)? .* cannot be used together\" actual\n> > +\t'\n> > +}\n>\n> I do not think this patch is needed (I'd rather see people learn the\n> trick of running with \"-i\" and rely on the fact that the trash\n> directory is left intact to be inspected), but if you must, it may\n> make more sense to add test_must_contain to make a failed 'grep\n> \"$@\"' easier to see, similar to the way that test_must_be_empty\n> helps a failing 'test !  -s \"$1\"', something along the lines of ...\n>\n> [...]\n>\n> Thanks.\n\nUnderstood. As mentioned in the discussion for patch 2/8, these changes\n(trying to `cat actual` on `test_cleanup`) will be reverted for the next\nrevision across all patches.\n\n"},{"id":"475721","messageId":"auz2uadzp3k2b3ycmut6hhqelatwgnodrpqg7kalf2amzjknmq@gnqwpwzeomm3","threadId":"59607","inReplyTo":"xmqqfs8yjisl.fsf@gitster.g","subject":"Re: [PATCH v9 4/8] t2400: add tests to verify --quiet","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-20T02:48:34Z","receivedAt":"2023-04-20T02:49:21Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"On 23/04/17 02:33PM, Junio C Hamano wrote:\n> Jacob Abel <jacobabel@nullpo.dev> writes:\n>\n> > [...]\n>\n> It is good to test the --quiet option.  It is not good to have the\n> ineffective \"cat actual\" when test_must_be_empty is already used.\n> Probably the same comment applies to the rest of the patch.\n>\n> Thanks.\n\nUnderstood. As mentioned in the patch 2/8 discussion, this change (the `cat\nactual`) will be reverted in the next revision.\n\n"},{"id":"475722","messageId":"wk2wpftowc7wqeaqrylxq7eci4bkmuffwu75h4qugxulcgsk2b@gw6ouq3g5g2j","threadId":"59607","inReplyTo":"20230417093255.31079-1-jacobabel@nullpo.dev","subject":"Re: [PATCH v9 0/8] worktree: Support `--orphan` when creating new worktrees","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-04-20T03:05:35Z","receivedAt":"2023-04-20T03:07:05Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"On 23/04/17 09:33AM, Jacob Abel wrote:\n>\n> [...]\n\nI've noticed on the lore that this revision didn't continue the existing\nthread[1] for the patchset. It seems this is because the `In-Reply-To`\nheader was stripped from the message due to a bug with the MTA/mail bridge\nI use on my dev machine [2].\n\nApologies for any confusion this may have caused.\n\nFor continuity purposes I can either:\n\nA. RESEND the revision to the main thread with a note in the cover letter\ntagging the discussions from this thread so far.\n\nB. Reply to [1] in the main thread to point to this thread and then\neventually publish v10 in reply to that reply message in the main thread.\n\nC. Reply to [1] in the main thread to point to this thread and then\neventually publish v10 in reply to this cover letter.\n\nLet me know which would be the least disruptive/most ideal way forward or\nif there is another way I should approach this.\n\nThanks.\n\n1. https://lore.kernel.org/git/20230109173227.29264-1-jacobabel@nullpo.dev/\n2. https://github.com/ProtonMail/proton-bridge/issues/374\n\n"},{"id":"476382","messageId":"xmqqcz3j4t68.fsf@gitster.g","threadId":"59607","inReplyTo":"wk2wpftowc7wqeaqrylxq7eci4bkmuffwu75h4qugxulcgsk2b@gw6ouq3g5g2j","subject":"Re: [PATCH v9 0/8] worktree: Support `--orphan` when creating new worktrees","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-05-01T21:51:11Z","receivedAt":"2023-05-01T21:51:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jacob Abel <jacobabel@nullpo.dev> writes:\n\n> I've noticed on the lore that this revision didn't continue the existing\n> thread[1] for the patchset. It seems this is because the `In-Reply-To`\n> header was stripped from the message due to a bug with the MTA/mail bridge\n> I use on my dev machine [2].\n>\n> Apologies for any confusion this may have caused.\n>\n> For continuity purposes I can either:\n>\n> A. RESEND the revision to the main thread with a note in the cover letter\n> tagging the discussions from this thread so far.\n>\n> B. Reply to [1] in the main thread to point to this thread and then\n> eventually publish v10 in reply to that reply message in the main thread.\n>\n> C. Reply to [1] in the main thread to point to this thread and then\n> eventually publish v10 in reply to this cover letter.\n>\n> Let me know which would be the least disruptive/most ideal way forward or\n> if there is another way I should approach this.\n>\n> Thanks.\n>\n> 1. https://lore.kernel.org/git/20230109173227.29264-1-jacobabel@nullpo.dev/\n> 2. https://github.com/ProtonMail/proton-bridge/issues/374\n\nOnce the thread is broken, it is broken.  You gave a link in the\nmessage I am responding to, to make it easier for people to go back\nto earlier iterations and that is good enough, I think.\n\nYour eventual v10 can be sent with its cover set as a reply to the\ncover of v9 and we will be fine.\n\nIt seems that we may need another iteration, but if I reclal\ncorrectly what remains are all minor issues?\n\nThanks.\n"},{"id":"476410","messageId":"m7crhly2j3k76whydbtdwhvzghvoql436g3lqtst4v3ahv75aa@wq5zjepgrxz5","threadId":"59607","inReplyTo":"xmqqcz3j4t68.fsf@gitster.g","subject":"Re: [PATCH v9 0/8] worktree: Support `--orphan` when creating new worktrees","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-05-02T05:48:06Z","receivedAt":"2023-05-02T05:50:30Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"On 23/05/01 02:51PM, Junio C Hamano wrote:\n> Jacob Abel <jacobabel@nullpo.dev> writes:\n> > [...]\n>\n> Once the thread is broken, it is broken.  You gave a link in the\n> message I am responding to, to make it easier for people to go back\n> to earlier iterations and that is good enough, I think.\n\nUnderstood.\n\n>\n> Your eventual v10 can be sent with its cover set as a reply to the\n> cover of v9 and we will be fine.\n>\n> It seems that we may need another iteration, but if I reclal\n> correctly what remains are all minor issues?\n>\n> Thanks.\n\nBased on the feedback so far yes. However I have yet to get any reviews\non patches 5/8 through 8/8 for this revision so it's unclear to me if\nthey were fine/didn't require any changes or if they just weren't\nreviewed yet?\n\nAlso I have yet to hear anything back about whether to keep patches\n7/8 and 8/8 in this patchset or break them out into their own followup\npatchset (original note about this was in the cover letter [1]).\n\n1. https://lore.kernel.org/git/20230417093255.31079-1-jacobabel@nullpo.dev/\n\n"},{"id":"477489","messageId":"20230517214711.12467-1-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230417093255.31079-1-jacobabel@nullpo.dev","subject":"[RESEND PATCH v10 0/8] worktree: Support `--orphan` when creating new worktrees","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-05-17T21:47:30Z","receivedAt":"2023-05-17T21:47:46Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"This patchset introduces the ability to create new worktrees from orphan/unborn\nbranches and introduces DWIM behavior to create worktrees from an orphan branch\nwhen no valid refs exists locally in the repository (as is typical in newly\ninitialized repositories) or on a remote (when `--guess-remote` is used). \n\nThis addresses the issue of `git worktree add` failing when attempting to create\na worktree from a newly initialized repository (which can be seen in this SO\nquestion [1]).\n\nNOTE: Resend of Patch v10 as my mail setup tampered with and broke the patchset. \noriginal v10 cover: <20230507120530.14669-1-jacobabel@nullpo.dev>\n\nThis patchset has eight parts:\n  * adding `-B` to the usage docs (noticed during dev and it seemed too small\n    to justify a separate submission)\n  * cleaning up a left-behind worktree in t2400\n  * adding a helper fn to simplify testing for mutual exclusion of options\n    in `t/t2400-worktree-add.sh`\n  * adding additional test cases to verify both that behavior doesn't change\n    when using `--quiet` and that the extraneous output is properly suppressed.\n  * adding the ability to create a worktree from an unborn/orphan branch\n    to `git-worktree-add`\n  * adding an advise for using --orphan when `git worktree add` fails due to \n    a bad ref.\n  * adding functionality to DWIM when there are no existing branches and the\n    user likely intends to create an orphan branch.\n  * updating worktree add to emit a warning (containing debug information \n    about the current HEAD) when trying to use a HEAD that points to a\n    non-existant (or unborn) reference and there exist other valid branches.\n\nChanges from v9:\n  * Revert `test_when_finished cat actual` changes in t2400 (2/8)[2].\n  * Rename commit 2/8 to reflect changes.\n  * Revert `test_when_finished cat actual` changes in t2400 (3/8)[3].\n  * Revert `test_when_finished cat actual` changes in t2400 (4/8)[4].\n  * Revert `test_when_finished cat actual` changes in t2400 (5/8).\n  * Remove extraneous whitespace from command in t2400 (5/8).\n  * Revert `test_when_finished cat actual` changes in t2400 (6/8).\n  * Include `advice.h` in `worktree.c` to resolve missing include when \n    applying patch on top of main (6/8).\n  * Revert `test_when_finished cat actual` changes in t2400 (7/8).\n  * Remove extraneous whitespace from comment in `worktree.c` (7/8).\n\n1. https://stackoverflow.com/a/68717229/15064705/\n2. https://lore.kernel.org/git/xmqq8reqkyfz.fsf@gitster.g/\n3. https://lore.kernel.org/git/xmqqmt36jixr.fsf@gitster.g/\n4. https://lore.kernel.org/git/xmqqfs8yjisl.fsf@gitster.g/\n\nJacob Abel (8):\n  worktree add: include -B in usage docs\n  t2400: cleanup created worktree in test\n  t2400: refactor \"worktree add\" opt exclusion tests\n  t2400: add tests to verify --quiet\n  worktree add: add --orphan flag\n  worktree add: introduce \"try --orphan\" hint\n  worktree add: extend DWIM to infer --orphan\n  worktree add: emit warn when there is a bad HEAD\n\n Documentation/config/advice.txt |   4 +\n Documentation/git-worktree.txt  |  16 +-\n advice.c                        |   1 +\n advice.h                        |   1 +\n builtin/worktree.c              | 227 +++++++++++++-\n t/t2400-worktree-add.sh         | 507 +++++++++++++++++++++++++++++++-\n 6 files changed, 735 insertions(+), 21 deletions(-)\n\nRange-diff against v9:\n1:  91153fdb4c = 1:  91153fdb4c worktree add: include -B in usage docs\n2:  8cfbc89dd5 ! 2:  0f30e9a9e3 t2400: print captured git output when finished\n    @@ Metadata\n     Author: Jacob Abel <jacobabel@nullpo.dev>\n     \n      ## Commit message ##\n    -    t2400: print captured git output when finished\n    -\n    -    Update tests that capture stderr so that at the end of the test they\n    -    print the captured text back out to stderr. This simplifies debugging\n    -    when inspecting test logs after executing with `-x`.\n    +    t2400: cleanup created worktree in test\n     \n         Signed-off-by: Jacob Abel <jacobabel@nullpo.dev>\n     \n    @@ t/t2400-worktree-add.sh: test_expect_success 'add -B' '\n      \n      test_expect_success 'add --quiet' '\n     +\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n    -+\ttest_when_finished cat actual >&2 &&\n      \tgit worktree add --quiet another-worktree main 2>actual &&\n      \ttest_must_be_empty actual\n      '\n3:  ab03d92c3a ! 3:  06e8c53bc6 t2400: refactor \"worktree add\" opt exclusion tests\n    @@ t/t2400-worktree-add.sh: test_expect_success '\"add\" no auto-vivify with --detach\n     +test_wt_add_excl () {\n     +\tlocal opts=\"$*\" &&\n     +\ttest_expect_success \"'worktree add' with '$opts' has mutually exclusive options\" '\n    -+\t\ttest_when_finished cat actual >&2 &&\n     +\t\ttest_must_fail git worktree add $opts 2>actual &&\n     +\t\tgrep -E \"fatal:( options)? .* cannot be used together\" actual\n     +\t'\n4:  d9a3468c93 ! 4:  d9330db91f t2400: add tests to verify --quiet\n    @@ t/t2400-worktree-add.sh: test_expect_success 'add --quiet' '\n     +test_expect_success 'add --quiet -b' '\n     +\ttest_when_finished \"git branch -D quietnewbranch\" &&\n     +\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n    -+\ttest_when_finished cat actual >&2 &&\n     +\tgit worktree add --quiet -b quietnewbranch another-worktree 2>actual &&\n     +\ttest_must_be_empty actual\n     +'\n    @@ t/t2400-worktree-add.sh: test_expect_success 'git worktree add --guess-remote se\n      '\n     +test_expect_success 'git worktree add --guess-remote sets up tracking (quiet)' '\n     +\ttest_when_finished rm -rf repo_a repo_b foo &&\n    -+\ttest_when_finished cat repo_b/actual >&2 &&\n     +\tsetup_remote_repo repo_a repo_b &&\n     +\t(\n     +\t\tcd repo_b &&\n5:  8ef9587deb ! 5:  a5a78e5f53 worktree add: add --orphan flag\n    @@ t/t2400-worktree-add.sh: test_expect_success 'add --quiet -b' '\n     +\n     +test_expect_success '\"add --orphan --quiet\"' '\n     +\ttest_when_finished \"git worktree remove -f -f orphandir\" &&\n    -+\ttest_when_finished cat log.actual >&2 &&\n     +\tgit worktree add --quiet --orphan -b neworphan orphandir 2>log.actual &&\n     +\ttest_must_be_empty log.actual &&\n     +\techo refs/heads/neworphan >expected &&\n    @@ t/t2400-worktree-add.sh: test_expect_success 'add --quiet -b' '\n     +\ttest_when_finished \"rm -rf empty_repo\" &&\n     +\techo refs/heads/newbranch >expected &&\n     +\tGIT_DIR=\"empty_repo\" git init --bare &&\n    -+\tgit -C empty_repo  worktree add --orphan -b newbranch worktreedir &&\n    ++\tgit -C empty_repo worktree add --orphan -b newbranch worktreedir &&\n     +\tgit -C empty_repo/worktreedir symbolic-ref HEAD >actual &&\n     +\ttest_cmp expected actual\n     +'\n6:  d2800266f9 ! 6:  96b1946e64 worktree add: introduce \"try --orphan\" hint\n    @@ advice.h: struct string_list;\n      int git_default_advice_config(const char *var, const char *value);\n     \n      ## builtin/worktree.c ##\n    +@@\n    + #include \"cache.h\"\n    + #include \"abspath.h\"\n    ++#include \"advice.h\"\n    + #include \"checkout.h\"\n    + #include \"config.h\"\n    + #include \"builtin.h\"\n     @@\n      #define BUILTIN_WORKTREE_UNLOCK_USAGE \\\n      \tN_(\"git worktree unlock <worktree>\")\n    @@ t/t2400-worktree-add.sh: test_expect_success '\"add\" worktree with orphan branch,\n     +\t\tgit init repo &&\n     +\t\t(cd repo && test_commit commit) &&\n     +\t\tgit -C repo switch --orphan noref &&\n    -+\t\ttest_when_finished cat actual >&2 &&\n     +\t\ttest_must_fail git -C repo worktree add $opts foobar/ 2>actual &&\n     +\t\t! grep \"error: unknown switch\" actual &&\n     +\t\tgrep \"hint: If you meant to create a worktree containing a new orphan branch\" actual &&\n    @@ t/t2400-worktree-add.sh: test_expect_success '\"add\" worktree with orphan branch,\n     +\ttest_when_finished \"rm -rf repo\" &&\n     +\tgit init repo &&\n     +\t(cd repo && test_commit commit) &&\n    -+\ttest_when_finished cat actual >&2 &&\n     +\ttest_must_fail git -C repo worktree add --quiet foobar_branch foobar/ 2>actual &&\n     +\t! grep \"error: unknown switch\" actual &&\n     +\t! grep \"hint: If you meant to create a worktree containing a new orphan branch\" actual\n7:  e5e139766c ! 7:  52fef9672c worktree add: extend DWIM to infer --orphan\n    @@ builtin/worktree.c: static void print_preparing_worktree_line(int detach,\n     +/**\n     + * Determines whether `--orphan` should be inferred in the evaluation of\n     + * `worktree add path/` or `worktree add -b branch path/` and emits an error\n    -+ * if the supplied arguments would produce an illegal combination  when the\n    ++ * if the supplied arguments would produce an illegal combination when the\n     + * `--orphan` flag is included.\n     + *\n     + * `opts` and `opt_track` contain the other options & flags supplied to the\n    @@ t/t2400-worktree-add.sh: test_expect_success 'git worktree --no-guess-remote opt\n     +\t\tthen\n     +\t\t\ttest_when_finished git -C repo worktree remove ../foo\n     +\t\tfi &&\n    -+\t\tif [ $use_cd -eq 1 ]\n    -+\t\tthen\n    -+\t\t\ttest_when_finished cat \"$git_ns/actual\" >&2\n    -+\t\telse\n    -+\t\t\ttest_when_finished cat actual >&2\n    -+\t\tfi &&\n     +\t\t(\n     +\t\t\tif [ $use_cd -eq 1 ]\n     +\t\t\tthen\n8:  296226ffd5 = 8:  8c3fded12b worktree add: emit warn when there is a bad HEAD\n-- \n2.39.3\n\n\n"},{"id":"477490","messageId":"20230517214711.12467-2-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230517214711.12467-1-jacobabel@nullpo.dev","subject":"[RESEND PATCH v10 1/8] worktree add: include -B in usage docs","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-05-17T21:48:21Z","receivedAt":"2023-05-17T21:48:34Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Document `-B` next to where `-b` is already documented to bring the\nusage docs in line with other commands such as git checkout.\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n Documentation/git-worktree.txt | 2 +-\n builtin/worktree.c             | 2 +-\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 063d6eeb99..b9c12779f1 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]]\n-\t\t   [-b <new-branch>] <path> [<commit-ish>]\n+\t\t   [(-b | -B) <new-branch>] <path> [<commit-ish>]\n 'git worktree list' [-v | --porcelain [-z]]\n 'git worktree lock' [--reason <string>] <worktree>\n 'git worktree move' <worktree> <new-path>\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 39e9e5c9ce..d1b4b53f2c 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -22,7 +22,7 @@\n \n #define BUILTIN_WORKTREE_ADD_USAGE \\\n \tN_(\"git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]\\n\" \\\n-\t   \"                 [-b <new-branch>] <path> [<commit-ish>]\")\n+\t   \"                 [(-b | -B) <new-branch>] <path> [<commit-ish>]\")\n #define BUILTIN_WORKTREE_LIST_USAGE \\\n \tN_(\"git worktree list [-v | --porcelain [-z]]\")\n #define BUILTIN_WORKTREE_LOCK_USAGE \\\n-- \n2.39.3\n\n\n"},{"id":"477492","messageId":"20230517214711.12467-3-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230517214711.12467-1-jacobabel@nullpo.dev","subject":"[RESEND PATCH v10 2/8] t2400: cleanup created worktree in test","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-05-17T21:48:28Z","receivedAt":"2023-05-17T21:48:55Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Signed-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n t/t2400-worktree-add.sh | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex d587e0b20d..a3f108347a 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -326,6 +326,7 @@ test_expect_success 'add -B' '\n '\n \n test_expect_success 'add --quiet' '\n+\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n \tgit worktree add --quiet another-worktree main 2>actual &&\n \ttest_must_be_empty actual\n '\n-- \n2.39.3\n\n\n"},{"id":"477493","messageId":"20230517214711.12467-4-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230517214711.12467-1-jacobabel@nullpo.dev","subject":"[RESEND PATCH v10 3/8] t2400: refactor \"worktree add\" opt exclusion tests","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-05-17T21:48:35Z","receivedAt":"2023-05-17T21:49:01Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Pull duplicate test code into a function so that additional opt\ncombinations can be tested succinctly.\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n t/t2400-worktree-add.sh | 23 +++++++++++++----------\n 1 file changed, 13 insertions(+), 10 deletions(-)\n\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex a3f108347a..0ca3ec2022 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -298,17 +298,20 @@ test_expect_success '\"add\" no auto-vivify with --detach and <branch> omitted' '\n \ttest_must_fail git -C mish/mash symbolic-ref HEAD\n '\n \n-test_expect_success '\"add\" -b/-B mutually exclusive' '\n-\ttest_must_fail git worktree add -b poodle -B poodle bamboo main\n-'\n-\n-test_expect_success '\"add\" -b/--detach mutually exclusive' '\n-\ttest_must_fail git worktree add -b poodle --detach bamboo main\n-'\n+# Helper function to test mutually exclusive options.\n+#\n+# Note: Quoted arguments containing spaces are not supported.\n+test_wt_add_excl () {\n+\tlocal opts=\"$*\" &&\n+\ttest_expect_success \"'worktree add' with '$opts' has mutually exclusive options\" '\n+\t\ttest_must_fail git worktree add $opts 2>actual &&\n+\t\tgrep -E \"fatal:( options)? .* cannot be used together\" actual\n+\t'\n+}\n \n-test_expect_success '\"add\" -B/--detach mutually exclusive' '\n-\ttest_must_fail git worktree add -B poodle --detach bamboo main\n-'\n+test_wt_add_excl -b poodle -B poodle bamboo main\n+test_wt_add_excl -b poodle --detach bamboo main\n+test_wt_add_excl -B poodle --detach bamboo main\n \n test_expect_success '\"add -B\" fails if the branch is checked out' '\n \tgit rev-parse newmain >before &&\n-- \n2.39.3\n\n\n"},{"id":"477494","messageId":"20230517214711.12467-5-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230517214711.12467-1-jacobabel@nullpo.dev","subject":"[RESEND PATCH v10 4/8] t2400: add tests to verify --quiet","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-05-17T21:48:40Z","receivedAt":"2023-05-17T21:49:03Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Add tests to verify that the command performs operations the same with\n`--quiet` as without it. Additionally verifies that all non-fatal output\nis suppressed.\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n t/t2400-worktree-add.sh | 36 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 36 insertions(+)\n\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex 0ca3ec2022..841f15f59e 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -334,6 +334,13 @@ test_expect_success 'add --quiet' '\n \ttest_must_be_empty actual\n '\n \n+test_expect_success 'add --quiet -b' '\n+\ttest_when_finished \"git branch -D quietnewbranch\" &&\n+\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n+\tgit worktree add --quiet -b quietnewbranch another-worktree 2>actual &&\n+\ttest_must_be_empty actual\n+'\n+\n test_expect_success 'local clone from linked checkout' '\n \tgit clone --local here here-clone &&\n \t( cd here-clone && git fsck )\n@@ -532,6 +539,35 @@ test_expect_success 'git worktree add --guess-remote sets up tracking' '\n \t\ttest_cmp_rev refs/remotes/repo_a/foo refs/heads/foo\n \t)\n '\n+test_expect_success 'git worktree add --guess-remote sets up tracking (quiet)' '\n+\ttest_when_finished rm -rf repo_a repo_b foo &&\n+\tsetup_remote_repo repo_a repo_b &&\n+\t(\n+\t\tcd repo_b &&\n+\t\tgit worktree add --quiet --guess-remote ../foo 2>actual &&\n+\t\ttest_must_be_empty actual\n+\t) &&\n+\t(\n+\t\tcd foo &&\n+\t\ttest_branch_upstream foo repo_a foo &&\n+\t\ttest_cmp_rev refs/remotes/repo_a/foo refs/heads/foo\n+\t)\n+'\n+\n+test_expect_success 'git worktree --no-guess-remote (quiet)' '\n+\ttest_when_finished rm -rf repo_a repo_b foo &&\n+\tsetup_remote_repo repo_a repo_b &&\n+\t(\n+\t\tcd repo_b &&\n+\t\tgit worktree add --quiet --no-guess-remote ../foo\n+\t) &&\n+\t(\n+\t\tcd foo &&\n+\t\ttest_must_fail git config \"branch.foo.remote\" &&\n+\t\ttest_must_fail git config \"branch.foo.merge\" &&\n+\t\ttest_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo\n+\t)\n+'\n \n test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' '\n \ttest_when_finished rm -rf repo_a repo_b foo &&\n-- \n2.39.3\n\n\n"},{"id":"477495","messageId":"20230517214711.12467-7-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230517214711.12467-1-jacobabel@nullpo.dev","subject":"[RESEND PATCH v10 6/8] worktree add: introduce \"try --orphan\" hint","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-05-17T21:48:52Z","receivedAt":"2023-05-17T21:49:10Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Add a new advice/hint in `git worktree add` for when the user\ntries to create a new worktree from a reference that doesn't exist.\n\nCurrent Behavior:\n\n% git init foo\nInitialized empty Git repository in /path/to/foo/\n% touch file\n% git -C foo commit -q -a -m \"test commit\"\n% git -C foo switch --orphan norefbranch\n% git -C foo worktree add newbranch/\nPreparing worktree (new branch 'newbranch')\nfatal: invalid reference: HEAD\n%\n\nNew Behavior:\n\n% git init --bare foo\nInitialized empty Git repository in /path/to/foo/\n% touch file\n% git -C foo commit -q -a -m \"test commit\"\n% git -C foo switch --orphan norefbranch\n% git -C foo worktree add newbranch/\nPreparing worktree (new branch 'newbranch')\nhint: If you meant to create a worktree containing a new orphan branch\nhint: (branch with no commits) for this repository, you can do so\nhint: using the --orphan option:\nhint:\nhint:   git worktree add --orphan newbranch/\nhint:\nhint: Disable this message with \"git config advice.worktreeAddOrphan false\"\nfatal: invalid reference: HEAD\n% git -C foo worktree add -b newbranch2 new_wt/\nPreparing worktree (new branch 'newbranch')\nhint: If you meant to create a worktree containing a new orphan branch\nhint: (branch with no commits) for this repository, you can do so\nhint: using the --orphan option:\nhint:\nhint:   git worktree add --orphan -b newbranch2 new_wt/\nhint:\nhint: Disable this message with \"git config advice.worktreeAddOrphan false\"\nfatal: invalid reference: HEAD\n%\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n Documentation/config/advice.txt |  4 ++++\n advice.c                        |  1 +\n advice.h                        |  1 +\n builtin/worktree.c              | 26 +++++++++++++++++++++++\n t/t2400-worktree-add.sh         | 37 +++++++++++++++++++++++++++++++++\n 5 files changed, 69 insertions(+)\n\ndiff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\nindex c96b5b2e5d..c548a91e67 100644\n--- a/Documentation/config/advice.txt\n+++ b/Documentation/config/advice.txt\n@@ -138,4 +138,8 @@ advice.*::\n \t\tcheckout.\n \tdiverging::\n \t\tAdvice shown when a fast-forward is not possible.\n+\tworktreeAddOrphan::\n+\t\tAdvice shown when a user tries to create a worktree from an\n+\t\tinvalid reference, to instruct how to create a new orphan\n+\t\tbranch instead.\n --\ndiff --git a/advice.c b/advice.c\nindex d6232439c3..e5a9bb9b44 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -78,6 +78,7 @@ static struct {\n \t[ADVICE_SUBMODULES_NOT_UPDATED] \t\t= { \"submodulesNotUpdated\", 1 },\n \t[ADVICE_UPDATE_SPARSE_PATH]\t\t\t= { \"updateSparsePath\", 1 },\n \t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n+\t[ADVICE_WORKTREE_ADD_ORPHAN]\t\t\t= { \"worktreeAddOrphan\", 1 },\n };\n \n static const char turn_off_instructions[] =\ndiff --git a/advice.h b/advice.h\nindex 0f584163f5..2affbe1426 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -49,6 +49,7 @@ struct string_list;\n \tADVICE_UPDATE_SPARSE_PATH,\n \tADVICE_WAITING_FOR_EDITOR,\n \tADVICE_SKIPPED_CHERRY_PICKS,\n+\tADVICE_WORKTREE_ADD_ORPHAN,\n };\n \n int git_default_advice_config(const char *var, const char *value);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 48de7fc3b0..15bdb380c7 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"abspath.h\"\n+#include \"advice.h\"\n #include \"checkout.h\"\n #include \"config.h\"\n #include \"builtin.h\"\n@@ -39,6 +40,20 @@\n #define BUILTIN_WORKTREE_UNLOCK_USAGE \\\n \tN_(\"git worktree unlock <worktree>\")\n \n+#define WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT \\\n+\t_(\"If you meant to create a worktree containing a new orphan branch\\n\" \\\n+\t\"(branch with no commits) for this repository, you can do so\\n\" \\\n+\t\"using the --orphan flag:\\n\" \\\n+\t\"\\n\" \\\n+\t\"\tgit worktree add --orphan -b %s %s\\n\")\n+\n+#define WORKTREE_ADD_ORPHAN_NO_DASH_B_HINT_TEXT \\\n+\t_(\"If you meant to create a worktree containing a new orphan branch\\n\" \\\n+\t\"(branch with no commits) for this repository, you can do so\\n\" \\\n+\t\"using the --orphan flag:\\n\" \\\n+\t\"\\n\" \\\n+\t\"\tgit worktree add --orphan %s\\n\")\n+\n static const char * const git_worktree_usage[] = {\n \tBUILTIN_WORKTREE_ADD_USAGE,\n \tBUILTIN_WORKTREE_LIST_USAGE,\n@@ -634,6 +649,7 @@ static int add(int ac, const char **av, const char *prefix)\n \tconst char *opt_track = NULL;\n \tconst char *lock_reason = NULL;\n \tint keep_locked = 0;\n+\tint used_new_branch_options;\n \tstruct option options[] = {\n \t\tOPT__FORCE(&opts.force,\n \t\t\t   N_(\"checkout <branch> even if already checked out in other worktree\"),\n@@ -686,6 +702,7 @@ static int add(int ac, const char **av, const char *prefix)\n \n \tpath = prefix_filename(prefix, av[0]);\n \tbranch = ac < 2 ? \"HEAD\" : av[1];\n+\tused_new_branch_options = new_branch || new_branch_force;\n \n \tif (!strcmp(branch, \"-\"))\n \t\tbranch = \"@{-1}\";\n@@ -728,6 +745,15 @@ static int add(int ac, const char **av, const char *prefix)\n \t}\n \n \tif (!opts.orphan && !lookup_commit_reference_by_name(branch)) {\n+\t\tint attempt_hint = !opts.quiet && (ac < 2);\n+\t\tif (attempt_hint && used_new_branch_options) {\n+\t\t\tadvise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN,\n+\t\t\t\tWORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT,\n+\t\t\t\tnew_branch, path);\n+\t\t} else if (attempt_hint) {\n+\t\t\tadvise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN,\n+\t\t\t\tWORKTREE_ADD_ORPHAN_NO_DASH_B_HINT_TEXT, path);\n+\t\t}\n \t\tdie(_(\"invalid reference: %s\"), branch);\n \t}\n \ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex fba90582b6..46eef26179 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -401,6 +401,43 @@ test_expect_success '\"add\" worktree with orphan branch, lock, and reason' '\n \ttest_cmp expect .git/worktrees/orphan-with-lock-reason/locked\n '\n \n+# Note: Quoted arguments containing spaces are not supported.\n+test_wt_add_orphan_hint () {\n+\tlocal context=\"$1\" &&\n+\tlocal use_branch=$2 &&\n+\tshift 2 &&\n+\tlocal opts=\"$*\" &&\n+\ttest_expect_success \"'worktree add' show orphan hint in bad/orphan HEAD w/ $context\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(cd repo && test_commit commit) &&\n+\t\tgit -C repo switch --orphan noref &&\n+\t\ttest_must_fail git -C repo worktree add $opts foobar/ 2>actual &&\n+\t\t! grep \"error: unknown switch\" actual &&\n+\t\tgrep \"hint: If you meant to create a worktree containing a new orphan branch\" actual &&\n+\t\tif [ $use_branch -eq 1 ]\n+\t\tthen\n+\t\t\tgrep -E \"^hint:\\s+git worktree add --orphan -b \\S+ \\S+\\s*$\" actual\n+\t\telse\n+\t\t\tgrep -E \"^hint:\\s+git worktree add --orphan \\S+\\s*$\" actual\n+\t\tfi\n+\n+\t'\n+}\n+\n+test_wt_add_orphan_hint 'no opts' 0\n+test_wt_add_orphan_hint '-b' 1 -b foobar_branch\n+test_wt_add_orphan_hint '-B' 1 -B foobar_branch\n+\n+test_expect_success \"'worktree add' doesn't show orphan hint in bad/orphan HEAD w/ --quiet\" '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(cd repo && test_commit commit) &&\n+\ttest_must_fail git -C repo worktree add --quiet foobar_branch foobar/ 2>actual &&\n+\t! grep \"error: unknown switch\" actual &&\n+\t! grep \"hint: If you meant to create a worktree containing a new orphan branch\" actual\n+'\n+\n test_expect_success 'local clone from linked checkout' '\n \tgit clone --local here here-clone &&\n \t( cd here-clone && git fsck )\n-- \n2.39.3\n\n\n"},{"id":"477496","messageId":"20230517214711.12467-6-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230517214711.12467-1-jacobabel@nullpo.dev","subject":"[RESEND PATCH v10 5/8] worktree add: add --orphan flag","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-05-17T21:48:47Z","receivedAt":"2023-05-17T21:49:13Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Add support for creating an orphan branch when adding a new worktree.\nThe functionality of this flag is equivalent to git switch's --orphan\noption.\n\nCurrent Behavior:\n% git -C foo.git --no-pager branch -l\n+ main\n% git -C foo.git worktree add main/\nPreparing worktree (new branch 'main')\nHEAD is now at 6c93a75 a commit\n%\n\n% git init bar.git\nInitialized empty Git repository in /path/to/bar.git/\n% git -C bar.git --no-pager branch -l\n\n% git -C bar.git worktree add main/\nPreparing worktree (new branch 'main')\nfatal: not a valid object name: 'HEAD'\n%\n\nNew Behavior:\n\n% git -C foo.git --no-pager branch -l\n+ main\n% git -C foo.git worktree add main/\nPreparing worktree (new branch 'main')\nHEAD is now at 6c93a75 a commit\n%\n\n% git init --bare bar.git\nInitialized empty Git repository in /path/to/bar.git/\n% git -C bar.git --no-pager branch -l\n\n% git -C bar.git worktree add main/\nPreparing worktree (new branch 'main')\nfatal: invalid reference: HEAD\n% git -C bar.git worktree add --orphan -b main/\nPreparing worktree (new branch 'main')\n% git -C bar.git worktree add --orphan -b newbranch worktreedir/\nPreparing worktree (new branch 'newbranch')\n%\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n Documentation/git-worktree.txt |  6 ++-\n builtin/worktree.c             | 67 +++++++++++++++++++++++++++------\n t/t2400-worktree-add.sh        | 68 ++++++++++++++++++++++++++++++++++\n 3 files changed, 129 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex b9c12779f1..485d865eb2 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]]\n-\t\t   [(-b | -B) <new-branch>] <path> [<commit-ish>]\n+\t\t   [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]\n 'git worktree list' [-v | --porcelain [-z]]\n 'git worktree lock' [--reason <string>] <worktree>\n 'git worktree move' <worktree> <new-path>\n@@ -222,6 +222,10 @@ This can also be set up as the default behaviour by using the\n \tWith `prune`, do not remove anything; just report what it would\n \tremove.\n \n+--orphan::\n+\tWith `add`, make the new worktree and index empty, associating\n+\tthe worktree with a new orphan/unborn branch named `<new-branch>`.\n+\n --porcelain::\n \tWith `list`, output in an easy-to-parse format for scripts.\n \tThis format will remain stable across Git versions and regardless of user\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex d1b4b53f2c..48de7fc3b0 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -22,7 +22,8 @@\n \n #define BUILTIN_WORKTREE_ADD_USAGE \\\n \tN_(\"git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]\\n\" \\\n-\t   \"                 [(-b | -B) <new-branch>] <path> [<commit-ish>]\")\n+\t   \"                 [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]\")\n+\n #define BUILTIN_WORKTREE_LIST_USAGE \\\n \tN_(\"git worktree list [-v | --porcelain [-z]]\")\n #define BUILTIN_WORKTREE_LOCK_USAGE \\\n@@ -95,6 +96,7 @@ struct add_opts {\n \tint detach;\n \tint quiet;\n \tint checkout;\n+\tint orphan;\n \tconst char *keep_locked;\n };\n \n@@ -368,6 +370,22 @@ static int checkout_worktree(const struct add_opts *opts,\n \treturn run_command(&cp);\n }\n \n+static int make_worktree_orphan(const char * ref, const struct add_opts *opts,\n+\t\t\t\tstruct strvec *child_env)\n+{\n+\tstruct strbuf symref = STRBUF_INIT;\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\tvalidate_new_branchname(ref, &symref, 0);\n+\tstrvec_pushl(&cp.args, \"symbolic-ref\", \"HEAD\", symref.buf, NULL);\n+\tif (opts->quiet)\n+\t\tstrvec_push(&cp.args, \"--quiet\");\n+\tstrvec_pushv(&cp.env, child_env->v);\n+\tstrbuf_release(&symref);\n+\tcp.git_cmd = 1;\n+\treturn run_command(&cp);\n+}\n+\n static int add_worktree(const char *path, const char *refname,\n \t\t\tconst struct add_opts *opts)\n {\n@@ -397,7 +415,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tdie_if_checked_out(symref.buf, 0);\n \t}\n \tcommit = lookup_commit_reference_by_name(refname);\n-\tif (!commit)\n+\tif (!commit && !opts->orphan)\n \t\tdie(_(\"invalid reference: %s\"), refname);\n \n \tname = worktree_basename(path, &len);\n@@ -486,10 +504,10 @@ static int add_worktree(const char *path, const char *refname,\n \tstrvec_pushf(&child_env, \"%s=%s\", GIT_WORK_TREE_ENVIRONMENT, path);\n \tcp.git_cmd = 1;\n \n-\tif (!is_branch)\n+\tif (!is_branch && commit) {\n \t\tstrvec_pushl(&cp.args, \"update-ref\", \"HEAD\",\n \t\t\t     oid_to_hex(&commit->object.oid), NULL);\n-\telse {\n+\t} else {\n \t\tstrvec_pushl(&cp.args, \"symbolic-ref\", \"HEAD\",\n \t\t\t     symref.buf, NULL);\n \t\tif (opts->quiet)\n@@ -501,6 +519,10 @@ static int add_worktree(const char *path, const char *refname,\n \tif (ret)\n \t\tgoto done;\n \n+\tif (opts->orphan &&\n+\t    (ret = make_worktree_orphan(refname, opts, &child_env)))\n+\t\tgoto done;\n+\n \tif (opts->checkout &&\n \t    (ret = checkout_worktree(opts, &child_env)))\n \t\tgoto done;\n@@ -520,7 +542,7 @@ static int add_worktree(const char *path, const char *refname,\n \t * Hook failure does not warrant worktree deletion, so run hook after\n \t * is_junk is cleared, but do return appropriate code when hook fails.\n \t */\n-\tif (!ret && opts->checkout) {\n+\tif (!ret && opts->checkout && !opts->orphan) {\n \t\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n \n \t\tstrvec_pushl(&opt.env, \"GIT_DIR\", \"GIT_WORK_TREE\", NULL);\n@@ -568,7 +590,7 @@ static void print_preparing_worktree_line(int detach,\n \t\telse {\n \t\t\tstruct commit *commit = lookup_commit_reference_by_name(branch);\n \t\t\tif (!commit)\n-\t\t\t\tdie(_(\"invalid reference: %s\"), branch);\n+\t\t\t\tBUG(_(\"unreachable: invalid reference: %s\"), branch);\n \t\t\tfprintf_ln(stderr, _(\"Preparing worktree (detached HEAD %s)\"),\n \t\t\t\t  repo_find_unique_abbrev(the_repository, &commit->object.oid, DEFAULT_ABBREV));\n \t\t}\n@@ -620,6 +642,7 @@ static int add(int ac, const char **av, const char *prefix)\n \t\t\t   N_(\"create a new branch\")),\n \t\tOPT_STRING('B', NULL, &new_branch_force, N_(\"branch\"),\n \t\t\t   N_(\"create or reset a branch\")),\n+\t\tOPT_BOOL(0, \"orphan\", &opts.orphan, N_(\"create unborn/orphaned branch\")),\n \t\tOPT_BOOL('d', \"detach\", &opts.detach, N_(\"detach HEAD at named commit\")),\n \t\tOPT_BOOL(0, \"checkout\", &opts.checkout, N_(\"populate the new working tree\")),\n \t\tOPT_BOOL(0, \"lock\", &keep_locked, N_(\"keep the new working tree locked\")),\n@@ -640,6 +663,17 @@ static int add(int ac, const char **av, const char *prefix)\n \tac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0);\n \tif (!!opts.detach + !!new_branch + !!new_branch_force > 1)\n \t\tdie(_(\"options '%s', '%s', and '%s' cannot be used together\"), \"-b\", \"-B\", \"--detach\");\n+\tif (opts.detach && opts.orphan)\n+\t\tdie(_(\"options '%s', and '%s' cannot be used together\"),\n+\t\t    \"--orphan\", \"--detach\");\n+\tif (opts.orphan && opt_track)\n+\t\tdie(_(\"'%s' and '%s' cannot be used together\"), \"--orphan\", \"--track\");\n+\tif (opts.orphan && !opts.checkout)\n+\t\tdie(_(\"'%s' and '%s' cannot be used together\"), \"--orphan\",\n+\t\t    \"--no-checkout\");\n+\tif (opts.orphan && ac == 2)\n+\t\tdie(_(\"'%s' and '%s' cannot be used together\"), \"--orphan\",\n+\t\t    _(\"<commit-ish>\"));\n \tif (lock_reason && !keep_locked)\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--reason\", \"--lock\");\n \tif (lock_reason)\n@@ -668,13 +702,17 @@ static int add(int ac, const char **av, const char *prefix)\n \t\tstrbuf_release(&symref);\n \t}\n \n-\tif (ac < 2 && !new_branch && !opts.detach) {\n+\tif (opts.orphan && !new_branch) {\n+\t\tint n;\n+\t\tconst char *s = worktree_basename(path, &n);\n+\t\tnew_branch = xstrndup(s, n);\n+\t} else if (new_branch || opts.detach || opts.orphan) {\n+\t\t// No-op\n+\t} else if (ac < 2) {\n \t\tconst char *s = dwim_branch(path, &new_branch);\n \t\tif (s)\n \t\t\tbranch = s;\n-\t}\n-\n-\tif (ac == 2 && !new_branch && !opts.detach) {\n+\t} else if (ac == 2) {\n \t\tstruct object_id oid;\n \t\tstruct commit *commit;\n \t\tconst char *remote;\n@@ -688,10 +726,17 @@ static int add(int ac, const char **av, const char *prefix)\n \t\t\t}\n \t\t}\n \t}\n+\n+\tif (!opts.orphan && !lookup_commit_reference_by_name(branch)) {\n+\t\tdie(_(\"invalid reference: %s\"), branch);\n+\t}\n+\n \tif (!opts.quiet)\n \t\tprint_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force);\n \n-\tif (new_branch) {\n+\tif (opts.orphan) {\n+\t\tbranch = new_branch;\n+\t} else if (new_branch) {\n \t\tstruct child_process cp = CHILD_PROCESS_INIT;\n \t\tcp.git_cmd = 1;\n \t\tstrvec_push(&cp.args, \"branch\");\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex 841f15f59e..fba90582b6 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -312,6 +312,10 @@ test_wt_add_excl () {\n test_wt_add_excl -b poodle -B poodle bamboo main\n test_wt_add_excl -b poodle --detach bamboo main\n test_wt_add_excl -B poodle --detach bamboo main\n+test_wt_add_excl --orphan --detach bamboo\n+test_wt_add_excl --orphan --no-checkout bamboo\n+test_wt_add_excl --orphan bamboo main\n+test_wt_add_excl --orphan -b bamboo wtdir/ main\n \n test_expect_success '\"add -B\" fails if the branch is checked out' '\n \tgit rev-parse newmain >before &&\n@@ -341,6 +345,62 @@ test_expect_success 'add --quiet -b' '\n \ttest_must_be_empty actual\n '\n \n+test_expect_success '\"add --orphan\"' '\n+\ttest_when_finished \"git worktree remove -f -f orphandir\" &&\n+\tgit worktree add --orphan -b neworphan orphandir &&\n+\techo refs/heads/neworphan >expected &&\n+\tgit -C orphandir symbolic-ref HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '\"add --orphan (no -b)\"' '\n+\ttest_when_finished \"git worktree remove -f -f neworphan\" &&\n+\tgit worktree add --orphan neworphan &&\n+\techo refs/heads/neworphan >expected &&\n+\tgit -C neworphan symbolic-ref HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '\"add --orphan --quiet\"' '\n+\ttest_when_finished \"git worktree remove -f -f orphandir\" &&\n+\tgit worktree add --quiet --orphan -b neworphan orphandir 2>log.actual &&\n+\ttest_must_be_empty log.actual &&\n+\techo refs/heads/neworphan >expected &&\n+\tgit -C orphandir symbolic-ref HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '\"add --orphan\" fails if the branch already exists' '\n+\ttest_when_finished \"git branch -D existingbranch\" &&\n+\tgit worktree add -b existingbranch orphandir main &&\n+\tgit worktree remove orphandir &&\n+\ttest_must_fail git worktree add --orphan -b existingbranch orphandir\n+'\n+\n+test_expect_success '\"add --orphan\" with empty repository' '\n+\ttest_when_finished \"rm -rf empty_repo\" &&\n+\techo refs/heads/newbranch >expected &&\n+\tGIT_DIR=\"empty_repo\" git init --bare &&\n+\tgit -C empty_repo worktree add --orphan -b newbranch worktreedir &&\n+\tgit -C empty_repo/worktreedir symbolic-ref HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '\"add\" worktree with orphan branch and lock' '\n+\tgit worktree add --lock --orphan -b orphanbr orphan-with-lock &&\n+\ttest_when_finished \"git worktree unlock orphan-with-lock || :\" &&\n+\ttest -f .git/worktrees/orphan-with-lock/locked\n+'\n+\n+test_expect_success '\"add\" worktree with orphan branch, lock, and reason' '\n+\tlock_reason=\"why not\" &&\n+\tgit worktree add --detach --lock --reason \"$lock_reason\" orphan-with-lock-reason main &&\n+\ttest_when_finished \"git worktree unlock orphan-with-lock-reason || :\" &&\n+\ttest -f .git/worktrees/orphan-with-lock-reason/locked &&\n+\techo \"$lock_reason\" >expect &&\n+\ttest_cmp expect .git/worktrees/orphan-with-lock-reason/locked\n+'\n+\n test_expect_success 'local clone from linked checkout' '\n \tgit clone --local here here-clone &&\n \t( cd here-clone && git fsck )\n@@ -457,6 +517,14 @@ setup_remote_repo () {\n \t)\n }\n \n+test_expect_success '\"add\" <path> <remote/branch> w/ no HEAD' '\n+\ttest_when_finished rm -rf repo_upstream repo_local foo &&\n+\tsetup_remote_repo repo_upstream repo_local &&\n+\tgit -C repo_local config --bool core.bare true &&\n+\tgit -C repo_local branch -D main &&\n+\tgit -C repo_local worktree add ./foo repo_upstream/foo\n+'\n+\n test_expect_success '--no-track avoids setting up tracking' '\n \ttest_when_finished rm -rf repo_upstream repo_local foo &&\n \tsetup_remote_repo repo_upstream repo_local &&\n-- \n2.39.3\n\n\n"},{"id":"477497","messageId":"20230517214711.12467-8-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230517214711.12467-1-jacobabel@nullpo.dev","subject":"[RESEND PATCH v10 7/8] worktree add: extend DWIM to infer --orphan","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-05-17T21:48:58Z","receivedAt":"2023-05-17T21:49:26Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Extend DWIM to try to infer `--orphan` when in an empty repository. i.e.\na repository with an invalid/unborn HEAD, no local branches, and if\n`--guess-remote` is used then no remote branches.\n\nThis behavior is equivalent to `git switch -c` or `git checkout -b` in\nan empty repository.\n\nAlso warn the user (overriden with `-f`/`--force`) when they likely\nintend to checkout a remote branch to the worktree but have not yet\nfetched from the remote. i.e. when using `--guess-remote` and there is a\nremote but no local or remote refs.\n\nCurrent Behavior:\n% git --no-pager branch --list --remotes\n% git remote\norigin\n% git workree add ../main\nhint: If you meant to create a worktree containing a new orphan branch\n[...]\nhint: Disable this message with \"git config advice.worktreeAddOrphan false\"\nfatal: invalid reference: HEAD\n% git workree add --guess-remote ../main\nhint: If you meant to create a worktree containing a new orphan branch\n[...]\nhint: Disable this message with \"git config advice.worktreeAddOrphan false\"\nfatal: invalid reference: HEAD\n% git fetch --quiet\n% git --no-pager branch --list --remotes\norigin/HEAD -> origin/main\norigin/main\n% git workree add --guess-remote ../main\nPreparing worktree (new branch 'main')\nbranch 'main' set up to track 'origin/main'.\nHEAD is now at dadc8e6dac commit message\n%\n\nNew Behavior:\n% git --no-pager branch --list --remotes\n% git remote\norigin\n% git workree add ../main\nNo possible source branch, inferring '--orphan'\nPreparing worktree (new branch 'main')\n% git worktree remove ../main\n% git workree add --guess-remote ../main\nfatal: No local or remote refs exist despite at least one remote\npresent, stopping; use 'add -f' to overide or fetch a remote first\n% git workree add --guess-remote -f ../main\nNo possible source branch, inferring '--orphan'\nPreparing worktree (new branch 'main')\n% git worktree remove ../main\n% git fetch --quiet\n% git --no-pager branch --list --remotes\norigin/HEAD -> origin/main\norigin/main\n% git workree add --guess-remote ../main\nPreparing worktree (new branch 'main')\nbranch 'main' set up to track 'origin/main'.\nHEAD is now at dadc8e6dac commit message\n%\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n Documentation/git-worktree.txt |  10 +\n builtin/worktree.c             | 114 +++++++++++-\n t/t2400-worktree-add.sh        | 326 +++++++++++++++++++++++++++++++++\n 3 files changed, 449 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 485d865eb2..a4fbf5e838 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -95,6 +95,16 @@ exist, a new branch based on `HEAD` is automatically created as if\n `-b <branch>` was given.  If `<branch>` does exist, it will be checked out\n in the new worktree, if it's not checked out anywhere else, otherwise the\n command will refuse to create the worktree (unless `--force` is used).\n++\n+If `<commit-ish>` is omitted, neither `--detach`, or `--orphan` is\n+used, and there are no valid local branches (or remote branches if\n+`--guess-remote` is specified) then, as a convenience, the new worktree is\n+associated with a new orphan branch named `<branch>` (after\n+`$(basename <path>)` if neither `-b` or `-B` is used) as if `--orphan` was\n+passed to the command. In the event the repository has a remote and\n+`--guess-remote` is used, but no remote or local branches exist, then the\n+command fails with a warning reminding the user to fetch from their remote\n+first (or override by using `-f/--force`).\n \n list::\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 15bdb380c7..093b2cb032 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -12,6 +12,7 @@\n #include \"strvec.h\"\n #include \"branch.h\"\n #include \"refs.h\"\n+#include \"remote.h\"\n #include \"run-command.h\"\n #include \"hook.h\"\n #include \"sigchain.h\"\n@@ -40,6 +41,9 @@\n #define BUILTIN_WORKTREE_UNLOCK_USAGE \\\n \tN_(\"git worktree unlock <worktree>\")\n \n+#define WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT \\\n+\t_(\"No possible source branch, inferring '--orphan'\")\n+\n #define WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT \\\n \t_(\"If you meant to create a worktree containing a new orphan branch\\n\" \\\n \t\"(branch with no commits) for this repository, you can do so\\n\" \\\n@@ -613,6 +617,107 @@ static void print_preparing_worktree_line(int detach,\n \t}\n }\n \n+/**\n+ * Callback to short circuit iteration over refs on the first reference\n+ * corresponding to a valid oid.\n+ *\n+ * Returns 0 on failure and non-zero on success.\n+ */\n+static int first_valid_ref(const char *refname,\n+\t\t\t   const struct object_id *oid,\n+\t\t\t   int flags,\n+\t\t\t   void *cb_data)\n+{\n+\treturn 1;\n+}\n+\n+/**\n+ * Verifies HEAD and determines whether there exist any valid local references.\n+ *\n+ * - Checks whether HEAD points to a valid reference.\n+ *\n+ * - Checks whether any valid local branches exist.\n+ *\n+ * Returns 1 if any of the previous checks are true, otherwise returns 0.\n+ */\n+static int can_use_local_refs(const struct add_opts *opts)\n+{\n+\tif (head_ref(first_valid_ref, NULL)) {\n+\t\treturn 1;\n+\t} else if (for_each_branch_ref(first_valid_ref, NULL)) {\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+/**\n+ * Reports whether the necessary flags were set and whether the repository has\n+ * remote references to attempt DWIM tracking of upstream branches.\n+ *\n+ * 1. Checks that `--guess-remote` was used or `worktree.guessRemote = true`.\n+ *\n+ * 2. Checks whether any valid remote branches exist.\n+ *\n+ * 3. Checks that there exists at least one remote and emits a warning/error\n+ *    if both checks 1. and 2. are false (can be bypassed with `--force`).\n+ *\n+ * Returns 1 if checks 1. and 2. are true, otherwise 0.\n+ */\n+static int can_use_remote_refs(const struct add_opts *opts)\n+{\n+\tif (!guess_remote) {\n+\t\tif (!opts->quiet)\n+\t\t\tfprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);\n+\t\treturn 0;\n+\t} else if (for_each_remote_ref(first_valid_ref, NULL)) {\n+\t\treturn 1;\n+\t} else if (!opts->force && remote_get(NULL)) {\n+\t\tdie(_(\"No local or remote refs exist despite at least one remote\\n\"\n+\t\t      \"present, stopping; use 'add -f' to overide or fetch a remote first\"));\n+\t} else if (!opts->quiet) {\n+\t\tfprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);\n+\t}\n+\treturn 0;\n+}\n+\n+/**\n+ * Determines whether `--orphan` should be inferred in the evaluation of\n+ * `worktree add path/` or `worktree add -b branch path/` and emits an error\n+ * if the supplied arguments would produce an illegal combination when the\n+ * `--orphan` flag is included.\n+ *\n+ * `opts` and `opt_track` contain the other options & flags supplied to the\n+ * command.\n+ *\n+ * remote determines whether to check `can_use_remote_refs()` or not. This\n+ * is primarily to differentiate between the basic `add` DWIM and `add -b`.\n+ *\n+ * Returns 1 when inferring `--orphan`, 0 otherwise, and emits an error when\n+ * `--orphan` is inferred but doing so produces an illegal combination of\n+ * options and flags. Additionally produces an error when remote refs are\n+ * checked and the repo is in a state that looks like the user added a remote\n+ * but forgot to fetch (and did not override the warning with -f).\n+ */\n+static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote)\n+{\n+\tif (can_use_local_refs(opts)) {\n+\t\treturn 0;\n+\t} else if (remote && can_use_remote_refs(opts)) {\n+\t\treturn 0;\n+\t} else if (!opts->quiet) {\n+\t\tfprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);\n+\t}\n+\n+\tif (opt_track) {\n+\t\tdie(_(\"'%s' and '%s' cannot be used together\"), \"--orphan\",\n+\t\t    \"--track\");\n+\t} else if (!opts->checkout) {\n+\t\tdie(_(\"'%s' and '%s' cannot be used together\"), \"--orphan\",\n+\t\t    \"--no-checkout\");\n+\t}\n+\treturn 1;\n+}\n+\n static const char *dwim_branch(const char *path, const char **new_branch)\n {\n \tint n;\n@@ -723,12 +828,19 @@ static int add(int ac, const char **av, const char *prefix)\n \t\tint n;\n \t\tconst char *s = worktree_basename(path, &n);\n \t\tnew_branch = xstrndup(s, n);\n-\t} else if (new_branch || opts.detach || opts.orphan) {\n+\t} else if (opts.orphan || opts.detach) {\n \t\t// No-op\n+\t} else if (ac < 2 && new_branch) {\n+\t\t// DWIM: Infer --orphan when repo has no refs.\n+\t\topts.orphan = dwim_orphan(&opts, !!opt_track, 0);\n \t} else if (ac < 2) {\n+\t\t// DWIM: Guess branch name from path.\n \t\tconst char *s = dwim_branch(path, &new_branch);\n \t\tif (s)\n \t\t\tbranch = s;\n+\n+\t\t// DWIM: Infer --orphan when repo has no refs.\n+\t\topts.orphan = (!s) && dwim_orphan(&opts, !!opt_track, 1);\n \t} else if (ac == 2) {\n \t\tstruct object_id oid;\n \t\tstruct commit *commit;\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex 46eef26179..c7ca8df586 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -705,6 +705,332 @@ test_expect_success 'git worktree --no-guess-remote option overrides config' '\n \t)\n '\n \n+test_dwim_orphan () {\n+\tlocal info_text=\"No possible source branch, inferring '--orphan'\" &&\n+\tlocal fetch_error_text=\"fatal: No local or remote refs exist despite at least one remote\" &&\n+\tlocal orphan_hint=\"hint: If you meant to create a worktree containing a new orphan branch\" &&\n+\tlocal invalid_ref_regex=\"^fatal: invalid reference:\\s\\+.*\" &&\n+\tlocal bad_combo_regex=\"^fatal: '[a-z-]\\+' and '[a-z-]\\+' cannot be used together\" &&\n+\n+\tlocal git_ns=\"repo\" &&\n+\tlocal dashc_args=\"-C $git_ns\" &&\n+\tlocal use_cd=0 &&\n+\n+\tlocal bad_head=0 &&\n+\tlocal empty_repo=1 &&\n+\tlocal local_ref=0 &&\n+\tlocal use_quiet=0 &&\n+\tlocal remote=0 &&\n+\tlocal remote_ref=0 &&\n+\tlocal use_new_branch=0 &&\n+\n+\tlocal outcome=\"$1\" &&\n+\tlocal outcome_text &&\n+\tlocal success &&\n+\tshift &&\n+\tlocal args=\"\" &&\n+\tlocal context=\"\" &&\n+\tcase \"$outcome\" in\n+\t\"infer\")\n+\t\tsuccess=1 &&\n+\t\toutcome_text='\"add\" DWIM infer --orphan'\n+\t\t;;\n+\t\"no_infer\")\n+\t\tsuccess=1 &&\n+\t\toutcome_text='\"add\" DWIM doesnt infer --orphan'\n+\t\t;;\n+\t\"fetch_error\")\n+\t\tsuccess=0 &&\n+\t\toutcome_text='\"add\" error need fetch'\n+\t\t;;\n+\t\"fatal_orphan_bad_combo\")\n+\t\tsuccess=0 &&\n+\t\toutcome_text='\"add\" error inferred \"--orphan\" gives illegal opts combo'\n+\t\t;;\n+\t*)\n+\t\techo \"test_dwim_orphan(): invalid outcome: '$outcome'\" >&2 &&\n+\t\treturn 1\n+\t\t;;\n+\tesac &&\n+\twhile [ $# -gt 0 ]\n+\tdo\n+\t\tcase \"$1\" in\n+\t\t# How and from where to create the worktree\n+\t\t\"-C_repo\")\n+\t\t\tuse_cd=0 &&\n+\t\t\tgit_ns=\"repo\" &&\n+\t\t\tdashc_args=\"-C $git_ns\" &&\n+\t\t\tcontext=\"$context, 'git -C repo'\"\n+\t\t\t;;\n+\t\t\"-C_wt\")\n+\t\t\tuse_cd=0 &&\n+\t\t\tgit_ns=\"wt\" &&\n+\t\t\tdashc_args=\"-C $git_ns\" &&\n+\t\t\tcontext=\"$context, 'git -C wt'\"\n+\t\t\t;;\n+\t\t\"cd_repo\")\n+\t\t\tuse_cd=1 &&\n+\t\t\tgit_ns=\"repo\" &&\n+\t\t\tdashc_args=\"\" &&\n+\t\t\tcontext=\"$context, 'cd repo && git'\"\n+\t\t\t;;\n+\t\t\"cd_wt\")\n+\t\t\tuse_cd=1 &&\n+\t\t\tgit_ns=\"wt\" &&\n+\t\t\tdashc_args=\"\" &&\n+\t\t\tcontext=\"$context, 'cd wt && git'\"\n+\t\t\t;;\n+\n+\t\t# Bypass the \"pull first\" warning\n+\t\t\"force\")\n+\t\t\targs=\"$args --force\" &&\n+\t\t\tcontext=\"$context, --force\"\n+\t\t\t;;\n+\n+\t\t# Try to use remote refs when DWIM\n+\t\t\"guess_remote\")\n+\t\t\targs=\"$args --guess-remote\" &&\n+\t\t\tcontext=\"$context, --guess-remote\"\n+\t\t\t;;\n+\t\t\"no_guess_remote\")\n+\t\t\targs=\"$args --no-guess-remote\" &&\n+\t\t\tcontext=\"$context, --no-guess-remote\"\n+\t\t\t;;\n+\n+\t\t# Whether there is at least one local branch present\n+\t\t\"local_ref\")\n+\t\t\tempty_repo=0 &&\n+\t\t\tlocal_ref=1 &&\n+\t\t\tcontext=\"$context, >=1 local branches\"\n+\t\t\t;;\n+\t\t\"no_local_ref\")\n+\t\t\tempty_repo=0 &&\n+\t\t\tcontext=\"$context, 0 local branches\"\n+\t\t\t;;\n+\n+\t\t# Whether the HEAD points at a valid ref (skip this opt when no refs)\n+\t\t\"good_head\")\n+\t\t\t# requires: local_ref\n+\t\t\tcontext=\"$context, valid HEAD\"\n+\t\t\t;;\n+\t\t\"bad_head\")\n+\t\t\tbad_head=1 &&\n+\t\t\tcontext=\"$context, invalid (or orphan) HEAD\"\n+\t\t\t;;\n+\n+\t\t# Whether the code path is tested with the base add command or -b\n+\t\t\"no_-b\")\n+\t\t\tuse_new_branch=0 &&\n+\t\t\tcontext=\"$context, no --branch\"\n+\t\t\t;;\n+\t\t\"-b\")\n+\t\t\tuse_new_branch=1 &&\n+\t\t\tcontext=\"$context, --branch\"\n+\t\t\t;;\n+\n+\t\t# Whether to check that all output is suppressed (except errors)\n+\t\t# or that the output is as expected\n+\t\t\"quiet\")\n+\t\t\tuse_quiet=1 &&\n+\t\t\targs=\"$args --quiet\" &&\n+\t\t\tcontext=\"$context, --quiet\"\n+\t\t\t;;\n+\t\t\"no_quiet\")\n+\t\t\tuse_quiet=0 &&\n+\t\t\tcontext=\"$context, no --quiet (expect output)\"\n+\t\t\t;;\n+\n+\t\t# Whether there is at least one remote attached to the repo\n+\t\t\"remote\")\n+\t\t\tempty_repo=0 &&\n+\t\t\tremote=1 &&\n+\t\t\tcontext=\"$context, >=1 remotes\"\n+\t\t\t;;\n+\t\t\"no_remote\")\n+\t\t\tempty_repo=0 &&\n+\t\t\tremote=0 &&\n+\t\t\tcontext=\"$context, 0 remotes\"\n+\t\t\t;;\n+\n+\t\t# Whether there is at least one valid remote ref\n+\t\t\"remote_ref\")\n+\t\t\t# requires: remote\n+\t\t\tempty_repo=0 &&\n+\t\t\tremote_ref=1 &&\n+\t\t\tcontext=\"$context, >=1 fetched remote branches\"\n+\t\t\t;;\n+\t\t\"no_remote_ref\")\n+\t\t\tempty_repo=0 &&\n+\t\t\tremote_ref=0 &&\n+\t\t\tcontext=\"$context, 0 fetched remote branches\"\n+\t\t\t;;\n+\n+\t\t# Options or flags that become illegal when --orphan is inferred\n+\t\t\"no_checkout\")\n+\t\t\targs=\"$args --no-checkout\" &&\n+\t\t\tcontext=\"$context, --no-checkout\"\n+\t\t\t;;\n+\t\t\"track\")\n+\t\t\targs=\"$args --track\" &&\n+\t\t\tcontext=\"$context, --track\"\n+\t\t\t;;\n+\n+\t\t# All other options are illegal\n+\t\t*)\n+\t\t\techo \"test_dwim_orphan(): invalid arg: '$1'\" >&2 &&\n+\t\t\treturn 1\n+\t\t\t;;\n+\t\tesac &&\n+\t\tshift\n+\tdone &&\n+\tcontext=\"${context#', '}\" &&\n+\tif [ $use_new_branch -eq 1 ]\n+\tthen\n+\t\targs=\"$args -b foo\"\n+\telse\n+\t\tcontext=\"DWIM (no --branch), $context\"\n+\tfi &&\n+\tif [ $empty_repo -eq 1 ]\n+\tthen\n+\t\tcontext=\"empty repo, $context\"\n+\tfi &&\n+\targs=\"$args ../foo\" &&\n+\tcontext=\"${context%', '}\" &&\n+\ttest_expect_success \"$outcome_text w/ $context\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\tif [ $local_ref -eq 1 ] && [ \"$git_ns\" = \"repo\" ]\n+\t\tthen\n+\t\t\t(cd repo && test_commit commit) &&\n+\t\t\tif [ $bad_head -eq 1 ]\n+\t\t\tthen\n+\t\t\t\tgit -C repo symbolic-ref HEAD refs/heads/badbranch\n+\t\t\tfi\n+\t\telif [ $local_ref -eq 1 ] && [ \"$git_ns\" = \"wt\" ]\n+\t\tthen\n+\t\t\ttest_when_finished \"git -C repo worktree remove -f ../wt\" &&\n+\t\t\tgit -C repo worktree add --orphan -b main ../wt &&\n+\t\t\t(cd wt && test_commit commit) &&\n+\t\t\tif [ $bad_head -eq 1 ]\n+\t\t\tthen\n+\t\t\t\tgit -C wt symbolic-ref HEAD refs/heads/badbranch\n+\t\t\tfi\n+\t\telif [ $local_ref -eq 0 ] && [ \"$git_ns\" = \"wt\" ]\n+\t\tthen\n+\t\t\ttest_when_finished \"git -C repo worktree remove -f ../wt\" &&\n+\t\t\tgit -C repo worktree add --orphan -b orphanbranch ../wt\n+\t\tfi &&\n+\n+\t\tif [ $remote -eq 1 ]\n+\t\tthen\n+\t\t\ttest_when_finished \"rm -rf upstream\" &&\n+\t\t\tgit init upstream &&\n+\t\t\t(cd upstream && test_commit commit) &&\n+\t\t\tgit -C upstream switch -c foo &&\n+\t\t\tgit -C repo remote add upstream ../upstream\n+\t\tfi &&\n+\n+\t\tif [ $remote_ref -eq 1 ]\n+\t\tthen\n+\t\t\tgit -C repo fetch\n+\t\tfi &&\n+\t\tif [ $success -eq 1 ]\n+\t\tthen\n+\t\t\ttest_when_finished git -C repo worktree remove ../foo\n+\t\tfi &&\n+\t\t(\n+\t\t\tif [ $use_cd -eq 1 ]\n+\t\t\tthen\n+\t\t\t\tcd $git_ns\n+\t\t\tfi &&\n+\t\t\tif [ \"$outcome\" = \"infer\" ]\n+\t\t\tthen\n+\t\t\t\tgit $dashc_args worktree add $args 2>actual &&\n+\t\t\t\tif [ $use_quiet -eq 1 ]\n+\t\t\t\tthen\n+\t\t\t\t\ttest_must_be_empty actual\n+\t\t\t\telse\n+\t\t\t\t\tgrep \"$info_text\" actual\n+\t\t\t\tfi\n+\t\t\telif [ \"$outcome\" = \"no_infer\" ]\n+\t\t\tthen\n+\t\t\t\tgit $dashc_args worktree add $args 2>actual &&\n+\t\t\t\tif [ $use_quiet -eq 1 ]\n+\t\t\t\tthen\n+\t\t\t\t\ttest_must_be_empty actual\n+\t\t\t\telse\n+\t\t\t\t\t! grep \"$info_text\" actual\n+\t\t\t\tfi\n+\t\t\telif [ \"$outcome\" = \"fetch_error\" ]\n+\t\t\tthen\n+\t\t\t\ttest_must_fail git $dashc_args worktree add $args 2>actual &&\n+\t\t\t\tgrep \"$fetch_error_text\" actual\n+\t\t\telif [ \"$outcome\" = \"fatal_orphan_bad_combo\" ]\n+\t\t\tthen\n+\t\t\t\ttest_must_fail git $dashc_args worktree add $args 2>actual &&\n+\t\t\t\tif [ $use_quiet -eq 1 ]\n+\t\t\t\tthen\n+\t\t\t\t\t! grep \"$info_text\" actual\n+\t\t\t\telse\n+\t\t\t\t\tgrep \"$info_text\" actual\n+\t\t\t\tfi &&\n+\t\t\t\tgrep \"$bad_combo_regex\" actual\n+\t\t\telif [ \"$outcome\" = \"warn_bad_head\" ]\n+\t\t\tthen\n+\t\t\t\ttest_must_fail git $dashc_args worktree add $args 2>actual &&\n+\t\t\t\tif [ $use_quiet -eq 1 ]\n+\t\t\t\tthen\n+\t\t\t\t\tgrep \"$invalid_ref_regex\" actual &&\n+\t\t\t\t\t! grep \"$orphan_hint\" actual\n+\t\t\t\telse\n+\t\t\t\t\theadpath=$(git $dashc_args rev-parse --sq --path-format=absolute --git-path HEAD) &&\n+\t\t\t\t\theadcontents=$(cat \"$headpath\") &&\n+\t\t\t\t\tgrep \"HEAD points to an invalid (or orphaned) reference\" actual &&\n+\t\t\t\t\tgrep \"HEAD path:\\s*.$headpath.\" actual &&\n+\t\t\t\t\tgrep \"HEAD contents:\\s*.$headcontents.\" actual &&\n+\t\t\t\t\tgrep \"$orphan_hint\" actual &&\n+\t\t\t\t\t! grep \"$info_text\" actual\n+\t\t\t\tfi &&\n+\t\t\t\tgrep \"$invalid_ref_regex\" actual\n+\t\t\telse\n+\t\t\t\t# Unreachable\n+\t\t\t\tfalse\n+\t\t\tfi\n+\t\t) &&\n+\t\tif [ $success -ne 1 ]\n+\t\tthen\n+\t\t\ttest_path_is_missing foo\n+\t\tfi\n+\t'\n+}\n+\n+for quiet_mode in \"no_quiet\" \"quiet\"\n+do\n+\tfor changedir_type in \"cd_repo\" \"cd_wt\" \"-C_repo\" \"-C_wt\"\n+\tdo\n+\t\tdwim_test_args=\"$quiet_mode $changedir_type\"\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args no_-b\n+\t\ttest_dwim_orphan 'no_infer' $dwim_test_args no_-b local_ref good_head\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref no_remote no_remote_ref no_guess_remote\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref no_guess_remote\n+\t\ttest_dwim_orphan 'fetch_error' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote force\n+\t\ttest_dwim_orphan 'no_infer' $dwim_test_args no_-b no_local_ref remote remote_ref guess_remote\n+\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args -b\n+\t\ttest_dwim_orphan 'no_infer' $dwim_test_args -b local_ref good_head\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref no_remote no_remote_ref no_guess_remote\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref no_guess_remote\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref guess_remote\n+\t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote remote_ref guess_remote\n+\tdone\n+\n+\ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b no_checkout\n+\ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b track\n+\ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode -b no_checkout\n+\ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode -b track\n+done\n+\n post_checkout_hook () {\n \ttest_when_finished \"rm -rf .git/hooks\" &&\n \tmkdir .git/hooks &&\n-- \n2.39.3\n\n\n"},{"id":"477498","messageId":"20230517214711.12467-9-jacobabel@nullpo.dev","threadId":"59607","inReplyTo":"20230517214711.12467-1-jacobabel@nullpo.dev","subject":"[RESEND PATCH v10 8/8] worktree add: emit warn when there is a bad HEAD","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-05-17T21:49:06Z","receivedAt":"2023-05-17T21:49:28Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"Add a warning to `worktree add` when the command tries to reference\nHEAD, there exist valid local branches, and the HEAD points to a\nnon-existent reference.\n\nCurrent Behavior:\n% git -C foo worktree list\n/path/to/repo/foo     dadc8e6dac [main]\n/path/to/repo/foo_wt  0000000000 [badref]\n% git -C foo worktree add ../wt1\nPreparing worktree (new branch 'wt1')\nHEAD is now at dadc8e6dac dummy commit\n% git -C foo_wt worktree add ../wt2\nhint: If you meant to create a worktree containing a new orphan branch\n[...]\nhint: Disable this message with \"git config advice.worktreeAddOrphan false\"\nfatal: invalid reference: HEAD\n%\n\nNew Behavior:\n% git -C foo worktree list\n/path/to/repo/foo     dadc8e6dac [main]\n/path/to/repo/foo_wt  0000000000 [badref]\n% git -C foo worktree add ../wt1\nPreparing worktree (new branch 'wt1')\nHEAD is now at dadc8e6dac dummy commit\n% git -C foo_wt worktree add ../wt2\nwarning: HEAD points to an invalid (or orphaned) reference.\nHEAD path: '/path/to/repo/foo/.git/worktrees/foo_wt/HEAD'\nHEAD contents: 'ref: refs/heads/badref'\nhint: If you meant to create a worktree containing a new orphan branch\n[...]\nhint: Disable this message with \"git config advice.worktreeAddOrphan false\"\nfatal: invalid reference: HEAD\n%\n\nSigned-off-by: Jacob Abel <jacobabel@nullpo.dev>\n---\n builtin/worktree.c      | 34 +++++++++++++++++++++++++++++-----\n t/t2400-worktree-add.sh | 18 +++++++++++++++++-\n 2 files changed, 46 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 093b2cb032..5f62084334 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -638,6 +638,9 @@ static int first_valid_ref(const char *refname,\n  *\n  * - Checks whether any valid local branches exist.\n  *\n+ * - Emits a warning if there exist any valid branches but HEAD does not point\n+ *   to a valid reference.\n+ *\n  * Returns 1 if any of the previous checks are true, otherwise returns 0.\n  */\n static int can_use_local_refs(const struct add_opts *opts)\n@@ -645,6 +648,23 @@ static int can_use_local_refs(const struct add_opts *opts)\n \tif (head_ref(first_valid_ref, NULL)) {\n \t\treturn 1;\n \t} else if (for_each_branch_ref(first_valid_ref, NULL)) {\n+\t\tif (!opts->quiet) {\n+\t\t\tstruct strbuf path = STRBUF_INIT;\n+\t\t\tstruct strbuf contents = STRBUF_INIT;\n+\n+\t\t\tstrbuf_add_real_path(&path, get_worktree_git_dir(NULL));\n+\t\t\tstrbuf_addstr(&path, \"/HEAD\");\n+\t\t\tstrbuf_read_file(&contents, path.buf, 64);\n+\t\t\tstrbuf_stripspace(&contents, 0);\n+\t\t\tstrbuf_strip_suffix(&contents, \"\\n\");\n+\n+\t\t\twarning(_(\"HEAD points to an invalid (or orphaned) reference.\\n\"\n+\t\t\t\t  \"HEAD path: '%s'\\n\"\n+\t\t\t\t  \"HEAD contents: '%s'\"),\n+\t\t\t\t  path.buf, contents.buf);\n+\t\t\tstrbuf_release(&path);\n+\t\t\tstrbuf_release(&contents);\n+\t\t}\n \t\treturn 1;\n \t}\n \treturn 0;\n@@ -666,16 +686,12 @@ static int can_use_local_refs(const struct add_opts *opts)\n static int can_use_remote_refs(const struct add_opts *opts)\n {\n \tif (!guess_remote) {\n-\t\tif (!opts->quiet)\n-\t\t\tfprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);\n \t\treturn 0;\n \t} else if (for_each_remote_ref(first_valid_ref, NULL)) {\n \t\treturn 1;\n \t} else if (!opts->force && remote_get(NULL)) {\n \t\tdie(_(\"No local or remote refs exist despite at least one remote\\n\"\n \t\t      \"present, stopping; use 'add -f' to overide or fetch a remote first\"));\n-\t} else if (!opts->quiet) {\n-\t\tfprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);\n \t}\n \treturn 0;\n }\n@@ -828,8 +844,12 @@ static int add(int ac, const char **av, const char *prefix)\n \t\tint n;\n \t\tconst char *s = worktree_basename(path, &n);\n \t\tnew_branch = xstrndup(s, n);\n-\t} else if (opts.orphan || opts.detach) {\n+\t} else if (opts.orphan) {\n \t\t// No-op\n+\t} else if (opts.detach) {\n+\t\t// Check HEAD\n+\t\tif (!strcmp(branch, \"HEAD\"))\n+\t\t\tcan_use_local_refs(&opts);\n \t} else if (ac < 2 && new_branch) {\n \t\t// DWIM: Infer --orphan when repo has no refs.\n \t\topts.orphan = dwim_orphan(&opts, !!opt_track, 0);\n@@ -854,6 +874,10 @@ static int add(int ac, const char **av, const char *prefix)\n \t\t\t\tbranch = remote;\n \t\t\t}\n \t\t}\n+\n+\t\tif (!strcmp(branch, \"HEAD\"))\n+\t\t\tcan_use_local_refs(&opts);\n+\n \t}\n \n \tif (!opts.orphan && !lookup_commit_reference_by_name(branch)) {\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex c7ca8df586..0ac468e69e 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -722,6 +722,7 @@ test_dwim_orphan () {\n \tlocal use_quiet=0 &&\n \tlocal remote=0 &&\n \tlocal remote_ref=0 &&\n+\tlocal use_detach=0 &&\n \tlocal use_new_branch=0 &&\n \n \tlocal outcome=\"$1\" &&\n@@ -747,6 +748,10 @@ test_dwim_orphan () {\n \t\tsuccess=0 &&\n \t\toutcome_text='\"add\" error inferred \"--orphan\" gives illegal opts combo'\n \t\t;;\n+\t\"warn_bad_head\")\n+\t\tsuccess=0 &&\n+\t\toutcome_text='\"add\" error, warn on bad HEAD, hint use orphan'\n+\t\t;;\n \t*)\n \t\techo \"test_dwim_orphan(): invalid outcome: '$outcome'\" >&2 &&\n \t\treturn 1\n@@ -818,7 +823,7 @@ test_dwim_orphan () {\n \t\t\tcontext=\"$context, invalid (or orphan) HEAD\"\n \t\t\t;;\n \n-\t\t# Whether the code path is tested with the base add command or -b\n+\t\t# Whether the code path is tested with the base add command, -b, or --detach\n \t\t\"no_-b\")\n \t\t\tuse_new_branch=0 &&\n \t\t\tcontext=\"$context, no --branch\"\n@@ -827,6 +832,10 @@ test_dwim_orphan () {\n \t\t\tuse_new_branch=1 &&\n \t\t\tcontext=\"$context, --branch\"\n \t\t\t;;\n+\t\t\"detach\")\n+\t\t\tuse_detach=1 &&\n+\t\t\tcontext=\"$context, --detach\"\n+\t\t\t;;\n \n \t\t# Whether to check that all output is suppressed (except errors)\n \t\t# or that the output is as expected\n@@ -887,6 +896,9 @@ test_dwim_orphan () {\n \tif [ $use_new_branch -eq 1 ]\n \tthen\n \t\targs=\"$args -b foo\"\n+\telif [ $use_detach -eq 1 ]\n+\tthen\n+\t\targs=\"$args --detach\"\n \telse\n \t\tcontext=\"DWIM (no --branch), $context\"\n \tfi &&\n@@ -1023,6 +1035,10 @@ do\n \t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref no_guess_remote\n \t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref guess_remote\n \t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote remote_ref guess_remote\n+\n+\t\ttest_dwim_orphan 'warn_bad_head' $dwim_test_args no_-b local_ref bad_head\n+\t\ttest_dwim_orphan 'warn_bad_head' $dwim_test_args -b local_ref bad_head\n+\t\ttest_dwim_orphan 'warn_bad_head' $dwim_test_args detach local_ref bad_head\n \tdone\n \n \ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b no_checkout\n-- \n2.39.3\n\n\n"},{"id":"480349","messageId":"20230809064724.13032-1-tenglong.tl@alibaba-inc.com","threadId":"59607","inReplyTo":"20230517214711.12467-8-jacobabel@nullpo.dev","subject":"RESEND [PATCH v10 7/8] worktree add: extend DWIM to infer --orphan","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2023-08-09T06:47:22Z","receivedAt":"2023-08-09T06:47:43Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"\n\n> +static int can_use_remote_refs(const struct add_opts *opts)\n> +{\n> +\tif (!guess_remote) {\n> +\t\tif (!opts->quiet)\n> +\t\t\tfprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);\n> +\t\treturn 0;\n> +\t} else if (for_each_remote_ref(first_valid_ref, NULL)) {\n> +\t\treturn 1;\n> +\t} else if (!opts->force && remote_get(NULL)) {\n> +\t\tdie(_(\"No local or remote refs exist despite at least one remote\\n\"\n> +\t\t      \"present, stopping; use 'add -f' to overide or fetch a remote first\"));\n> +\t} else if (!opts->quiet) {\n> +\t\tfprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);\n> +\t}\n> +\treturn 0;\n> +}\n\ns/overide/override?\n\n builtin/worktree.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 4cd01842..10db70b7 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -696,7 +696,7 @@ static int can_use_remote_refs(const struct add_opts *opts)\n                return 1;\n        } else if (!opts->force && remote_get(NULL)) {\n                die(_(\"No local or remote refs exist despite at least one remote\\n\"\n-                     \"present, stopping; use 'add -f' to overide or fetch a remote first\"));\n+                     \"present, stopping; use 'add -f' to override or fetch a remote first\"));\n        }\n        return 0;\n }\n\n---\n\nI found it when working at l10n round of 2.42.0, maybe it's not late to fix\nthis typo：\n\nThanks.\n"},{"id":"480561","messageId":"6gxybfz4vu5onk6m7aqg2xuvlyipyp4dywujgjverfbeliw7pc@u4f5czxfl26s","threadId":"59607","inReplyTo":"20230809064724.13032-1-tenglong.tl@alibaba-inc.com","subject":"Re: RESEND [PATCH v10 7/8] worktree add: extend DWIM to infer --orphan","fromName":"Jacob Abel","fromEmail":"jacobabel@nullpo.dev","sentAt":"2023-08-11T17:43:37Z","receivedAt":"2023-08-11T17:43:46Z","isPatch":true,"sender":{"key":"jacobabel@nullpo.dev","avatar":"https://avatars.githubusercontent.com/u/9424043?v=4"},"body":"On 23/08/09 02:47PM, Teng Long wrote:\n> \n> > [...]\n> \n> s/overide/override?\n> \n>  builtin/worktree.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n> \n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> index 4cd01842..10db70b7 100644\n> --- a/builtin/worktree.c\n> +++ b/builtin/worktree.c\n> @@ -696,7 +696,7 @@ static int can_use_remote_refs(const struct add_opts *opts)\n>                 return 1;\n>         } else if (!opts->force && remote_get(NULL)) {\n>                 die(_(\"No local or remote refs exist despite at least one remote\\n\"\n> -                     \"present, stopping; use 'add -f' to overide or fetch a remote first\"));\n> +                     \"present, stopping; use 'add -f' to override or fetch a remote first\"));\n>         }\n>         return 0;\n>  }\n> \n> ---\n> \n> I found it when working at l10n round of 2.42.0, maybe it's not late to fix\n> this typo：\n> \n> Thanks.\n\nAh yep. That's on me. I'll put together a patch of the above and send it\nout, cc-ing you before the end of the day today.\n\n"}]}