threads / rfc / 39750

RFC patchworktree: replace "checkout --to" with "worktree new"

Subject: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

## tl;dr

27 messages between Jun 30, 2015 and Jul 2, 2015. Diffs are folded; open one to read it.

replies: 26people: 5as markdown or json

Eric Sunshine· Jun 30, 2015, 04:56 UTC · lore

The command "git checkout --to <path>" is something of an anachronism, encompassing functionality somewhere between "checkout" and "clone". The introduction of the git-worktree command, however, provides a proper and intuitive place to house such functionality. Consequently, re-implement "git checkout --to" as "git worktree new".

As a side-effect, linked worktree creation becomes much more discoverable with its own dedicated command, whereas `--to` was easily overlooked amid the plethora of options recognized by git-checkout.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---

I've long felt that Duy's linked-worktree functionality was a bit oddly named as "git checkout --to", but, since I could never come up with a better name, I never made mention of it. However, with Duy's introduction of the git-worktree command[1], we now have a much more appropriate and discoverable place to house the "git checkout --to" functionality, and upon seeing his patch, I was ready to reply with the suggestion to relocate "git checkout --to" to "git worktree new", however, Junio beat me to it[2]. So, in response, this patch does exactly that. It applies atop [1].

This is primarily a code and documentation relocation patch, with minor new code added to builtin/worktree.c. Specifically:

* git-checkout.txt:"--to" description moved to git-worktree.txt:"new".
* git-checkout.txt:"MULTIPLE WORKING TREES" moved to
  git-worktree.txt:"DESCRIPTION", with "checkout --to" replaced by
  "worktree new" as necessary. git-worktree.txt could probably use a bit
  of reorganization, but that can be done as a separate patch.
* builtin/checkout.c:remove_junk() and remove_junk_on_signal() moved
  verbatim to builtin/worktree.c.
* builtin/checkout.c:prepare_linked_checkout() moved to
  builtin/worktree.c:new_worktree() nearly verbatim. The following small
  changes were needed (which might possibly be better done as separate
  preparatory patches):
  - The "no branch specified" check was dropped since git-worktree lacks
    the machinery for parsing git-checkout command-line arguments, and
    thus simply doesn't know if a branch/ref was provided, or in what
    form.  I'm not sure yet how to replace this check.
  - checkout.c:prepare_linked_checkout() (temporarily) fakes up a HEAD
    ref with a valid object-id in the new worktree to pacify
    is_git_directory(). It does so using the branch/ref from the
    command-line which it already resolved. worktree.c, however, doesn't
    have access to this information, so I instead added code to resolve
    and use HEAD for the fakement.
  - The "Enter %s (identifier %s)" message is suppressed in checkout.c
    if --quiet is specified, however, worktree.c does not have a --quiet
    option, so the message is printed unconditionally.
  - argv[] for the sub git-checkout invocation is hand-crafted in
    worktree.c rather than merely being re-used from the original "git
    checkout --to" as it was in checkout.c.
