Skip to content

Commit 128e549

Browse files
OneDeuxTriSeiGogitster
authored andcommitted
worktree add: extend DWIM to infer --orphan
Extend DWIM to try to infer `--orphan` when in an empty repository. i.e. a repository with an invalid/unborn HEAD, no local branches, and if `--guess-remote` is used then no remote branches. This behavior is equivalent to `git switch -c` or `git checkout -b` in an empty repository. Also warn the user (overriden with `-f`/`--force`) when they likely intend to checkout a remote branch to the worktree but have not yet fetched from the remote. i.e. when using `--guess-remote` and there is a remote but no local or remote refs. Current Behavior: % git --no-pager branch --list --remotes % git remote origin % git workree add ../main 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 % git workree add --guess-remote ../main 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 % git fetch --quiet % git --no-pager branch --list --remotes origin/HEAD -> origin/main origin/main % git workree add --guess-remote ../main Preparing worktree (new branch 'main') branch 'main' set up to track 'origin/main'. HEAD is now at dadc8e6 commit message % New Behavior: % git --no-pager branch --list --remotes % git remote origin % git workree add ../main No possible source branch, inferring '--orphan' Preparing worktree (new branch 'main') % git worktree remove ../main % git workree add --guess-remote ../main fatal: No local or remote refs exist despite at least one remote present, stopping; use 'add -f' to overide or fetch a remote first % git workree add --guess-remote -f ../main No possible source branch, inferring '--orphan' Preparing worktree (new branch 'main') % git worktree remove ../main % git fetch --quiet % git --no-pager branch --list --remotes origin/HEAD -> origin/main origin/main % git workree add --guess-remote ../main Preparing worktree (new branch 'main') branch 'main' set up to track 'origin/main'. HEAD is now at dadc8e6 commit message % Signed-off-by: Jacob Abel <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 35f0383 commit 128e549

File tree

3 files changed

+449
-1
lines changed

3 files changed

+449
-1
lines changed

Documentation/git-worktree.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ exist, a new branch based on `HEAD` is automatically created as if
9595
`-b <branch>` was given. If `<branch>` does exist, it will be checked out
9696
in the new worktree, if it's not checked out anywhere else, otherwise the
9797
command will refuse to create the worktree (unless `--force` is used).
98+
+
99+
If `<commit-ish>` is omitted, neither `--detach`, or `--orphan` is
100+
used, and there are no valid local branches (or remote branches if
101+
`--guess-remote` is specified) then, as a convenience, the new worktree is
102+
associated with a new orphan branch named `<branch>` (after
103+
`$(basename <path>)` if neither `-b` or `-B` is used) as if `--orphan` was
104+
passed to the command. In the event the repository has a remote and
105+
`--guess-remote` is used, but no remote or local branches exist, then the
106+
command fails with a warning reminding the user to fetch from their remote
107+
first (or override by using `-f/--force`).
98108

99109
list::
100110

builtin/worktree.c

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "strvec.h"
1313
#include "branch.h"
1414
#include "refs.h"
15+
#include "remote.h"
1516
#include "run-command.h"
1617
#include "hook.h"
1718
#include "sigchain.h"
@@ -40,6 +41,9 @@
4041
#define BUILTIN_WORKTREE_UNLOCK_USAGE \
4142
N_("git worktree unlock <worktree>")
4243

44+
#define WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT \
45+
_("No possible source branch, inferring '--orphan'")
46+
4347
#define WORKTREE_ADD_ORPHAN_WITH_DASH_B_HINT_TEXT \
4448
_("If you meant to create a worktree containing a new orphan branch\n" \
4549
"(branch with no commits) for this repository, you can do so\n" \
@@ -613,6 +617,107 @@ static void print_preparing_worktree_line(int detach,
613617
}
614618
}
615619

