Skip to content

Commit 7ab8918

Browse files
OneDeuxTriSeiGogitster
authored andcommitted
worktree add: add --orphan flag
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 + main % git -C foo.git worktree add main/ Preparing worktree (new branch 'main') HEAD is now at 6c93a75 a commit % % git init bar.git Initialized empty Git repository in /path/to/bar.git/ % git -C bar.git --no-pager branch -l % git -C bar.git worktree add main/ Preparing worktree (new branch 'main') fatal: not a valid object name: 'HEAD' % New Behavior: % git -C foo.git --no-pager branch -l + main % git -C foo.git worktree add main/ Preparing worktree (new branch 'main') HEAD is now at 6c93a75 a commit % % git init --bare bar.git Initialized empty Git repository in /path/to/bar.git/ % git -C bar.git --no-pager branch -l % git -C bar.git worktree add main/ Preparing worktree (new branch 'main') fatal: invalid reference: HEAD % 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 protected]> Signed-off-by: Jacob Abel <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 9ccdace commit 7ab8918

File tree

3 files changed

+129
-12
lines changed

3 files changed

+129
-12
lines changed

Documentation/git-worktree.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]]
13-
[(-b | -B) <new-branch>] <path> [<commit-ish>]
13+
[--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]
1414
'git worktree list' [-v | --porcelain [-z]]
1515
'git worktree lock' [--reason <string>] <worktree>
1616
'git worktree move' <worktree> <new-path>
@@ -222,6 +222,10 @@ This can also be set up as the default behaviour by using the
222222
With `prune`, do not remove anything; just report what it would
223223
remove.
224224

225+
--orphan::
226+
With `add`, make the new worktree and index empty, associating
227+
the worktree with a new orphan/unborn branch named `<new-branch>`.
228+
225229
--porcelain::
226230
With `list`, output in an easy-to-parse format for scripts.
227231
This format will remain stable across Git versions and regardless of user

builtin/worktree.c

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222

2323
#define BUILTIN_WORKTREE_ADD_USAGE \
2424
N_("git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]\n" \
25-
" [(-b | -B) <new-branch>] <path> [<commit-ish>]")
25+
" [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]")
26+
2627
#define BUILTIN_WORKTREE_LIST_USAGE \
2728
N_("git worktree list [-v | --porcelain [-z]]")
2829
#define BUILTIN_WORKTREE_LOCK_USAGE \
@@ -95,6 +96,7 @@ struct add_opts {
9596
int detach;
9697
int quiet;
9798
int checkout;
99+
int orphan;
98100
const char *keep_locked;
99101
};
100102

@@ -368,6 +370,22 @@ static int checkout_worktree(const struct add_opts *opts,
368370
return run_command(&cp);
369371
}
370372