* builtin/worktree.c:new() is new. It recognizes a --force option ("git
  worktree new --force <path> <branch>") which allows a branch to be
  checked out in a new worktree even if already checked out in some
  other worktree (thus, mirroring the functionality of "git checkout
  --ignore-other-worktrees").
* t2025-checkout-to.sh became t2025-worktree-new.sh. I'm not sure if the
  test number still makes sense or if it should be changed, however, it
  resides alongside its t2026-prune-linked-checkouts.sh counterpart.

[1]: http://git.661346.n2.nabble.com/PATCH-worktree-new-place-for-git-prune-worktrees-tp7634619.html [2]: http://git.661346.n2.nabble.com/PATCH-worktree-new-place-for-git-prune-worktrees-tp7634619p7634638.html

 Documentation/git-checkout.txt                    |  72 ----------
 Documentation/git-worktree.txt                    |  79 ++++++++++-
 builtin/checkout.c                                | 152 +--------------------
 builtin/worktree.c                                | 157 ++++++++++++++++++++++
 t/{t2025-checkout-to.sh => t2025-worktree-new.sh} |  44 +++---
 t/t2026-prune-linked-checkouts.sh                 |   2 +-
 6 files changed, 260 insertions(+), 246 deletions(-)
 rename t/{t2025-checkout-to.sh => t2025-worktree-new.sh} (56%)
Show changes to 7 files +260 −244

Documentation/git-checkout.txt, Documentation/git-worktree.txt, builtin/checkout.c, builtin/worktree.c, t/t2025-checkout-to.sh, t/t2025-worktree-new.sh, t/t2026-prune-linked-checkouts.sh

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index d263a56..e19f03a 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -225,13 +225,6 @@ This means that you can use `git checkout -p` to selectively discard
 edits from your current working tree. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 
---to=<path>::
-	Check out a branch in a separate working directory at
-	`<path>`. A new working directory is linked to the current
-	repository, sharing everything except working directory
-	specific files such as HEAD, index... See "MULTIPLE WORKING
-	TREES" section for more information.
-
 --ignore-other-worktrees::
 	`git checkout` refuses when the wanted ref is already checked
 	out by another worktree. This option makes it check the ref
@@ -401,71 +394,6 @@ $ git reflog -2 HEAD # or
 $ git log -g -2 HEAD
 ------------
 
-MULTIPLE WORKING TREES
-----------------------
-
-A git repository can support multiple working trees, allowing you to check
-out more than one branch at a time.  With `git checkout --to` a new working
-tree is associated with the repository.  This new working tree is called a
-"linked working tree" as opposed to the "main working tree" prepared by "git
-init" or "git clone".  A repository has one main working tree (if it's not a
-bare repository) and zero or more linked working trees.
-
-Each linked working tree has a private sub-directory in the repository's
-$GIT_DIR/worktrees directory.  The private sub-directory's name is usually
-the base name of the linked working tree's path, possibly appended with a
-number to make it unique.  For example, when `$GIT_DIR=/path/main/.git` the
-command `git checkout --to /path/other/test-next next` creates the linked
-working tree in `/path/other/test-next` and also creates a
-`$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`
-if `test-next` is already taken).
-
-Within a linked working tree, $GIT_DIR is set to point to this private
-directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
-$GIT_COMMON_DIR is set to point back to the main working tree's $GIT_DIR
-(e.g. `/path/main/.git`). These settings are made in a `.git` file located at
-the top directory of the linked working tree.
-
-Path resolution via `git rev-parse --git-path` uses either
-$GIT_DIR or $GIT_COMMON_DIR depending on the path. For example, in the
-linked working tree `git rev-parse --git-path HEAD` returns
-`/path/main/.git/worktrees/test-next/HEAD` (not
-`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
-rev-parse --git-path refs/heads/master` uses
-$GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,
-since refs are shared across all working trees.
-
-See linkgit:gitrepository-layout[5] for more information. The rule of
-thumb is do not make any assumption about whether a path belongs to
-$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
-inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
-
-When you are done with a linked working tree you can simply delete it.
-The working tree's entry in the repository's $GIT_DIR/worktrees
-directory will eventually be removed automatically (see
-`gc.pruneworktreesexpire` in linkgit::git-config[1]), or you can run
-`git prune --worktrees` in the main or any linked working tree to
-clean up any stale entries in $GIT_DIR/worktrees.
-
-If you move a linked working directory to another file system, or
-within a file system that does not support hard links, you need to run
-at least one git command inside the linked working directory
-(e.g. `git status`) in order to update its entry in $GIT_DIR/worktrees
-so that it does not get automatically removed.
-
-To prevent a $GIT_DIR/worktrees entry from from being pruned (which
-can be useful in some situations, such as when the
-entry's working tree is stored on a portable device), add a file named
-'locked' to the entry's directory. The file contains the reason in
-plain text. For example, if a linked working tree's `.git` file points
-to `/path/main/.git/worktrees/test-next` then a file named
-`/path/main/.git/worktrees/test-next/locked` will prevent the
-`test-next` entry from being pruned.  See
-linkgit:gitrepository-layout[5] for details.
-
-Multiple checkout support for submodules is incomplete. It is NOT
-recommended to make multiple checkouts of a superproject.
-
 EXAMPLES
 --------
 
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 41103e5..8f13281 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -9,16 +9,85 @@ git-worktree - Manage multiple worktrees
 SYNOPSIS
 --------
 [verse]
+'git worktree new' [-f] <path> [<checkout-options>] <branch>
 'git worktree prune' [-n] [-v] [--expire <expire>]
 
 DESCRIPTION
 -----------
 
-Manage multiple worktrees attached to the same repository. These are
-created by the command `git checkout --to`.
+Manage multiple worktrees attached to the same repository.
+
+A git repository can support multiple working trees, allowing you to check
+out more than one branch at a time.  With `git worktree new` a new working
+tree is associated with the repository.  This new working tree is called a
+"linked working tree" as opposed to the "main working tree" prepared by "git
+init" or "git clone".  A repository has one main working tree (if it's not a
+bare repository) and zero or more linked working trees.
+
+Each linked working tree has a private sub-directory in the repository's
+$GIT_DIR/worktrees directory.  The private sub-directory's name is usually
+the base name of the linked working tree's path, possibly appended with a
+number to make it unique.  For example, when `$GIT_DIR=/path/main/.git` the
+command `git worktree new /path/other/test-next next` creates the linked
+working tree in `/path/other/test-next` and also creates a
+`$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`
+if `test-next` is already taken).
+
+Within a linked working tree, $GIT_DIR is set to point to this private
+directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
+$GIT_COMMON_DIR is set to point back to the main working tree's $GIT_DIR
+(e.g. `/path/main/.git`). These settings are made in a `.git` file located at
+the top directory of the linked working tree.
+
+Path resolution via `git rev-parse --git-path` uses either
+$GIT_DIR or $GIT_COMMON_DIR depending on the path. For example, in the
+linked working tree `git rev-parse --git-path HEAD` returns
+`/path/main/.git/worktrees/test-next/HEAD` (not
+`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
+rev-parse --git-path refs/heads/master` uses
+$GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,
+since refs are shared across all working trees.
+
+See linkgit:gitrepository-layout[5] for more information. The rule of
+thumb is do not make any assumption about whether a path belongs to
+$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
+inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
+
+When you are done with a linked working tree you can simply delete it.
+The working tree's entry in the repository's $GIT_DIR/worktrees
+directory will eventually be removed automatically (see
+`gc.pruneworktreesexpire` in linkgit::git-config[1]), or you can run
+`git prune --worktrees` in the main or any linked working tree to
+clean up any stale entries in $GIT_DIR/worktrees.
+
+If you move a linked working directory to another file system, or
+within a file system that does not support hard links, you need to run
+at least one git command inside the linked working directory
+(e.g. `git status`) in order to update its entry in $GIT_DIR/worktrees
+so that it does not get automatically removed.
+
+To prevent a $GIT_DIR/worktrees entry from from being pruned (which
+can be useful in some situations, such as when the
+entry's working tree is stored on a portable device), add a file named
+'locked' to the entry's directory. The file contains the reason in
+plain text. For example, if a linked working tree's `.git` file points
+to `/path/main/.git/worktrees/test-next` then a file named
+`/path/main/.git/worktrees/test-next/locked` will prevent the
+`test-next` entry from being pruned.  See
+linkgit:gitrepository-layout[5] for details.
+
+Multiple checkout support for submodules is incomplete. It is NOT
+recommended to make multiple checkouts of a superproject.
 
 COMMANDS
 --------
+new::
+
+Check out a branch in a separate working directory at
+`<path>`. A new working directory is linked to the current
+repository, sharing everything except working directory
+specific files such as HEAD, index, etc.
+
 prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
@@ -26,6 +95,12 @@ Prune working tree information in $GIT_DIR/worktrees.
 OPTIONS
 -------
 
+-f::
+--force::
+	By default, `git worktree new` refuses to create a new worktree when
+	<branch> is already checked out by another worktree. This option
+	overrides that safeguard.
+
 -n::
 --dry-run::
 	Do not remove anything; just report what it would
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 9b49f0e..3dd5694 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -51,8 +51,6 @@ struct checkout_opts {
 	struct pathspec pathspec;
 	struct tree *source_tree;
 
-	const char *new_worktree;
-	const char **saved_argv;
 	int new_worktree_mode;
 };
 
@@ -273,8 +271,8 @@ static int checkout_paths(const struct checkout_opts *opts,
 		die(_("Cannot update paths and switch to branch '%s' at the same time."),
 		    opts->new_branch);
 
-	if (opts->new_worktree)
-		die(_("'%s' cannot be used with updating paths"), "--to");
+	if (opts->new_worktree_mode)
+		die(_("'%s' cannot be used with updating paths"), "git worktree new");
 
 	if (opts->patch_mode)
 		return run_add_interactive(revision, "--patch=checkout",
@@ -850,138 +848,6 @@ static int switch_branches(const struct checkout_opts *opts,
 	return ret || writeout_error;
 }
 
-static char *junk_work_tree;
-static char *junk_git_dir;
-static int is_junk;
-static pid_t junk_pid;
-
-static void remove_junk(void)
-{
-	struct strbuf sb = STRBUF_INIT;
-	if (!is_junk || getpid() != junk_pid)
-		return;
-	if (junk_git_dir) {
-		strbuf_addstr(&sb, junk_git_dir);
-		remove_dir_recursively(&sb, 0);
-		strbuf_reset(&sb);
-	}
-	if (junk_work_tree) {
-		strbuf_addstr(&sb, junk_work_tree);
-		remove_dir_recursively(&sb, 0);
-	}
-	strbuf_release(&sb);
-}
-
-static void remove_junk_on_signal(int signo)
-{
-	remove_junk();
-	sigchain_pop(signo);
-	raise(signo);
-}
-
-static int prepare_linked_checkout(const struct checkout_opts *opts,
-				   struct branch_info *new)
-{
-	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
-	struct strbuf sb = STRBUF_INIT;
-	const char *path = opts->new_worktree, *name;
-	struct stat st;
-	struct child_process cp;
-	int counter = 0, len, ret;
-
-	if (!new->commit)
-		die(_("no branch specified"));
-	if (file_exists(path) && !is_empty_dir(path))
-		die(_("'%s' already exists"), path);
-
-	len = strlen(path);
-	while (len && is_dir_sep(path[len - 1]))
-		len--;
-
-	for (name = path + len - 1; name > path; name--)
-		if (is_dir_sep(*name)) {
-			name++;
-			break;
-		}
-	strbuf_addstr(&sb_repo,
-		      git_path("worktrees/%.*s", (int)(path + len - name), name));
-	len = sb_repo.len;
-	if (safe_create_leading_directories_const(sb_repo.buf))
-		die_errno(_("could not create leading directories of '%s'"),
-			  sb_repo.buf);
-	while (!stat(sb_repo.buf, &st)) {
-		counter++;
-		strbuf_setlen(&sb_repo, len);
-		strbuf_addf(&sb_repo, "%d", counter);
-	}
-	name = strrchr(sb_repo.buf, '/') + 1;
-
-	junk_pid = getpid();
-	atexit(remove_junk);
-	sigchain_push_common(remove_junk_on_signal);
-
-	if (mkdir(sb_repo.buf, 0777))
-		die_errno(_("could not create directory of '%s'"), sb_repo.buf);
-	junk_git_dir = xstrdup(sb_repo.buf);
-	is_junk = 1;
-
-	/*
-	 * lock the incomplete repo so prune won't delete it, unlock
-	 * after the preparation is over.
-	 */
-	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
-	write_file(sb.buf, 1, "initializing\n");
-
-	strbuf_addf(&sb_git, "%s/.git", path);
-	if (safe_create_leading_directories_const(sb_git.buf))
-		die_errno(_("could not create leading directories of '%s'"),
-			  sb_git.buf);
-	junk_work_tree = xstrdup(path);
-
-	strbuf_reset(&sb);
-	strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
-	write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
-	write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n",
-		   real_path(get_git_common_dir()), name);
-	/*
-	 * This is to keep resolve_ref() happy. We need a valid HEAD
-	 * or is_git_directory() will reject the directory. Any valid
-	 * value would do because this value will be ignored and
-	 * replaced at the next (real) checkout.
-	 */
-	strbuf_reset(&sb);
-	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
-	write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
-	strbuf_reset(&sb);
-	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
-	write_file(sb.buf, 1, "../..\n");
-
-	if (!opts->quiet)
-		fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
-
-	setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
-	setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
-	setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
-	memset(&cp, 0, sizeof(cp));
-	cp.git_cmd = 1;
-	cp.argv = opts->saved_argv;
-	ret = run_command(&cp);
-	if (!ret) {
-		is_junk = 0;
-		free(junk_work_tree);
-		free(junk_git_dir);
-		junk_work_tree = NULL;
-		junk_git_dir = NULL;
-	}
-	strbuf_reset(&sb);
-	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
-	unlink_or_warn(sb.buf);
-	strbuf_release(&sb);
-	strbuf_release(&sb_repo);
-	strbuf_release(&sb_git);
-	return ret;
-}
-
 static int git_checkout_config(const char *var, const char *value, void *cb)
 {
 	if (!strcmp(var, "diff.ignoresubmodules")) {
@@ -1321,9 +1187,6 @@ static int checkout_branch(struct checkout_opts *opts,
 		die(_("Cannot switch branch to a non-commit '%s'"),
 		    new->name);
 
-	if (opts->new_worktree)
-		return prepare_linked_checkout(opts, new);
-
 	if (!new->commit && opts->new_branch) {
 		unsigned char rev[20];
 		int flag;
@@ -1366,8 +1229,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			 N_("do not limit pathspecs to sparse entries only")),
 		OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
 				N_("second guess 'git checkout <no-such-branch>'")),
-		OPT_FILENAME(0, "to", &opts.new_worktree,
-			   N_("check a branch out in a separate working directory")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		OPT_END(),
@@ -1378,9 +1239,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.overwrite_ignore = 1;
 	opts.prefix = prefix;
 
-	opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2));
-	memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));
-
 	gitmodules_config();
 	git_config(git_checkout_config, &opts);
 
@@ -1389,13 +1247,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	/* recursive execution from checkout_new_worktree() */
 	opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL;
-	if (opts.new_worktree_mode)
-		opts.new_worktree = NULL;
 
-	if (!opts.new_worktree)
-		setup_work_tree();
+	setup_work_tree();
 
 	if (conflict_style) {
 		opts.merge = 1; /* implied */
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 2a729c6..6486f09 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -2,8 +2,11 @@
 #include "builtin.h"
 #include "dir.h"
 #include "parse-options.h"
+#include "run-command.h"
+#include "sigchain.h"
 
 static const char * const worktree_usage[] = {
+	N_("git worktree new [<options>] <path> [<checkout-options>] <branch>"),
 	N_("git worktree prune [<options>]"),
 	NULL
 };
@@ -119,6 +122,158 @@ static int prune(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static char *junk_work_tree;
+static char *junk_git_dir;
+static int is_junk;
+static pid_t junk_pid;
+
+static void remove_junk(void)
+{
+	struct strbuf sb = STRBUF_INIT;
+	if (!is_junk || getpid() != junk_pid)
+		return;
+	if (junk_git_dir) {
+		strbuf_addstr(&sb, junk_git_dir);
+		remove_dir_recursively(&sb, 0);
+		strbuf_reset(&sb);
+	}
+	if (junk_work_tree) {
+		strbuf_addstr(&sb, junk_work_tree);
+		remove_dir_recursively(&sb, 0);
+	}
+	strbuf_release(&sb);
+}
+
+static void remove_junk_on_signal(int signo)
+{
+	remove_junk();
+	sigchain_pop(signo);
+	raise(signo);
+}
+
+static int new_worktree(const char *path, int force, const char **av)
+{
+	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
+	struct strbuf sb = STRBUF_INIT;
+	const char *name;
+	struct stat st;
+	struct child_process cp;
+	int counter = 0, len, ret;
+	unsigned char rev[20];
+
+	if (file_exists(path) && !is_empty_dir(path))
+		die(_("'%s' already exists"), path);
+
+	len = strlen(path);
+	while (len && is_dir_sep(path[len - 1]))
+		len--;
+
+	for (name = path + len - 1; name > path; name--)
+		if (is_dir_sep(*name)) {
+			name++;
+			break;
+		}
+	strbuf_addstr(&sb_repo,
+		      git_path("worktrees/%.*s", (int)(path + len - name), name));
+	len = sb_repo.len;
+	if (safe_create_leading_directories_const(sb_repo.buf))
+		die_errno(_("could not create leading directories of '%s'"),
+			  sb_repo.buf);
+	while (!stat(sb_repo.buf, &st)) {
+		counter++;
+		strbuf_setlen(&sb_repo, len);
+		strbuf_addf(&sb_repo, "%d", counter);
+	}
+	name = strrchr(sb_repo.buf, '/') + 1;
+
+	junk_pid = getpid();
+	atexit(remove_junk);
+	sigchain_push_common(remove_junk_on_signal);
+
+	if (mkdir(sb_repo.buf, 0777))
+		die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+	junk_git_dir = xstrdup(sb_repo.buf);
+	is_junk = 1;
+
+	/*
+	 * lock the incomplete repo so prune won't delete it, unlock
+	 * after the preparation is over.
+	 */
+	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+	write_file(sb.buf, 1, "initializing\n");
+
+	strbuf_addf(&sb_git, "%s/.git", path);
+	if (safe_create_leading_directories_const(sb_git.buf))
+		die_errno(_("could not create leading directories of '%s'"),
+			  sb_git.buf);
+	junk_work_tree = xstrdup(path);
+
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
+	write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
+	write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n",
+		   real_path(get_git_common_dir()), name);
+	/*
+	 * This is to keep resolve_ref() happy. We need a valid HEAD
+	 * or is_git_directory() will reject the directory. Any valid
+	 * value would do because this value will be ignored and
+	 * replaced at the next (real) checkout.
+	 */
+	if (!resolve_ref_unsafe("HEAD", 0, rev, NULL))
+		die(_("unable to resolve HEAD"));
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
+	write_file(sb.buf, 1, "%s\n", sha1_to_hex(rev));
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
+	write_file(sb.buf, 1, "../..\n");
+
+	fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
+
+	setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
+	setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
+	setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
+	memset(&cp, 0, sizeof(cp));
+	cp.git_cmd = 1;
+	argv_array_push(&cp.args, "checkout");
+	if (force)
+		argv_array_push(&cp.args, "--ignore-other-worktrees");
+	for (; *av; av++)
+		argv_array_push(&cp.args, *av);
+	ret = run_command(&cp);
+	if (!ret) {
+		is_junk = 0;
+		free(junk_work_tree);
+		free(junk_git_dir);
+		junk_work_tree = NULL;
+		junk_git_dir = NULL;
+	}
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+	unlink_or_warn(sb.buf);
+	strbuf_release(&sb);
+	strbuf_release(&sb_repo);
+	strbuf_release(&sb_git);
+	return ret;
+}
+
+static int new(int ac, const char **av, const char *prefix)
+{
+	int force = 0;
+	const char *path;
+	struct option options[] = {
+		OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")),
+		OPT_END()
+	};
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage,
+			   PARSE_OPT_STOP_AT_NON_OPTION);
+	if (ac < 2)
+		usage_with_options(worktree_usage, options);
+	path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
+	return new_worktree(path, force, av + 1);
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -127,6 +282,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 
 	if (ac < 2)
 		usage_with_options(worktree_usage, options);
+	if (!strcmp(av[1], "new"))
+		return new(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "prune"))
 		return prune(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
diff --git a/t/t2025-checkout-to.sh b/t/t2025-worktree-new.sh
similarity index 56%
rename from t/t2025-checkout-to.sh
rename to t/t2025-worktree-new.sh
index f8e4df4..d43b352 100755
--- a/t/t2025-checkout-to.sh
+++ b/t/t2025-worktree-new.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='test git checkout --to'
+test_description='test git worktree new'
 
 . ./test-lib.sh
 
@@ -8,29 +8,29 @@ test_expect_success 'setup' '
 	test_commit init
 '
 
-test_expect_success 'checkout --to not updating paths' '
-	test_must_fail git checkout --to -- init.t
+test_expect_success '"new" not updating paths' '
+	test_must_fail git worktree new -- init.t
 '
 
-test_expect_success 'checkout --to an existing worktree' '
+test_expect_success '"new" an existing worktree' '
 	mkdir -p existing/subtree &&
-	test_must_fail git checkout --detach --to existing master
+	test_must_fail git worktree new existing --detach master
 '
 
-test_expect_success 'checkout --to an existing empty worktree' '
+test_expect_success '"new" an existing empty worktree' '
 	mkdir existing_empty &&
-	git checkout --detach --to existing_empty master
+	git worktree new existing_empty --detach master
 '
 
-test_expect_success 'checkout --to refuses to checkout locked branch' '
-	test_must_fail git checkout --to zere master &&
+test_expect_success '"new" refuses to checkout locked branch' '
+	test_must_fail git worktree new zere master &&
 	! test -d zere &&
 	! test -d .git/worktrees/zere
 '
 
-test_expect_success 'checkout --to a new worktree' '
+test_expect_success '"new" worktree' '
 	git rev-parse HEAD >expect &&
-	git checkout --detach --to here master &&
+	git worktree new here --detach master &&
 	(
 		cd here &&
 		test_cmp ../init.t init.t &&
@@ -41,27 +41,27 @@ test_expect_success 'checkout --to a new worktree' '
 	)
 '
 
-test_expect_success 'checkout --to a new worktree from a subdir' '
+test_expect_success '"new" worktree from a subdir' '
 	(
 		mkdir sub &&
 		cd sub &&
-		git checkout --detach --to here master &&
+		git worktree new here --detach master &&
 		cd here &&
 		test_cmp ../../init.t init.t
 	)
 '
 
-test_expect_success 'checkout --to from a linked checkout' '
+test_expect_success '"new" from a linked checkout' '
 	(
 		cd here &&
-		git checkout --detach --to nested-here master &&
+		git worktree new nested-here --detach master &&
 		cd nested-here &&
 		git fsck
 	)
 '
 
-test_expect_success 'checkout --to a new worktree creating new branch' '
-	git checkout --to there -b newmaster master &&
+test_expect_success '"new" worktree creating new branch' '
+	git worktree new there -b newmaster master &&
 	(
 		cd there &&
 		test_cmp ../init.t init.t &&
@@ -82,7 +82,7 @@ test_expect_success 'die the same branch is already checked out' '
 test_expect_success 'not die the same branch is already checked out' '
 	(
 		cd here &&
-		git checkout --ignore-other-worktrees --to anothernewmaster newmaster
+		git worktree new --force anothernewmaster newmaster
 	)
 '
 
@@ -93,15 +93,15 @@ test_expect_success 'not die on re-checking out current branch' '
 	)
 '
 
-test_expect_success 'checkout --to from a bare repo' '
+test_expect_success '"new" from a bare repo' '
 	(
 		git clone --bare . bare &&
 		cd bare &&
-		git checkout --to ../there2 -b bare-master master
+		git worktree new ../there2 -b bare-master master
 	)
 '
 
-test_expect_success 'checkout from a bare repo without --to' '
+test_expect_success 'checkout from a bare repo without "worktree new"' '
 	(
 		cd bare &&
 		test_must_fail git checkout master
@@ -121,7 +121,7 @@ test_expect_success 'checkout with grafts' '
 	EOF
 	git log --format=%s -2 >actual &&
 	test_cmp expected actual &&
-	git checkout --detach --to grafted master &&
+	git worktree new grafted --detach master &&
 	git --git-dir=grafted/.git log --format=%s -2 >actual &&
 	test_cmp expected actual
 '
diff --git a/t/t2026-prune-linked-checkouts.sh b/t/t2026-prune-linked-checkouts.sh
index e872f02..d50688c 100755
--- a/t/t2026-prune-linked-checkouts.sh
+++ b/t/t2026-prune-linked-checkouts.sh
@@ -88,7 +88,7 @@ test_expect_success 'not prune recent checkouts' '
 
 test_expect_success 'not prune proper checkouts' '
 	test_when_finished rm -r .git/worktrees &&
-	git checkout "--to=$PWD/nop" --detach master &&
+	git worktree new "$PWD/nop" --detach master &&
 	git worktree prune &&
 	test -d .git/worktrees/nop
 '
-- 
2.5.0.rc0.203.gd595659
Duy Nguyen· Jun 30, 2015, 09:23 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Tue, Jun 30, 2015 at 11:56 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 21 quoted lines
> The command "git checkout --to <path>" is something of an anachronism,
> encompassing functionality somewhere between "checkout" and "clone".
> The introduction of the git-worktree command, however, provides a proper
> and intuitive place to house such functionality. Consequently,
> re-implement "git checkout --to" as "git worktree new".
>
> As a side-effect, linked worktree creation becomes much more
> discoverable with its own dedicated command, whereas `--to` was easily
> overlooked amid the plethora of options recognized by git-checkout.
>
> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
> ---
>
> I've long felt that Duy's linked-worktree functionality was a bit oddly
> named as "git checkout --to", but, since I could never come up with a
> better name, I never made mention of it. However, with Duy's
> introduction of the git-worktree command[1], we now have a much more
> appropriate and discoverable place to house the "git checkout --to"
> functionality, and upon seeing his patch, I was ready to reply with the
> suggestion to relocate "git checkout --to" to "git worktree new",
> however, Junio beat me to it[2].

Didn't know you guys were so eager to move this code around :D Jokes aside, it's good that it's raised now before --to is set in stone.

I think this is like "git checkout -b" vs "git branch". We pack so many things in 'checkout' that it's a source of both convenience and confusion. I never use "git branch" to create a new branch and if I had a way to tell checkout to "move away and delete previous branch", I would probably stop using "git branch -d/-D" too. "--to" is another "-b" in this sense.

"git worktree new" definitely makes sense (maybe stick with verbs like "create", I'm not sure if we have some convention in existing commands), but should we remove "git checkout --to"? I could do "git co -b foo --to bar" for example. Maybe "--to" is not used that often that "git worktree new" would feel less convenient as a replacement. If we are not sure about "--to" (I'm not), I think we just remove it now because we can always add it back later.

Show 9 quoted lines
> diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
> index 41103e5..8f13281 100644
> --- a/Documentation/git-worktree.txt
> +++ b/Documentation/git-worktree.txt
> @@ -9,16 +9,85 @@ git-worktree - Manage multiple worktrees
>  SYNOPSIS
>  --------
>  [verse]
> +'git worktree new' [-f] <path> [<checkout-options>] <branch>

Should we follow clone syntax and put the <path> (as destination) after <branch> ("source")? Maybe not, because in the clone case, explicit destination is optional, not like this.. Or.. maybe <branch> could be optional in this case. 'git worktree new' without a branch will create a new branch, named closely after the destination. Existing branch can be specified via an option..

-- 
Duy
Junio C Hamano· Jun 30, 2015, 16:33 UTC · re: Duy Nguyen · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

Duy Nguyen <pclouds@gmail.com> writes:
Show 6 quoted lines
> I think this is like "git checkout -b" vs "git branch". We pack so
> many things in 'checkout' that it's a source of both convenience and
> confusion. I never use "git branch" to create a new branch and if I
> had a way to tell checkout to "move away and delete previous branch",
> I would probably stop using "git branch -d/-D" too. "--to" is another
> "-b" in this sense.

I didn't know "checkout --to" included "create a worktree elsewhere and chdir there"; if that "and chdir there" is not something you are doing, then I do not think "checkout -b" vs "branch" analogy applies.

> "git worktree new" definitely makes sense (maybe stick with verbs like
> "create", I'm not sure if we have some convention in existing
> commands), but should we remove "git checkout --to"?
I'm in favor of removing "--to" before it escapes the lab.

I am ambivalent about "new", but that is only because I know about the 'new-workdir' in contrib/. If I pretend to be a naive end user, I'd think a verb subcommand would be more in line with the rest of the system than "new".

I however do not think "create" is a good verb.

Wouldn't "git worktree $the-command-in-question" be a management command that adds a new worktree to the existing collection, like "remote add", "notes add", etc. do? Perhaps "git worktree list" and "git worktree remove $that_one" would be in its future?

That suggests "add" may be a better choice for "worktree".

The only subcommand that I can think of offhand that says "create" is "bundle"; after generates a new bundle, its presence is not known to the repository the bundle was created out of, so not using "add" but calling the operation "create" is fine for "bundle".

Duy Nguyen· Jul 1, 2015, 10:46 UTC · re: Junio C Hamano · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Tue, Jun 30, 2015 at 11:33 PM, Junio C Hamano <gitster@pobox.com> wrote:
Show 12 quoted lines
> Duy Nguyen <pclouds@gmail.com> writes:
>
>> I think this is like "git checkout -b" vs "git branch". We pack so
>> many things in 'checkout' that it's a source of both convenience and
>> confusion. I never use "git branch" to create a new branch and if I
>> had a way to tell checkout to "move away and delete previous branch",
>> I would probably stop using "git branch -d/-D" too. "--to" is another
>> "-b" in this sense.
>
> I didn't know "checkout --to" included "create a worktree elsewhere
> and chdir there"; if that "and chdir there" is not something you are
> doing, then I do not think "checkout -b" vs "branch" analogy applies.

Heh.. I do want that "chdir" (even for git-init and git-clone). We can't issue "cd" command back to the parent shell, but we can spawn a new shell with new cwd. But because the target dir is usually at the end of the command line (except for "--to") and "cd !$" is not much to type, it never bothers me enough to do something more. I think this is another reason I prefer "git worktree add" to have the target dir at the end.

-- 
Duy
Eric Sunshine· Jun 30, 2015, 22:02 UTC · re: Duy Nguyen · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Tue, Jun 30, 2015 at 5:23 AM, Duy Nguyen <pclouds@gmail.com> wrote:
Show 11 quoted lines
> On Tue, Jun 30, 2015 at 11:56 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>> The command "git checkout --to <path>" is something of an anachronism,
>> encompassing functionality somewhere between "checkout" and "clone".
>> The introduction of the git-worktree command, however, provides a proper
>> and intuitive place to house such functionality. Consequently,
>> re-implement "git checkout --to" as "git worktree new".
>
> I think this is like "git checkout -b" vs "git branch". We pack so
> many things in 'checkout' that it's a source of both convenience and
> confusion. I never use "git branch" to create a new branch [...]
>  "--to" is another "-b" in this sense.

I too always use "git checkout -b", but, like Junio, I don't think this is an apt analogy. "git checkout -b" is shorthand for two commands "git branch" and "git checkout", whereas "git checkout --to" is not.

> "git worktree new" definitely makes sense (maybe stick with verbs like
> "create", I'm not sure if we have some convention in existing
> commands), but should we remove "git checkout --to"? I could do "git
> co -b foo --to bar" for example.

You can still do that with "git worktree new bar -b foo", which is effectively the same as "git checkout --to bar -b foo" (with s/checkout/worktree/ and s/--to/new/ applied), though perhaps you don't find it as obvious or natural.

> If we are not sure about "--to" (I'm not), I think we just remove it
> now because we can always add it back later.

I'm not excited about keeping "git checkout --to" as an alias for "git worktree new", however, removing it now should not harm us since, as you say, it can be added back later if needed.

Show 10 quoted lines
>>  SYNOPSIS
>>  --------
>> +'git worktree new' [-f] <path> [<checkout-options>] <branch>
>
> Should we follow clone syntax and put the <path> (as destination)
> after <branch> ("source")? Maybe not, because in the clone case,
> explicit destination is optional, not like this.. Or.. maybe <branch>
> could be optional in this case. 'git worktree new' without a branch
> will create a new branch, named closely after the destination.
> Existing branch can be specified via an option..

I'm not wedded to this particular argument order, though it does have the advantage that it's clear which options belong to "worktree new" and which to "checkout".

As for making <branch> optional and auto-vivifying a new branch named after <path>, that's something we can consider later (I think).

Eric Sunshine· Jul 1, 2015, 06:37 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Tue, Jun 30, 2015 at 6:02 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 17 quoted lines
> On Tue, Jun 30, 2015 at 5:23 AM, Duy Nguyen <pclouds@gmail.com> wrote:
>> On Tue, Jun 30, 2015 at 11:56 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>>> The command "git checkout --to <path>" is something of an anachronism,
>>> encompassing functionality somewhere between "checkout" and "clone".
>>> The introduction of the git-worktree command, however, provides a proper
>>> and intuitive place to house such functionality. Consequently,
>>> re-implement "git checkout --to" as "git worktree new".
>>
>> "git worktree new" definitely makes sense (maybe stick with verbs like
>> "create", I'm not sure if we have some convention in existing
>> commands), but should we remove "git checkout --to"? I could do "git
>> co -b foo --to bar" for example.
>
> You can still do that with "git worktree new bar -b foo", which is
> effectively the same as "git checkout --to bar -b foo" (with
> s/checkout/worktree/ and s/--to/new/ applied), though perhaps you
> don't find it as obvious or natural.

I had never understood why you chose to plug the linked-worktree functionality into git-checkout via --to, but this usage pattern (creating a new branch and checking it out into a new worktree as one operation) goes a long way toward explaining why you consider git-checkout a proper home for linked-worktree creation. I don't think that justification was ever mentioned when the series was being presented (or, if it was, I must have missed it). Now it makes much more sense, and I can better appreciate your desire to keep "git checkout --to" as an alias for "git worktree add". Thanks for explaining it.

(Having said that, replacing "git checkout --to" with "git worktree add" still seems a preferable first step, while keeping open the door to re-add "git checkout --to" later if we become convinced that it's worthwhile.)

Junio C Hamano· Jun 30, 2015, 17:13 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

Eric Sunshine <sunshine@sunshineco.com> writes:
> * t2025-checkout-to.sh became t2025-worktree-new.sh. I'm not sure if the
>   test number still makes sense or if it should be changed, however, it
>   resides alongside its t2026-prune-linked-checkouts.sh counterpart.
You'd need to adjust t7410 as well, perhaps like so:
Show changes to t/t7410-submodule-checkout-to.sh +2 −2
diff --git a/t/t7410-submodule-checkout-to.sh b/t/t7410-submodule-checkout-to.sh
index 8f30aed..d037e51 100755
--- a/t/t7410-submodule-checkout-to.sh
+++ b/t/t7410-submodule-checkout-to.sh
@@ -33,7 +33,7 @@ rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1
 test_expect_success 'checkout main' \
     'mkdir default_checkout &&
     (cd clone/main &&
-	git checkout --to "$base_path/default_checkout/main" "$rev1_hash_main")'
+	git worktree new "$base_path/default_checkout/main" "$rev1_hash_main")'
 
 test_expect_failure 'can see submodule diffs just after checkout' \
     '(cd default_checkout/main && git diff --submodule master"^!" | grep "file1 updated")'
@@ -41,7 +41,7 @@ test_expect_failure 'can see submodule diffs just after checkout' \
 test_expect_success 'checkout main and initialize independed clones' \
     'mkdir fully_cloned_submodule &&
     (cd clone/main &&
-	git checkout --to "$base_path/fully_cloned_submodule/main" "$rev1_hash_main") &&
+	git worktree new "$base_path/fully_cloned_submodule/main" "$rev1_hash_main") &&
     (cd fully_cloned_submodule/main && git submodule update)'
 
 test_expect_success 'can see submodule diffs after independed cloning' \
Eric Sunshine· Jun 30, 2015, 22:11 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Tue, Jun 30, 2015 at 12:56 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 16 quoted lines
> The command "git checkout --to <path>" is something of an anachronism,
> encompassing functionality somewhere between "checkout" and "clone".
> The introduction of the git-worktree command, however, provides a proper
> and intuitive place to house such functionality. Consequently,
> re-implement "git checkout --to" as "git worktree new".
> [...]
> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
> ---
> This is primarily a code and documentation relocation patch, with minor
> new code added to builtin/worktree.c. Specifically:
>
> * builtin/worktree.c:new() is new. It recognizes a --force option ("git
>   worktree new --force <path> <branch>") which allows a branch to be
>   checked out in a new worktree even if already checked out in some
>   other worktree (thus, mirroring the functionality of "git checkout
>   --ignore-other-worktrees").

Speaking of "git worktree new --force", should we revisit "git checkout --ignore-other-worktrees" before it gets set in stone? In particular, I'm wondering if it makes sense to overload git-checkout's existing --force option to encompass the functionality of --ignore-other-worktrees as well. I don't think there would be any semantic conflict by overloading --force, and I do think that --force is more discoverable and more intuitive.

Junio C Hamano· Jun 30, 2015, 22:27 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

Eric Sunshine <sunshine@sunshineco.com> writes:
Show 8 quoted lines
> On Tue, Jun 30, 2015 at 12:56 AM, Eric Sunshine <sunshine@sunshineco.com> wrote
> Speaking of "git worktree new --force", should we revisit "git
> checkout --ignore-other-worktrees" before it gets set in stone? In
> particular, I'm wondering if it makes sense to overload git-checkout's
> existing --force option to encompass the functionality of
> --ignore-other-worktrees as well. I don't think there would be any
> semantic conflict by overloading --force, and I do think that --force
> is more discoverable and more intuitive.

"git checkout -f" is to throw-away local changes, which is a very sensible thing to do and I can see why that would be useful, but does --ignore-other-worktrees have the same kind of common-ness?

It primarily is a safety measure, and if the user wants to jump around freely to different commits in multiple worktrees, a more sensible thing to do so without getting the "nono, you have that branch checked out elsewhere" is to detach HEADs in the non-primary worktrees that may want to have the same commit checked out as the current branch of the primary worktree.

I would mildly object to make --ignore-other-worktrees more discoverable and moderately object to make that feature more accessible by overloading it into "--force". I personally would not mind if we removed "--ignore-other-worktrees", but that might be going too far ;-)

Mikael Magnusson· Jul 1, 2015, 04:48 UTC · re: Junio C Hamano · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Wed, Jul 1, 2015 at 12:27 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 27 quoted lines
> Eric Sunshine <sunshine@sunshineco.com> writes:
>
>> On Tue, Jun 30, 2015 at 12:56 AM, Eric Sunshine <sunshine@sunshineco.com> wrote
>> Speaking of "git worktree new --force", should we revisit "git
>> checkout --ignore-other-worktrees" before it gets set in stone? In
>> particular, I'm wondering if it makes sense to overload git-checkout's
>> existing --force option to encompass the functionality of
>> --ignore-other-worktrees as well. I don't think there would be any
>> semantic conflict by overloading --force, and I do think that --force
>> is more discoverable and more intuitive.
>
> "git checkout -f" is to throw-away local changes, which is a very
> sensible thing to do and I can see why that would be useful, but
> does --ignore-other-worktrees have the same kind of common-ness?
>
> It primarily is a safety measure, and if the user wants to jump
> around freely to different commits in multiple worktrees, a more
> sensible thing to do so without getting the "nono, you have that
> branch checked out elsewhere" is to detach HEADs in the non-primary
> worktrees that may want to have the same commit checked out as the
> current branch of the primary worktree.
>
> I would mildly object to make --ignore-other-worktrees more
> discoverable and moderately object to make that feature more
> accessible by overloading it into "--force".  I personally would not
> mind if we removed "--ignore-other-worktrees", but that might be
> going too far ;-)

This probably falls under "not common", but one of my uses for git new-workdir is to check out the current branch in another directory, rebase it to upstream, delete that worktree, and then git reset --hard in the original checkout. The result is a rebased branch that touches a minimum of source files so the rebuild is faster. (In some projects I have a lot of local commits that get rebased, but maybe upstream only touched a single .c file).

-- 
Mikael Magnusson
Mark Levedahl· Jun 30, 2015, 22:32 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On 06/30/2015 06:11 PM, Eric Sunshine wrote:
Show 26 quoted lines
> On Tue, Jun 30, 2015 at 12:56 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>> The command "git checkout --to <path>" is something of an anachronism,
>> encompassing functionality somewhere between "checkout" and "clone".
>> The introduction of the git-worktree command, however, provides a proper
>> and intuitive place to house such functionality. Consequently,
>> re-implement "git checkout --to" as "git worktree new".
>> [...]
>> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
>> ---
>> This is primarily a code and documentation relocation patch, with minor
>> new code added to builtin/worktree.c. Specifically:
>>
>> * builtin/worktree.c:new() is new. It recognizes a --force option ("git
>>    worktree new --force <path> <branch>") which allows a branch to be
>>    checked out in a new worktree even if already checked out in some
>>    other worktree (thus, mirroring the functionality of "git checkout
>>    --ignore-other-worktrees").
>
> Speaking of "git worktree new --force", should we revisit "git
> checkout --ignore-other-worktrees" before it gets set in stone? In
> particular, I'm wondering if it makes sense to overload git-checkout's
> existing --force option to encompass the functionality of
> --ignore-other-worktrees as well. I don't think there would be any
> semantic conflict by overloading --force, and I do think that --force
> is more discoverable and more intuitive.
>

I agree with -f subsuming --ignore...: -f/--force should really mean "do this if at all possible", not just "ignore some checks". Similar to rm -f, etc.

Maintaining --ignore-other-worktrees, and making that a configurable option (worktree.ignoreothers??) would allow selectively ignoring just this one issue, perhaps permanently, but not the others -f already overrides. This would make sense if other options were added to ignore other subsets of checks that can block a checkout, probably not otherwise.

Mark
Junio C Hamano· Jul 1, 2015, 16:53 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

From: Eric Sunshine <sunshine@sunshineco.com>

The command "git checkout --to <path>" is something of an anachronism, encompassing functionality somewhere between "checkout" and "clone". The introduction of the git-worktree command, however, provides a proper and intuitive place to house such functionality. Consequently, re-implement "git checkout --to" as "git worktree add".

As a side-effect, linked worktree creation becomes much more discoverable with its own dedicated command, whereas `--to` was easily overlooked amid the plethora of options recognized by git-checkout.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 * Duy seems to think "worktree add" is coming, too, so here is a
   trivial tweak of your patch from the last month, with test
   adjustments to 7410 I sent earlier squashed in.
   I noticed GIT_CHECKOUT_NEW_WORKTREE environment variabl that does
   not seem to be documented.  Is this something we still need?
   The log message of 529fef20 (checkout: support checking out into
   a new working directory, 2014-11-30) does not tell us much.
 Documentation/git-checkout.txt    |  72 -----------------
 Documentation/git-worktree.txt    |  79 ++++++++++++++++++-
 builtin/checkout.c                | 152 +-----------------------------------
 builtin/worktree.c                | 157 ++++++++++++++++++++++++++++++++++++++
 t/t2025-checkout-to.sh            | 137 ---------------------------------
 t/t2025-worktree-add.sh           | 137 +++++++++++++++++++++++++++++++++
 t/t2026-prune-linked-checkouts.sh |   2 +-
 t/t7410-submodule-checkout-to.sh  |   4 +-
 8 files changed, 377 insertions(+), 363 deletions(-)
 delete mode 100755 t/t2025-checkout-to.sh
 create mode 100755 t/t2025-worktree-add.sh
Show changes to 8 files +377 −361

Documentation/git-checkout.txt, Documentation/git-worktree.txt, builtin/checkout.c, builtin/worktree.c, t/t2025-checkout-to.sh, t/t2025-worktree-add.sh, t/t2026-prune-linked-checkouts.sh, t/t7410-submodule-checkout-to.sh

diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 72def5b..efe6a02 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -225,13 +225,6 @@ This means that you can use `git checkout -p` to selectively discard
 edits from your current working tree. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 
---to=<path>::
-	Check out a branch in a separate working directory at
-	`<path>`. A new working directory is linked to the current
-	repository, sharing everything except working directory
-	specific files such as HEAD, index... See "MULTIPLE WORKING
-	TREES" section for more information.
-
 --ignore-other-worktrees::
 	`git checkout` refuses when the wanted ref is already checked
 	out by another worktree. This option makes it check the ref
@@ -401,71 +394,6 @@ $ git reflog -2 HEAD # or
 $ git log -g -2 HEAD
 ------------
 
-MULTIPLE WORKING TREES
-----------------------
-
-A git repository can support multiple working trees, allowing you to check
-out more than one branch at a time.  With `git checkout --to` a new working
-tree is associated with the repository.  This new working tree is called a
-"linked working tree" as opposed to the "main working tree" prepared by "git
-init" or "git clone".  A repository has one main working tree (if it's not a
-bare repository) and zero or more linked working trees.
-
-Each linked working tree has a private sub-directory in the repository's
-$GIT_DIR/worktrees directory.  The private sub-directory's name is usually
-the base name of the linked working tree's path, possibly appended with a
-number to make it unique.  For example, when `$GIT_DIR=/path/main/.git` the
-command `git checkout --to /path/other/test-next next` creates the linked
-working tree in `/path/other/test-next` and also creates a
-`$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`
-if `test-next` is already taken).
-
-Within a linked working tree, $GIT_DIR is set to point to this private
-directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
-$GIT_COMMON_DIR is set to point back to the main working tree's $GIT_DIR
-(e.g. `/path/main/.git`). These settings are made in a `.git` file located at
-the top directory of the linked working tree.
-
-Path resolution via `git rev-parse --git-path` uses either
-$GIT_DIR or $GIT_COMMON_DIR depending on the path. For example, in the
-linked working tree `git rev-parse --git-path HEAD` returns
-`/path/main/.git/worktrees/test-next/HEAD` (not
-`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
-rev-parse --git-path refs/heads/master` uses
-$GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,
-since refs are shared across all working trees.
-
-See linkgit:gitrepository-layout[5] for more information. The rule of
-thumb is do not make any assumption about whether a path belongs to
-$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
-inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
-
-When you are done with a linked working tree you can simply delete it.
-The working tree's entry in the repository's $GIT_DIR/worktrees
-directory will eventually be removed automatically (see
-`gc.pruneworktreesexpire` in linkgit::git-config[1]), or you can run
-`git prune --worktrees` in the main or any linked working tree to
-clean up any stale entries in $GIT_DIR/worktrees.
-
-If you move a linked working directory to another file system, or
-within a file system that does not support hard links, you need to run
-at least one git command inside the linked working directory
-(e.g. `git status`) in order to update its entry in $GIT_DIR/worktrees
-so that it does not get automatically removed.
-
-To prevent a $GIT_DIR/worktrees entry from from being pruned (which
-can be useful in some situations, such as when the
-entry's working tree is stored on a portable device), add a file named
-'locked' to the entry's directory. The file contains the reason in
-plain text. For example, if a linked working tree's `.git` file points
-to `/path/main/.git/worktrees/test-next` then a file named
-`/path/main/.git/worktrees/test-next/locked` will prevent the
-`test-next` entry from being pruned.  See
-linkgit:gitrepository-layout[5] for details.
-
-Multiple checkout support for submodules is incomplete. It is NOT
-recommended to make multiple checkouts of a superproject.
-
 EXAMPLES
 --------
 
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 41103e5..94dce6d 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -9,16 +9,85 @@ git-worktree - Manage multiple worktrees
 SYNOPSIS
 --------
 [verse]
+'git worktree add' [-f] <path> [<checkout-options>] <branch>
 'git worktree prune' [-n] [-v] [--expire <expire>]
 
 DESCRIPTION
 -----------
 
-Manage multiple worktrees attached to the same repository. These are
-created by the command `git checkout --to`.
+Manage multiple worktrees attached to the same repository.
+
+A git repository can support multiple working trees, allowing you to check
+out more than one branch at a time.  With `git worktree add` a new working
+tree is created and gets associated with the repository.  This new working tree is called a
+"linked working tree" as opposed to the "main working tree" prepared by "git
+init" or "git clone".  A repository has one main working tree (if it's not a
+bare repository) and zero or more linked working trees.
+
+Each linked working tree has a private sub-directory in the repository's
+$GIT_DIR/worktrees directory.  The private sub-directory's name is usually
+the base name of the linked working tree's path, possibly appended with a
+number to make it unique.  For example, when `$GIT_DIR=/path/main/.git` the
+command `git worktree add /path/other/test-next next` creates the linked
+working tree in `/path/other/test-next` and also creates a
+`$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`
+if `test-next` is already taken).
+
+Within a linked working tree, $GIT_DIR is set to point to this private
+directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
+$GIT_COMMON_DIR is set to point back to the main working tree's $GIT_DIR
+(e.g. `/path/main/.git`). These settings are made in a `.git` file located at
+the top directory of the linked working tree.
+
+Path resolution via `git rev-parse --git-path` uses either
+$GIT_DIR or $GIT_COMMON_DIR depending on the path. For example, in the
+linked working tree `git rev-parse --git-path HEAD` returns
+`/path/main/.git/worktrees/test-next/HEAD` (not
+`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
+rev-parse --git-path refs/heads/master` uses
+$GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,
+since refs are shared across all working trees.
+
+See linkgit:gitrepository-layout[5] for more information. The rule of
+thumb is do not make any assumption about whether a path belongs to
+$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
+inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
+
+When you are done with a linked working tree you can simply delete it.
+The working tree's entry in the repository's $GIT_DIR/worktrees
+directory will eventually be removed automatically (see
+`gc.pruneworktreesexpire` in linkgit::git-config[1]), or you can run
+`git prune --worktrees` in the main or any linked working tree to
+clean up any stale entries in $GIT_DIR/worktrees.
+
+If you move a linked working directory to another file system, or
+within a file system that does not support hard links, you need to run
+at least one git command inside the linked working directory
+(e.g. `git status`) in order to update its entry in $GIT_DIR/worktrees
+so that it does not get automatically removed.
+
+To prevent a $GIT_DIR/worktrees entry from from being pruned (which
+can be useful in some situations, such as when the
+entry's working tree is stored on a portable device), add a file named
+'locked' to the entry's directory. The file contains the reason in
+plain text. For example, if a linked working tree's `.git` file points
+to `/path/main/.git/worktrees/test-next` then a file named
+`/path/main/.git/worktrees/test-next/locked` will prevent the
+`test-next` entry from being pruned.  See
+linkgit:gitrepository-layout[5] for details.
+
+Multiple checkout support for submodules is incomplete. It is NOT
+recommended to make multiple checkouts of a superproject.
 
 COMMANDS
 --------
+add::
+
+Check out a branch in a separate working directory at
+`<path>`. A new working directory is linked to the current
+repository, sharing everything except working directory
+specific files such as HEAD, index, etc.
+
 prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
@@ -26,6 +95,12 @@ Prune working tree information in $GIT_DIR/worktrees.
 OPTIONS
 -------
 
+-f::
+--force::
+	By default, `git worktree add` refuses to create a new worktree when
+	<branch> is already checked out by another worktree. This option
+	overrides that safeguard.
+
 -n::
 --dry-run::
 	Do not remove anything; just report what it would
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2079aa4..439c061 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -51,8 +51,6 @@ struct checkout_opts {
 	struct pathspec pathspec;
 	struct tree *source_tree;
 
-	const char *new_worktree;
-	const char **saved_argv;
 	int new_worktree_mode;
 };
 
@@ -255,8 +253,8 @@ static int checkout_paths(const struct checkout_opts *opts,
 		die(_("Cannot update paths and switch to branch '%s' at the same time."),
 		    opts->new_branch);
 
-	if (opts->new_worktree)
-		die(_("'%s' cannot be used with updating paths"), "--to");
+	if (opts->new_worktree_mode)
+		die(_("'%s' cannot be used with updating paths"), "git worktree add");
 
 	if (opts->patch_mode)
 		return run_add_interactive(revision, "--patch=checkout",
@@ -825,138 +823,6 @@ static int switch_branches(const struct checkout_opts *opts,
 	return ret || writeout_error;
 }
 
-static char *junk_work_tree;
-static char *junk_git_dir;
-static int is_junk;
-static pid_t junk_pid;
-
-static void remove_junk(void)
-{
-	struct strbuf sb = STRBUF_INIT;
-	if (!is_junk || getpid() != junk_pid)
-		return;
-	if (junk_git_dir) {
-		strbuf_addstr(&sb, junk_git_dir);
-		remove_dir_recursively(&sb, 0);
-		strbuf_reset(&sb);
-	}
-	if (junk_work_tree) {
-		strbuf_addstr(&sb, junk_work_tree);
-		remove_dir_recursively(&sb, 0);
-	}
-	strbuf_release(&sb);
-}
-
-static void remove_junk_on_signal(int signo)
-{
-	remove_junk();
-	sigchain_pop(signo);
-	raise(signo);
-}
-
-static int prepare_linked_checkout(const struct checkout_opts *opts,
-				   struct branch_info *new)
-{
-	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
-	struct strbuf sb = STRBUF_INIT;
-	const char *path = opts->new_worktree, *name;
-	struct stat st;
-	struct child_process cp;
-	int counter = 0, len, ret;
-
-	if (!new->commit)
-		die(_("no branch specified"));
-	if (file_exists(path) && !is_empty_dir(path))
-		die(_("'%s' already exists"), path);
-
-	len = strlen(path);
-	while (len && is_dir_sep(path[len - 1]))
-		len--;
-
-	for (name = path + len - 1; name > path; name--)
-		if (is_dir_sep(*name)) {
-			name++;
-			break;
-		}
-	strbuf_addstr(&sb_repo,
-		      git_path("worktrees/%.*s", (int)(path + len - name), name));
-	len = sb_repo.len;
-	if (safe_create_leading_directories_const(sb_repo.buf))
-		die_errno(_("could not create leading directories of '%s'"),
-			  sb_repo.buf);
-	while (!stat(sb_repo.buf, &st)) {
-		counter++;
-		strbuf_setlen(&sb_repo, len);
-		strbuf_addf(&sb_repo, "%d", counter);
-	}
-	name = strrchr(sb_repo.buf, '/') + 1;
-
-	junk_pid = getpid();
-	atexit(remove_junk);
-	sigchain_push_common(remove_junk_on_signal);
-
-	if (mkdir(sb_repo.buf, 0777))
-		die_errno(_("could not create directory of '%s'"), sb_repo.buf);
-	junk_git_dir = xstrdup(sb_repo.buf);
-	is_junk = 1;
-
-	/*
-	 * lock the incomplete repo so prune won't delete it, unlock
-	 * after the preparation is over.
-	 */
-	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
-	write_file(sb.buf, 1, "initializing\n");
-
-	strbuf_addf(&sb_git, "%s/.git", path);
-	if (safe_create_leading_directories_const(sb_git.buf))
-		die_errno(_("could not create leading directories of '%s'"),
-			  sb_git.buf);
-	junk_work_tree = xstrdup(path);
-
-	strbuf_reset(&sb);
-	strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
-	write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
-	write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n",
-		   real_path(get_git_common_dir()), name);
-	/*
-	 * This is to keep resolve_ref() happy. We need a valid HEAD
-	 * or is_git_directory() will reject the directory. Any valid
-	 * value would do because this value will be ignored and
-	 * replaced at the next (real) checkout.
-	 */
-	strbuf_reset(&sb);
-	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
-	write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
-	strbuf_reset(&sb);
-	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
-	write_file(sb.buf, 1, "../..\n");
-
-	if (!opts->quiet)
-		fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
-
-	setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
-	setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
-	setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
-	memset(&cp, 0, sizeof(cp));
-	cp.git_cmd = 1;
-	cp.argv = opts->saved_argv;
-	ret = run_command(&cp);
-	if (!ret) {
-		is_junk = 0;
-		free(junk_work_tree);
-		free(junk_git_dir);
-		junk_work_tree = NULL;
-		junk_git_dir = NULL;
-	}
-	strbuf_reset(&sb);
-	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
-	unlink_or_warn(sb.buf);
-	strbuf_release(&sb);
-	strbuf_release(&sb_repo);
-	strbuf_release(&sb_git);
-	return ret;
-}
-
 static int git_checkout_config(const char *var, const char *value, void *cb)
 {
 	if (!strcmp(var, "diff.ignoresubmodules")) {
@@ -1295,9 +1161,6 @@ static int checkout_branch(struct checkout_opts *opts,
 		free(head_ref);
 	}
 
-	if (opts->new_worktree)
-		return prepare_linked_checkout(opts, new);
-
 	if (!new->commit && opts->new_branch) {
 		unsigned char rev[20];
 		int flag;
@@ -1340,8 +1203,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			 N_("do not limit pathspecs to sparse entries only")),
 		OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
 				N_("second guess 'git checkout no-such-branch'")),
-		OPT_FILENAME(0, "to", &opts.new_worktree,
-			   N_("check a branch out in a separate working directory")),
 		OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
 			 N_("do not check if another worktree is holding the given ref")),
 		OPT_END(),
@@ -1352,9 +1213,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.overwrite_ignore = 1;
 	opts.prefix = prefix;
 
-	opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2));
-	memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));
-
 	gitmodules_config();
 	git_config(git_checkout_config, &opts);
 
@@ -1363,13 +1221,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	/* recursive execution from checkout_new_worktree() */
 	opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL;
-	if (opts.new_worktree_mode)
-		opts.new_worktree = NULL;
 
-	if (!opts.new_worktree)
-		setup_work_tree();
+	setup_work_tree();
 
 	if (conflict_style) {
 		opts.merge = 1; /* implied */
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 2a729c6..0983003 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -2,8 +2,11 @@
 #include "builtin.h"
 #include "dir.h"
 #include "parse-options.h"
+#include "run-command.h"
+#include "sigchain.h"
 
 static const char * const worktree_usage[] = {
+	N_("git worktree add [<options>] <path> [<checkout-options>] <branch>"),
 	N_("git worktree prune [<options>]"),
 	NULL
 };
@@ -119,6 +122,158 @@ static int prune(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static char *junk_work_tree;
+static char *junk_git_dir;
+static int is_junk;
+static pid_t junk_pid;
+
+static void remove_junk(void)
+{
+	struct strbuf sb = STRBUF_INIT;
+	if (!is_junk || getpid() != junk_pid)
+		return;
+	if (junk_git_dir) {
+		strbuf_addstr(&sb, junk_git_dir);
+		remove_dir_recursively(&sb, 0);
+		strbuf_reset(&sb);
+	}
+	if (junk_work_tree) {
+		strbuf_addstr(&sb, junk_work_tree);
+		remove_dir_recursively(&sb, 0);
+	}
+	strbuf_release(&sb);
+}
+
+static void remove_junk_on_signal(int signo)
+{
+	remove_junk();
+	sigchain_pop(signo);
+	raise(signo);
+}
+
+static int add_worktree(const char *path, int force, const char **av)
+{
+	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
+	struct strbuf sb = STRBUF_INIT;
+	const char *name;
+	struct stat st;
+	struct child_process cp;
+	int counter = 0, len, ret;
+	unsigned char rev[20];
+
+	if (file_exists(path) && !is_empty_dir(path))
+		die(_("'%s' already exists"), path);
+
+	len = strlen(path);
+	while (len && is_dir_sep(path[len - 1]))
+		len--;
+
+	for (name = path + len - 1; name > path; name--)
+		if (is_dir_sep(*name)) {
+			name++;
+			break;
+		}
+	strbuf_addstr(&sb_repo,
+		      git_path("worktrees/%.*s", (int)(path + len - name), name));
+	len = sb_repo.len;
+	if (safe_create_leading_directories_const(sb_repo.buf))
+		die_errno(_("could not create leading directories of '%s'"),
+			  sb_repo.buf);
+	while (!stat(sb_repo.buf, &st)) {
+		counter++;
+		strbuf_setlen(&sb_repo, len);
+		strbuf_addf(&sb_repo, "%d", counter);
+	}
+	name = strrchr(sb_repo.buf, '/') + 1;
+
+	junk_pid = getpid();
+	atexit(remove_junk);
+	sigchain_push_common(remove_junk_on_signal);
+
+	if (mkdir(sb_repo.buf, 0777))
+		die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+	junk_git_dir = xstrdup(sb_repo.buf);
+	is_junk = 1;
+
+	/*
+	 * lock the incomplete repo so prune won't delete it, unlock
+	 * after the preparation is over.
+	 */
+	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+	write_file(sb.buf, 1, "initializing\n");
+
+	strbuf_addf(&sb_git, "%s/.git", path);
+	if (safe_create_leading_directories_const(sb_git.buf))
+		die_errno(_("could not create leading directories of '%s'"),
+			  sb_git.buf);
+	junk_work_tree = xstrdup(path);
+
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
+	write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf));
+	write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n",
+		   real_path(get_git_common_dir()), name);
+	/*
+	 * This is to keep resolve_ref() happy. We need a valid HEAD
+	 * or is_git_directory() will reject the directory. Any valid
+	 * value would do because this value will be ignored and
+	 * replaced at the next (real) checkout.
+	 */
+	if (!resolve_ref_unsafe("HEAD", 0, rev, NULL))
+		die(_("unable to resolve HEAD"));
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
+	write_file(sb.buf, 1, "%s\n", sha1_to_hex(rev));
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
+	write_file(sb.buf, 1, "../..\n");
+
+	fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
+
+	setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
+	setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
+	setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
+	memset(&cp, 0, sizeof(cp));
+	cp.git_cmd = 1;
+	argv_array_push(&cp.args, "checkout");
+	if (force)
+		argv_array_push(&cp.args, "--ignore-other-worktrees");
+	for (; *av; av++)
+		argv_array_push(&cp.args, *av);
+	ret = run_command(&cp);
+	if (!ret) {
+		is_junk = 0;
+		free(junk_work_tree);
+		free(junk_git_dir);
+		junk_work_tree = NULL;
+		junk_git_dir = NULL;
+	}
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+	unlink_or_warn(sb.buf);
+	strbuf_release(&sb);
+	strbuf_release(&sb_repo);
+	strbuf_release(&sb_git);
+	return ret;
+}
+
+static int add(int ac, const char **av, const char *prefix)
+{
+	int force = 0;
+	const char *path;
+	struct option options[] = {
+		OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")),
+		OPT_END()
+	};
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage,
+			   PARSE_OPT_STOP_AT_NON_OPTION);
+	if (ac < 2)
+		usage_with_options(worktree_usage, options);
+	path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
+	return add_worktree(path, force, av + 1);
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -127,6 +282,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 
 	if (ac < 2)
 		usage_with_options(worktree_usage, options);
+	if (!strcmp(av[1], "add"))
+		return add(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "prune"))
 		return prune(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
diff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh
deleted file mode 100755
index a8d9336..0000000
--- a/t/t2025-checkout-to.sh
+++ /dev/null
@@ -1,137 +0,0 @@
-#!/bin/sh
-
-test_description='test git checkout --to'
-
-. ./test-lib.sh
-
-test_expect_success 'setup' '
-	test_commit init
-'
-
-test_expect_success 'checkout --to not updating paths' '
-	test_must_fail git checkout --to -- init.t
-'
-
-test_expect_success 'checkout --to an existing worktree' '
-	mkdir -p existing/subtree &&
-	test_must_fail git checkout --detach --to existing master
-'
-
-test_expect_success 'checkout --to an existing empty worktree' '
-	mkdir existing_empty &&
-	git checkout --detach --to existing_empty master
-'
-
-test_expect_success 'checkout --to refuses to checkout locked branch' '
-	test_must_fail git checkout --to zere master &&
-	! test -d zere &&
-	! test -d .git/worktrees/zere
-'
-
-test_expect_success 'checking out paths not complaining about linked checkouts' '
-	(
-	cd existing_empty &&
-	echo dirty >>init.t &&
-	git checkout master -- init.t
-	)
-'
-
-test_expect_success 'checkout --to a new worktree' '
-	git rev-parse HEAD >expect &&
-	git checkout --detach --to here master &&
-	(
-		cd here &&
-		test_cmp ../init.t init.t &&
-		test_must_fail git symbolic-ref HEAD &&
-		git rev-parse HEAD >actual &&
-		test_cmp ../expect actual &&
-		git fsck
-	)
-'
-
-test_expect_success 'checkout --to a new worktree from a subdir' '
-	(
-		mkdir sub &&
-		cd sub &&
-		git checkout --detach --to here master &&
-		cd here &&
-		test_cmp ../../init.t init.t
-	)
-'
-
-test_expect_success 'checkout --to from a linked checkout' '
-	(
-		cd here &&
-		git checkout --detach --to nested-here master &&
-		cd nested-here &&
-		git fsck
-	)
-'
-
-test_expect_success 'checkout --to a new worktree creating new branch' '
-	git checkout --to there -b newmaster master &&
-	(
-		cd there &&
-		test_cmp ../init.t init.t &&
-		git symbolic-ref HEAD >actual &&
-		echo refs/heads/newmaster >expect &&
-		test_cmp expect actual &&
-		git fsck
-	)
-'
-
-test_expect_success 'die the same branch is already checked out' '
-	(
-		cd here &&
-		test_must_fail git checkout newmaster
-	)
-'
-
-test_expect_success 'not die the same branch is already checked out' '
-	(
-		cd here &&
-		git checkout --ignore-other-worktrees --to anothernewmaster newmaster
-	)
-'
-
-test_expect_success 'not die on re-checking out current branch' '
-	(
-		cd there &&
-		git checkout newmaster
-	)
-'
-
-test_expect_success 'checkout --to from a bare repo' '
-	(
-		git clone --bare . bare &&
-		cd bare &&
-		git checkout --to ../there2 -b bare-master master
-	)
-'
-
-test_expect_success 'checkout from a bare repo without --to' '
-	(
-		cd bare &&
-		test_must_fail git checkout master
-	)
-'
-
-test_expect_success 'checkout with grafts' '
-	test_when_finished rm .git/info/grafts &&
-	test_commit abc &&
-	SHA1=`git rev-parse HEAD` &&
-	test_commit def &&
-	test_commit xyz &&
-	echo "`git rev-parse HEAD` $SHA1" >.git/info/grafts &&
-	cat >expected <<-\EOF &&
-	xyz
-	abc
-	EOF
-	git log --format=%s -2 >actual &&
-	test_cmp expected actual &&
-	git checkout --detach --to grafted master &&
-	git --git-dir=grafted/.git log --format=%s -2 >actual &&
-	test_cmp expected actual
-'
-
-test_done
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
new file mode 100755
index 0000000..a757988
--- /dev/null
+++ b/t/t2025-worktree-add.sh
@@ -0,0 +1,137 @@
+#!/bin/sh
+
+test_description='test git worktree add'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit init
+'
+
+test_expect_success '"add" not updating paths' '
+	test_must_fail git worktree add -- init.t
+'
+
+test_expect_success '"add" an existing worktree' '
+	mkdir -p existing/subtree &&
+	test_must_fail git worktree add existing --detach master
+'
+
+test_expect_success '"add" an existing empty worktree' '
+	mkdir existing_empty &&
+	git worktree add existing_empty --detach master
+'
+
+test_expect_success '"add" refuses to checkout locked branch' '
+	test_must_fail git worktree add zere master &&
+	! test -d zere &&
+	! test -d .git/worktrees/zere
+'
+
+test_expect_success 'checking out paths not complaining about linked checkouts' '
+	(
+	cd existing_empty &&
+	echo dirty >>init.t &&
+	git checkout master -- init.t
+	)
+'
+
+test_expect_success '"add" worktree' '
+	git rev-parse HEAD >expect &&
+	git worktree add here --detach master &&
+	(
+		cd here &&
+		test_cmp ../init.t init.t &&
+		test_must_fail git symbolic-ref HEAD &&
+		git rev-parse HEAD >actual &&
+		test_cmp ../expect actual &&
+		git fsck
+	)
+'
+
+test_expect_success '"add" worktree from a subdir' '
+	(
+		mkdir sub &&
+		cd sub &&
+		git worktree add here --detach master &&
+		cd here &&
+		test_cmp ../../init.t init.t
+	)
+'
+
+test_expect_success '"add" from a linked checkout' '
+	(
+		cd here &&
+		git worktree add nested-here --detach master &&
+		cd nested-here &&
+		git fsck
+	)
+'
+
+test_expect_success '"add" worktree creating new branch' '
+	git worktree add there -b newmaster master &&
+	(
+		cd there &&
+		test_cmp ../init.t init.t &&
+		git symbolic-ref HEAD >actual &&
+		echo refs/heads/newmaster >expect &&
+		test_cmp expect actual &&
+		git fsck
+	)
+'
+
+test_expect_success 'die the same branch is already checked out' '
+	(
+		cd here &&
+		test_must_fail git checkout newmaster
+	)
+'
+
+test_expect_success 'not die the same branch is already checked out' '
+	(
+		cd here &&
+		git worktree add --force anothernewmaster newmaster
+	)
+'
+
+test_expect_success 'not die on re-checking out current branch' '
+	(
+		cd there &&
+		git checkout newmaster
+	)
+'
+
+test_expect_success '"add" from a bare repo' '
+	(
+		git clone --bare . bare &&
+		cd bare &&
+		git worktree add ../there2 -b bare-master master
+	)
+'
+
+test_expect_success 'checkout from a bare repo without "worktree add"' '
+	(
+		cd bare &&
+		test_must_fail git checkout master
+	)
+'
+
+test_expect_success 'checkout with grafts' '
+	test_when_finished rm .git/info/grafts &&
+	test_commit abc &&
+	SHA1=`git rev-parse HEAD` &&
+	test_commit def &&
+	test_commit xyz &&
+	echo "`git rev-parse HEAD` $SHA1" >.git/info/grafts &&
+	cat >expected <<-\EOF &&
+	xyz
+	abc
+	EOF
+	git log --format=%s -2 >actual &&
+	test_cmp expected actual &&
+	git worktree add grafted --detach master &&
+	git --git-dir=grafted/.git log --format=%s -2 >actual &&
+	test_cmp expected actual
+'
+
+test_done
diff --git a/t/t2026-prune-linked-checkouts.sh b/t/t2026-prune-linked-checkouts.sh
index e872f02..c74c935 100755
--- a/t/t2026-prune-linked-checkouts.sh
+++ b/t/t2026-prune-linked-checkouts.sh
@@ -88,7 +88,7 @@ test_expect_success 'not prune recent checkouts' '
 
 test_expect_success 'not prune proper checkouts' '
 	test_when_finished rm -r .git/worktrees &&
-	git checkout "--to=$PWD/nop" --detach master &&
+	git worktree add "$PWD/nop" --detach master &&
 	git worktree prune &&
 	test -d .git/worktrees/nop
 '
diff --git a/t/t7410-submodule-checkout-to.sh b/t/t7410-submodule-checkout-to.sh
index 8f30aed..3f609e8 100755
--- a/t/t7410-submodule-checkout-to.sh
+++ b/t/t7410-submodule-checkout-to.sh
@@ -33,7 +33,7 @@ rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1
 test_expect_success 'checkout main' \
     'mkdir default_checkout &&
     (cd clone/main &&
-	git checkout --to "$base_path/default_checkout/main" "$rev1_hash_main")'
+	git worktree add "$base_path/default_checkout/main" "$rev1_hash_main")'
 
 test_expect_failure 'can see submodule diffs just after checkout' \
     '(cd default_checkout/main && git diff --submodule master"^!" | grep "file1 updated")'
@@ -41,7 +41,7 @@ test_expect_failure 'can see submodule diffs just after checkout' \
 test_expect_success 'checkout main and initialize independed clones' \
     'mkdir fully_cloned_submodule &&
     (cd clone/main &&
-	git checkout --to "$base_path/fully_cloned_submodule/main" "$rev1_hash_main") &&
+	git worktree add "$base_path/fully_cloned_submodule/main" "$rev1_hash_main") &&
     (cd fully_cloned_submodule/main && git submodule update)'
 
 test_expect_success 'can see submodule diffs after independed cloning' \