620+
/**
621+
* Callback to short circuit iteration over refs on the first reference
622+
* corresponding to a valid oid.
623+
*
624+
* Returns 0 on failure and non-zero on success.
625+
*/
626+
static int first_valid_ref(const char *refname,
627+
const struct object_id *oid,
628+
int flags,
629+
void *cb_data)
630+
{
631+
return 1;
632+
}
633+
634+
/**
635+
* Verifies HEAD and determines whether there exist any valid local references.
636+
*
637+
* - Checks whether HEAD points to a valid reference.
638+
*
639+
* - Checks whether any valid local branches exist.
640+
*
641+
* Returns 1 if any of the previous checks are true, otherwise returns 0.
642+
*/
643+
static int can_use_local_refs(const struct add_opts *opts)
644+
{
645+
if (head_ref(first_valid_ref, NULL)) {
646+
return 1;
647+
} else if (for_each_branch_ref(first_valid_ref, NULL)) {
648+
return 1;
649+
}
650+
return 0;
651+
}
652+
653+
/**
654+
* Reports whether the necessary flags were set and whether the repository has
655+
* remote references to attempt DWIM tracking of upstream branches.
656+
*
657+
* 1. Checks that `--guess-remote` was used or `worktree.guessRemote = true`.
658+
*
659+
* 2. Checks whether any valid remote branches exist.
660+
*
661+
* 3. Checks that there exists at least one remote and emits a warning/error
662+
* if both checks 1. and 2. are false (can be bypassed with `--force`).
663+
*
664+
* Returns 1 if checks 1. and 2. are true, otherwise 0.
665+
*/
666+
static int can_use_remote_refs(const struct add_opts *opts)
667+
{
668+
if (!guess_remote) {
669+
if (!opts->quiet)
670+
fprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);
671+
return 0;
672+
} else if (for_each_remote_ref(first_valid_ref, NULL)) {
673+
return 1;
674+
} else if (!opts->force && remote_get(NULL)) {
675+
die(_("No local or remote refs exist despite at least one remote\n"
676+
"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);
679+
}
680+
return 0;
681+
}
682+
683+
/**
684+
* Determines whether `--orphan` should be inferred in the evaluation of
685+
* `worktree add path/` or `worktree add -b branch path/` and emits an error
686+
* if the supplied arguments would produce an illegal combination when the
687+
* `--orphan` flag is included.
688+
*
689+
* `opts` and `opt_track` contain the other options & flags supplied to the
690+
* command.
691+
*
692+
* remote determines whether to check `can_use_remote_refs()` or not. This
693+
* is primarily to differentiate between the basic `add` DWIM and `add -b`.
694+
*
695+
* Returns 1 when inferring `--orphan`, 0 otherwise, and emits an error when
696+
* `--orphan` is inferred but doing so produces an illegal combination of
697+
* options and flags. Additionally produces an error when remote refs are
698+
* checked and the repo is in a state that looks like the user added a remote
699+
* but forgot to fetch (and did not override the warning with -f).
700+
*/
701+
static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote)
702+
{
703+
if (can_use_local_refs(opts)) {
704+
return 0;
705+
} else if (remote && can_use_remote_refs(opts)) {
706+
return 0;
707+
} else if (!opts->quiet) {
708+
fprintf_ln(stderr, WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT);
709+
}
710+
711+
if (opt_track) {
712+
die(_("'%s' and '%s' cannot be used together"), "--orphan",
713+
"--track");
714+
} else if (!opts->checkout) {
715+
die(_("'%s' and '%s' cannot be used together"), "--orphan",
716+
"--no-checkout");
717+
}
718+
return 1;
719+
}
720+
616721
static const char *dwim_branch(const char *path, const char **new_branch)
617722
{
618723
int n;
@@ -723,12 +828,19 @@ static int add(int ac, const char **av, const char *prefix)
723828
int n;
724829
const char *s = worktree_basename(path, &n);
725830
new_branch = xstrndup(s, n);
726-
} else if (new_branch || opts.detach || opts.orphan) {
831+
} else if (opts.orphan || opts.detach) {
727832
// No-op
833+
} else if (ac < 2 && new_branch) {
834+
// DWIM: Infer --orphan when repo has no refs.
835+
opts.orphan = dwim_orphan(&opts, !!opt_track, 0);
728836
} else if (ac < 2) {
837+
// DWIM: Guess branch name from path.
729838
const char *s = dwim_branch(path, &new_branch);
730839
if (s)
731840
branch = s;
841+
842+
// DWIM: Infer --orphan when repo has no refs.
843+
opts.orphan = (!s) && dwim_orphan(&opts, !!opt_track, 1);
732844
} else if (ac == 2) {
733845
struct object_id oid;
734846
struct commit *commit;

0 commit comments

Comments
 (0)