diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt index c96b5b2e5d..c548a91e67 100644 --- a/Documentation/config/advice.txt +++ b/Documentation/config/advice.txt @@ -138,4 +138,8 @@ advice.*:: checkout. diverging:: Advice shown when a fast-forward is not possible. + worktreeAddOrphan:: + Advice shown when a user tries to create a worktree from an + invalid reference, to instruct how to create a new orphan + branch instead. -- diff --git a/advice.c b/advice.c index d6232439c3..e5a9bb9b44 100644 --- a/advice.c +++ b/advice.c @@ -78,6 +78,7 @@ static struct { [ADVICE_SUBMODULES_NOT_UPDATED] = { "submodulesNotUpdated", 1 }, [ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath", 1 }, [ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 }, + [ADVICE_WORKTREE_ADD_ORPHAN] = { "worktreeAddOrphan", 1 }, }; static const char turn_off_instructions[] = diff --git a/advice.h b/advice.h index 0f584163f5..2affbe1426 100644 --- a/advice.h +++ b/advice.h @@ -49,6 +49,7 @@ struct string_list; ADVICE_UPDATE_SPARSE_PATH, ADVICE_WAITING_FOR_EDITOR, ADVICE_SKIPPED_CHERRY_PICKS, + ADVICE_WORKTREE_ADD_ORPHAN, }; int git_default_advice_config(const char *var, const char *value); diff --git a/builtin/worktree.c b/builtin/worktree.c index 48de7fc3b0..15bdb380c7 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -1,5 +1,6 @@ #include "cache.h" #include "abspath.h" +#include "advice.h" #include "checkout.h" #include "config.h" #include "builtin.h" @@ -39,6 +40,20 @@ #define BUILTIN_WORKTREE_UNLOCK_USAGE \ N_("git worktree unlock ") +#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, @@ -634,6 +649,7 @@ 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 even if already checked out in other worktree"), @@ -686,6 +702,7 @@ 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}"; @@ -728,6 +745,15 @@ static int add(int ac, const char **av, const char *prefix) } 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); } diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index fba90582b6..46eef26179 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -401,6 +401,43 @@ test_expect_success '"add" worktree with orphan branch, lock, and reason' ' test_cmp expect .git/worktrees/orphan-with-lock-reason/locked ' +# Note: Quoted arguments containing spaces are not supported. +test_wt_add_orphan_hint () { + local context="$1" && + local use_branch=$2 && + shift 2 && + local opts="$*" && + 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_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 && + 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_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_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 && ( cd here-clone && git fsck )