-- 
2.5.0-rc0-209-g5e1f148
Eric Sunshine· Jul 1, 2015, 17:13 UTC · re: Junio C Hamano · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Wed, Jul 1, 2015 at 12:53 PM, Junio C Hamano <gitster@pobox.com> wrote:
Show 19 quoted lines
> From: Eric Sunshine <sunshine@sunshineco.com>
>
> The command "git checkout --to <path>" is something of an anachronism,
> encompassing functionality somewhere between "checkout" and "clone".
> The introduction of the git-worktree command, however, provides a proper
> and intuitive place to house such functionality. Consequently,
> re-implement "git checkout --to" as "git worktree add".
>
> As a side-effect, linked worktree creation becomes much more
> discoverable with its own dedicated command, whereas `--to` was easily
> overlooked amid the plethora of options recognized by git-checkout.
>
> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>
>  * Duy seems to think "worktree add" is coming, too, so here is a
>    trivial tweak of your patch from the last month, with test
>    adjustments to 7410 I sent earlier squashed in.

Thanks. I was planning on re-rolling to use the new name ("add" rather than "new") and to squash in the t7410 fix. Plus, I think the changes I had to make to prepare_linked_checkout() in order to move it to worktree.c should become separate preparatory patches so that the actual relocation can become pure code movement (as much as possible).

