Subject: [PATCH v9 0/8] worktree: Support `--orphan` when creating new worktrees

From: Jacob Abel <[email-masked]>
Date: Mon, 17 Apr 2023 09:33:22 +0000

This patchset introduces the ability to create new worktrees from orphan/unborn
branches and introduces DWIM behavior to create worktrees from an orphan branch
when no valid refs exists locally in the repository (as is typical in newly
initialized repositories) or on a remote (when `--guess-remote` is used).

This addresses the issue of `git worktree add` failing when attempting to create
a worktree from a newly initialized repository (which can be seen in this SO
question [1]).

Note: The last 2 patches in this patchset were initially intended to be part of
a "part 2" patchset but given that they are so closely tied to this patchset I
have been developing the two together. I'm fine with either splitting the
patchset and resending it or leaving them rolled together, whichever is
easier/more convenient for everyone.

This patchset has eight parts:
  * adding `-B` to the usage docs (noticed during dev and it seemed too small
    to justify a separate submission)
  * updating test cases to still show the output of git commands when the test
    script is run with `-x` to aid in debugging.
  * adding a helper fn to simplify testing for mutual exclusion of options
    in `t/t2400-worktree-add.sh`
  * adding additional test cases to verify both that behavior doesn't change
    when using `--quiet` and that the extraneous output is properly suppressed.
  * adding the ability to create a worktree from an unborn/orphan branch
    to `git-worktree-add`
  * adding an advise for using --orphan when `git worktree add` fails due to
    a bad ref.
  * adding functionality to DWIM when there are no existing branches and the
    user likely intends to create an orphan branch.
  * updating worktree add to emit a warning (containing debug information
    about the current HEAD) when trying to use a HEAD that points to a
    non-existant (or unborn) reference and there exist other valid branches.

Changes from v8 (patches 1/8 - 6/8):
  * Rebase to a newer commit on main (from c03801e19c to 9857273be0) to bypass
    build failures caused by curl deprecation compile warnings & to eliminate
    merge conflicts. The below range diff is made against a v8 rebased against
    the same point
  * Touched up commit messages.
  * Shortened title for patch 6/8 to fit in 50 character limit.
  * Updated tests to print stderr on test exit for tests which capture stderr
    to improve debugging of individual test failures.
  * Added tests to verify `--quiet` is actually quiet and doesn't otherwise
    change command behavior.
  * Changed `--orphan` from an option to a flag that can be used either on its
    own or with `-b`/`-B` as requested in [2].
  * Pulled a conditional and `die()` out of `print_preparing_worktree_line()`
    so that it'd always be checked regardless of `--quiet`. This change was
    made because a bug was introduced in an early revision of v9 that caused
    behavior to differ depending on whether `--quiet` was supplied to the
    command. To limit the changes made, the original `die()` was left as a
    `BUG()`.
  * Moved `!lookup_commit_reference_by_name(branch)` check and `--orphan` hint
    to the line before the call to `print_preparing_worktree_line()` to combine
    with the conditional from the above/previous change.
  * Wrapped `--orphan` advice/hint in conditional to suppress display when
    using `--quiet`.
  * Updated `--orphan` advice/hint to match the `add -b branch dir/` vs
    `add dir/` syntax initially supplied by the user.
  * Updated `--orphan` hint tests to check presence on bad HEAD instead of
    empty repo.

