worktree add: emit warn when there is a bad HEAD

Add a warning to `worktree add` when the command tries to reference HEAD, there exist valid local branches, and the HEAD points to a non-existent reference. Current Behavior: % git -C foo worktree list /path/to/repo/foo dadc8e6dac [main] /path/to/repo/foo_wt 0000000000 [badref] % git -C foo worktree add ../wt1 Preparing worktree (new branch 'wt1') HEAD is now at dadc8e6dac dummy commit % git -C foo_wt worktree add ../wt2 hint: If you meant to create a worktree containing a new orphan branch [...] hint: Disable this message with "git config advice.worktreeAddOrphan false" fatal: invalid reference: HEAD % New Behavior: % git -C foo worktree list /path/to/repo/foo dadc8e6dac [main] /path/to/repo/foo_wt 0000000000 [badref] % git -C foo worktree add ../wt1 Preparing worktree (new branch 'wt1') HEAD is now at dadc8e6dac dummy commit % git -C foo_wt worktree add ../wt2 warning: HEAD points to an invalid (or orphaned) reference. HEAD path: '/path/to/repo/foo/.git/worktrees/foo_wt/HEAD' HEAD contents: 'ref: refs/heads/badref' hint: If you meant to create a worktree containing a new orphan branch [...] 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:49 UTC 926c40d04b5a77e00d3b22a5d2696c22b81569cc
2 files changed +46 -6
builtin/worktree.c
+29 -5
@@ -638,6 +638,9 @@ static int first_valid_ref(const char *refname,
638 *
639 * - Checks whether any valid local branches exist.
640 *
641 + * - Emits a warning if there exist any valid branches but HEAD does not point
642 + * to a valid reference.
643 + *
644 * Returns 1 if any of the previous checks are true, otherwise returns 0.
645 */
646 static int can_use_local_refs(const struct add_opts *opts)
@@ -645,6 +648,23 @@ static int can_use_local_refs(const struct add_opts *opts)
648 if (head_ref(first_valid_ref, NULL)) {
649 return 1;
650 } else if (for_each_branch_ref(first_valid_ref, NULL)) {
651 + if (!opts->quiet) {
652 + struct strbuf path = STRBUF_INIT;
653 + struct strbuf contents = STRBUF_INIT;
654 +
655 + strbuf_add_real_path(&path, get_worktree_git_dir(NULL));
656 + strbuf_addstr(&path, "/HEAD");
657 + strbuf_read_file(&contents, path.buf, 64);
658 + strbuf_stripspace(&contents, 0);
659 + strbuf_strip_suffix(&contents, "\n");
660 +
661 + warning(_("HEAD points to an invalid (or orphaned) reference.\n"
662 + "HEAD path: '%s'\n"
663 + "HEAD contents: '%s'"),
664 + path.buf, contents.buf);
665 + strbuf_release(&path);
666 + strbuf_release(&contents);
667 + }
668 return 1;
669 }
670 return 0;
@@ -666,16 +686,12 @@ static int can_use_local_refs(const struct add_opts *opts)
686 static int can_use_remote_refs(const struct add_opts *opts)
687 {
688 if (!guess_remote) {
669 - if (!opts->quiet)
670 - fprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);
689 return 0;
690 } else if (for_each_remote_ref(first_valid_ref, NULL)) {
691 return 1;
692 } else if (!opts->force && remote_get(NULL)) {
693 die(_("No local or remote refs exist despite at least one remote\n"
694 "present, stopping; use 'add -f' to overide or fetch a remote first"));
677 - } else if (!opts->quiet) {
678 - fprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);
695 }
696 return 0;
697 }
@@ -828,8 +844,12 @@ static int add(int ac, const char **av, const char *prefix)
844 int n;
845 const char *s = worktree_basename(path, &n);
846 new_branch = xstrndup(s, n);
831 - } else if (opts.orphan || opts.detach) {
847 + } else if (opts.orphan) {
848 // No-op
849 + } else if (opts.detach) {
850 + // Check HEAD
851 + if (!strcmp(branch, "HEAD"))
852 + can_use_local_refs(&opts);
853 } else if (ac < 2 && new_branch) {
854 // DWIM: Infer --orphan when repo has no refs.
855 opts.orphan = dwim_orphan(&opts, !!opt_track, 0);
@@ -854,6 +874,10 @@ static int add(int ac, const char **av, const char *prefix)
874 branch = remote;
875 }
876 }
877 +
878 + if (!strcmp(branch, "HEAD"))
879 + can_use_local_refs(&opts);
880 +
881 }
882
883 if (!opts.orphan && !lookup_commit_reference_by_name(branch)) {
t/t2400-worktree-add.sh
+17 -1
@@ -722,6 +722,7 @@ test_dwim_orphan () {
722 local use_quiet=0 &&
723 local remote=0 &&
724 local remote_ref=0 &&
725 + local use_detach=0 &&
726 local use_new_branch=0 &&
727
728 local outcome="$1" &&
@@ -747,6 +748,10 @@ test_dwim_orphan () {
748 success=0 &&
749 outcome_text='"add" error inferred "--orphan" gives illegal opts combo'
750 ;;
751 + "warn_bad_head")
752 + success=0 &&
753 + outcome_text='"add" error, warn on bad HEAD, hint use orphan'
754 + ;;
755 *)
756 echo "test_dwim_orphan(): invalid outcome: '$outcome'" >&2 &&
757 return 1
@@ -818,7 +823,7 @@ test_dwim_orphan () {
823 context="$context, invalid (or orphan) HEAD"
824 ;;
825
821 - # Whether the code path is tested with the base add command or -b
826 + # Whether the code path is tested with the base add command, -b, or --detach
827 "no_-b")
828 use_new_branch=0 &&
829 context="$context, no --branch"
@@ -827,6 +832,10 @@ test_dwim_orphan () {
832 use_new_branch=1 &&
833 context="$context, --branch"
834 ;;
835 + "detach")
836 + use_detach=1 &&
837 + context="$context, --detach"
838 + ;;
839
840 # Whether to check that all output is suppressed (except errors)
841 # or that the output is as expected
@@ -887,6 +896,9 @@ test_dwim_orphan () {
896 if [ $use_new_branch -eq 1 ]
897 then
898 args="$args -b foo"
899 + elif [ $use_detach -eq 1 ]
900 + then
901 + args="$args --detach"
902 else
903 context="DWIM (no --branch), $context"
904 fi &&
@@ -1023,6 +1035,10 @@ do
1035 test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref no_guess_remote
1036 test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref guess_remote
1037 test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote remote_ref guess_remote
1038 +
1039 + test_dwim_orphan 'warn_bad_head' $dwim_test_args no_-b local_ref bad_head
1040 + test_dwim_orphan 'warn_bad_head' $dwim_test_args -b local_ref bad_head
1041 + test_dwim_orphan 'warn_bad_head' $dwim_test_args detach local_ref bad_head
1042 done
1043
1044 test_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b no_checkout