diff mbox series

[3/4] worktree add: add --orphan flag

Message ID 20221104010242.11555-4-jacobabel@nullpo.dev (mailing list archive)
State Superseded
Headers show
Series worktree: Support `--orphan` when creating new worktrees | expand

Commit Message

Jacob Abel Nov. 4, 2022, 1:03 a.m. UTC
Adds support for creating an orphan branch when adding a new worktree.
This functionality is equivalent to git checkout'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. Example usage included below.

$ GIT_DIR=".git" git init --bare
$ git worktree add --orphan master master/

Signed-off-by: Jacob Abel <jacobabel@nullpo.dev>
---
 Documentation/git-worktree.txt | 18 ++++++++-
 builtin/worktree.c             | 68 +++++++++++++++++++++++++---------
 2 files changed, 68 insertions(+), 18 deletions(-)

--
2.37.4

Comments

Ævar Arnfjörð Bjarmason Nov. 4, 2022, 1:33 a.m. UTC | #1
On Fri, Nov 04 2022, Jacob Abel wrote:

>  	commit = lookup_commit_reference_by_name(refname);
> -	if (!commit)
> +

Here.

> +	if (!commit && !opts->implicit)
>  		die(_("invalid reference: %s"), refname);
>
>  	name = worktree_basename(path, &len);
> @@ -482,10 +487,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);

And here we have a stray style change, in this case conforming to our
CodingGuidelines (it's agnostic on the former), but IMO better to keep
this out, or split it into a "various style stuff" commit, makes this
harder to review...

> -	else {
> +	} else {
>  		strvec_pushl(&cp.args, "symbolic-ref", "HEAD",
>  			     symref.buf, NULL);
>  		if (opts->quiet)
> @@ -516,7 +521,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_branch) {
>  		struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
>
>  		strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
> @@ -608,33 +613,52 @@ 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;
> +

ditto, we don't usually \n\n split up varibale decls.

>  	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),

This is just a stray refactoring of existing code to not-our-usual-style
(first arg is on the same line as the "(", rest aligned with "(").

>  		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")),

Ditto, these look like they're too-long in the pre-image, but please
resist re-flowing existing code while at it.

>  		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")),

ditto.

>  		OPT_END()
>  	};
>
>  	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)

