While working with the worktree based git workflow, I realised that setting up a new git repository required switching between the traditional and worktree based workflows. Searching online I found a SO answer [1] which seemed to support this and which indicated that adding support for this should not be technically difficult. This patchset has two parts: * adding `-B` to the usage docs (noticed during dev and it seemed too small to justify a separate submission) * adding orphan branch functionality (as is present in `git-switch`) to `git-worktree-add` Changes from v2: * Changed orphan creation behavior to match `git switch --orphan` instead of `git checkout --orphan` [2][3]. As a result `--orphan` no longer accepts a `<commit-ish>` and creates the orphan branch with a clean working directory. * Removed the `opts.implicit` flag as it is no longer needed and `opts.orphan_branch` can be used instead. * No longer set `opts.force` when creating an orphan branch (as checkout can no longer fail in a way that `--force` would prevent). * Updated tests to no longer provide a `<commit-ish>`. * Removed no longer relevant test. * Added additional cleanup to tests. 1. https://stackoverflow.com/a/68717229/15064705/ 2. https://lore.kernel.org/git/CAPig+cSVzewXpk+eDSC-W-+Q8X_7ikZXXeSQbmpHBcdLCU5svw@xxxxxxxxxxxxxx/ 3. https://lore.kernel.org/git/20221110212132.3se4imsksjo3gsso@phi/ Jacob Abel (2): worktree add: Include -B in usage docs worktree add: add --orphan flag Documentation/git-worktree.txt | 14 +++++++- builtin/worktree.c | 64 ++++++++++++++++++++++++++++++---- t/t2400-worktree-add.sh | 45 ++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 8 deletions(-) Range-diff against v2: 1: f35d78cfb4 = 1: f35d78cfb4 worktree add: Include -B in usage docs 2: 653be67e8a ! 2: c040c87c6d worktree add: add --orphan flag @@ Commit message worktree add: add --orphan flag Adds support for creating an orphan branch when adding a new worktree. - This functionality is equivalent to git checkout's --orphan flag. + 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 @@ Documentation/git-worktree.txt: exist, a new branch based on `HEAD` is automatic command will refuse to create the worktree (unless `--force` is used). ++ +------------ -+$ git worktree add --orphan <branch> <path> [<commit-ish>] ++$ git worktree add --orphan <branch> <path> +------------ ++ -+Create a worktree containing an orphan branch named `<branch>` based -+on `<commit-ish>`. If `<commit-ish>` is not specified, the new orphan branch -+will be created based on `HEAD`. -++ -+Note that unlike with `-b` or `-B`, this operation will succeed even if -+`<commit-ish>` is a branch that is currently checked out somewhere else. ++Create a worktree containing an orphan branch named `<branch>` with a ++clean working directory. See `--orphan` in linkgit:git-switch[1] for ++more details. list:: @@ Documentation/git-worktree.txt: This can also be set up as the default behaviour +--orphan <new-branch>:: + With `add`, create a new orphan branch named `<new-branch>` in the new -+ worktree based on `<commit-ish>`. If `<commit-ish>` is omitted, it -+ defaults to `HEAD`. ++ worktree. See `--orphan` in linkgit:git-switch[1] for details. + --porcelain:: With `list`, output in an easy-to-parse format for scripts. @@ builtin/worktree.c: struct add_opts { int detach; int quiet; int checkout; -+ int implicit; + const char *orphan_branch; const char *keep_locked; }; @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam } commit = lookup_commit_reference_by_name(refname); - if (!commit) -+ if (!commit && !opts->implicit) ++ if (!commit && !opts->orphan_branch) die(_("invalid reference: %s"), refname); name = worktree_basename(path, &len); @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam if (ret) goto done; -- if (opts->checkout && -- (ret = checkout_worktree(opts, &child_env))) -- goto done; -+ if (opts->checkout) { -+ ret = checkout_worktree(opts, &child_env); -+ if (opts->orphan_branch && !ret) -+ ret = make_worktree_orphan(opts, &child_env); -+ if (ret) -+ goto done; -+ } - - is_junk = 0; - FREE_AND_NULL(junk_work_tree); ++ if (opts->orphan_branch && ++ (ret = make_worktree_orphan(opts, &child_env))) ++ goto done; ++ + if (opts->checkout && + (ret = checkout_worktree(opts, &child_env))) + goto done; @@ builtin/worktree.c: 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. @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix) 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")), @@ builtin/worktree.c: 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); -- if (!!opts.detach + !!new_branch + !!new_branch_force > 1) -- die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach"); -+ opts.implicit = ac < 2; -+ + 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 + !!new_branch + !!new_branch_force + !!opts.orphan_branch > 1) + die(_("options '%s', '%s', '%s', and '%s' cannot be used together"), + "-b", "-B", "--orphan", "--detach"); @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix) die(_("the option '%s' requires '%s'"), "--reason", "--lock"); if (lock_reason) @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix) - usage_with_options(git_worktree_add_usage, options); - - path = prefix_filename(prefix, av[0]); -- branch = ac < 2 ? "HEAD" : av[1]; -+ branch = opts.implicit ? "HEAD" : av[1]; - if (!strcmp(branch, "-")) branch = "@{-1}"; @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix) + * are staged, opts.orphan_branch should be treated as both a boolean + * indicating that `--orphan` was selected and as the name of the new + * orphan branch from this point on. -+ * -+ * When creating a new orphan, force checkout regardless of whether -+ * the existing branch is already checked out. + */ + if (opts.orphan_branch) { + new_branch = opts.orphan_branch; -+ opts.force = 1; + } + + if (ac < 2 && !new_branch && !opts.detach && !opts.orphan_branch) { @@ t/t2400-worktree-add.sh: test_expect_success '"add" -B/--detach mutually exclusi ' +test_expect_success '"add" --orphan/-b mutually exclusive' ' -+ test_must_fail git worktree add --orphan poodle -b poodle bamboo main ++ test_must_fail git worktree add --orphan poodle -b poodle bamboo +' + +test_expect_success '"add" --orphan/-B mutually exclusive' ' -+ test_must_fail git worktree add --orphan poodle -B poodle bamboo main ++ test_must_fail git worktree add --orphan poodle -B poodle bamboo +' + +test_expect_success '"add" --orphan/--detach mutually exclusive' ' -+ test_must_fail git worktree add --orphan poodle --detach bamboo main ++ test_must_fail git worktree add --orphan poodle --detach bamboo +' + +test_expect_success '"add" --orphan/--no-checkout mutually exclusive' ' -+ test_must_fail git worktree add --orphan poodle --no-checkout bamboo main ++ test_must_fail git worktree add --orphan poodle --no-checkout bamboo +' + +test_expect_success '"add" -B/--detach mutually exclusive' ' @@ t/t2400-worktree-add.sh: test_expect_success 'add --quiet' ' +test_expect_success '"add --orphan"' ' + test_when_finished "git worktree remove -f -f orphandir" && -+ git worktree add --orphan neworphan orphandir main && ++ git worktree add --orphan neworphan orphandir && + echo refs/heads/neworphan >expected && + git -C orphandir symbolic-ref HEAD >actual && -+ test_cmp expected actual && -+ git -C orphandir diff main ++ 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 main && ++ test_must_fail git worktree add --orphan existingbranch orphandir2 && + test ! -d orphandir2 +' + -+test_expect_success '"add --orphan" fails if the commit-ish doesnt exist' ' -+ test_must_fail git worktree add --orphan badcommitish orphandir eee2222 && -+ test ! -d orphandir -+' -+ +test_expect_success '"add --orphan" with empty repository' ' + test_when_finished "rm -rf empty_repo" && + echo refs/heads/newbranch >expected && -- 2.37.4