Changes from v8 (patches 7/8 & 8/8):
  * Extended DWIM to infer `--orphan` when no other branches exist in the repo
    (or remotely when using `--guess-remote` while not using `-b`) [3][4].
  * Added checks to warn/die when inferring `--orphan` causes the set of
    supplied options & flags to produce an illegal combination.
  * Added extensive tests to verify new DWIM behavior.
  * Added a failure/warning when the user likely forgot to fetch from an
    upstream repo (i.e. when there is a remote, guess_remote is enabled, and
    there aren't any local or remote branches in the repo). Can be bypassed
    with `--force`/`-f` [3].
  * Updated documentation for worktree-add to mention `--orphan` when
    discussing situations where DWIM behavior does or does not occur.
  * Added a warning when the current namespace's HEAD points to an invalid
    or non-existant reference and the user is trying to create a new worktree
    from that HEAD.

1. https://stackoverflow.com/a/68717229/15064705/
2. https://lore.kernel.org/git/[email-masked]/
3. https://lore.kernel.org/git/20230119222003.qcdrhcsvjlyab6af@phi/
4. https://lore.kernel.org/git/20230118224020.vrytmeyt3vbanoh2@phi/

Jacob Abel (8):
  worktree add: include -B in usage docs
  t2400: print captured git output when finished
  t2400: refactor "worktree add" opt exclusion tests
  t2400: add tests to verify --quiet
  worktree add: add --orphan flag
  worktree add: introduce "try --orphan" hint
  worktree add: extend DWIM to infer --orphan
  worktree add: emit warn when there is a bad HEAD

 Documentation/config/advice.txt |   4 +
 Documentation/git-worktree.txt  |  16 +-
 advice.c                        |   1 +
 advice.h                        |   1 +
 builtin/worktree.c              | 226 +++++++++++++-
 t/t2400-worktree-add.sh         | 520 +++++++++++++++++++++++++++++++-
 6 files changed, 747 insertions(+), 21 deletions(-)

Range-diff against v8:
1:  cbda416378 = 1:  91153fdb4c worktree add: include -B in usage docs
-:  ---------- > 2:  8cfbc89dd5 t2400: print captured git output when finished
2:  5f83015779 ! 3:  ab03d92c3a worktree add: refactor opt exclusion tests
    @@ Metadata
     Author: Jacob Abel <[email-masked]>

      ## Commit message ##
    -    worktree add: refactor opt exclusion tests
    +    t2400: refactor "worktree add" opt exclusion tests

         Pull duplicate test code into a function so that additional opt
         combinations can be tested succinctly.
    @@ t/t2400-worktree-add.sh: test_expect_success '"add" no auto-vivify with --detach
     +test_wt_add_excl () {
     +	local opts="$*" &&
     +	test_expect_success "'worktree add' with '$opts' has mutually exclusive options" '
    ++		test_when_finished cat actual >&2 &&
     +		test_must_fail git worktree add $opts 2>actual &&
     +		grep -E "fatal:( options)? .* cannot be used together" actual
     +	'
-:  ---------- > 4:  d9a3468c93 t2400: add tests to verify --quiet
3:  6ac19eeeae ! 5:  8ef9587deb worktree add: add --orphan flag
    @@ Metadata
      ## Commit message ##
         worktree add: add --orphan flag

    -    Adds support for creating an orphan branch when adding a new worktree.
    -    This functionality is equivalent to git switch's --orphan flag.
    -
    -    The original reason this feature was implemented was to allow a user
    -    to initialise a new repository using solely the worktree oriented
    -    workflow.
    +    Add support for creating an orphan branch when adding a new worktree.
    +    The functionality of this flag is equivalent to git switch's --orphan
    +    option.

         Current Behavior:
         % git -C foo.git --no-pager branch -l
    @@ Commit message
         % git -C bar.git worktree add main/
         Preparing worktree (new branch 'main')
         fatal: invalid reference: HEAD
    -    % git -C bar.git worktree add --orphan main main/
    +    % git -C bar.git worktree add --orphan -b main/
         Preparing worktree (new branch 'main')
    +    % git -C bar.git worktree add --orphan -b newbranch worktreedir/
    +    Preparing worktree (new branch 'newbranch')
         %

         Signed-off-by: Ævar Arnfjörð Bjarmason <[email-masked]>
    @@ Commit message

      ## Documentation/git-worktree.txt ##
     @@ Documentation/git-worktree.txt: SYNOPSIS
    + --------
      [verse]
      'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]]
    - 		   [(-b | -B) <new-branch>] <path> [<commit-ish>]
    -+'git worktree add' [-f] [--lock [--reason <string>]]
    -+		   --orphan <new-branch> <path>
    +-		   [(-b | -B) <new-branch>] <path> [<commit-ish>]
    ++		   [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]
      'git worktree list' [-v | --porcelain [-z]]
      'git worktree lock' [--reason <string>] <worktree>
      'git worktree move' <worktree> <new-path>
    -@@ Documentation/git-worktree.txt: exist, a new branch based on `HEAD` is automatically created as if
    - `-b <branch>` was given.  If `<branch>` does exist, it will be checked out
    - in the new worktree, if it's not checked out anywhere else, otherwise the
    - command will refuse to create the worktree (unless `--force` is used).
    -++
    -+------------
    -+$ git worktree add --orphan <branch> <path>
    -+------------
    -++
    -+Create a worktree containing no files, with an empty index, and associated
    -+with a new orphan branch named `<branch>`. The first commit made on this new
    -+branch will have no parents and will be the root of a new history disconnected
    -+from any other branches.
    -
    - list::
    -
     @@ Documentation/git-worktree.txt: This can also be set up as the default behaviour by using the
      	With `prune`, do not remove anything; just report what it would
      	remove.

    -+--orphan <new-branch>::
    ++--orphan::
     +	With `add`, make the new worktree and index empty, associating
    -+	the worktree with a new orphan branch named `<new-branch>`.
    ++	the worktree with a new orphan/unborn branch named `<new-branch>`.
     +
      --porcelain::
      	With `list`, output in an easy-to-parse format for scripts.
    @@ builtin/worktree.c
      #define BUILTIN_WORKTREE_ADD_USAGE \
      	N_("git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]\n" \
     -	   "                 [(-b | -B) <new-branch>] <path> [<commit-ish>]")
    -+	   "                 [(-b | -B) <new-branch>] <path> [<commit-ish>]"), \
    -+	N_("git worktree add [-f] [--lock [--reason <string>]]\n" \
    -+	   "                 --orphan <new-branch> <path>")
    ++	   "                 [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]")
     +
      #define BUILTIN_WORKTREE_LIST_USAGE \
      	N_("git worktree list [-v | --porcelain [-z]]")
    @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam
      		struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;

      		strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
    -@@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)
    - 	char *path;
    - 	const char *branch;
    - 	const char *new_branch = NULL;
    -+	const char *orphan_branch = NULL;
    - 	const char *opt_track = NULL;
    - 	const char *lock_reason = NULL;
    - 	int keep_locked = 0;
    +@@ builtin/worktree.c: static void print_preparing_worktree_line(int detach,
    + 		else {
    + 			struct commit *commit = lookup_commit_reference_by_name(branch);
    + 			if (!commit)
    +-				die(_("invalid reference: %s"), branch);
    ++				BUG(_("unreachable: invalid reference: %s"), branch);
    + 			fprintf_ln(stderr, _("Preparing worktree (detached HEAD %s)"),
    + 				  repo_find_unique_abbrev(the_repository, &commit->object.oid, DEFAULT_ABBREV));
    + 		}
     @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)
      			   N_("create a new branch")),
      		OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
      			   N_("create or reset a branch")),
    -+		OPT_STRING(0, "orphan", &orphan_branch, N_("branch"),
    -+			   N_("new unparented branch")),
    ++		OPT_BOOL(0, "orphan", &opts.orphan, N_("create unborn/orphaned branch")),
      		OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")),
      		OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")),
      		OPT_BOOL(0, "lock", &keep_locked, N_("keep the new working tree locked")),
     @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)
    - 	memset(&opts, 0, sizeof(opts));
    - 	opts.checkout = 1;
      	ac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0);
    -+	opts.orphan = !!orphan_branch;
      	if (!!opts.detach + !!new_branch + !!new_branch_force > 1)
      		die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach");
    -+	if (!!opts.detach + !!opts.orphan + !!new_branch + !!new_branch_force > 1)
    -+		die(_("options '%s', '%s', '%s', and '%s' cannot be used together"),
    -+		    "-b", "-B", "--orphan", "--detach");
    ++	if (opts.detach && opts.orphan)
    ++		die(_("options '%s', and '%s' cannot be used together"),
    ++		    "--orphan", "--detach");
     +	if (opts.orphan && opt_track)
     +		die(_("'%s' and '%s' cannot be used together"), "--orphan", "--track");
     +	if (opts.orphan && !opts.checkout)
    @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)
      	}

     -	if (ac < 2 && !new_branch && !opts.detach) {
    -+	if (opts.orphan) {
    -+		new_branch = orphan_branch;
    -+	} else if (ac < 2 && !new_branch && !opts.detach) {
    ++	if (opts.orphan && !new_branch) {
    ++		int n;
    ++		const char *s = worktree_basename(path, &n);
    ++		new_branch = xstrndup(s, n);
    ++	} else if (new_branch || opts.detach || opts.orphan) {
    ++		// No-op
    ++	} else if (ac < 2) {
      		const char *s = dwim_branch(path, &new_branch);
      		if (s)
      			branch = s;
    +-	}
    +-
    +-	if (ac == 2 && !new_branch && !opts.detach) {
    ++	} else if (ac == 2) {
    + 		struct object_id oid;
    + 		struct commit *commit;
    + 		const char *remote;
     @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)
    + 			}
    + 		}
    + 	}
    ++
    ++	if (!opts.orphan && !lookup_commit_reference_by_name(branch)) {
    ++		die(_("invalid reference: %s"), branch);
    ++	}
    ++
      	if (!opts.quiet)
      		print_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force);

     -	if (new_branch) {
     +	if (opts.orphan) {
     +		branch = new_branch;
    -+	} else if (!lookup_commit_reference_by_name(branch)) {
    -+		die(_("invalid reference: %s"), branch);
     +	} else if (new_branch) {
      		struct child_process cp = CHILD_PROCESS_INIT;
      		cp.git_cmd = 1;
    @@ t/t2400-worktree-add.sh: test_wt_add_excl () {
      test_wt_add_excl -b poodle -B poodle bamboo main
      test_wt_add_excl -b poodle --detach bamboo main
      test_wt_add_excl -B poodle --detach bamboo main
    -+test_wt_add_excl -B poodle --orphan poodle bamboo
    -+test_wt_add_excl -b poodle --orphan poodle bamboo
    -+test_wt_add_excl --orphan poodle --detach bamboo
    -+test_wt_add_excl --orphan poodle --no-checkout bamboo
    -+test_wt_add_excl --orphan poodle bamboo main
    ++test_wt_add_excl --orphan --detach bamboo
    ++test_wt_add_excl --orphan --no-checkout bamboo
    ++test_wt_add_excl --orphan bamboo main
    ++test_wt_add_excl --orphan -b bamboo wtdir/ main

      test_expect_success '"add -B" fails if the branch is checked out' '
      	git rev-parse newmain >before &&
    -@@ t/t2400-worktree-add.sh: test_expect_success 'add --quiet' '
    +@@ t/t2400-worktree-add.sh: test_expect_success 'add --quiet -b' '
      	test_must_be_empty actual
      '

     +test_expect_success '"add --orphan"' '
     +	test_when_finished "git worktree remove -f -f orphandir" &&
    -+	git worktree add --orphan neworphan orphandir &&
    ++	git worktree add --orphan -b neworphan orphandir &&
    ++	echo refs/heads/neworphan >expected &&
    ++	git -C orphandir symbolic-ref HEAD >actual &&
    ++	test_cmp expected actual
    ++'
    ++
    ++test_expect_success '"add --orphan (no -b)"' '
    ++	test_when_finished "git worktree remove -f -f neworphan" &&
    ++	git worktree add --orphan neworphan &&
    ++	echo refs/heads/neworphan >expected &&
    ++	git -C neworphan symbolic-ref HEAD >actual &&
    ++	test_cmp expected actual
    ++'
    ++
    ++test_expect_success '"add --orphan --quiet"' '
    ++	test_when_finished "git worktree remove -f -f orphandir" &&
    ++	test_when_finished cat log.actual >&2 &&
    ++	git worktree add --quiet --orphan -b neworphan orphandir 2>log.actual &&
    ++	test_must_be_empty log.actual &&
     +	echo refs/heads/neworphan >expected &&
     +	git -C orphandir symbolic-ref HEAD >actual &&
     +	test_cmp expected actual
    @@ t/t2400-worktree-add.sh: test_expect_success 'add --quiet' '
     +
     +test_expect_success '"add --orphan" fails if the branch already exists' '
     +	test_when_finished "git branch -D existingbranch" &&
    -+	test_when_finished "git worktree remove -f -f orphandir" &&
     +	git worktree add -b existingbranch orphandir main &&
    -+	test_must_fail git worktree add --orphan existingbranch orphandir2 &&
    -+	test_path_is_missing orphandir2
    ++	git worktree remove orphandir &&
    ++	test_must_fail git worktree add --orphan -b existingbranch orphandir
     +'
     +
     +test_expect_success '"add --orphan" with empty repository' '
     +	test_when_finished "rm -rf empty_repo" &&
     +	echo refs/heads/newbranch >expected &&
     +	GIT_DIR="empty_repo" git init --bare &&
    -+	git -C empty_repo  worktree add --orphan newbranch worktreedir &&
    ++	git -C empty_repo  worktree add --orphan -b newbranch worktreedir &&
     +	git -C empty_repo/worktreedir symbolic-ref HEAD >actual &&
     +	test_cmp expected actual
     +'
     +
     +test_expect_success '"add" worktree with orphan branch and lock' '
    -+	git worktree add --lock --orphan orphanbr orphan-with-lock &&
    ++	git worktree add --lock --orphan -b orphanbr orphan-with-lock &&
     +	test_when_finished "git worktree unlock orphan-with-lock || :" &&
     +	test -f .git/worktrees/orphan-with-lock/locked
     +'
4:  3d76a5b6b8 ! 6:  d2800266f9 worktree add: add hint to direct users towards --orphan
    @@ Metadata
     Author: Jacob Abel <[email-masked]>

      ## Commit message ##
    -    worktree add: add hint to direct users towards --orphan
    +    worktree add: introduce "try --orphan" hint

    -    Adds a new advice/hint in `git worktree add` for when the user
    +    Add a new advice/hint in `git worktree add` for when the user
         tries to create a new worktree from a reference that doesn't exist.

         Current Behavior:

    -    % git init --bare foo.git
    -    Initialized empty Git repository in /path/to/foo.git/
    -    % git -C foo.git worktree add main/
    -    Preparing worktree (new branch 'main')
    +    % git init foo
    +    Initialized empty Git repository in /path/to/foo/
    +    % touch file
    +    % git -C foo commit -q -a -m "test commit"
    +    % git -C foo switch --orphan norefbranch
    +    % git -C foo worktree add newbranch/
    +    Preparing worktree (new branch 'newbranch')
         fatal: invalid reference: HEAD
         %

         New Behavior:

    -    % git init --bare foo.git
    -    Initialized empty Git repository in /path/to/foo.git/
    -    % git -C foo.git worktree add main/
    -    Preparing worktree (new branch 'main')
    +    % git init --bare foo
    +    Initialized empty Git repository in /path/to/foo/
    +    % touch file
    +    % git -C foo commit -q -a -m "test commit"
    +    % git -C foo switch --orphan norefbranch
    +    % git -C foo worktree add newbranch/
    +    Preparing worktree (new branch 'newbranch')
         hint: If you meant to create a worktree containing a new orphan branch
         hint: (branch with no commits) for this repository, you can do so
         hint: using the --orphan option:
         hint:
    -    hint:   git worktree add --orphan main ./main
    +    hint:   git worktree add --orphan newbranch/
    +    hint:
    +    hint: Disable this message with "git config advice.worktreeAddOrphan false"
    +    fatal: invalid reference: HEAD
    +    % git -C foo worktree add -b newbranch2 new_wt/
    +    Preparing worktree (new branch 'newbranch')
    +    hint: If you meant to create a worktree containing a new orphan branch
    +    hint: (branch with no commits) for this repository, you can do so
    +    hint: using the --orphan option:
    +    hint:
    +    hint:   git worktree add --orphan -b newbranch2 new_wt/
         hint:
         hint: Disable this message with "git config advice.worktreeAddOrphan false"
         fatal: invalid reference: HEAD
    @@ advice.h: struct string_list;
      int git_default_advice_config(const char *var, const char *value);

      ## builtin/worktree.c ##
    +@@
    + #define BUILTIN_WORKTREE_UNLOCK_USAGE \
    + 	N_("git worktree unlock <worktree>")
    +
    ++#define WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT \
    ++	_("If you meant to create a worktree containing a new orphan branch\n" \
    ++	"(branch with no commits) for this repository, you can do so\n" \
    ++	"using the --orphan flag:\n" \
    ++	"\n" \
    ++	"	git worktree add --orphan -b %s %s\n")
    ++
    ++#define WORKTREE_ADD_ORPHAN_NO_DASH_B_HINT_TEXT \
    ++	_("If you meant to create a worktree containing a new orphan branch\n" \
    ++	"(branch with no commits) for this repository, you can do so\n" \
    ++	"using the --orphan flag:\n" \
    ++	"\n" \
    ++	"	git worktree add --orphan %s\n")
    ++
    + static const char * const git_worktree_usage[] = {
    + 	BUILTIN_WORKTREE_ADD_USAGE,
    + 	BUILTIN_WORKTREE_LIST_USAGE,
    +@@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)
    + 	const char *opt_track = NULL;
    + 	const char *lock_reason = NULL;
    + 	int keep_locked = 0;
    ++	int used_new_branch_options;
    + 	struct option options[] = {
    + 		OPT__FORCE(&opts.force,
    + 			   N_("checkout <branch> even if already checked out in other worktree"),
    +@@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)
    +
    + 	path = prefix_filename(prefix, av[0]);
    + 	branch = ac < 2 ? "HEAD" : av[1];
    ++	used_new_branch_options = new_branch || new_branch_force;
    +
    + 	if (!strcmp(branch, "-"))
    + 		branch = "@{-1}";
     @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix)
    - 	if (opts.orphan) {
    - 		branch = new_branch;
    - 	} else if (!lookup_commit_reference_by_name(branch)) {
    -+		advise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN,
    -+			_("If you meant to create a worktree containing a new orphan branch\n"
    -+			"(branch with no commits) for this repository, you can do so\n"
    -+			"using the --orphan option:\n"
    -+			"\n"
    -+			"	git worktree add --orphan %s %s\n"), new_branch, path);
    + 	}
    +
    + 	if (!opts.orphan && !lookup_commit_reference_by_name(branch)) {
    ++		int attempt_hint = !opts.quiet && (ac < 2);
    ++		if (attempt_hint && used_new_branch_options) {
    ++			advise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN,
    ++				WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT,
    ++				new_branch, path);
    ++		} else if (attempt_hint) {
    ++			advise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN,
    ++				WORKTREE_ADD_ORPHAN_NO_DASH_B_HINT_TEXT, path);
    ++		}
      		die(_("invalid reference: %s"), branch);
    - 	} else if (new_branch) {
    - 		struct child_process cp = CHILD_PROCESS_INIT;
    + 	}
    +

      ## t/t2400-worktree-add.sh ##
     @@ t/t2400-worktree-add.sh: test_expect_success '"add" worktree with orphan branch, lock, and reason' '
    @@ t/t2400-worktree-add.sh: test_expect_success '"add" worktree with orphan branch,
      '

     +# Note: Quoted arguments containing spaces are not supported.
    -+test_wt_add_empty_repo_orphan_hint () {
    ++test_wt_add_orphan_hint () {
     +	local context="$1" &&
    -+	shift &&
    ++	local use_branch=$2 &&
    ++	shift 2 &&
     +	local opts="$*" &&
    -+	test_expect_success "'worktree add' show orphan hint in empty repo w/ $context" '
    -+		test_when_finished "rm -rf empty_repo" &&
    -+		GIT_DIR="empty_repo" git init --bare &&
    -+		test_must_fail git -C empty_repo worktree add $opts foobar/ 2>actual &&
    ++	test_expect_success "'worktree add' show orphan hint in bad/orphan HEAD w/ $context" '
    ++		test_when_finished "rm -rf repo" &&
    ++		git init repo &&
    ++		(cd repo && test_commit commit) &&
    ++		git -C repo switch --orphan noref &&
    ++		test_when_finished cat actual >&2 &&
    ++		test_must_fail git -C repo worktree add $opts foobar/ 2>actual &&
     +		! grep "error: unknown switch" actual &&
    -+		grep "hint: If you meant to create a worktree containing a new orphan branch" actual
    ++		grep "hint: If you meant to create a worktree containing a new orphan branch" actual &&
    ++		if [ $use_branch -eq 1 ]
    ++		then
    ++			grep -E "^hint:\s+git worktree add --orphan -b \S+ \S+\s*$" actual
    ++		else
    ++			grep -E "^hint:\s+git worktree add --orphan \S+\s*$" actual
    ++		fi
    ++
     +	'
     +}
     +
    -+test_wt_add_empty_repo_orphan_hint 'DWIM'
    -+test_wt_add_empty_repo_orphan_hint '-b' -b foobar_branch
    -+test_wt_add_empty_repo_orphan_hint '-B' -B foobar_branch
    ++test_wt_add_orphan_hint 'no opts' 0
    ++test_wt_add_orphan_hint '-b' 1 -b foobar_branch
    ++test_wt_add_orphan_hint '-B' 1 -B foobar_branch
    ++
    ++test_expect_success "'worktree add' doesn't show orphan hint in bad/orphan HEAD w/ --quiet" '
    ++	test_when_finished "rm -rf repo" &&
    ++	git init repo &&
    ++	(cd repo && test_commit commit) &&
    ++	test_when_finished cat actual >&2 &&
    ++	test_must_fail git -C repo worktree add --quiet foobar_branch foobar/ 2>actual &&
    ++	! grep "error: unknown switch" actual &&
    ++	! grep "hint: If you meant to create a worktree containing a new orphan branch" actual
    ++'
     +
      test_expect_success 'local clone from linked checkout' '
      	git clone --local here here-clone &&
-:  ---------- > 7:  e5e139766c worktree add: extend DWIM to infer --orphan
-:  ---------- > 8:  296226ffd5 worktree add: emit warn when there is a bad HEAD
--
2.39.2