The continued "if" is mis-indented, and that "1" is on a line of its
own...
Jacob Abel Nov. 4, 2022, 4:11 a.m. UTC | #2
On 22/11/04 02:33AM, Ævar Arnfjörð Bjarmason wrote:
>
> > ...
>
> Here.
>
> > ...
>
> And here we have a stray style change, in this case conforming to our
> CodingGuidelines (it's agnostic on the former), but IMO better to keep
> this out, or split it into a "various style stuff" commit, makes this
> harder to review...

I believe those changes were introduced when I ran `make style`. I can revert
these changes in v2.

> > ...
>
> ditto, we don't usually \n\n split up varibale decls.

Noted. Will fix in v2.

> > ...
>
> This is just a stray refactoring of existing code to not-our-usual-style
> (first arg is on the same line as the "(", rest aligned with "(").
>
> > ...
>
> Ditto, these look like they're too-long in the pre-image, but please
> resist re-flowing existing code while at it.
>
> > ...
>
> ditto.

Ditto the comment above regarding `make style`.

> > +	if (!!opts.detach + !!new_branch + !!new_branch_force +
> > +		    !!opts.orphan_branch >
> > +	    1)
>
> The continued "if" is mis-indented, and that "1" is on a line of its
> own...

Ditto the comment above regarding `make style`. Also I'm not exactly sure
what the tool tried to do here but I was initially hesitant to override
the formatter.
Eric Sunshine Nov. 4, 2022, 5:03 a.m. UTC | #3
On Thu, Nov 3, 2022 at 9:07 PM Jacob Abel <jacobabel@nullpo.dev> wrote:
> Adds support for creating an orphan branch when adding a new worktree.
> This functionality is equivalent to git checkout's --orphan flag.
> [...]
> Signed-off-by: Jacob Abel <jacobabel@nullpo.dev>
> ---
> diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
> @@ -95,6 +95,17 @@ exist, a new branch based on `HEAD` is automatically created as if
> +------------
> +$ git worktree add --orphan <branch> <path> [<commit-ish>]
> +------------
> ++
> +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.

Are we sure we want to be modeling this after `git checkout --orphan`?
If I understand correctly, that option has long been considered (by
some) too clunky, which is why `git switch --orphan` was simplified to
accept only a branch name but no commit-ish, and to start the orphan
branch with an empty directory. My own feeling is that modeling it
after `git switch --orphan` is probably the way to go...

> @@ -222,6 +233,11 @@ This can also be set up as the default behaviour by using the
> +--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`.

...which would mean that this would no longer talk about `<commit-ish>`.

> diff --git a/builtin/worktree.c b/builtin/worktree.c
> @@ -608,33 +613,52 @@ static int add(int ac, const char **av, const char *prefix)
>         struct option options[] = {
> +               OPT_STRING(0, "orphan", &opts.orphan_branch, N_("branch"),
> +                          N_("create a new unparented branch")),

The short help message for `git switch --orphan` and `git checkout
--orphan` say simply "new unparented branch", so this message should
probably follow suit (or consistency and to ease the job of
translators).

> -       if (!!opts.detach + !!new_branch + !!new_branch_force > 1)
> -               die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach");
> +               die(_("options '%s', '%s', '%s', and '%s' cannot be used together"),
> +                   "-b", "-B", "--orphan", "--detach");

Good to see this interlock updated for --orphan.

> +       if (opts.orphan_branch && opt_track)
> +               die(_("'%s' cannot be used with '%s'"), "--orphan", "--track");
> +       if (opts.orphan_branch && !opts.checkout)
> +               die(_("'%s' cannot be used with '%s'"), "--orphan",
> +                   "--no-checkout");

Good to have these additional interlocks. I think, however, for the
sake of translators, we should use the same terminology as the
existing message above (i.e. "options ... cannot be used together").

> +       /*
> +        * 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.
> +        */

This can probably be worded a bit differently to make it clear that
from this point onward, those other variables are interpreted as if
they are booleans. Moreover, we can make this even clearer by
following the example of -B in which (by necessity due to
parse-options) the local variable in add() is a `const char *`, but
its counterpart in `struct add_opts` is a boolean (int).
Jacob Abel Nov. 4, 2022, 4:41 p.m. UTC | #4
On 22/11/04 01:03AM, Eric Sunshine wrote:
> On Thu, Nov 3, 2022 at 9:07 PM Jacob Abel <jacobabel@nullpo.dev> wrote:
> > ...
>
> Are we sure we want to be modeling this after `git checkout --orphan`?
> If I understand correctly, that option has long been considered (by
> some) too clunky, which is why `git switch --orphan` was simplified to
> accept only a branch name but no commit-ish, and to start the orphan
> branch with an empty directory. My own feeling is that modeling it
> after `git switch --orphan` is probably the way to go...

I would argue that the `git checkout --orphan` command format is preferable to
`git switch --orphan` when creating new worktrees. Reason being that in many
cases (except when working in a new repo), if you are trying to create a
worktree from an orphan you will be doing it with a different commit-ish
currently checked out in your worktree than the one you want to use for the
orphan (or you aren't in any worktree).

Requiring the commit-ish to be inferred would limit the user to checking out
an orphan from an existing worktree (in which case they could just create a
new worktree normally and use `git switch --orphan` to move that to an orphan
branch).

> > ...
>
> The short help message for `git switch --orphan` and `git checkout
> --orphan` say simply "new unparented branch", so this message should
> probably follow suit (or consistency and to ease the job of
> translators).

Noted.

> > ...
>
> Good to have these additional interlocks. I think, however, for the
> sake of translators, we should use the same terminology as the
> existing message above (i.e. "options ... cannot be used together").

Noted.

>
> > +       /*
> > +        * 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.
> > +        */
>
> This can probably be worded a bit differently to make it clear that
> from this point onward, those other variables are interpreted as if
> they are booleans. Moreover, we can make this even clearer by
> following the example of -B in which (by necessity due to
> parse-options) the local variable in add() is a `const char *`, but
> its counterpart in `struct add_opts` is a boolean (int).

The one thing to note with `opts.orphan_branch` is that it is used as both a
string and a boolean later in `add_worktree()`. Since orphan branches don't
have any commits tied to them, we have to check out the original commit-ish
in `add_worktree()` and then convert it to an orphan of name
`opts.orphan_branch` instead of creating the branch prior to entering
`add_worktree()` (as is done for `-B` and `-b`).

I do agree that the comment should probably be re-worded. I'll update it to
be clearer in v2.
Eric Sunshine Nov. 10, 2022, 4:13 a.m. UTC | #5
On Fri, Nov 4, 2022 at 12:42 PM Jacob Abel <jacobabel@nullpo.dev> wrote:
> On 22/11/04 01:03AM, Eric Sunshine wrote:
> > On Thu, Nov 3, 2022 at 9:07 PM Jacob Abel <jacobabel@nullpo.dev> wrote:
> > Are we sure we want to be modeling this after `git checkout --orphan`?
> > If I understand correctly, that option has long been considered (by
> > some) too clunky, which is why `git switch --orphan` was simplified to
> > accept only a branch name but no commit-ish, and to start the orphan
> > branch with an empty directory. My own feeling is that modeling it
> > after `git switch --orphan` is probably the way to go...
>
> I would argue that the `git checkout --orphan` command format is preferable to
> `git switch --orphan` when creating new worktrees. Reason being that in many
> cases (except when working in a new repo), if you are trying to create a
> worktree from an orphan you will be doing it with a different commit-ish
> currently checked out in your worktree than the one you want to use for the
> orphan (or you aren't in any worktree).

I guess I'm not understanding the use-case being described here or
that this series is trying to address. In my own experience, the very,
very few times I've used --orphan was when I needed a branch with no
existing history (i.e. "orphan") and with no existing files. For that
use-case, `git switch --orphan` is ideal, whereas `git checkout
--orphan` is a bother since it requires manually removing all content
from the directory and clearing the index.

> Requiring the commit-ish to be inferred would limit the user to checking out
> an orphan from an existing worktree (in which case they could just create a
> new worktree normally and use `git switch --orphan` to move that to an orphan
> branch).

I'm not following what you mean by inferred commit-ish. `git switch
--orphan` does not infer any commit-ish; it starts the orphaned branch
with an empty directory, hence there is no commit-ish involved.

The `git switch --orphan` behavior was intentionally implemented to
"fix" what has long been considered (by some) a UX botch in the
behavior of `git checkout --orphan`. It was argued that in the vast
majority of cases, people wanted an orphan branch to mean both "no
history" and "no files". So, in that sense, it feels like a step
backward to adopt `git checkout --orphan` when introducing `git
worktree --orphan`.

But, as I said, I'm genuinely not grasping your use-case, so I'm
having trouble understanding why you consider `git checkout --orphan`
a better model. If you can elaborate your use-case more thoroughly,
perhaps it would help (at least me).
Jacob Abel Nov. 10, 2022, 9:21 p.m. UTC | #6
On 22/11/09 11:13PM, Eric Sunshine wrote:
> On Fri, Nov 4, 2022 at 12:42 PM Jacob Abel <jacobabel@nullpo.dev> wrote:
> > On 22/11/04 01:03AM, Eric Sunshine wrote:
> > > On Thu, Nov 3, 2022 at 9:07 PM Jacob Abel <jacobabel@nullpo.dev> wrote:
> > > Are we sure we want to be modeling this after `git checkout --orphan`?
> > > If I understand correctly, that option has long been considered (by
> > > some) too clunky, which is why `git switch --orphan` was simplified to
> > > accept only a branch name but no commit-ish, and to start the orphan
> > > branch with an empty directory. My own feeling is that modeling it
> > > after `git switch --orphan` is probably the way to go...
> >
> > I would argue that the `git checkout --orphan` command format is preferable to
> > `git switch --orphan` when creating new worktrees. Reason being that in many
> > cases (except when working in a new repo), if you are trying to create a
> > worktree from an orphan you will be doing it with a different commit-ish
> > currently checked out in your worktree than the one you want to use for the
> > orphan (or you aren't in any worktree).
>
> I guess I'm not understanding the use-case being described here or
> that this series is trying to address. In my own experience, the very,
> very few times I've used --orphan was when I needed a branch with no
> existing history (i.e. "orphan") and with no existing files. For that
> use-case, `git switch --orphan` is ideal, whereas `git checkout
> --orphan` is a bother since it requires manually removing all content
> from the directory and clearing the index.
>
> > Requiring the commit-ish to be inferred would limit the user to checking out
> > an orphan from an existing worktree (in which case they could just create a
> > new worktree normally and use `git switch --orphan` to move that to an orphan
> > branch).
>
> I'm not following what you mean by inferred commit-ish. `git switch
> --orphan` does not infer any commit-ish; it starts the orphaned branch
> with an empty directory, hence there is no commit-ish involved.
>
> The `git switch --orphan` behavior was intentionally implemented to
> "fix" what has long been considered (by some) a UX botch in the
> behavior of `git checkout --orphan`. It was argued that in the vast
> majority of cases, people wanted an orphan branch to mean both "no
> history" and "no files". So, in that sense, it feels like a step
> backward to adopt `git checkout --orphan` when introducing `git
> worktree --orphan`.
>
> But, as I said, I'm genuinely not grasping your use-case, so I'm
> having trouble understanding why you consider `git checkout --orphan`
> a better model. If you can elaborate your use-case more thoroughly,
> perhaps it would help (at least me).

Ah I see where my misunderstanding was. I have significantly less experience
with `git switch` vs `git checkout` so prior to responding I was trying to
understand the difference in behaviour and I ended up misunderstanding what
`git switch --orphan` was doing.

I wrongly assumed that `git switch --orphan` was doing the same thing as
`git checkout --orphan` but using the currently checked out branch.
Additionally I had assumed that there was an important reason for being able
to create orphans from existing branches and that not being able to select
which branch to use would somehow be removing functionality.

After re-reading your replies, I can see that this is not the case and that
I jumped the gun on my reply prior to doing my research properly. I will make
the requested change (moving from `git checkout` to `git switch` semantics)
for v3. Apologies for the misunderstanding.
diff mbox series

Patch

diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 4dd658012b..92bd75564f 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -10,7 +10,7 @@  SYNOPSIS
 --------
 [verse]
 'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]]
-		   [[-b | -B] <new-branch>] <path> [<commit-ish>]
+		   [[-b | -B | --orphan] <new-branch>] <path> [<commit-ish>]
 'git worktree list' [-v | --porcelain [-z]]
 'git worktree lock' [--reason <string>] <worktree>
 'git worktree move' <worktree> <new-path>
@@ -95,6 +95,17 @@  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> [<commit-ish>]
+------------
++
+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.

 list::

@@ -222,6 +233,11 @@  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`, 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`.
+
 --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 d40f771848..70f319a6b5 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -17,7 +17,7 @@ 

 #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>]")
 #define BUILTIN_WORKTREE_LIST_USAGE \
 	N_("git worktree list [-v | --porcelain [-z]]")
 #define BUILTIN_WORKTREE_LOCK_USAGE \
@@ -90,6 +90,8 @@  struct add_opts {
 	int detach;
 	int quiet;
 	int checkout;
+	int implicit;
+	const char *orphan_branch;
 	const char *keep_locked;
 };

@@ -360,6 +362,8 @@  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);
 }
@@ -393,7 +397,8 @@  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);

 	name = worktree_basename(path, &len);
@@ -482,10 +487,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)
@@ -516,7 +521,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_branch) {
 		struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;

 		strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
@@ -608,33 +613,52 @@  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()
 	};

 	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)
+		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");
+	if (opts.orphan_branch && !opts.checkout)
+		die(_("'%s' cannot be used with '%s'"), "--orphan",
+		    "--no-checkout");
 	if (lock_reason && !keep_locked)
 		die(_("the option '%s' requires '%s'"), "--reason", "--lock");
 	if (lock_reason)
@@ -646,11 +670,16 @@  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}";

+	/*
+	 * 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.
+	 */
 	if (new_branch_force) {
 		struct strbuf symref = STRBUF_INIT;

@@ -663,6 +692,11 @@  static int add(int ac, const char **av, const char *prefix)
 		strbuf_release(&symref);
 	}

+	if (opts.orphan_branch) {
+		new_branch = opts.orphan_branch;
+		opts.force = 1;
+	}
+
 	if (ac < 2 && !new_branch && !opts.detach) {
 		const char *s = dwim_branch(path, &new_branch);
 		if (s)
@@ -686,7 +720,7 @@  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 (new_branch && !opts.orphan_branch) {
 		struct child_process cp = CHILD_PROCESS_INIT;
 		cp.git_cmd = 1;
 		strvec_push(&cp.args, "branch");