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-checkout`) to `git-worktree-add` Changes from v1: * Reverted change to `checkout_worktree()` [2]. Instead we now change the HEAD after `git reset --hard` with a call to `git symbolic-ref`. * Removed noise-changes and weird formatting from the patchset. * Updated tests and squashed them into the main `--orphan` patch as requested [3]. * Improved test cleanup. * Clarify comments regarding `new_branch_force` and `opts.orphan_branch` [4]. 1. https://stackoverflow.com/a/68717229/15064705 2. https://lore.kernel.org/git/20221104010242.11555-3-jacobabel@xxxxxxxxxx/ 3. https://lore.kernel.org/git/221104.86k04bzeaa.gmgdl@xxxxxxxxxxxxxxxxxxx/ 4. https://lore.kernel.org/git/20221104164147.izizapz5mdwwalxu@phi/ Jacob Abel (2): worktree add: Include -B in usage docs worktree add: add --orphan flag Documentation/git-worktree.txt | 18 +++++++- builtin/worktree.c | 81 ++++++++++++++++++++++++++++------ t/t2400-worktree-add.sh | 50 +++++++++++++++++++++ 3 files changed, 135 insertions(+), 14 deletions(-) Range-diff against v1: 1: d74a58b3bb ! 1: f35d78cfb4 worktree add: Include -B in usage docs @@ Documentation/git-worktree.txt: SYNOPSIS ## builtin/worktree.c ## @@ - #include "worktree.h" - #include "quote.h" --#define BUILTIN_WORKTREE_ADD_USAGE \ -+#define BUILTIN_WORKTREE_ADD_USAGE \ + #define BUILTIN_WORKTREE_ADD_USAGE \ N_("git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]\n" \ - " [-b <new-branch>] <path> [<commit-ish>]") + " [[-b | -B] <new-branch>] <path> [<commit-ish>]") 2: 4e56a9494e < -: ---------- builtin/worktree.c: Update checkout_worktree() to use git-worktree 3: b8b4098ff5 ! 2: 653be67e8a worktree add: add --orphan flag @@ Documentation/git-worktree.txt: This can also be set up as the default behaviour ## builtin/worktree.c ## @@ - #define BUILTIN_WORKTREE_ADD_USAGE \ + #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 | --orphan] <new-branch>] <path> [<commit-ish>]") @@ builtin/worktree.c: struct add_opts { }; @@ builtin/worktree.c: static int checkout_worktree(const struct add_opts *opts, - strvec_pushl(&cp.args, "checkout", "--no-recurse-submodules", NULL); - if (opts->quiet) - strvec_push(&cp.args, "--quiet"); -+ if (opts->orphan_branch) -+ strvec_pushl(&cp.args, "--orphan", opts->orphan_branch, NULL); - strvec_pushv(&cp.env, child_env->v); return run_command(&cp); } + ++static int make_worktree_orphan(const struct add_opts *opts, ++ struct strvec *child_env) ++{ ++ int ret; ++ struct strbuf symref = STRBUF_INIT; ++ struct child_process cp = CHILD_PROCESS_INIT; ++ cp.git_cmd = 1; ++ ++ validate_new_branchname(opts->orphan_branch, &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); ++ ret = run_command(&cp); ++ strbuf_release(&symref); ++ return ret; ++} ++ + static int add_worktree(const char *path, const char *refname, + const struct add_opts *opts) + { @@ builtin/worktree.c: 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->implicit) die(_("invalid reference: %s"), refname); @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam strvec_pushl(&cp.args, "symbolic-ref", "HEAD", symref.buf, NULL); if (opts->quiet) +@@ builtin/worktree.c: static int add_worktree(const char *path, const char *refname, + 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); @@ 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_worktree(const char *path, const char *refnam strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL); @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix) - const char *opt_track = NULL; - const char *lock_reason = NULL; - int keep_locked = 0; -+ - struct option options[] = { -- OPT__FORCE(&opts.force, -- N_("checkout <branch> even if already checked out in other worktree"), -- PARSE_OPT_NOCOMPLETE), -+ OPT__FORCE( -+ &opts.force, -+ N_("checkout <branch> even if already checked out in other worktree"), -+ PARSE_OPT_NOCOMPLETE), - OPT_STRING('b', NULL, &new_branch, N_("branch"), N_("create a new branch")), OPT_STRING('B', NULL, &new_branch_force, N_("branch"), N_("create or reset a 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")), + OPT_STRING(0, "orphan", &opts.orphan_branch, N_("branch"), -+ N_("create a 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")), - OPT_STRING(0, "reason", &lock_reason, N_("string"), - N_("reason for locking")), - OPT__QUIET(&opts.quiet, N_("suppress progress reporting")), - OPT_PASSTHRU(0, "track", &opt_track, NULL, - N_("set up tracking mode (see git-branch(1))"), - PARSE_OPT_NOARG | PARSE_OPT_OPTARG), -- OPT_BOOL(0, "guess-remote", &guess_remote, -- N_("try to match the new branch name with a remote-tracking branch")), -+ OPT_BOOL( -+ 0, "guess-remote", &guess_remote, -+ N_("try to match the new branch name with a remote-tracking branch")), - OPT_END() - }; - ++ 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")), +@@ 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 + -+ !!opts.orphan_branch > -+ 1) ++ 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"); + if (opts.orphan_branch && opt_track) -+ die(_("'%s' cannot be used with '%s'"), "--orphan", "--track"); ++ die(_("'%s' and '%s' cannot be used together"), "--orphan", "--track"); + if (opts.orphan_branch && !opts.checkout) -+ die(_("'%s' cannot be used with '%s'"), "--orphan", ++ die(_("'%s' and '%s' cannot be used together"), "--orphan", + "--no-checkout"); if (lock_reason && !keep_locked) die(_("the option '%s' requires '%s'"), "--reason", "--lock"); @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix) branch = "@{-1}"; + /* -+ * From here on, new_branch will contain the branch to be checked out, -+ * and new_branch_force and opts.orphan_branch will tell us which one of -+ * -b/-B/--orphan is being used. ++ * When creating a new branch, new_branch now contains the branch to ++ * create. ++ * ++ * Past this point, new_branch_force can be treated solely as a ++ * boolean flag to indicate whether `-B` was selected. + */ if (new_branch_force) { struct strbuf symref = STRBUF_INIT; @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix) strbuf_release(&symref); } +- if (ac < 2 && !new_branch && !opts.detach) { ++ /* ++ * As the orphan cannot be created until the contents of branch ++ * 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) { ++ if (ac < 2 && !new_branch && !opts.detach && !opts.orphan_branch) { const char *s = dwim_branch(path, &new_branch); if (s) + branch = s; @@ builtin/worktree.c: 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); @@ builtin/worktree.c: static int add(int ac, const char **av, const char *prefix) struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; strvec_push(&cp.args, "branch"); + + ## t/t2400-worktree-add.sh ## +@@ t/t2400-worktree-add.sh: test_expect_success '"add" -B/--detach mutually exclusive' ' + test_must_fail git worktree add -B poodle --detach bamboo main + ' + ++test_expect_success '"add" --orphan/-b mutually exclusive' ' ++ test_must_fail git worktree add --orphan poodle -b poodle bamboo main ++' ++ ++test_expect_success '"add" --orphan/-B mutually exclusive' ' ++ test_must_fail git worktree add --orphan poodle -B poodle bamboo main ++' ++ ++test_expect_success '"add" --orphan/--detach mutually exclusive' ' ++ test_must_fail git worktree add --orphan poodle --detach bamboo main ++' ++ ++test_expect_success '"add" --orphan/--no-checkout mutually exclusive' ' ++ test_must_fail git worktree add --orphan poodle --no-checkout bamboo main ++' ++ ++test_expect_success '"add" -B/--detach mutually exclusive' ' ++ test_must_fail git worktree add -B poodle --detach bamboo main ++' ++ + test_expect_success '"add -B" fails if the branch is checked out' ' + git rev-parse newmain >before && + test_must_fail git worktree add -B newmain bamboo main && +@@ t/t2400-worktree-add.sh: 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 main && ++ echo refs/heads/neworphan >expected && ++ git -C orphandir symbolic-ref HEAD >actual && ++ test_cmp expected actual && ++ git -C orphandir diff main ++' ++ ++test_expect_success '"add --orphan" fails if the branch already exists' ' ++ 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 ! -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 && ++ 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 'local clone from linked checkout' ' + git clone --local here here-clone && + ( cd here-clone && git fsck ) 4: a167f440c3 < -: ---------- worktree add: Add unit tests for --orphan -- 2.37.4