worktree add: introduce "try --orphan" hint

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 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 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 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 % Signed-off-by: Jacob Abel <jacobabel@nullpo.dev> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jacob Abel committed May 17, 2023 at 21:48 UTC 35f0383ca61db8f0b6ee1e9002150bbb13649993
5 files changed +69
Documentation/config/advice.txt
+4
@@ -138,4 +138,8 @@ advice.*::
138 checkout.
139 diverging::
140 Advice shown when a fast-forward is not possible.
141 + worktreeAddOrphan::
142 + Advice shown when a user tries to create a worktree from an
143 + invalid reference, to instruct how to create a new orphan
144 + branch instead.
145 --
advice.c
+1
@@ -78,6 +78,7 @@ static struct {
78 [ADVICE_SUBMODULES_NOT_UPDATED] = { "submodulesNotUpdated", 1 },
79 [ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath", 1 },
80 [ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 },
81 + [ADVICE_WORKTREE_ADD_ORPHAN] = { "worktreeAddOrphan", 1 },
82 };
83
84 static const char turn_off_instructions[] =
advice.h
+1
@@ -49,6 +49,7 @@ struct string_list;
49 ADVICE_UPDATE_SPARSE_PATH,
50 ADVICE_WAITING_FOR_EDITOR,
51 ADVICE_SKIPPED_CHERRY_PICKS,
52 + ADVICE_WORKTREE_ADD_ORPHAN,
53 };
54
55 int git_default_advice_config(const char *var, const char *value);
builtin/worktree.c
+26
@@ -1,5 +1,6 @@
1 #include "cache.h"
2 #include "abspath.h"
3 +#include "advice.h"
4 #include "checkout.h"
5 #include "config.h"
6 #include "builtin.h"
@@ -39,6 +40,20 @@
40 #define BUILTIN_WORKTREE_UNLOCK_USAGE \
41 N_("git worktree unlock <worktree>")
42
43 +#define WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT \
44 + _("If you meant to create a worktree containing a new orphan branch\n" \
45 + "(branch with no commits) for this repository, you can do so\n" \
46 + "using the --orphan flag:\n" \
47 + "\n" \
48 + " git worktree add --orphan -b %s %s\n")
49 +
50 +#define WORKTREE_ADD_ORPHAN_NO_DASH_B_HINT_TEXT \
51 + _("If you meant to create a worktree containing a new orphan branch\n" \
52 + "(branch with no commits) for this repository, you can do so\n" \
53 + "using the --orphan flag:\n" \
54 + "\n" \
55 + " git worktree add --orphan %s\n")
56 +
57 static const char * const git_worktree_usage[] = {
58 BUILTIN_WORKTREE_ADD_USAGE,
59 BUILTIN_WORKTREE_LIST_USAGE,
@@ -634,6 +649,7 @@ static int add(int ac, const char **av, const char *prefix)
649 const char *opt_track = NULL;
650 const char *lock_reason = NULL;
651 int keep_locked = 0;
652 + int used_new_branch_options;
653 struct option options[] = {
654 OPT__FORCE(&opts.force,
655 N_("checkout <branch> even if already checked out in other worktree"),
@@ -686,6 +702,7 @@ static int add(int ac, const char **av, const char *prefix)
702
703 path = prefix_filename(prefix, av[0]);
704 branch = ac < 2 ? "HEAD" : av[1];
705 + used_new_branch_options = new_branch || new_branch_force;
706
707 if (!strcmp(branch, "-"))
708 branch = "@{-1}";
@@ -728,6 +745,15 @@ static int add(int ac, const char **av, const char *prefix)
745 }
746
747 if (!opts.orphan && !lookup_commit_reference_by_name(branch)) {
748 + int attempt_hint = !opts.quiet && (ac < 2);
749 + if (attempt_hint && used_new_branch_options) {
750 + advise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN,
751 + WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT,
752 + new_branch, path);
753 + } else if (attempt_hint) {
754 + advise_if_enabled(ADVICE_WORKTREE_ADD_ORPHAN,
755 + WORKTREE_ADD_ORPHAN_NO_DASH_B_HINT_TEXT, path);
756 + }
757 die(_("invalid reference: %s"), branch);
758 }
759
t/t2400-worktree-add.sh
+37
@@ -401,6 +401,43 @@ test_expect_success '"add" worktree with orphan branch, lock, and reason' '
401 test_cmp expect .git/worktrees/orphan-with-lock-reason/locked
402 '
403
404 +# Note: Quoted arguments containing spaces are not supported.
405 +test_wt_add_orphan_hint () {
406 + local context="$1" &&
407 + local use_branch=$2 &&
408 + shift 2 &&
409 + local opts="$*" &&
410 + test_expect_success "'worktree add' show orphan hint in bad/orphan HEAD w/ $context" '
411 + test_when_finished "rm -rf repo" &&
412 + git init repo &&
413 + (cd repo && test_commit commit) &&
414 + git -C repo switch --orphan noref &&
415 + test_must_fail git -C repo worktree add $opts foobar/ 2>actual &&
416 + ! grep "error: unknown switch" actual &&
417 + grep "hint: If you meant to create a worktree containing a new orphan branch" actual &&
418 + if [ $use_branch -eq 1 ]
419 + then
420 + grep -E "^hint:\s+git worktree add --orphan -b \S+ \S+\s*$" actual
421 + else
422 + grep -E "^hint:\s+git worktree add --orphan \S+\s*$" actual
423 + fi
424 +
425 + '
426 +}
427 +
428 +test_wt_add_orphan_hint 'no opts' 0
429 +test_wt_add_orphan_hint '-b' 1 -b foobar_branch
430 +test_wt_add_orphan_hint '-B' 1 -B foobar_branch
431 +
432 +test_expect_success "'worktree add' doesn't show orphan hint in bad/orphan HEAD w/ --quiet" '
433 + test_when_finished "rm -rf repo" &&
434 + git init repo &&
435 + (cd repo && test_commit commit) &&
436 + test_must_fail git -C repo worktree add --quiet foobar_branch foobar/ 2>actual &&
437 + ! grep "error: unknown switch" actual &&
438 + ! grep "hint: If you meant to create a worktree containing a new orphan branch" actual
439 +'
440 +
441 test_expect_success 'local clone from linked checkout' '
442 git clone --local here here-clone &&
443 ( cd here-clone && git fsck )