Also, I was planning on including Duy's patch in the re-roll since it missed a s/prune --worktrees/worktree prune/ in Documentation/git-checkout.txt.

>    I noticed GIT_CHECKOUT_NEW_WORKTREE environment variabl that does
>    not seem to be documented.  Is this something we still need?
>    The log message of 529fef20 (checkout: support checking out into
>    a new working directory, 2014-11-30) does not tell us much.

Yes, it's still used for the same purpose as before the conversion: as a private signal to the sub git-checkout invocation that it's operating on a new worktree. When defined, it sets the 'new_worktree_mode' flag in checkout.c, and there are still a few bits of code which apparently need to know about it. It would be nice to eliminate this special knowledge from checkout.c, however, I'm not yet familiar enough with the checkout code to determine if doing so is viable.

For the re-roll, I was planning on renaming it to GIT_NEW_WORKTREE_MODE or something (or add a private command-line option to checkout, but that may be overkill).

Eric Sunshine· Jul 1, 2015, 17:16 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Wed, Jul 1, 2015 at 1:13 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 15 quoted lines
> On Wed, Jul 1, 2015 at 12:53 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> From: Eric Sunshine <sunshine@sunshineco.com>
>>
>> The command "git checkout --to <path>" is something of an anachronism,
>> encompassing functionality somewhere between "checkout" and "clone".
>> The introduction of the git-worktree command, however, provides a proper
>> and intuitive place to house such functionality. Consequently,
>> re-implement "git checkout --to" as "git worktree add".
>> ---
>>
>>  * Duy seems to think "worktree add" is coming, too, so here is a
>>    trivial tweak of your patch from the last month, with test
>>    adjustments to 7410 I sent earlier squashed in.
>
> Thanks. I was planning on re-rolling...