373+
static int make_worktree_orphan(const char * ref, const struct add_opts *opts,
374+
struct strvec *child_env)
375+
{
376+
struct strbuf symref = STRBUF_INIT;
377+
struct child_process cp = CHILD_PROCESS_INIT;
378+
379+
validate_new_branchname(ref, &symref, 0);
380+
strvec_pushl(&cp.args, "symbolic-ref", "HEAD", symref.buf, NULL);
381+
if (opts->quiet)
382+
strvec_push(&cp.args, "--quiet");
383+
strvec_pushv(&cp.env, child_env->v);
384+
strbuf_release(&symref);
385+
cp.git_cmd = 1;
386+
return run_command(&cp);
387+
}
388+
371389
static int add_worktree(const char *path, const char *refname,
372390
const struct add_opts *opts)
373391
{
@@ -397,7 +415,7 @@ static int add_worktree(const char *path, const char *refname,
397415
die_if_checked_out(symref.buf, 0);
398416
}
399417
commit = lookup_commit_reference_by_name(refname);
400-
if (!commit)
418+
if (!commit && !opts->orphan)
401419
die(_("invalid reference: %s"), refname);
402420

403421
name = worktree_basename(path, &len);
@@ -486,10 +504,10 @@ static int add_worktree(const char *path, const char *refname,
486504
strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
487505
cp.git_cmd = 1;
488506

489-
if (!is_branch)
507+
if (!is_branch && commit) {
490508
strvec_pushl(&cp.args, "update-ref", "HEAD",
491509
oid_to_hex(&commit->object.oid), NULL);
492-
else {
510+
} else {
493511
strvec_pushl(&cp.args, "symbolic-ref", "HEAD",
494512
symref.buf, NULL);
495513
if (opts->quiet)
@@ -501,6 +519,10 @@ static int add_worktree(const char *path, const char *refname,
501519
if (ret)
502520
goto done;
503521

522+
if (opts->orphan &&
523+
(ret = make_worktree_orphan(refname, opts, &child_env)))
524+
goto done;
525+
504526
if (opts->checkout &&
505527
(ret = checkout_worktree(opts, &child_env)))
506528
goto done;
@@ -520,7 +542,7 @@ static int add_worktree(const char *path, const char *refname,
520542
* Hook failure does not warrant worktree deletion, so run hook after
521543
* is_junk is cleared, but do return appropriate code when hook fails.
522544
*/
523-
if (!ret && opts->checkout) {
545+
if (!ret && opts->checkout && !opts->orphan) {
524546
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
525547

526548
strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
@@ -568,7 +590,7 @@ static void print_preparing_worktree_line(int detach,
568590
else {
569591
struct commit *commit = lookup_commit_reference_by_name(branch);
570592
if (!commit)
571-
die(_("invalid reference: %s"), branch);
593+
BUG(_("unreachable: invalid reference: %s"), branch);
572594
fprintf_ln(stderr, _("Preparing worktree (detached HEAD %s)"),
573595
repo_find_unique_abbrev(the_repository, &commit->object.oid, DEFAULT_ABBREV));
574596
}
@@ -620,6 +642,7 @@ static int add(int ac, const char **av, const char *prefix)
620642
N_("create a new branch")),
621643
OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
622644
N_("create or reset a branch")),
645+
OPT_BOOL(0, "orphan", &opts.orphan, N_("create unborn/orphaned branch")),
623646
OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")),
624647
OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")),
625648
OPT_BOOL(0, "lock", &keep_locked, N_("keep the new working tree locked")),
@@ -640,6 +663,17 @@ static int add(int ac, const char **av, const char *prefix)
640663
ac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0);
641664
if (!!opts.detach + !!new_branch + !!new_branch_force > 1)
642665
die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach");
666+
if (opts.detach && opts.orphan)
667+
die(_("options '%s', and '%s' cannot be used together"),
668+
"--orphan", "--detach");
669+
if (opts.orphan && opt_track)
670+
die(_("'%s' and '%s' cannot be used together"), "--orphan", "--track");
671+
if (opts.orphan && !opts.checkout)
672+
die(_("'%s' and '%s' cannot be used together"), "--orphan",
673+
"--no-checkout");
674+
if (opts.orphan && ac == 2)
675+
die(_("'%s' and '%s' cannot be used together"), "--orphan",
676+
_("<commit-ish>"));
643677
if (lock_reason && !keep_locked)
644678
die(_("the option '%s' requires '%s'"), "--reason", "--lock");
645679
if (lock_reason)
@@ -668,13 +702,17 @@ static int add(int ac, const char **av, const char *prefix)
668702
strbuf_release(&symref);
669703
}
670704

671-
if (ac < 2 && !new_branch && !opts.detach) {
705+
if (opts.orphan && !new_branch) {
706+
int n;
707+
const char *s = worktree_basename(path, &n);
708+
new_branch = xstrndup(s, n);
709+
} else if (new_branch || opts.detach || opts.orphan) {
710+
// No-op
711+
} else if (ac < 2) {
672712
const char *s = dwim_branch(path, &new_branch);
673713
if (s)
674714
branch = s;
675-
}
676-
677-
if (ac == 2 && !new_branch && !opts.detach) {
715+
} else if (ac == 2) {
678716
struct object_id oid;
679717
struct commit *commit;
680718
const char *remote;
@@ -688,10 +726,17 @@ static int add(int ac, const char **av, const char *prefix)
688726
}
689727
}
690728
}
729+
730+
if (!opts.orphan && !lookup_commit_reference_by_name(branch)) {
731+
die(_("invalid reference: %s"), branch);
732+
}
733+
691734
if (!opts.quiet)
692735
print_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force);
693736

694-
if (new_branch) {
737+
if (opts.orphan) {
738+
branch = new_branch;
739+
} else if (new_branch) {
695740
struct child_process cp = CHILD_PROCESS_INIT;
696741
cp.git_cmd = 1;
697742
strvec_push(&cp.args, "branch");

t/t2400-worktree-add.sh

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,10 @@ test_wt_add_excl () {
312312
test_wt_add_excl -b poodle -B poodle bamboo main
313313
test_wt_add_excl -b poodle --detach bamboo main
314314
test_wt_add_excl -B poodle --detach bamboo main
315+
test_wt_add_excl --orphan --detach bamboo
316+
test_wt_add_excl --orphan --no-checkout bamboo
317+
test_wt_add_excl --orphan bamboo main
318+
test_wt_add_excl --orphan -b bamboo wtdir/ main
315319

316320
test_expect_success '"add -B" fails if the branch is checked out' '
317321
git rev-parse newmain >before &&
@@ -341,6 +345,62 @@ test_expect_success 'add --quiet -b' '
341345
test_must_be_empty actual
342346
'
343347

348+
test_expect_success '"add --orphan"' '
349+
test_when_finished "git worktree remove -f -f orphandir" &&
350+
git worktree add --orphan -b neworphan orphandir &&
351+
echo refs/heads/neworphan >expected &&
352+
git -C orphandir symbolic-ref HEAD >actual &&
353+
test_cmp expected actual
354+
'
355+
356+
test_expect_success '"add --orphan (no -b)"' '
357+
test_when_finished "git worktree remove -f -f neworphan" &&
358+
git worktree add --orphan neworphan &&
359+
echo refs/heads/neworphan >expected &&
360+
git -C neworphan symbolic-ref HEAD >actual &&
361+
test_cmp expected actual
362+
'
363+
364+
test_expect_success '"add --orphan --quiet"' '
365+
test_when_finished "git worktree remove -f -f orphandir" &&
366+
git worktree add --quiet --orphan -b neworphan orphandir 2>log.actual &&
367+
test_must_be_empty log.actual &&
368+
echo refs/heads/neworphan >expected &&
369+
git -C orphandir symbolic-ref HEAD >actual &&
370+
test_cmp expected actual
371+
'
372+
373+
test_expect_success '"add --orphan" fails if the branch already exists' '
374+
test_when_finished "git branch -D existingbranch" &&
375+
git worktree add -b existingbranch orphandir main &&
376+
git worktree remove orphandir &&
377+
test_must_fail git worktree add --orphan -b existingbranch orphandir
378+
'
379+
380+
test_expect_success '"add --orphan" with empty repository' '
381+
test_when_finished "rm -rf empty_repo" &&
382+
echo refs/heads/newbranch >expected &&
383+
GIT_DIR="empty_repo" git init --bare &&
384+
git -C empty_repo worktree add --orphan -b newbranch worktreedir &&
385+
git -C empty_repo/worktreedir symbolic-ref HEAD >actual &&
386+
test_cmp expected actual
387+
'
388+
389+
test_expect_success '"add" worktree with orphan branch and lock' '
390+
git worktree add --lock --orphan -b orphanbr orphan-with-lock &&
391+
test_when_finished "git worktree unlock orphan-with-lock || :" &&
392+
test -f .git/worktrees/orphan-with-lock/locked
393+
'
394+
395+
test_expect_success '"add" worktree with orphan branch, lock, and reason' '
396+
lock_reason="why not" &&
397+
git worktree add --detach --lock --reason "$lock_reason" orphan-with-lock-reason main &&
398+
test_when_finished "git worktree unlock orphan-with-lock-reason || :" &&
399+
test -f .git/worktrees/orphan-with-lock-reason/locked &&
400+
echo "$lock_reason" >expect &&
401+
test_cmp expect .git/worktrees/orphan-with-lock-reason/locked
402+
'
403+
344404
test_expect_success 'local clone from linked checkout' '
345405
git clone --local here here-clone &&
346406
( cd here-clone && git fsck )
@@ -457,6 +517,14 @@ setup_remote_repo () {
457517
)
458518
}
459519

520+
test_expect_success '"add" <path> <remote/branch> w/ no HEAD' '
521+
test_when_finished rm -rf repo_upstream repo_local foo &&
522+
setup_remote_repo repo_upstream repo_local &&
523+
git -C repo_local config --bool core.bare true &&
524+
git -C repo_local branch -D main &&
525+
git -C repo_local worktree add ./foo repo_upstream/foo
526+
'
527+
460528
test_expect_success '--no-track avoids setting up tracking' '
461529
test_when_finished rm -rf repo_upstream repo_local foo &&
462530
setup_remote_repo repo_upstream repo_local &&

0 commit comments

Comments
 (0)