Message ID | 20230109173227.29264-4-jacobabel@nullpo.dev (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | worktree: Support `--orphan` when creating new worktrees | expand |
Hi Jacob On 09/01/2023 17:33, Jacob Abel wrote: > Adds support for creating an orphan branch when adding a new worktree. > This functionality is equivalent to git switch's --orphan flag. > > The original reason this feature was implemented was to allow a user > to initialise a new repository using solely the worktree oriented > workflow. > > 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 main main/ It's perhaps a bit late to bring this up but I've only just realized that it is unfortunate that --orphan takes a branch name rather than working in conjunction with -b/-B. It means that in the common case where the branch name is the same as the worktree the user has to repeat it on the command line as shown above. It also means there is no way to force an orphan branch (that's admittedly quite niche). If instead --orphan did not take an argument we could have git worktree add --orphan main git worktree add --orphan -b topic main git worktree add --orphan -B topic main Best Wishes Phillip > Preparing worktree (new branch 'main') > % > > Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> > Signed-off-by: Jacob Abel <jacobabel@nullpo.dev> > --- > Documentation/git-worktree.txt | 15 +++++++++ > builtin/worktree.c | 59 ++++++++++++++++++++++++++++++---- > t/t2400-worktree-add.sh | 53 ++++++++++++++++++++++++++++++ > 3 files changed, 120 insertions(+), 7 deletions(-) > > diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt > index b9c12779f1..d78460c29c 100644 > --- a/Documentation/git-worktree.txt > +++ b/Documentation/git-worktree.txt > @@ -11,6 +11,8 @@ SYNOPSIS > [verse] > 'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]] > [(-b | -B) <new-branch>] <path> [<commit-ish>] > +'git worktree add' [-f] [--lock [--reason <string>]] > + --orphan <new-branch> <path> > 'git worktree list' [-v | --porcelain [-z]] > 'git worktree lock' [--reason <string>] <worktree> > 'git worktree move' <worktree> <new-path> > @@ -95,6 +97,15 @@ exist, a new branch based on `HEAD` is automatically created as if > `-b <branch>` was given. If `<branch>` does exist, it will be checked out > in the new worktree, if it's not checked out anywhere else, otherwise the > command will refuse to create the worktree (unless `--force` is used). > ++ > +------------ > +$ git worktree add --orphan <branch> <path> > +------------ > ++ > +Create a worktree containing no files, with an empty index, and associated > +with a new orphan branch named `<branch>`. The first commit made on this new > +branch will have no parents and will be the root of a new history disconnected > +from any other branches. > > list:: > > @@ -222,6 +233,10 @@ This can also be set up as the default behaviour by using the > With `prune`, do not remove anything; just report what it would > remove. > > +--orphan <new-branch>:: > + With `add`, make the new worktree and index empty, associating > + the worktree with a new orphan branch named `<new-branch>`. > + > --porcelain:: > With `list`, output in an easy-to-parse format for scripts. > This format will remain stable across Git versions and regardless of user > diff --git a/builtin/worktree.c b/builtin/worktree.c > index ddb33f48a0..ac82c5feda 100644 > --- a/builtin/worktree.c > +++ b/builtin/worktree.c > @@ -17,7 +17,10 @@ > > #define BUILTIN_WORKTREE_ADD_USAGE \ > N_("git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]\n" \ > - " [(-b | -B) <new-branch>] <path> [<commit-ish>]") > + " [(-b | -B) <new-branch>] <path> [<commit-ish>]"), \ > + N_("git worktree add [-f] [--lock [--reason <string>]]\n" \ > + " --orphan <new-branch> <path>") > + > #define BUILTIN_WORKTREE_LIST_USAGE \ > N_("git worktree list [-v | --porcelain [-z]]") > #define BUILTIN_WORKTREE_LOCK_USAGE \ > @@ -90,6 +93,7 @@ struct add_opts { > int detach; > int quiet; > int checkout; > + int orphan; > const char *keep_locked; > }; > > @@ -364,6 +368,22 @@ static int checkout_worktree(const struct add_opts *opts, > return run_command(&cp); > } > > +static int make_worktree_orphan(const char * ref, const struct add_opts *opts, > + struct strvec *child_env) > +{ > + struct strbuf symref = STRBUF_INIT; > + struct child_process cp = CHILD_PROCESS_INIT; > + > + validate_new_branchname(ref, &symref, 0); > + strvec_pushl(&cp.args, "symbolic-ref", "HEAD", symref.buf, NULL); > + if (opts->quiet) > + strvec_push(&cp.args, "--quiet"); > + strvec_pushv(&cp.env, child_env->v); > + strbuf_release(&symref); > + cp.git_cmd = 1; > + return run_command(&cp); > +} > + > static int add_worktree(const char *path, const char *refname, > const struct add_opts *opts) > { > @@ -393,7 +413,7 @@ static int add_worktree(const char *path, const char *refname, > die_if_checked_out(symref.buf, 0); > } > commit = lookup_commit_reference_by_name(refname); > - if (!commit) > + if (!commit && !opts->orphan) > die(_("invalid reference: %s"), refname); > > name = worktree_basename(path, &len); > @@ -482,10 +502,10 @@ static int add_worktree(const char *path, const char *refname, > strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); > cp.git_cmd = 1; > > - if (!is_branch) > + if (!is_branch && commit) { > strvec_pushl(&cp.args, "update-ref", "HEAD", > oid_to_hex(&commit->object.oid), NULL); > - else { > + } else { > strvec_pushl(&cp.args, "symbolic-ref", "HEAD", > symref.buf, NULL); > if (opts->quiet) > @@ -497,6 +517,10 @@ static int add_worktree(const char *path, const char *refname, > if (ret) > goto done; > > + if (opts->orphan && > + (ret = make_worktree_orphan(refname, opts, &child_env))) > + goto done; > + > if (opts->checkout && > (ret = checkout_worktree(opts, &child_env))) > goto done; > @@ -516,7 +540,7 @@ static int add_worktree(const char *path, const char *refname, > * Hook failure does not warrant worktree deletion, so run hook after > * is_junk is cleared, but do return appropriate code when hook fails. > */ > - if (!ret && opts->checkout) { > + if (!ret && opts->checkout && !opts->orphan) { > struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; > > strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL); > @@ -605,6 +629,7 @@ static int add(int ac, const char **av, const char *prefix) > char *path; > const char *branch; > const char *new_branch = NULL; > + const char *orphan_branch = NULL; > const char *opt_track = NULL; > const char *lock_reason = NULL; > int keep_locked = 0; > @@ -616,6 +641,8 @@ static int add(int ac, const char **av, const char *prefix) > N_("create a new branch")), > OPT_STRING('B', NULL, &new_branch_force, N_("branch"), > N_("create or reset a branch")), > + OPT_STRING(0, "orphan", &orphan_branch, N_("branch"), > + N_("new unparented branch")), > OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")), > OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")), > OPT_BOOL(0, "lock", &keep_locked, N_("keep the new working tree locked")), > @@ -633,8 +660,20 @@ static int add(int ac, const char **av, const char *prefix) > memset(&opts, 0, sizeof(opts)); > opts.checkout = 1; > ac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0); > + opts.orphan = !!orphan_branch; > if (!!opts.detach + !!new_branch + !!new_branch_force > 1) > die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach"); > + if (!!opts.detach + !!opts.orphan + !!new_branch + !!new_branch_force > 1) > + die(_("options '%s', '%s', '%s', and '%s' cannot be used together"), > + "-b", "-B", "--orphan", "--detach"); > + if (opts.orphan && opt_track) > + die(_("'%s' and '%s' cannot be used together"), "--orphan", "--track"); > + if (opts.orphan && !opts.checkout) > + die(_("'%s' and '%s' cannot be used together"), "--orphan", > + "--no-checkout"); > + if (opts.orphan && ac == 2) > + die(_("'%s' and '%s' cannot be used together"), "--orphan", > + _("<commit-ish>")); > if (lock_reason && !keep_locked) > die(_("the option '%s' requires '%s'"), "--reason", "--lock"); > if (lock_reason) > @@ -663,7 +702,9 @@ static int add(int ac, const char **av, const char *prefix) > strbuf_release(&symref); > } > > - if (ac < 2 && !new_branch && !opts.detach) { > + if (opts.orphan) { > + new_branch = orphan_branch; > + } else if (ac < 2 && !new_branch && !opts.detach) { > const char *s = dwim_branch(path, &new_branch); > if (s) > branch = s; > @@ -686,7 +727,11 @@ static int add(int ac, const char **av, const char *prefix) > if (!opts.quiet) > print_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force); > > - if (new_branch) { > + if (opts.orphan) { > + branch = new_branch; > + } else if (!lookup_commit_reference_by_name(branch)) { > + die(_("invalid reference: %s"), branch); > + } else if (new_branch) { > struct child_process cp = CHILD_PROCESS_INIT; > cp.git_cmd = 1; > strvec_push(&cp.args, "branch"); > diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh > index dd729a00d8..45f896dcd0 100755 > --- a/t/t2400-worktree-add.sh > +++ b/t/t2400-worktree-add.sh > @@ -312,6 +312,11 @@ test_wt_add_excl () { > test_wt_add_excl -b poodle -B poodle bamboo main > test_wt_add_excl -b poodle --detach bamboo main > test_wt_add_excl -B poodle --detach bamboo main > +test_wt_add_excl -B poodle --orphan poodle bamboo > +test_wt_add_excl -b poodle --orphan poodle bamboo > +test_wt_add_excl --orphan poodle --detach bamboo > +test_wt_add_excl --orphan poodle --no-checkout bamboo > +test_wt_add_excl --orphan poodle bamboo main > > test_expect_success '"add -B" fails if the branch is checked out' ' > git rev-parse newmain >before && > @@ -333,6 +338,46 @@ test_expect_success 'add --quiet' ' > test_must_be_empty actual > ' > > +test_expect_success '"add --orphan"' ' > + test_when_finished "git worktree remove -f -f orphandir" && > + git worktree add --orphan neworphan orphandir && > + echo refs/heads/neworphan >expected && > + git -C orphandir symbolic-ref HEAD >actual && > + test_cmp expected actual > +' > + > +test_expect_success '"add --orphan" fails if the branch already exists' ' > + test_when_finished "git branch -D existingbranch" && > + test_when_finished "git worktree remove -f -f orphandir" && > + git worktree add -b existingbranch orphandir main && > + test_must_fail git worktree add --orphan existingbranch orphandir2 && > + test_path_is_missing orphandir2 > +' > + > +test_expect_success '"add --orphan" with empty repository' ' > + test_when_finished "rm -rf empty_repo" && > + echo refs/heads/newbranch >expected && > + GIT_DIR="empty_repo" git init --bare && > + git -C empty_repo worktree add --orphan newbranch worktreedir && > + git -C empty_repo/worktreedir symbolic-ref HEAD >actual && > + test_cmp expected actual > +' > + > +test_expect_success '"add" worktree with orphan branch and lock' ' > + git worktree add --lock --orphan orphanbr orphan-with-lock && > + test_when_finished "git worktree unlock orphan-with-lock || :" && > + test -f .git/worktrees/orphan-with-lock/locked > +' > + > +test_expect_success '"add" worktree with orphan branch, lock, and reason' ' > + lock_reason="why not" && > + git worktree add --detach --lock --reason "$lock_reason" orphan-with-lock-reason main && > + test_when_finished "git worktree unlock orphan-with-lock-reason || :" && > + test -f .git/worktrees/orphan-with-lock-reason/locked && > + echo "$lock_reason" >expect && > + test_cmp expect .git/worktrees/orphan-with-lock-reason/locked > +' > + > test_expect_success 'local clone from linked checkout' ' > git clone --local here here-clone && > ( cd here-clone && git fsck ) > @@ -449,6 +494,14 @@ setup_remote_repo () { > ) > } > > +test_expect_success '"add" <path> <remote/branch> w/ no HEAD' ' > + test_when_finished rm -rf repo_upstream repo_local foo && > + setup_remote_repo repo_upstream repo_local && > + git -C repo_local config --bool core.bare true && > + git -C repo_local branch -D main && > + git -C repo_local worktree add ./foo repo_upstream/foo > +' > + > test_expect_success '--no-track avoids setting up tracking' ' > test_when_finished rm -rf repo_upstream repo_local foo && > setup_remote_repo repo_upstream repo_local && > -- > 2.38.2 > >
Phillip Wood <phillip.wood123@gmail.com> writes: >> % git -C bar.git worktree add --orphan main main/ > > It's perhaps a bit late to bring this up but I've only just realized > that it is unfortunate that --orphan takes a branch name rather than > working in conjunction with -b/-B. It means that in the common case > where the branch name is the same as the worktree the user has to > repeat it on the command line as shown above. It also means there is > no way to force an orphan branch (that's admittedly quite niche). If > instead --orphan did not take an argument we could have > > git worktree add --orphan main > git worktree add --orphan -b topic main > git worktree add --orphan -B topic main Good point. I am not sure if it is too late, though. Thanks.
On 23/01/13 10:20AM, Phillip Wood wrote: > Hi Jacob > > On 09/01/2023 17:33, Jacob Abel wrote: > > [...] > > It's perhaps a bit late to bring this up but I've only just realized > that it is unfortunate that --orphan takes a branch name rather than > working in conjunction with -b/-B. It means that in the common case > where the branch name is the same as the worktree the user has to repeat > it on the command line as shown above. It also means there is no way to > force an orphan branch (that's admittedly quite niche). If instead > --orphan did not take an argument we could have > > git worktree add --orphan main > git worktree add --orphan -b topic main > git worktree add --orphan -B topic main > > Best Wishes > > Phillip > > > [...] I think this is a good idea and something similar was brought up previously however I originally wanted to handle this and a common --orphan DWYM in a later patch. > git worktree add --orphan main I am OK implementing this option and have been workshopping it prior to responding. I think I have it worked out now as an additional patch which can be be applied on top of the v8 patchset. I'll reply to this message with the one-off patch to get feedback. Since this is essentially a discrete change on top of v8, I can either keep it as a separate patch or reroll depending on how much needs to be changed (and what would be easier for everyone). > git worktree add --orphan -b topic main > git worktree add --orphan -B topic main I am hesitant to add these as they break away from the syntax used in `git switch` and `git checkout`. Also apologies for the tangent but while researching this path, I noticed that --orphan behaves unexpectedly on both `git switch` and `git checkout` when mixed with `-c` and `-b` respectively. % git switch --orphan -c foobar fatal: invalid reference: foobar % git switch -c --orphan foobar fatal: invalid reference: foobar % git checkout -b --orphan foobar fatal: 'foobar' is not a commit and a branch '--orphan' cannot be created from it % git checkout --orphan -b foobar fatal: 'foobar' is not a commit and a branch '-b' cannot be created from it I tried this on my system install as well as from a fresh fetch of next FWIW. [Info: fresh build from next] git version 2.39.0.287.g8cbeef4abd cpu: x86_64 built from commit: 8cbeef4abda4907dd68ea144d9dcb85f0b49c3e6 sizeof-long: 8 sizeof-size_t: 8 shell-path: /bin/sh [Info: system install] git version 2.38.2 cpu: x86_64 no commit associated with this build sizeof-long: 8 sizeof-size_t: 8 shell-path: /bin/sh If this bug is something that needs to be addressed, I can dig a bit deeper and put together a patch for it in the next few days. VR, Abel
Jacob Abel <jacobabel@nullpo.dev> writes: >> git worktree add --orphan -b topic main >> git worktree add --orphan -B topic main > > I am hesitant to add these as they break away from the syntax used in > `git switch` and `git checkout`. Not that I care too deeply, but doesn't it introduce end-user confusion if we try to be compatible with "git checkout --orphan <branch>", while allowing this to be compatible with the default choice of the branch name done by "git worktree add"? "--orphan" in "git checkout" behaves similar to "-b|-B" in that it always wants a name, but "git worktree add" wants to make it optional. By the way "--orphan" in checkout|switch wants to take a name for itself, e.g. git checkout --orphan $name [$commit] git checkout -b $name [$commit] git checkout -B $name [$commit] so it is impossible to force their "--orphan" to rename an existing branch, which is probalby a design mistake we may want to fix. In any case, as I said, I do not care too deeply which way you guys decide to go, because I think the whole "orphan" UI is a design mistake that instills a broken mental model to its users [*]. But let's wait a bit more to see which among (1) git worktree add [[--orphan] -b $branch] $path This allows --orphan to act as a modifier to existing -b, (2) git worktree add [(--orphan|-b) $branch] $path This allows --orphan to be another mode of -b, or (3) git worktree add [--orphan [$branch]|(-b $branch)] $path This allows --orphan to default to $(basename $path) people prefer. [Footnote] * I am not saying that it is wrong or useless to keep an unrelated history, especially one that records trees that have no relevance to the main history like created with "switch --orphan", in the same repository. Allowing "git switch --orphan" to create such a separate history in the same repository blurs the distinction. It would help newbies to form the right mental model if they start a separate repository that the separate history originates in, and pull from it to bootstrap the unrelated history in the local repository.
On January 14, 2023 10:09 PM, Junio C Hamano wrote: >Jacob Abel <jacobabel@nullpo.dev> writes: > >>> git worktree add --orphan -b topic main >>> git worktree add --orphan -B topic main >> >> I am hesitant to add these as they break away from the syntax used in >> `git switch` and `git checkout`. > >Not that I care too deeply, but doesn't it introduce end-user confusion if we try to >be compatible with "git checkout --orphan <branch>", while allowing this to be >compatible with the default choice of the branch name done by "git worktree >add"? "--orphan" in "git checkout" behaves similar to "-b|-B" in that it always >wants a name, but "git worktree add" wants to make it optional. > >By the way "--orphan" in checkout|switch wants to take a name for itself, e.g. > > git checkout --orphan $name [$commit] > git checkout -b $name [$commit] > git checkout -B $name [$commit] > >so it is impossible to force their "--orphan" to rename an existing branch, which is >probalby a design mistake we may want to fix. > >In any case, as I said, I do not care too deeply which way you guys decide to go, >because I think the whole "orphan" UI is a design mistake that instills a broken >mental model to its users [*]. > >But let's wait a bit more to see which among > >(1) git worktree add [[--orphan] -b $branch] $path > This allows --orphan to act as a modifier to existing -b, > >(2) git worktree add [(--orphan|-b) $branch] $path > This allows --orphan to be another mode of -b, or > >(3) git worktree add [--orphan [$branch]|(-b $branch)] $path > This allows --orphan to default to $(basename $path) > >people prefer. > > >[Footnote] > >* I am not saying that it is wrong or useless to keep an unrelated > history, especially one that records trees that have no relevance > to the main history like created with "switch --orphan", in the > same repository. Allowing "git switch --orphan" to create such a > separate history in the same repository blurs the distinction. It > would help newbies to form the right mental model if they start a > separate repository that the separate history originates in, and > pull from it to bootstrap the unrelated history in the local > repository. I am wondering whether --detached is a more semantically consistent option. While --orphan has meaning in checkout (not one I ever liked), detached makes more sense as a description of what is intended here - as in not connected. --Randall
<rsbecker@nexbridge.com> writes:
> I am wondering whether --detached is a more semantically consistent option. While --orphan has meaning in checkout (not one I ever liked), detached makes more sense as a description of what is intended here - as in not connected.
An orphan is not even detached, if I understand correctly.
The state is what is called "being on an unborn branch", where your
HEAD does not even point at any commit. HEAD only knows a name of a
branch that is not yet created but will be when you make a commit.
While "(HEAD) being detached" means that you are on an existing
commit---it is just that future history you extend by making a
commit from that state will not be on any branch.
So if we wanted to fix the misnomer, s/orphan/unborn/ would be how I
would go about it.
Hi Jacob On 14/01/2023 22:47, Jacob Abel wrote: > On 23/01/13 10:20AM, Phillip Wood wrote: >> Hi Jacob >> >> On 09/01/2023 17:33, Jacob Abel wrote: >>> [...] >> >> It's perhaps a bit late to bring this up but I've only just realized >> that it is unfortunate that --orphan takes a branch name rather than >> working in conjunction with -b/-B. It means that in the common case >> where the branch name is the same as the worktree the user has to repeat >> it on the command line as shown above. It also means there is no way to >> force an orphan branch (that's admittedly quite niche). If instead >> --orphan did not take an argument we could have >> >> git worktree add --orphan main >> git worktree add --orphan -b topic main >> git worktree add --orphan -B topic main >> >> Best Wishes >> >> Phillip >> >>> [...] > > I think this is a good idea and something similar was brought up previously > however I originally wanted to handle this and a common --orphan DWYM in a later > patch. I think adding it in a later patch makes the implementation more complicated than it needs to be as you'll still have to support --orphan taking a name for backwards compatibility that means you need to handle git worktree add --orphan=main main git worktree add --orphan topic main git worktree add --orphan --lock main git worktree add --orphan -b topic main git worktree add --orphan -B topic main Rather than just the last three. Now you can probably get that to work by changing --orphan to take an optional argument but I think it would be simpler to have --orphan as a flag from the start. >> git worktree add --orphan main > > I am OK implementing this option and have been workshopping it prior to > responding. I think I have it worked out now as an additional patch which can be > be applied on top of the v8 patchset. > > I'll reply to this message with the one-off patch to get feedback. Since this is > essentially a discrete change on top of v8, I can either keep it as a separate > patch or reroll depending on how much needs to be changed (and what would be > easier for everyone). > >> git worktree add --orphan -b topic main >> git worktree add --orphan -B topic main > > I am hesitant to add these as they break away from the syntax used in > `git switch` and `git checkout`. When I wrote my original email I wrongly though that --orphan did not take an argument for "git checkout". While I think it is a mistake for checkout and switch to have --orphan take an argument they do at least always need a branch name with that option. "git worktree" add already has the branch name in the form of the worktree directory in the common case. > Also apologies for the tangent but while researching this path, I noticed that > --orphan behaves unexpectedly on both `git switch` and `git checkout` when mixed > with `-c` and `-b` respectively. > > % git switch --orphan -c foobar > fatal: invalid reference: foobar > > % git switch -c --orphan foobar > fatal: invalid reference: foobar > > % git checkout -b --orphan foobar > fatal: 'foobar' is not a commit and a branch '--orphan' cannot be created from it > > % git checkout --orphan -b foobar > fatal: 'foobar' is not a commit and a branch '-b' cannot be created from it The messages for checkout look better than the switch ones to me as they show the branch name which makes it clearer that we're treating what looks like an option as an argument. What in particular is unexpected here - --orphan and -b take an argument so they'll hoover up the next thing on the commandline whatever it is. Best Wishes Phillip > I tried this on my system install as well as from a fresh fetch of next FWIW. > > [Info: fresh build from next] > git version 2.39.0.287.g8cbeef4abd > cpu: x86_64 > built from commit: 8cbeef4abda4907dd68ea144d9dcb85f0b49c3e6 > sizeof-long: 8 > sizeof-size_t: 8 > shell-path: /bin/sh > > [Info: system install] > git version 2.38.2 > cpu: x86_64 > no commit associated with this build > sizeof-long: 8 > sizeof-size_t: 8 > shell-path: /bin/sh > > If this bug is something that needs to be addressed, I can dig a bit deeper and > put together a patch for it in the next few days. > > VR, > Abel >
On 23/01/14 07:09PM, Junio C Hamano wrote: > Jacob Abel <jacobabel@nullpo.dev> writes: > > >> git worktree add --orphan -b topic main > >> git worktree add --orphan -B topic main > > > > I am hesitant to add these as they break away from the syntax used in > > `git switch` and `git checkout`. > > Not that I care too deeply, but doesn't it introduce end-user > confusion if we try to be compatible with "git checkout --orphan > <branch>", while allowing this to be compatible with the default > choice of the branch name done by "git worktree add"? "--orphan" in > "git checkout" behaves similar to "-b|-B" in that it always wants a > name, but "git worktree add" wants to make it optional. Yes. I think it's a fairly minor degree of confusion but I agree that it adds potentially unneeded confusion. > > By the way "--orphan" in checkout|switch wants to take a name for > itself, e.g. > > git checkout --orphan $name [$commit] > git checkout -b $name [$commit] > git checkout -B $name [$commit] > > so it is impossible to force their "--orphan" to rename an existing > branch, which is probalby a design mistake we may want to fix. Can you elaborate on what you mean by "rename an existing branch" here? Do you mean like `git checkout --orphan $branchname` being able to convert an existing branch into an orphan/unborn branch? Also a small point but in an earlier thread [1], we made the decision to model functionality on `git switch --orphan $branch` instead of `git checkout --orphan $branch [$commit]`. > > In any case, as I said, I do not care too deeply which way you guys > decide to go, because I think the whole "orphan" UI is a design > mistake that instills a broken mental model to its users [*]. Understood. > > But let's wait a bit more to see which among > > (1) git worktree add [[--orphan] -b $branch] $path > This allows --orphan to act as a modifier to existing -b, > > (2) git worktree add [(--orphan|-b) $branch] $path > This allows --orphan to be another mode of -b, or > > (3) git worktree add [--orphan [$branch]|(-b $branch)] $path > This allows --orphan to default to $(basename $path) > > people prefer. > I'd personally argue that option 2 (the current behavior) is probably the cleanest path forward as option 3 requires a bit of awkward code [2] and `--orphan` is such an esoteric option that the user may only use it once or twice in the life of a given repository, if that. And eventually I'd like `git worktree add $path` to "just work" on a new/empty repository. However as things stand, there wasn't an easy way to do this without leading to potentially confusing behavior. It can be done, I just haven't taken the time to figure it out yet. Once `git worktree add $path` "just works" (when creating the first branch in a repo), I highly doubt anyone would use `--orphan` often enough to justify the use of shorthand options 1 or 3. > > [Footnote] > > * I am not saying that it is wrong or useless to keep an unrelated > history, especially one that records trees that have no relevance > to the main history like created with "switch --orphan", in the > same repository. Allowing "git switch --orphan" to create such a > separate history in the same repository blurs the distinction. It > would help newbies to form the right mental model if they start a > separate repository that the separate history originates in, and > pull from it to bootstrap the unrelated history in the local > repository. Definitely agreed that `--orphan` is esoteric and probably should be avoided by most users where possible. 1. https://lore.kernel.org/git/CAPig+cSVzewXpk+eDSC-W-+Q8X_7ikZXXeSQbmpHBcdLCU5svw@mail.gmail.com/ 2. https://lore.kernel.org/git/20230114224956.24801-1-jacobabel@nullpo.dev/
On 23/01/16 10:47AM, Phillip Wood wrote: > Hi Jacob > > On 14/01/2023 22:47, Jacob Abel wrote: > > On 23/01/13 10:20AM, Phillip Wood wrote: > >> Hi Jacob > >> > > [...] > > > > I'll reply to this message with the one-off patch to get feedback. Since this is > > essentially a discrete change on top of v8, I can either keep it as a separate > > patch or reroll depending on how much needs to be changed (and what would be > > easier for everyone). > > > >> git worktree add --orphan -b topic main > >> git worktree add --orphan -B topic main > > > > I am hesitant to add these as they break away from the syntax used in > > `git switch` and `git checkout`. > > When I wrote my original email I wrongly though that --orphan did not > take an argument for "git checkout". While I think it is a mistake for > checkout and switch to have --orphan take an argument they do at least > always need a branch name with that option. "git worktree" add already > has the branch name in the form of the worktree directory in the common > case. Understood. I'm not entirely opposed to making this change to OPT_BOOL but I have to wonder how often `--orphan` will actually be used by a given user and whether the slightly shorter invocation will be used regularly. With the base `git worktree add $path`, the shorthand/DWYM makes sense as it's used regularly but I don't see users working with `--orphan` outside of trying to create the first branch in a repository. And I'd like that operation of creating the first branch in a repo to eventually "just work" with the base command, i.e. `git worktree add main/`. The reason I hadn't yet added that is because I've yet to figure out how to get it to work without accidentally introducing potentially confusing situations and I didn't want to hold up introducing the core functionality itself. Once that main use-case "just works", I don't see users utilising `--orphan` except in very rare circumstances. Doubly so since the average user likely shouldn't be using `--orphan` in most cases. Hence the question of whether this change would be worth it vs the existing `--orphan $branchname $path` which is (for better or worse) consistent with `-b` and `-B`. > > > Also apologies for the tangent but while researching this path, I noticed that > > --orphan behaves unexpectedly on both `git switch` and `git checkout` when mixed > > with `-c` and `-b` respectively. > > > > % git switch --orphan -c foobar > > fatal: invalid reference: foobar > > > > % git switch -c --orphan foobar > > fatal: invalid reference: foobar > > % git checkout -b --orphan foobar > > fatal: 'foobar' is not a commit and a branch '--orphan' cannot be created from it > > > > % git checkout --orphan -b foobar > > fatal: 'foobar' is not a commit and a branch '-b' cannot be created from it > > The messages for checkout look better than the switch ones to me as they > show the branch name which makes it clearer that we're treating what > looks like an option as an argument. What in particular is unexpected > here - --orphan and -b take an argument so they'll hoover up the next > thing on the commandline whatever it is. > > Best Wishes > > Phillip > > > [...] Agreed. I wasn't sure if this would be something worth addressing in a patch but at the very least I can work on putting together a small patch for `git switch` since it doesn't seem to be hoovering the flags like `git checkout` does.
On 23/01/14 07:49PM, Junio C Hamano wrote: > <rsbecker@nexbridge.com> writes: > > > [...] > > An orphan is not even detached, if I understand correctly. > > The state is what is called "being on an unborn branch", where your > HEAD does not even point at any commit. HEAD only knows a name of a > branch that is not yet created but will be when you make a commit. > > While "(HEAD) being detached" means that you are on an existing > commit---it is just that future history you extend by making a > commit from that state will not be on any branch. > > So if we wanted to fix the misnomer, s/orphan/unborn/ would be how I > would go about it. > I would support making this change (s/orphan/unborn/) as it's definitely less confusing. Especially given that orphan already has a completely different, overloaded meaning when referring to orphaned objects & commits (in the context of garbage collection).
On Wed, Jan 18 2023, Jacob Abel wrote: > On 23/01/14 07:09PM, Junio C Hamano wrote: >> Jacob Abel <jacobabel@nullpo.dev> writes: >> >> >> git worktree add --orphan -b topic main >> >> git worktree add --orphan -B topic main >> > >> > I am hesitant to add these as they break away from the syntax used in >> > `git switch` and `git checkout`. >> >> Not that I care too deeply, but doesn't it introduce end-user >> confusion if we try to be compatible with "git checkout --orphan >> <branch>", while allowing this to be compatible with the default >> choice of the branch name done by "git worktree add"? "--orphan" in >> "git checkout" behaves similar to "-b|-B" in that it always wants a >> name, but "git worktree add" wants to make it optional. > > Yes. I think it's a fairly minor degree of confusion but I agree that it adds > potentially unneeded confusion. I think this topic is ready to advance as-is without Phillip's upthread suggestion (<e5aadd5d-9b85-4dc9-e9f7-117892b4b283@dunelm.org.uk>) to allow us to combine --orphan and -b and -B. I also think that UX suggestion is sensible, but if we do that we shouldn't just apply that to "git worktree", but also change the the corresponding "git switch" UX, on which this new "git worktree --orphan" is modeled. I don't think it's worth it to make the UX between the two inconsistent in this regard, so if "switch" doesn't learn to do this we'd be better off with not making "--orphan" a flag. But if we are going to make it a flag let's have both support the same sort of invocation. Therefore I think this series is ready as-is without this proposed UX change. We should first support the same sort of invocations that "swich" already supports. If we then want to change the UX later we should change it for both, not leave the two inconsistent.
On 18/01/2023 22:40, Jacob Abel wrote: > On 23/01/16 10:47AM, Phillip Wood wrote: >> Hi Jacob >> >> On 14/01/2023 22:47, Jacob Abel wrote: >>> On 23/01/13 10:20AM, Phillip Wood wrote: >>>> Hi Jacob >>>> >>> [...] >>> >>> I'll reply to this message with the one-off patch to get feedback. Since this is >>> essentially a discrete change on top of v8, I can either keep it as a separate >>> patch or reroll depending on how much needs to be changed (and what would be >>> easier for everyone). >>> >>>> git worktree add --orphan -b topic main >>>> git worktree add --orphan -B topic main >>> >>> I am hesitant to add these as they break away from the syntax used in >>> `git switch` and `git checkout`. >> >> When I wrote my original email I wrongly though that --orphan did not >> take an argument for "git checkout". While I think it is a mistake for >> checkout and switch to have --orphan take an argument they do at least >> always need a branch name with that option. "git worktree" add already >> has the branch name in the form of the worktree directory in the common >> case. > > Understood. > > I'm not entirely opposed to making this change to OPT_BOOL but I have to wonder > how often `--orphan` will actually be used by a given user and whether the > slightly shorter invocation will be used regularly. > > With the base `git worktree add $path`, the shorthand/DWYM makes sense as it's > used regularly but I don't see users working with `--orphan` outside of trying > to create the first branch in a repository. Your example use in the commit message shows the user using the same name for the branch and worktree. If that really is the likely use than I think we should make --orphan OPT_BOOL. If it is not the likely use perhaps you could update the commit message to show how you think it will be used. > And I'd like that operation of creating the first branch in a repo to eventually > "just work" with the base command, i.e. `git worktree add main/`. The reason I > hadn't yet added that is because I've yet to figure out how to get it to work > without accidentally introducing potentially confusing situations and I didn't > want to hold up introducing the core functionality itself. > > Once that main use-case "just works", I don't see users utilising `--orphan` > except in very rare circumstances. Doubly so since the average user likely > shouldn't be using `--orphan` in most cases. This brings us back to the question that we discussed earlier of whether we need --orphan at all. If we can get the main use-case working without it we'd perhaps be better doing that rather than adding an option no one ends up using. Best Wishes Phillip > Hence the question of whether this change would be worth it vs the existing > `--orphan $branchname $path` which is (for better or worse) consistent with `-b` > and `-B`. > >> >>> Also apologies for the tangent but while researching this path, I noticed that >>> --orphan behaves unexpectedly on both `git switch` and `git checkout` when mixed >>> with `-c` and `-b` respectively. >>> >>> % git switch --orphan -c foobar >>> fatal: invalid reference: foobar >>> >>> % git switch -c --orphan foobar >>> fatal: invalid reference: foobar >>> % git checkout -b --orphan foobar >>> fatal: 'foobar' is not a commit and a branch '--orphan' cannot be created from it >>> >>> % git checkout --orphan -b foobar >>> fatal: 'foobar' is not a commit and a branch '-b' cannot be created from it >> >> The messages for checkout look better than the switch ones to me as they >> show the branch name which makes it clearer that we're treating what >> looks like an option as an argument. What in particular is unexpected >> here - --orphan and -b take an argument so they'll hoover up the next >> thing on the commandline whatever it is. >> >> Best Wishes >> >> Phillip >> >>> [...] > > Agreed. I wasn't sure if this would be something worth addressing in a patch but > at the very least I can work on putting together a small patch for `git switch` > since it doesn't seem to be hoovering the flags like `git checkout` does. >
Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes: > I also think that UX suggestion is sensible, but if we do that we > shouldn't just apply that to "git worktree", but also change the the > corresponding "git switch" UX, on which this new "git worktree --orphan" > is modeled. But the thing is that "worktree --orphan" that wants to implicitly infer the name of the branch out of the basename of the worktree cannot be sensibly modeled after "switch" or "checkout" that do not have such a dwimmery. In any case, fixing UI mistakes after the fact is always painful once it is in the released version. When we know there is a new UI mistake in an unreleased version, it is sensible to grab the rare opportunity that we can avoid such a costly fixes later. So, I am not quite convinced by what you said, at least not yet. Thanks.
On 23/01/19 04:18PM, Phillip Wood wrote: > On 18/01/2023 22:40, Jacob Abel wrote: > > [...] > > Understood. > > > > I'm not entirely opposed to making this change to OPT_BOOL but I have to wonder > > how often `--orphan` will actually be used by a given user and whether the > > slightly shorter invocation will be used regularly. > > > > With the base `git worktree add $path`, the shorthand/DWYM makes sense as it's > > used regularly but I don't see users working with `--orphan` outside of trying > > to create the first branch in a repository. > > Your example use in the commit message shows the user using the same > name for the branch and worktree. If that really is the likely use than > I think we should make --orphan OPT_BOOL. If it is not the likely use > perhaps you could update the commit message to show how you think it > will be used. The example in the commit message is mostly a trivial example to show the change. I think whether someone uses the same name for the branch and worktree depends on how they use the feature. At least personally, generally I name my worktree based on either the relevant project/"hat" or using an "a/, b/, c/" or "alpha/, beta/, gamma/" style. So in my personal use, it'd look something like: git worktree add --orphan main alpha/ --- Or to occassionally break out a scratchpad that I may want to keep around for a while but not actually integrate into the feature branch in it's current form. This isn't really for code but rather so I can scratch out and document my logic prior to doing a formal write up at the end of a given feature. Of course since it doesn't make it into the main tree, when everything is written up, I can just toss the branch and let gc take care of it. This ends up looking like so: git worktree add --orphan scratch-1234-foobar-feature scratchpad/ And since `worktree add` only needs to be done once, I only do this the first time I set up my dev environment on a machine. After that I can just use `git switch --orphan` to create new scratchpad branches and `git switch` to swap between existing scratchpads. --- The first example I see as being the main use case (which could hopefully be DWYMed eventually) and the latter example is a quirk of my admittedly niche personal usecase for worktrees. > > And I'd like that operation of creating the first branch in a repo to eventually > > "just work" with the base command, i.e. `git worktree add main/`. The reason I > > hadn't yet added that is because I've yet to figure out how to get it to work > > without accidentally introducing potentially confusing situations and I didn't > > want to hold up introducing the core functionality itself. > > > > Once that main use-case "just works", I don't see users utilising `--orphan` > > except in very rare circumstances. Doubly so since the average user likely > > shouldn't be using `--orphan` in most cases. > > This brings us back to the question that we discussed earlier of whether > we need --orphan at all. If we can get the main use-case working without > it we'd perhaps be better doing that rather than adding an option no one > ends up using. At least personally, I'd rather expose the option for users who may potentially want it. I think it'd be useful regardless but in the same way `--orphan` is currently useful in `git switch`, which is for very specific niche cases and really only for power users rather than as a common tool everyday users are expected to know. And for the main use case, my concerns were: 1. If we DWYM and create a branch when there are no existing branches, what about the case where a user sets up the repo but forgot to fetch. i.e. if a user does: # Or instead of init --template, use some language's project init tool to # setup git hooks. % git init --bare --template <tmpldir> .git % git remote add origin <remote> % git worktree add main/ Should we just warn the user that they haven't fetched their remote yet while we prepare the worktree? 2. Suppose we also check whether the remote has been fetched and the remote has a branch matching the current branch name. Should we fail (as it currently does on main) with an advise to try the command `git worktree add main main` instead? Or should that command also "just work" 3. If we want to do the above, should it do this for all commands trying to create a worktree until at least one real branch (with commits) exists in the repo or should we only do this when the branch name matches the one defined in `init.defaultBranch`? > Best Wishes > > Phillip > > > [...]
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index b9c12779f1..d78460c29c 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -11,6 +11,8 @@ SYNOPSIS [verse] 'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]] [(-b | -B) <new-branch>] <path> [<commit-ish>] +'git worktree add' [-f] [--lock [--reason <string>]] + --orphan <new-branch> <path> 'git worktree list' [-v | --porcelain [-z]] 'git worktree lock' [--reason <string>] <worktree> 'git worktree move' <worktree> <new-path> @@ -95,6 +97,15 @@ exist, a new branch based on `HEAD` is automatically created as if `-b <branch>` was given. If `<branch>` does exist, it will be checked out in the new worktree, if it's not checked out anywhere else, otherwise the command will refuse to create the worktree (unless `--force` is used). ++ +------------ +$ git worktree add --orphan <branch> <path> +------------ ++ +Create a worktree containing no files, with an empty index, and associated +with a new orphan branch named `<branch>`. The first commit made on this new +branch will have no parents and will be the root of a new history disconnected +from any other branches. list:: @@ -222,6 +233,10 @@ This can also be set up as the default behaviour by using the With `prune`, do not remove anything; just report what it would remove. +--orphan <new-branch>:: + With `add`, make the new worktree and index empty, associating + the worktree with a new orphan branch named `<new-branch>`. + --porcelain:: With `list`, output in an easy-to-parse format for scripts. This format will remain stable across Git versions and regardless of user diff --git a/builtin/worktree.c b/builtin/worktree.c index ddb33f48a0..ac82c5feda 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -17,7 +17,10 @@ #define BUILTIN_WORKTREE_ADD_USAGE \ N_("git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]\n" \ - " [(-b | -B) <new-branch>] <path> [<commit-ish>]") + " [(-b | -B) <new-branch>] <path> [<commit-ish>]"), \ + N_("git worktree add [-f] [--lock [--reason <string>]]\n" \ + " --orphan <new-branch> <path>") + #define BUILTIN_WORKTREE_LIST_USAGE \ N_("git worktree list [-v | --porcelain [-z]]") #define BUILTIN_WORKTREE_LOCK_USAGE \ @@ -90,6 +93,7 @@ struct add_opts { int detach; int quiet; int checkout; + int orphan; const char *keep_locked; }; @@ -364,6 +368,22 @@ static int checkout_worktree(const struct add_opts *opts, return run_command(&cp); } +static int make_worktree_orphan(const char * ref, const struct add_opts *opts, + struct strvec *child_env) +{ + struct strbuf symref = STRBUF_INIT; + struct child_process cp = CHILD_PROCESS_INIT; + + validate_new_branchname(ref, &symref, 0); + strvec_pushl(&cp.args, "symbolic-ref", "HEAD", symref.buf, NULL); + if (opts->quiet) + strvec_push(&cp.args, "--quiet"); + strvec_pushv(&cp.env, child_env->v); + strbuf_release(&symref); + cp.git_cmd = 1; + return run_command(&cp); +} + static int add_worktree(const char *path, const char *refname, const struct add_opts *opts) { @@ -393,7 +413,7 @@ static int add_worktree(const char *path, const char *refname, die_if_checked_out(symref.buf, 0); } commit = lookup_commit_reference_by_name(refname); - if (!commit) + if (!commit && !opts->orphan) die(_("invalid reference: %s"), refname); name = worktree_basename(path, &len); @@ -482,10 +502,10 @@ static int add_worktree(const char *path, const char *refname, strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); cp.git_cmd = 1; - if (!is_branch) + if (!is_branch && commit) { strvec_pushl(&cp.args, "update-ref", "HEAD", oid_to_hex(&commit->object.oid), NULL); - else { + } else { strvec_pushl(&cp.args, "symbolic-ref", "HEAD", symref.buf, NULL); if (opts->quiet) @@ -497,6 +517,10 @@ static int add_worktree(const char *path, const char *refname, if (ret) goto done; + if (opts->orphan && + (ret = make_worktree_orphan(refname, opts, &child_env))) + goto done; + if (opts->checkout && (ret = checkout_worktree(opts, &child_env))) goto done; @@ -516,7 +540,7 @@ static int add_worktree(const char *path, const char *refname, * Hook failure does not warrant worktree deletion, so run hook after * is_junk is cleared, but do return appropriate code when hook fails. */ - if (!ret && opts->checkout) { + if (!ret && opts->checkout && !opts->orphan) { struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL); @@ -605,6 +629,7 @@ static int add(int ac, const char **av, const char *prefix) char *path; const char *branch; const char *new_branch = NULL; + const char *orphan_branch = NULL; const char *opt_track = NULL; const char *lock_reason = NULL; int keep_locked = 0; @@ -616,6 +641,8 @@ static int add(int ac, const char **av, const char *prefix) N_("create a new branch")), OPT_STRING('B', NULL, &new_branch_force, N_("branch"), N_("create or reset a branch")), + OPT_STRING(0, "orphan", &orphan_branch, N_("branch"), + N_("new unparented branch")), OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")), OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")), OPT_BOOL(0, "lock", &keep_locked, N_("keep the new working tree locked")), @@ -633,8 +660,20 @@ static int add(int ac, const char **av, const char *prefix) memset(&opts, 0, sizeof(opts)); opts.checkout = 1; ac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0); + opts.orphan = !!orphan_branch; if (!!opts.detach + !!new_branch + !!new_branch_force > 1) die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach"); + if (!!opts.detach + !!opts.orphan + !!new_branch + !!new_branch_force > 1) + die(_("options '%s', '%s', '%s', and '%s' cannot be used together"), + "-b", "-B", "--orphan", "--detach"); + if (opts.orphan && opt_track) + die(_("'%s' and '%s' cannot be used together"), "--orphan", "--track"); + if (opts.orphan && !opts.checkout) + die(_("'%s' and '%s' cannot be used together"), "--orphan", + "--no-checkout"); + if (opts.orphan && ac == 2) + die(_("'%s' and '%s' cannot be used together"), "--orphan", + _("<commit-ish>")); if (lock_reason && !keep_locked) die(_("the option '%s' requires '%s'"), "--reason", "--lock"); if (lock_reason) @@ -663,7 +702,9 @@ static int add(int ac, const char **av, const char *prefix) strbuf_release(&symref); } - if (ac < 2 && !new_branch && !opts.detach) { + if (opts.orphan) { + new_branch = orphan_branch; + } else if (ac < 2 && !new_branch && !opts.detach) { const char *s = dwim_branch(path, &new_branch); if (s) branch = s; @@ -686,7 +727,11 @@ static int add(int ac, const char **av, const char *prefix) if (!opts.quiet) print_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force); - if (new_branch) { + if (opts.orphan) { + branch = new_branch; + } else if (!lookup_commit_reference_by_name(branch)) { + die(_("invalid reference: %s"), branch); + } else if (new_branch) { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; strvec_push(&cp.args, "branch"); diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index dd729a00d8..45f896dcd0 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -312,6 +312,11 @@ test_wt_add_excl () { test_wt_add_excl -b poodle -B poodle bamboo main test_wt_add_excl -b poodle --detach bamboo main test_wt_add_excl -B poodle --detach bamboo main +test_wt_add_excl -B poodle --orphan poodle bamboo +test_wt_add_excl -b poodle --orphan poodle bamboo +test_wt_add_excl --orphan poodle --detach bamboo +test_wt_add_excl --orphan poodle --no-checkout bamboo +test_wt_add_excl --orphan poodle bamboo main test_expect_success '"add -B" fails if the branch is checked out' ' git rev-parse newmain >before && @@ -333,6 +338,46 @@ test_expect_success 'add --quiet' ' test_must_be_empty actual ' +test_expect_success '"add --orphan"' ' + test_when_finished "git worktree remove -f -f orphandir" && + git worktree add --orphan neworphan orphandir && + echo refs/heads/neworphan >expected && + git -C orphandir symbolic-ref HEAD >actual && + test_cmp expected actual +' + +test_expect_success '"add --orphan" fails if the branch already exists' ' + test_when_finished "git branch -D existingbranch" && + test_when_finished "git worktree remove -f -f orphandir" && + git worktree add -b existingbranch orphandir main && + test_must_fail git worktree add --orphan existingbranch orphandir2 && + test_path_is_missing orphandir2 +' + +test_expect_success '"add --orphan" with empty repository' ' + test_when_finished "rm -rf empty_repo" && + echo refs/heads/newbranch >expected && + GIT_DIR="empty_repo" git init --bare && + git -C empty_repo worktree add --orphan newbranch worktreedir && + git -C empty_repo/worktreedir symbolic-ref HEAD >actual && + test_cmp expected actual +' + +test_expect_success '"add" worktree with orphan branch and lock' ' + git worktree add --lock --orphan orphanbr orphan-with-lock && + test_when_finished "git worktree unlock orphan-with-lock || :" && + test -f .git/worktrees/orphan-with-lock/locked +' + +test_expect_success '"add" worktree with orphan branch, lock, and reason' ' + lock_reason="why not" && + git worktree add --detach --lock --reason "$lock_reason" orphan-with-lock-reason main && + test_when_finished "git worktree unlock orphan-with-lock-reason || :" && + test -f .git/worktrees/orphan-with-lock-reason/locked && + echo "$lock_reason" >expect && + test_cmp expect .git/worktrees/orphan-with-lock-reason/locked +' + test_expect_success 'local clone from linked checkout' ' git clone --local here here-clone && ( cd here-clone && git fsck ) @@ -449,6 +494,14 @@ setup_remote_repo () { ) } +test_expect_success '"add" <path> <remote/branch> w/ no HEAD' ' + test_when_finished rm -rf repo_upstream repo_local foo && + setup_remote_repo repo_upstream repo_local && + git -C repo_local config --bool core.bare true && + git -C repo_local branch -D main && + git -C repo_local worktree add ./foo repo_upstream/foo +' + test_expect_success '--no-track avoids setting up tracking' ' test_when_finished rm -rf repo_upstream repo_local foo && setup_remote_repo repo_upstream repo_local &&