I forgot to mention that the subject needs a slight update: "worktree add" rather than "worktree new".

Junio C Hamano· Jul 1, 2015, 17:32 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

Eric Sunshine <sunshine@sunshineco.com> writes:
Show 5 quoted lines
> Thanks. I was planning on re-rolling to use the new name ...
> ...
> For the re-roll, I was planning on renaming it to
> GIT_NEW_WORKTREE_MODE or something (or add a private command-line
> option to checkout, but that may be overkill).

OK, thanks, then I'll stop worrying about this and instead will wait an update from you ;-)

Eric Sunshine· Jul 1, 2015, 18:18 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Wed, Jul 1, 2015 at 1:13 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 14 quoted lines
> On Wed, Jul 1, 2015 at 12:53 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>  * Duy seems to think "worktree add" is coming, too, so here is a
>>    trivial tweak of your patch from the last month, with test
>>    adjustments to 7410 I sent earlier squashed in.
>
> Thanks. I was planning on re-rolling to use the new name ("add" rather
> than "new") and to squash in the t7410 fix. Plus, I think the changes
> I had to make to prepare_linked_checkout() in order to move it to
> worktree.c should become separate preparatory patches so that the
> actual relocation can become pure code movement (as much as possible).
>
> Also, I was planning on including Duy's patch in the re-roll since it
> missed a s/prune --worktrees/worktree prune/ in
> Documentation/git-checkout.txt.

Hmm, I see that Duy's patch is already in 'next'. Would it be better if I fixed the 's/prune --worktrees/worktree prune/' git-checkout.txt oversight via an incremental patch rather than including a corrected version of his patch with my re-roll?

Junio C Hamano· Jul 1, 2015, 18:52 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

Eric Sunshine <sunshine@sunshineco.com> writes:
Show 20 quoted lines
> On Wed, Jul 1, 2015 at 1:13 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>> On Wed, Jul 1, 2015 at 12:53 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>>  * Duy seems to think "worktree add" is coming, too, so here is a
>>>    trivial tweak of your patch from the last month, with test
>>>    adjustments to 7410 I sent earlier squashed in.
>>
>> Thanks. I was planning on re-rolling to use the new name ("add" rather
>> than "new") and to squash in the t7410 fix. Plus, I think the changes
>> I had to make to prepare_linked_checkout() in order to move it to
>> worktree.c should become separate preparatory patches so that the
>> actual relocation can become pure code movement (as much as possible).
>>
>> Also, I was planning on including Duy's patch in the re-roll since it
>> missed a s/prune --worktrees/worktree prune/ in
>> Documentation/git-checkout.txt.
>
> Hmm, I see that Duy's patch is already in 'next'. Would it be better
> if I fixed the 's/prune --worktrees/worktree prune/' git-checkout.txt
> oversight via an incremental patch rather than including a corrected
> version of his patch with my re-roll?

I may have mistaken what you said; I thought you were planning an incremental for the existing part, with a more complete reroll of "worktree add" than what I sent today, as separate patches.

Thanks.
Duy Nguyen· Jul 2, 2015, 01:07 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Thu, Jul 2, 2015 at 12:13 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 13 quoted lines
>>    I noticed GIT_CHECKOUT_NEW_WORKTREE environment variabl that does
>>    not seem to be documented.  Is this something we still need?
>>    The log message of 529fef20 (checkout: support checking out into
>>    a new working directory, 2014-11-30) does not tell us much.
>
> Yes, it's still used for the same purpose as before the conversion: as
> a private signal to the sub git-checkout invocation that it's
> operating on a new worktree. When defined, it sets the
> 'new_worktree_mode' flag in checkout.c, and there are still a few bits
> of code which apparently need to know about it. It would be nice to
> eliminate this special knowledge from checkout.c, however, I'm not yet
> familiar enough with the checkout code to determine if doing so is
> viable.

I think it can go away. When "--to" is used, I have to re-execute "git checkout" command again after creating the new worktree. I could process the command line arguments from the first execution, delete "--to", then use the remaining options to run checkout the second time. But I chose to pass the entire command line to the second execution. The env is used to let the second run know it should ignore "--to" (or we get infinite recursion). With "git worktree add" this recursion disappears and this env var has no reason to exist.

-- 
Duy
Eric Sunshine· Jul 2, 2015, 02:52 UTC · re: Duy Nguyen · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Wed, Jul 1, 2015 at 9:07 PM, Duy Nguyen <pclouds@gmail.com> wrote:
Show 23 quoted lines
> On Thu, Jul 2, 2015 at 12:13 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>>>    I noticed GIT_CHECKOUT_NEW_WORKTREE environment variabl that does
>>>    not seem to be documented.  Is this something we still need?
>>>    The log message of 529fef20 (checkout: support checking out into
>>>    a new working directory, 2014-11-30) does not tell us much.
>>
>> Yes, it's still used for the same purpose as before the conversion: as
>> a private signal to the sub git-checkout invocation that it's
>> operating on a new worktree. When defined, it sets the
>> 'new_worktree_mode' flag in checkout.c, and there are still a few bits
>> of code which apparently need to know about it. It would be nice to
>> eliminate this special knowledge from checkout.c, however, I'm not yet
>> familiar enough with the checkout code to determine if doing so is
>> viable.
>
> I think it can go away. When "--to" is used, I have to re-execute "git
> checkout" command again after creating the new worktree. I could
> process the command line arguments from the first execution, delete
> "--to", then use the remaining options to run checkout the second
> time. But I chose to pass the entire command line to the second
> execution. The env is used to let the second run know it should ignore
> "--to" (or we get infinite recursion). With "git worktree add" this
> recursion disappears and this env var has no reason to exist.

The recursion protection is indeed no longer needed and gets removed by the "worktree add" patch. However, there are still a few bits of code which want to know that the checkout is happening in a new worktree. I haven't examined them closely yet to diagnose if this specialized knowledge can be eliminated. Perhaps you can weight in. In particular:

checkout_paths:
    if (opts->new_worktree)
        die(_("'%s' cannot be used with updating paths"), "--to");
merge_working_tree:
    tree = parse_tree_indirect(old->commit &&
        !opts->new_worktree_mode ?
            old->commit->object.sha1 :
            EMPTY_TREE_SHA1_BIN);
switch_branches:
    if (!opts->quiet && !old.path && old.commit &&
        new->commit != old.commit && !opts->new_worktree_mode)
            orphaned_commit_warning(old.commit, new->commit);
Duy Nguyen· Jul 2, 2015, 12:41 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Thu, Jul 2, 2015 at 9:52 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 35 quoted lines
> On Wed, Jul 1, 2015 at 9:07 PM, Duy Nguyen <pclouds@gmail.com> wrote:
>> On Thu, Jul 2, 2015 at 12:13 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>>>>    I noticed GIT_CHECKOUT_NEW_WORKTREE environment variabl that does
>>>>    not seem to be documented.  Is this something we still need?
>>>>    The log message of 529fef20 (checkout: support checking out into
>>>>    a new working directory, 2014-11-30) does not tell us much.
>>>
>>> Yes, it's still used for the same purpose as before the conversion: as
>>> a private signal to the sub git-checkout invocation that it's
>>> operating on a new worktree. When defined, it sets the
>>> 'new_worktree_mode' flag in checkout.c, and there are still a few bits
>>> of code which apparently need to know about it. It would be nice to
>>> eliminate this special knowledge from checkout.c, however, I'm not yet
>>> familiar enough with the checkout code to determine if doing so is
>>> viable.
>>
>> I think it can go away. When "--to" is used, I have to re-execute "git
>> checkout" command again after creating the new worktree. I could
>> process the command line arguments from the first execution, delete
>> "--to", then use the remaining options to run checkout the second
>> time. But I chose to pass the entire command line to the second
>> execution. The env is used to let the second run know it should ignore
>> "--to" (or we get infinite recursion). With "git worktree add" this
>> recursion disappears and this env var has no reason to exist.
>
> The recursion protection is indeed no longer needed and gets removed
> by the "worktree add" patch. However, there are still a few bits of
> code which want to know that the checkout is happening in a new
> worktree. I haven't examined them closely yet to diagnose if this
> specialized knowledge can be eliminated. Perhaps you can weight in. In
> particular:
>
> checkout_paths:
>     if (opts->new_worktree)
>         die(_("'%s' cannot be used with updating paths"), "--to");
This one is easy, as "--to" is gone, no reason to report anything about "--to"
Show 5 quoted lines
> merge_working_tree:
>     tree = parse_tree_indirect(old->commit &&
>         !opts->new_worktree_mode ?
>             old->commit->object.sha1 :
>             EMPTY_TREE_SHA1_BIN);

I think it's to make sure empty sha-1 is used with --to. If old->commit->object.sha1 is used and it's something, a real two way merge may happen probably with not-so-fun consequences. If it's empty sha1, the effect is like "reset --hard", silent and reliable..

> switch_branches:
>     if (!opts->quiet && !old.path && old.commit &&
>         new->commit != old.commit && !opts->new_worktree_mode)
>             orphaned_commit_warning(old.commit, new->commit);
to suppress misleading warning if old.commit happens to be something.
-- 
Duy
Duy Nguyen· Jul 2, 2015, 12:50 UTC · re: Duy Nguyen · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Thu, Jul 2, 2015 at 7:41 PM, Duy Nguyen <pclouds@gmail.com> wrote:
Show 17 quoted lines
>> merge_working_tree:
>>     tree = parse_tree_indirect(old->commit &&
>>         !opts->new_worktree_mode ?
>>             old->commit->object.sha1 :
>>             EMPTY_TREE_SHA1_BIN);
>
> I think it's to make sure empty sha-1 is used with --to. If
> old->commit->object.sha1 is used and it's something, a real two way
> merge may happen probably with not-so-fun consequences. If it's empty
> sha1, the effect is like "reset --hard", silent and reliable..
>
>> switch_branches:
>>     if (!opts->quiet && !old.path && old.commit &&
>>         new->commit != old.commit && !opts->new_worktree_mode)
>>             orphaned_commit_warning(old.commit, new->commit);
>
> to suppress misleading warning if old.commit happens to be something.

Actually you may be right about not reverting these. We prepare the new worktree with a valid HEAD, that would make "old" valid and may trigger things if "git checkout" is used to populate the worktree. To suppress those "things", we need new_worktree_mode or something similar.

Unless we want to borrow fancy checkout options for "git worktree add", we probably should just export checkout() function from clone.c and use it instead of "git checkout". Much more lightweight and simpler (it's one-way merge). Then we can revert checkout.c to the version before "--to".

-- 
Duy
Eric Sunshine· Jul 2, 2015, 17:06 UTC · re: Duy Nguyen · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Thu, Jul 2, 2015 at 8:50 AM, Duy Nguyen <pclouds@gmail.com> wrote:
Show 24 quoted lines
> On Thu, Jul 2, 2015 at 7:41 PM, Duy Nguyen <pclouds@gmail.com> wrote:
>>> merge_working_tree:
>>>     tree = parse_tree_indirect(old->commit &&
>>>         !opts->new_worktree_mode ?
>>>             old->commit->object.sha1 :
>>>             EMPTY_TREE_SHA1_BIN);
>>
>> I think it's to make sure empty sha-1 is used with --to. If
>> old->commit->object.sha1 is used and it's something, a real two way
>> merge may happen probably with not-so-fun consequences. If it's empty
>> sha1, the effect is like "reset --hard", silent and reliable..
>>
>>> switch_branches:
>>>     if (!opts->quiet && !old.path && old.commit &&
>>>         new->commit != old.commit && !opts->new_worktree_mode)
>>>             orphaned_commit_warning(old.commit, new->commit);
>>
>> to suppress misleading warning if old.commit happens to be something.
>
> Actually you may be right about not reverting these. We prepare the
> new worktree with a valid HEAD, that would make "old" valid and may
> trigger things if "git checkout" is used to populate the worktree. To
> suppress those "things", we need new_worktree_mode or something
> similar.

Indeed. Since this is merely a private implementation detail, we don't necessarily have to resolve the issue fully for the "checkout --to" to "worktree add" conversion. It can be dealt with in a follow-on patch.

Show 5 quoted lines
> Unless we want to borrow fancy checkout options for "git worktree
> add", we probably should just export checkout() function from clone.c
> and use it instead of "git checkout". Much more lightweight and
> simpler (it's one-way merge). Then we can revert checkout.c to the
> version before "--to".

Interesting idea, but doesn't this lose the ability to create a new branch ("worktree add foo -b bar") and other useful options like --track?

Duy Nguyen· Jul 2, 2015, 22:41 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Fri, Jul 3, 2015 at 12:06 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 9 quoted lines
>> Unless we want to borrow fancy checkout options for "git worktree
>> add", we probably should just export checkout() function from clone.c
>> and use it instead of "git checkout". Much more lightweight and
>> simpler (it's one-way merge). Then we can revert checkout.c to the
>> version before "--to".
>
> Interesting idea, but doesn't this lose the ability to create a new
> branch ("worktree add foo -b bar") and other useful options like
> --track?

Those are what I call "fancy checkout options". I think we could start simple with clone.c:checkout.c() and maybe libify switch_branches() later on to gain --detach and stuff. There's another thing I missed, when the new worktree is set up, HEAD contains a dummy value. It's expected that the second checkout will update it with proper value (a ref, detached HEAD....). So if you avoid running "git chckout" in "worktree add" and use clone.c:checkout(), you have to re-do something similar.

-- 
Duy
Eric Sunshine· Jul 2, 2015, 16:59 UTC · re: Duy Nguyen · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Thu, Jul 2, 2015 at 8:41 AM, Duy Nguyen <pclouds@gmail.com> wrote:
Show 13 quoted lines
> On Thu, Jul 2, 2015 at 9:52 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>> The recursion protection is indeed no longer needed and gets removed
>> by the "worktree add" patch. However, there are still a few bits of
>> code which want to know that the checkout is happening in a new
>> worktree. I haven't examined them closely yet to diagnose if this
>> specialized knowledge can be eliminated. Perhaps you can weight in. In
>> particular:
>>
>> checkout_paths:
>>     if (opts->new_worktree)
>>         die(_("'%s' cannot be used with updating paths"), "--to");
>
> This one is easy, as "--to" is gone, no reason to report anything about "--to"

In the "worktree add" patch, I kept this one (with s/--to/worktree add/) assuming that your intention was that a new worktree should never start with a partial checkout due to specifying paths. Looking at it more closely, I'm still not convinced that it can be removed. Given:

    git worktree new <path> <branch> -- <file>

it creates <path> and checks out <file> (and only <file>) into <path>, however, the resulting worktree is "not on any branch". The latter, I think is because switch_branches() doesn't get called in this case; instead, it's just at whatever HEAD was faked up to appease is_git_directory().

Eric Sunshine· Jul 2, 2015, 18:45 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Wed, Jul 1, 2015 at 10:52 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 20 quoted lines
> However, there are still a few bits of
> code which want to know that the checkout is happening in a new
> worktree. I haven't examined them closely yet to diagnose if this
> specialized knowledge can be eliminated. Perhaps you can weight in. In
> particular:
>
> checkout_paths:
>     if (opts->new_worktree)
>         die(_("'%s' cannot be used with updating paths"), "--to");
>
> merge_working_tree:
>     tree = parse_tree_indirect(old->commit &&
>         !opts->new_worktree_mode ?
>             old->commit->object.sha1 :
>             EMPTY_TREE_SHA1_BIN);
>
> switch_branches:
>     if (!opts->quiet && !old.path && old.commit &&
>         new->commit != old.commit && !opts->new_worktree_mode)
>             orphaned_commit_warning(old.commit, new->commit);

There's another instance: 3473ad0 (checkout: don't require a work tree when checking out into a new one, 2014-11-30) added this:

    if (!new_worktree)
        setup_work_tree();
which the "worktree add" patch changed to:
    setup_work_tree();

which doesn't hurt (since setup_work_tree() protects itself against multiple invocations) but isn't semantically clean. If I understand correctly, I think a better approach would be to move the setup_work_tree() call to worktree.c just before it invokes git-checkout, and revert 3473ad0 entirely (including this bit):

    - { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
    +{ "checkout", cmd_checkout, RUN_SETUP },
so that git-checkout once again requires a worktree.
Eric Sunshine· Jul 2, 2015, 19:00 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Thu, Jul 2, 2015 at 2:45 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 20 quoted lines
> There's another instance: 3473ad0 (checkout: don't require a work tree
> when checking out into a new one, 2014-11-30) added this:
>
>     if (!new_worktree)
>         setup_work_tree();
>
> which the "worktree add" patch changed to:
>
>     setup_work_tree();
>
> which doesn't hurt (since setup_work_tree() protects itself against
> multiple invocations) but isn't semantically clean. If I understand
> correctly, I think a better approach would be to move the
> setup_work_tree() call to worktree.c just before it invokes
> git-checkout, and revert 3473ad0 entirely (including this bit):
>
>     - { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
>     +{ "checkout", cmd_checkout, RUN_SETUP },
>
> so that git-checkout once again requires a worktree.

I mis-stated that a bit. The bit about "multiple invocations" isn't relevant. The point is that I think that 3473ad0 can simply be reverted as long as worktree.c calls setup_work_tree() before invoking git-checkout.

Eric Sunshine· Jul 2, 2015, 19:19 UTC · re: Eric Sunshine · lore

Re: [RFC/PATCH] worktree: replace "checkout --to" with "worktree new"

On Thu, Jul 2, 2015 at 3:00 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 26 quoted lines
> On Thu, Jul 2, 2015 at 2:45 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>> There's another instance: 3473ad0 (checkout: don't require a work tree
>> when checking out into a new one, 2014-11-30) added this:
>>
>>     if (!new_worktree)
>>         setup_work_tree();
>>
>> which the "worktree add" patch changed to:
>>
>>     setup_work_tree();
>>
>> which doesn't hurt (since setup_work_tree() protects itself against
>> multiple invocations) but isn't semantically clean. If I understand
>> correctly, I think a better approach would be to move the
>> setup_work_tree() call to worktree.c just before it invokes
>> git-checkout, and revert 3473ad0 entirely (including this bit):
>>
>>     - { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
>>     +{ "checkout", cmd_checkout, RUN_SETUP },
>>
>> so that git-checkout once again requires a worktree.
>
> I mis-stated that a bit. The bit about "multiple invocations" isn't
> relevant. The point is that I think that 3473ad0 can simply be
> reverted as long as worktree.c calls setup_work_tree() before invoking
> git-checkout.

Please ignore my idiocy. Of course worktree.c can't call setup_work_tree() on behalf of the sub git-checkout invocation. Reverting 3473ad0 is the correct thing to do with the introduction of "worktree add" since it removes the special case of having to be able to run git-checkout without a worktree.

← back to recent threads