git@vger.kernel.org list mirror (unofficial, one of many)
 help / color / mirror / code / Atom feed
* [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree
@ 2015-07-16  8:20 Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 01/20] checkout: avoid resolving HEAD unnecessarily Eric Sunshine
                   ` (19 more replies)
  0 siblings, 20 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

This is v2 of [1] which rids git-checkout of the need to have
specialized knowledge that it's operating in a newly created linked
worktree, and decouples git-worktree from git-checkout. Thanks to Duy
and Junio for reviews.

A v1 to v2 interdiff is included below.

Changes since v1:

* patch 03/20: relocate from caller to callee a comment explaining why
  check_linked_checkout() resolves HEAD manually[2]

* patch 06/20 (new): drop "/.git" from "'blorp' already checked out at
  '/foo/bar/.git'" diagnostic[3]

* patch 07/20 & 08/20 (new): teach checked_linked_checkout() about
  symbolic-link HEAD[2] (to augment its existing symref-style HEAD
  knowledge)

* patch 14/20 (new): take advantage of 'struct child_process.env' to
  make it obvious that assigned environment variables are intended for
  child process invocations[2]

* patch 16/20: side-step potential problem with future pluggable
  backends answering ref_exists() incorrectly from cached information
  after we've invoked git-branch behind their backs[4,5], and add a few
  in-code comments explaining the logic

* reword several commit messages to avoid stressing "git reset --hard"
  as endgame, and instead emphasize removal of specialized worktree
  creation knowledge from git-checkout[2], and resolve conflation of new
  branch creation, setting of worktree HEAD, and new worktree
  population.

[1]: http://thread.gmane.org/gmane.comp.version-control.git/273856
[2]: http://article.gmane.org/gmane.comp.version-control.git/273921
[3]: http://thread.gmane.org/gmane.comp.version-control.git/274001
[4]: http://article.gmane.org/gmane.comp.version-control.git/273885
[5]: http://article.gmane.org/gmane.comp.version-control.git/273901

Eric Sunshine (20):
  checkout: avoid resolving HEAD unnecessarily
  checkout: name check_linked_checkouts() more meaningfully
  checkout: improve die_if_checked_out() robustness
  checkout: die_if_checked_out: simplify strbuf management
  checkout: generalize die_if_checked_out() branch name argument
  checkout: check_linked_checkout: improve "already checked out"
    aesthetic
  checkout: check_linked_checkout: simplify symref parsing
  checkout: teach check_linked_checkout() about symbolic link HEAD
  branch: publish die_if_checked_out()
  worktree: simplify new branch (-b/-B) option checking
  worktree: introduce options container
  worktree: make --detach mutually exclusive with -b/-B
  worktree: make branch creation distinct from worktree population
  worktree: elucidate environment variables intended for child processes
  worktree: add_worktree: construct worktree-population command locally
  worktree: detect branch-name/detached and error conditions locally
  worktree: make setup of new HEAD distinct from worktree population
  worktree: avoid resolving HEAD unnecessarily
  worktree: populate via "git reset --hard" rather than "git checkout"
  checkout: drop intimate knowledge of newly created worktree

 branch.c                |  67 +++++++++++++++++++++++++++
 branch.h                |   7 +++
 builtin/checkout.c      |  82 +++------------------------------
 builtin/worktree.c      | 120 ++++++++++++++++++++++++++++++++----------------
 t/t2025-worktree-add.sh |   8 ++++
 5 files changed, 169 insertions(+), 115 deletions(-)

-- 
2.5.0.rc2.378.g0af52e8


---- 8< ----
diff --git a/branch.c b/branch.c
index 7b8b9a3..dfd7698 100644
--- a/branch.c
+++ b/branch.c
@@ -315,22 +315,28 @@ static void check_linked_checkout(const char *branch, const char *id)
 	struct strbuf sb = STRBUF_INIT;
 	struct strbuf path = STRBUF_INIT;
 	struct strbuf gitdir = STRBUF_INIT;
-	const char *start, *end;
 
+	/*
+	 * $GIT_COMMON_DIR/HEAD is practically outside
+	 * $GIT_DIR so resolve_ref_unsafe() won't work (it
+	 * uses git_path). Parse the ref ourselves.
+	 */
 	if (id)
 		strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
 	else
 		strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
 
-	if (strbuf_read_file(&sb, path.buf, 0) < 0 ||
-	    !skip_prefix(sb.buf, "ref:", &start))
+	if (!strbuf_readlink(&sb, path.buf, 0)) {
+		if (!starts_with(sb.buf, "refs/") ||
+		    check_refname_format(sb.buf, 0))
+			goto done;
+	} else if (strbuf_read_file(&sb, path.buf, 0) >= 0 &&
+	    starts_with(sb.buf, "ref:")) {
+		strbuf_remove(&sb, 0, strlen("ref:"));
+		strbuf_trim(&sb);
+	} else
 		goto done;
-	while (isspace(*start))
-		start++;
-	end = start;
-	while (*end && !isspace(*end))
-		end++;
-	if (strncmp(start, branch, end - start) || branch[end - start] != '\0')
+	if (strcmp(sb.buf, branch))
 		goto done;
 	if (id) {
 		strbuf_reset(&path);
@@ -341,6 +347,7 @@ static void check_linked_checkout(const char *branch, const char *id)
 	} else
 		strbuf_addstr(&gitdir, get_git_common_dir());
 	skip_prefix(branch, "refs/heads/", &branch);
+	strbuf_strip_suffix(&gitdir, "/.git");
 	die(_("'%s' is already checked out at '%s'"), branch, gitdir.buf);
 done:
 	strbuf_release(&path);
@@ -354,11 +361,6 @@ void die_if_checked_out(const char *branch)
 	DIR *dir;
 	struct dirent *d;
 
-	/*
-	 * $GIT_COMMON_DIR/HEAD is practically outside
-	 * $GIT_DIR so resolve_ref_unsafe() won't work (it
-	 * uses git_path). Parse the ref ourselves.
-	 */
 	check_linked_checkout(branch, NULL);
 
 	strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 51c57bc..2873064 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -188,6 +188,7 @@ static int add_worktree(const char *path, const char *refname,
 	const char *name;
 	struct stat st;
 	struct child_process cp;
+	struct argv_array child_env = ARGV_ARRAY_INIT;
 	int counter = 0, len, ret;
 	struct strbuf symref = STRBUF_INIT;
 	struct commit *commit = NULL;
@@ -195,11 +196,14 @@ static int add_worktree(const char *path, const char *refname,
 	if (file_exists(path) && !is_empty_dir(path))
 		die(_("'%s' already exists"), path);
 
-	if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
-	    ref_exists(symref.buf)) {
+	/* is 'refname' a branch or commit? */
+	if (opts->force_new_branch) /* definitely a branch */
+		;
+	else if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
+		 ref_exists(symref.buf)) { /* it's a branch */
 		if (!opts->force)
 			die_if_checked_out(symref.buf);
-	} else {
+	} else { /* must be a commit */
 		commit = lookup_commit_reference_by_name(refname);
 		if (!commit)
 			die(_("invalid reference: %s"), refname);
@@ -262,8 +266,8 @@ static int add_worktree(const char *path, const char *refname,
 
 	fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
 
-	setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
-	setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
+	argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
+	argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
 	memset(&cp, 0, sizeof(cp));
 	cp.git_cmd = 1;
 
@@ -273,6 +277,7 @@ static int add_worktree(const char *path, const char *refname,
 	else
 		argv_array_pushl(&cp.args, "symbolic-ref", "HEAD",
 				 symref.buf, NULL);
+	cp.env = child_env.argv;
 	ret = run_command(&cp);
 	if (ret)
 		goto done;
@@ -280,6 +285,7 @@ static int add_worktree(const char *path, const char *refname,
 	cp.argv = NULL;
 	argv_array_clear(&cp.args);
 	argv_array_pushl(&cp.args, "reset", "--hard", NULL);
+	cp.env = child_env.argv;
 	ret = run_command(&cp);
 	if (!ret) {
 		is_junk = 0;
@@ -292,6 +298,7 @@ done:
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
 	unlink_or_warn(sb.buf);
+	argv_array_clear(&child_env);
 	strbuf_release(&sb);
 	strbuf_release(&symref);
 	strbuf_release(&sb_repo);
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
index ead8aa2..9e30690 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -83,6 +83,14 @@ test_expect_success 'die the same branch is already checked out' '
 	)
 '
 
+test_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' '
+	head=$(git -C there rev-parse --git-path HEAD) &&
+	ref=$(git -C there symbolic-ref HEAD) &&
+	rm "$head" &&
+	ln -s "$ref" "$head" &&
+	test_must_fail git -C here checkout newmaster
+'
+
 test_expect_success 'not die the same branch is already checked out' '
 	(
 		cd here &&
---- 8< ----

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 01/20] checkout: avoid resolving HEAD unnecessarily
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 02/20] checkout: name check_linked_checkouts() more meaningfully Eric Sunshine
                   ` (18 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

When --ignore-other-worktree is specified, we unconditionally skip the
check to see if the requested branch is already checked out in a linked
worktree. Since we know that we will be skipping that check, there is no
need to resolve HEAD in order to detect other conditions under which we
may skip the check.

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

No changes since v1.

 builtin/checkout.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 5754554..75f90a9 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1145,13 +1145,13 @@ static int checkout_branch(struct checkout_opts *opts,
 		die(_("Cannot switch branch to a non-commit '%s'"),
 		    new->name);
 
-	if (new->path && !opts->force_detach && !opts->new_branch) {
+	if (new->path && !opts->force_detach && !opts->new_branch &&
+	    !opts->ignore_other_worktrees) {
 		unsigned char sha1[20];
 		int flag;
 		char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
 		if (head_ref &&
-		    (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) &&
-		    !opts->ignore_other_worktrees)
+		    (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
 			check_linked_checkouts(new);
 		free(head_ref);
 	}
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 02/20] checkout: name check_linked_checkouts() more meaningfully
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 01/20] checkout: avoid resolving HEAD unnecessarily Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 03/20] checkout: improve die_if_checked_out() robustness Eric Sunshine
                   ` (17 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

check_linked_checkouts() doesn't just "check" linked checkouts for
"something"; specifically, it aborts the operation if the branch about
to be checked out is already checked out elsewhere. Therefore, rename it
to die_if_checked_out() to give a better indication of its function.
The more meaningful name will be particularly important when this
function is later published for use by other callers.

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

No changes since v1.

 builtin/checkout.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 75f90a9..e75fb5e 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -910,7 +910,7 @@ done:
 	strbuf_release(&gitdir);
 }
 
-static void check_linked_checkouts(struct branch_info *new)
+static void die_if_checked_out(struct branch_info *new)
 {
 	struct strbuf path = STRBUF_INIT;
 	DIR *dir;
@@ -1152,7 +1152,7 @@ static int checkout_branch(struct checkout_opts *opts,
 		char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
 		if (head_ref &&
 		    (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
-			check_linked_checkouts(new);
+			die_if_checked_out(new);
 		free(head_ref);
 	}
 
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 03/20] checkout: improve die_if_checked_out() robustness
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 01/20] checkout: avoid resolving HEAD unnecessarily Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 02/20] checkout: name check_linked_checkouts() more meaningfully Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 04/20] checkout: die_if_checked_out: simplify strbuf management Eric Sunshine
                   ` (16 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

die_if_checked_out() is intended to check if the branch about to be
checked out is already checked out either in the main worktree or in a
linked worktree. However, if .git/worktrees directory does not exist,
then it never bothers checking the main worktree, even though the
specified branch might indeed be checked out there, which is fragile
behavior.

This hasn't been a problem in practice since the current implementation
of "git worktree add" (and, earlier, "git checkout --to") always creates
.git/worktrees before die_if_checked_out() is called by the child "git
checkout" invocation which populates the new worktree.

However, git-worktree will eventually want to call die_if_checked_out()
itself rather than only doing so indirectly as a side-effect of invoking
git-checkout, and reliance upon order of operations (creating
.git/worktrees before checking if a branch is already checked out) is
fragile. As a general function, callers should not be expected to abide
by this undocumented and unwarranted restriction. Therefore, make
die_if_checked_out() more robust by checking the main worktree whether
.git/worktrees exists or not.

While here, also move a comment explaining why die_if_checked_out()'s
helper parses HEAD manually. Such information resides more naturally
with the helper itself rather than at its first point of call.

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

Changes since v1: relocate comment; reword commit message.

 builtin/checkout.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index e75fb5e..1992c41 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -880,6 +880,11 @@ static void check_linked_checkout(struct branch_info *new, const char *id)
 	struct strbuf gitdir = STRBUF_INIT;
 	const char *start, *end;
 
+	/*
+	 * $GIT_COMMON_DIR/HEAD is practically outside
+	 * $GIT_DIR so resolve_ref_unsafe() won't work (it
+	 * uses git_path). Parse the ref ourselves.
+	 */
 	if (id)
 		strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
 	else
@@ -916,19 +921,14 @@ static void die_if_checked_out(struct branch_info *new)
 	DIR *dir;
 	struct dirent *d;
 
+	check_linked_checkout(new, NULL);
+
 	strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
 	if ((dir = opendir(path.buf)) == NULL) {
 		strbuf_release(&path);
 		return;
 	}
 
-	/*
-	 * $GIT_COMMON_DIR/HEAD is practically outside
-	 * $GIT_DIR so resolve_ref_unsafe() won't work (it
-	 * uses git_path). Parse the ref ourselves.
-	 */
-	check_linked_checkout(new, NULL);
-
 	while ((d = readdir(dir)) != NULL) {
 		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
 			continue;
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 04/20] checkout: die_if_checked_out: simplify strbuf management
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (2 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 03/20] checkout: improve die_if_checked_out() robustness Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 05/20] checkout: generalize die_if_checked_out() branch name argument Eric Sunshine
                   ` (15 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

There is no reason to keep the strbuf active long after its last use.
By releasing it as early as possible, resource management is simplified
and there is less worry about future changes resulting in a leak.

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

No changes since v1.

 builtin/checkout.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 1992c41..c36bf8a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -924,17 +924,16 @@ static void die_if_checked_out(struct branch_info *new)
 	check_linked_checkout(new, NULL);
 
 	strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
-	if ((dir = opendir(path.buf)) == NULL) {
-		strbuf_release(&path);
+	dir = opendir(path.buf);
+	strbuf_release(&path);
+	if (!dir)
 		return;
-	}
 
 	while ((d = readdir(dir)) != NULL) {
 		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
 			continue;
 		check_linked_checkout(new, d->d_name);
 	}
-	strbuf_release(&path);
 	closedir(dir);
 }
 
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 05/20] checkout: generalize die_if_checked_out() branch name argument
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (3 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 04/20] checkout: die_if_checked_out: simplify strbuf management Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 06/20] checkout: check_linked_checkout: improve "already checked out" aesthetic Eric Sunshine
                   ` (14 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

The plan is to publish die_if_checked_out() so that callers other than
git-checkout can take advantage of it, however, those callers won't have
access to git-checkout's "struct branch_info". Therefore, change it to
accept the full name of the branch as a simple string instead.

While here, also give the argument a more meaningful name ("branch"
instead of "new").

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

No changes since v1.

 builtin/checkout.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index c36bf8a..177ad6a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -873,7 +873,7 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)
 	return NULL;
 }
 
-static void check_linked_checkout(struct branch_info *new, const char *id)
+static void check_linked_checkout(const char *branch, const char *id)
 {
 	struct strbuf sb = STRBUF_INIT;
 	struct strbuf path = STRBUF_INIT;
@@ -898,7 +898,7 @@ static void check_linked_checkout(struct branch_info *new, const char *id)
 	end = start;
 	while (*end && !isspace(*end))
 		end++;
-	if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0')
+	if (strncmp(start, branch, end - start) || branch[end - start] != '\0')
 		goto done;
 	if (id) {
 		strbuf_reset(&path);
@@ -908,20 +908,21 @@ static void check_linked_checkout(struct branch_info *new, const char *id)
 		strbuf_rtrim(&gitdir);
 	} else
 		strbuf_addstr(&gitdir, get_git_common_dir());
-	die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf);
+	skip_prefix(branch, "refs/heads/", &branch);
+	die(_("'%s' is already checked out at '%s'"), branch, gitdir.buf);
 done:
 	strbuf_release(&path);
 	strbuf_release(&sb);
 	strbuf_release(&gitdir);
 }
 
-static void die_if_checked_out(struct branch_info *new)
+static void die_if_checked_out(const char *branch)
 {
 	struct strbuf path = STRBUF_INIT;
 	DIR *dir;
 	struct dirent *d;
 
-	check_linked_checkout(new, NULL);
+	check_linked_checkout(branch, NULL);
 
 	strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
 	dir = opendir(path.buf);
@@ -932,7 +933,7 @@ static void die_if_checked_out(struct branch_info *new)
 	while ((d = readdir(dir)) != NULL) {
 		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
 			continue;
-		check_linked_checkout(new, d->d_name);
+		check_linked_checkout(branch, d->d_name);
 	}
 	closedir(dir);
 }
@@ -1151,7 +1152,7 @@ static int checkout_branch(struct checkout_opts *opts,
 		char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
 		if (head_ref &&
 		    (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
-			die_if_checked_out(new);
+			die_if_checked_out(new->path);
 		free(head_ref);
 	}
 
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 06/20] checkout: check_linked_checkout: improve "already checked out" aesthetic
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (4 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 05/20] checkout: generalize die_if_checked_out() branch name argument Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16 17:55   ` Junio C Hamano
  2015-07-16  8:20 ` [PATCH v2 07/20] checkout: check_linked_checkout: simplify symref parsing Eric Sunshine
                   ` (13 subsequent siblings)
  19 siblings, 1 reply; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

When check_linked_checkout() discovers that the branch is already
checked out elsewhere, it emits the diagnostic:

    'blorp' is already checked out at '/some/path/.git'

which is mildly misleading and a bit unsightly due to the trailing
"/.git". For the user, "/some/path" is significant, whereas "/.git" is
mere noise, so drop it.

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

New in v2.

 builtin/checkout.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 177ad6a..a331345 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -909,6 +909,7 @@ static void check_linked_checkout(const char *branch, const char *id)
 	} else
 		strbuf_addstr(&gitdir, get_git_common_dir());
 	skip_prefix(branch, "refs/heads/", &branch);
+	strbuf_strip_suffix(&gitdir, "/.git");
 	die(_("'%s' is already checked out at '%s'"), branch, gitdir.buf);
 done:
 	strbuf_release(&path);
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 07/20] checkout: check_linked_checkout: simplify symref parsing
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (5 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 06/20] checkout: check_linked_checkout: improve "already checked out" aesthetic Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 08/20] checkout: teach check_linked_checkout() about symbolic link HEAD Eric Sunshine
                   ` (12 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

check_linked_checkout() only understands symref-style HEAD (i.e. "ref:
refs/heads/master"), however, HEAD may also be a an actual symbolic link
(on platforms which support it), thus it will need to check that style
HEAD, as well (via readlink()). As a preparatory step, simplify parsing
of symref-style HEAD so the actual branch check can be re-used easily
for symbolic links (in an upcoming patch).

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

New in v2: code cleanup in preparation for new patch 08/20.

 builtin/checkout.c | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index a331345..3487712 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -878,7 +878,6 @@ static void check_linked_checkout(const char *branch, const char *id)
 	struct strbuf sb = STRBUF_INIT;
 	struct strbuf path = STRBUF_INIT;
 	struct strbuf gitdir = STRBUF_INIT;
-	const char *start, *end;
 
 	/*
 	 * $GIT_COMMON_DIR/HEAD is practically outside
@@ -890,15 +889,13 @@ static void check_linked_checkout(const char *branch, const char *id)
 	else
 		strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
 
-	if (strbuf_read_file(&sb, path.buf, 0) < 0 ||
-	    !skip_prefix(sb.buf, "ref:", &start))
+	if (strbuf_read_file(&sb, path.buf, 0) >= 0 &&
+	    starts_with(sb.buf, "ref:")) {
+		strbuf_remove(&sb, 0, strlen("ref:"));
+		strbuf_trim(&sb);
+	} else
 		goto done;
-	while (isspace(*start))
-		start++;
-	end = start;
-	while (*end && !isspace(*end))
-		end++;
-	if (strncmp(start, branch, end - start) || branch[end - start] != '\0')
+	if (strcmp(sb.buf, branch))
 		goto done;
 	if (id) {
 		strbuf_reset(&path);
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 08/20] checkout: teach check_linked_checkout() about symbolic link HEAD
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (6 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 07/20] checkout: check_linked_checkout: simplify symref parsing Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 09/20] branch: publish die_if_checked_out() Eric Sunshine
                   ` (11 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

check_linked_checkout() only understands symref-style HEAD (i.e. "ref:
refs/heads/master"), however, HEAD may also be a an actual symbolic link
(on platforms which support it). To accurately detect if a branch is
checked out elsewhere, it needs to handle symbolic link HEAD, as well.

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

New in v2.

The new test's title intentionally employs slightly odd grammar to match
the grammar of its brother test just above. Test title grammar could be
cleaned up in a separate patch, but the current series is already overly
long.

 builtin/checkout.c      | 6 +++++-
 t/t2025-worktree-add.sh | 8 ++++++++
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 3487712..3326aa8 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -889,7 +889,11 @@ static void check_linked_checkout(const char *branch, const char *id)
 	else
 		strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
 
-	if (strbuf_read_file(&sb, path.buf, 0) >= 0 &&
+	if (!strbuf_readlink(&sb, path.buf, 0)) {
+		if (!starts_with(sb.buf, "refs/") ||
+		    check_refname_format(sb.buf, 0))
+			goto done;
+	} else if (strbuf_read_file(&sb, path.buf, 0) >= 0 &&
 	    starts_with(sb.buf, "ref:")) {
 		strbuf_remove(&sb, 0, strlen("ref:"));
 		strbuf_trim(&sb);
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
index ead8aa2..9e30690 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -83,6 +83,14 @@ test_expect_success 'die the same branch is already checked out' '
 	)
 '
 
+test_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' '
+	head=$(git -C there rev-parse --git-path HEAD) &&
+	ref=$(git -C there symbolic-ref HEAD) &&
+	rm "$head" &&
+	ln -s "$ref" "$head" &&
+	test_must_fail git -C here checkout newmaster
+'
+
 test_expect_success 'not die the same branch is already checked out' '
 	(
 		cd here &&
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 09/20] branch: publish die_if_checked_out()
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (7 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 08/20] checkout: teach check_linked_checkout() about symbolic link HEAD Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 10/20] worktree: simplify new branch (-b/-B) option checking Eric Sunshine
                   ` (10 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

git-worktree currently conflates new branch creation, setting of HEAD in
the new wortkree, and worktree population into a single sub-invocation
of git-checkout. However, these operations will eventually be separated,
and git-worktree itself will need to be able to detect if the branch is
already checked out elsewhere, rather than relying upon git-branch to
make this determination, so publish die_if_checked_out().

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

Changes since v1: reword commit message.

 branch.c           | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 branch.h           |  7 ++++++
 builtin/checkout.c | 67 ------------------------------------------------------
 3 files changed, 74 insertions(+), 67 deletions(-)

diff --git a/branch.c b/branch.c
index 4bab55a..dfd7698 100644
--- a/branch.c
+++ b/branch.c
@@ -309,3 +309,70 @@ void remove_branch_state(void)
 	unlink(git_path("MERGE_MODE"));
 	unlink(git_path("SQUASH_MSG"));
 }
+
+static void check_linked_checkout(const char *branch, const char *id)
+{
+	struct strbuf sb = STRBUF_INIT;
+	struct strbuf path = STRBUF_INIT;
+	struct strbuf gitdir = STRBUF_INIT;
+
+	/*
+	 * $GIT_COMMON_DIR/HEAD is practically outside
+	 * $GIT_DIR so resolve_ref_unsafe() won't work (it
+	 * uses git_path). Parse the ref ourselves.
+	 */
+	if (id)
+		strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
+	else
+		strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
+
+	if (!strbuf_readlink(&sb, path.buf, 0)) {
+		if (!starts_with(sb.buf, "refs/") ||
+		    check_refname_format(sb.buf, 0))
+			goto done;
+	} else if (strbuf_read_file(&sb, path.buf, 0) >= 0 &&
+	    starts_with(sb.buf, "ref:")) {
+		strbuf_remove(&sb, 0, strlen("ref:"));
+		strbuf_trim(&sb);
+	} else
+		goto done;
+	if (strcmp(sb.buf, branch))
+		goto done;
+	if (id) {
+		strbuf_reset(&path);
+		strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
+		if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
+			goto done;
+		strbuf_rtrim(&gitdir);
+	} else
+		strbuf_addstr(&gitdir, get_git_common_dir());
+	skip_prefix(branch, "refs/heads/", &branch);
+	strbuf_strip_suffix(&gitdir, "/.git");
+	die(_("'%s' is already checked out at '%s'"), branch, gitdir.buf);
+done:
+	strbuf_release(&path);
+	strbuf_release(&sb);
+	strbuf_release(&gitdir);
+}
+
+void die_if_checked_out(const char *branch)
+{
+	struct strbuf path = STRBUF_INIT;
+	DIR *dir;
+	struct dirent *d;
+
+	check_linked_checkout(branch, NULL);
+
+	strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
+	dir = opendir(path.buf);
+	strbuf_release(&path);
+	if (!dir)
+		return;
+
+	while ((d = readdir(dir)) != NULL) {
+		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+			continue;
+		check_linked_checkout(branch, d->d_name);
+	}
+	closedir(dir);
+}
diff --git a/branch.h b/branch.h
index 64173ab..58aa45f 100644
--- a/branch.h
+++ b/branch.h
@@ -52,4 +52,11 @@ extern void install_branch_config(int flag, const char *local, const char *origi
  */
 extern int read_branch_desc(struct strbuf *, const char *branch_name);
 
+/*
+ * Check if a branch is checked out in the main worktree or any linked
+ * worktree and die (with a message describing its checkout location) if
+ * it is.
+ */
+extern void die_if_checked_out(const char *branch);
+
 #endif
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 3326aa8..4ae895c 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -873,73 +873,6 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)
 	return NULL;
 }
 
-static void check_linked_checkout(const char *branch, const char *id)
-{
-	struct strbuf sb = STRBUF_INIT;
-	struct strbuf path = STRBUF_INIT;
-	struct strbuf gitdir = STRBUF_INIT;
-
-	/*
-	 * $GIT_COMMON_DIR/HEAD is practically outside
-	 * $GIT_DIR so resolve_ref_unsafe() won't work (it
-	 * uses git_path). Parse the ref ourselves.
-	 */
-	if (id)
-		strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
-	else
-		strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
-
-	if (!strbuf_readlink(&sb, path.buf, 0)) {
-		if (!starts_with(sb.buf, "refs/") ||
-		    check_refname_format(sb.buf, 0))
-			goto done;
-	} else if (strbuf_read_file(&sb, path.buf, 0) >= 0 &&
-	    starts_with(sb.buf, "ref:")) {
-		strbuf_remove(&sb, 0, strlen("ref:"));
-		strbuf_trim(&sb);
-	} else
-		goto done;
-	if (strcmp(sb.buf, branch))
-		goto done;
-	if (id) {
-		strbuf_reset(&path);
-		strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
-		if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
-			goto done;
-		strbuf_rtrim(&gitdir);
-	} else
-		strbuf_addstr(&gitdir, get_git_common_dir());
-	skip_prefix(branch, "refs/heads/", &branch);
-	strbuf_strip_suffix(&gitdir, "/.git");
-	die(_("'%s' is already checked out at '%s'"), branch, gitdir.buf);
-done:
-	strbuf_release(&path);
-	strbuf_release(&sb);
-	strbuf_release(&gitdir);
-}
-
-static void die_if_checked_out(const char *branch)
-{
-	struct strbuf path = STRBUF_INIT;
-	DIR *dir;
-	struct dirent *d;
-
-	check_linked_checkout(branch, NULL);
-
-	strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
-	dir = opendir(path.buf);
-	strbuf_release(&path);
-	if (!dir)
-		return;
-
-	while ((d = readdir(dir)) != NULL) {
-		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
-			continue;
-		check_linked_checkout(branch, d->d_name);
-	}
-	closedir(dir);
-}
-
 static int parse_branchname_arg(int argc, const char **argv,
 				int dwim_new_local_branch_ok,
 				struct branch_info *new,
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 10/20] worktree: simplify new branch (-b/-B) option checking
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (8 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 09/20] branch: publish die_if_checked_out() Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 11/20] worktree: introduce options container Eric Sunshine
                   ` (9 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

Make 'new_branch' be the name of the new branch for both forced and
non-forced cases; and add boolean 'force_new_branch' to indicate forced
branch creation. This will simplify logic later on when git-worktree
handles branch creation locally rather than delegating it to
git-checkout as part of the worktree population phase.

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

No changes since v1.

 builtin/worktree.c | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 69248ba..c267410 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -272,7 +272,7 @@ static int add_worktree(const char *path, const char **child_argv)
 
 static int add(int ac, const char **av, const char *prefix)
 {
-	int force = 0, detach = 0;
+	int force = 0, detach = 0, force_new_branch;
 	const char *new_branch = NULL, *new_branch_force = NULL;
 	const char *path, *branch;
 	struct argv_array cmd = ARGV_ARRAY_INIT;
@@ -295,7 +295,11 @@ static int add(int ac, const char **av, const char *prefix)
 	path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
 	branch = ac < 2 ? "HEAD" : av[1];
 
-	if (ac < 2 && !new_branch && !new_branch_force) {
+	force_new_branch = !!new_branch_force;
+	if (force_new_branch)
+		new_branch = new_branch_force;
+
+	if (ac < 2 && !new_branch) {
 		int n;
 		const char *s = worktree_basename(path, &n);
 		new_branch = xstrndup(s, n);
@@ -305,9 +309,8 @@ static int add(int ac, const char **av, const char *prefix)
 	if (force)
 		argv_array_push(&cmd, "--ignore-other-worktrees");
 	if (new_branch)
-		argv_array_pushl(&cmd, "-b", new_branch, NULL);
-	if (new_branch_force)
-		argv_array_pushl(&cmd, "-B", new_branch_force, NULL);
+		argv_array_pushl(&cmd, force_new_branch ? "-B" : "-b",
+				 new_branch, NULL);
 	if (detach)
 		argv_array_push(&cmd, "--detach");
 	argv_array_push(&cmd, branch);
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 11/20] worktree: introduce options container
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (9 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 10/20] worktree: simplify new branch (-b/-B) option checking Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 12/20] worktree: make --detach mutually exclusive with -b/-B Eric Sunshine
                   ` (8 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

add_worktree() will eventually need to deal with some options itself, so
introduce a structure into which options can be conveniently bundled,
and pass it along to add_worktree().

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

No changes since v1.

 builtin/worktree.c | 45 +++++++++++++++++++++++++++------------------
 1 file changed, 27 insertions(+), 18 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index c267410..253382a 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -12,6 +12,13 @@ static const char * const worktree_usage[] = {
 	NULL
 };
 
+struct add_opts {
+	int force;
+	int detach;
+	const char *new_branch;
+	int force_new_branch;
+};
+
 static int show_only;
 static int verbose;
 static unsigned long expire;
@@ -171,7 +178,8 @@ static const char *worktree_basename(const char *path, int *olen)
 	return name;
 }
 
-static int add_worktree(const char *path, const char **child_argv)
+static int add_worktree(const char *path, const char **child_argv,
+			const struct add_opts *opts)
 {
 	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
 	struct strbuf sb = STRBUF_INIT;
@@ -272,22 +280,23 @@ static int add_worktree(const char *path, const char **child_argv)
 
 static int add(int ac, const char **av, const char *prefix)
 {
-	int force = 0, detach = 0, force_new_branch;
-	const char *new_branch = NULL, *new_branch_force = NULL;
+	struct add_opts opts;
+	const char *new_branch_force = NULL;
 	const char *path, *branch;
 	struct argv_array cmd = ARGV_ARRAY_INIT;
 	struct option options[] = {
-		OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")),
-		OPT_STRING('b', NULL, &new_branch, N_("branch"),
+		OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")),
+		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
 			   N_("create a new branch")),
 		OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
 			   N_("create or reset a branch")),
-		OPT_BOOL(0, "detach", &detach, N_("detach HEAD at named commit")),
+		OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")),
 		OPT_END()
 	};
 
+	memset(&opts, 0, sizeof(opts));
 	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
-	if (new_branch && new_branch_force)
+	if (opts.new_branch && new_branch_force)
 		die(_("-b and -B are mutually exclusive"));
 	if (ac < 1 || ac > 2)
 		usage_with_options(worktree_usage, options);
@@ -295,27 +304,27 @@ static int add(int ac, const char **av, const char *prefix)
 	path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
 	branch = ac < 2 ? "HEAD" : av[1];
 
-	force_new_branch = !!new_branch_force;
-	if (force_new_branch)
-		new_branch = new_branch_force;
+	opts.force_new_branch = !!new_branch_force;
+	if (opts.force_new_branch)
+		opts.new_branch = new_branch_force;
 
-	if (ac < 2 && !new_branch) {
+	if (ac < 2 && !opts.new_branch) {
 		int n;
 		const char *s = worktree_basename(path, &n);
-		new_branch = xstrndup(s, n);
+		opts.new_branch = xstrndup(s, n);
 	}
 
 	argv_array_push(&cmd, "checkout");
-	if (force)
+	if (opts.force)
 		argv_array_push(&cmd, "--ignore-other-worktrees");
-	if (new_branch)
-		argv_array_pushl(&cmd, force_new_branch ? "-B" : "-b",
-				 new_branch, NULL);
-	if (detach)
+	if (opts.new_branch)
+		argv_array_pushl(&cmd, opts.force_new_branch ? "-B" : "-b",
+				 opts.new_branch, NULL);
+	if (opts.detach)
 		argv_array_push(&cmd, "--detach");
 	argv_array_push(&cmd, branch);
 
-	return add_worktree(path, cmd.argv);
+	return add_worktree(path, cmd.argv, &opts);
 }
 
 int cmd_worktree(int ac, const char **av, const char *prefix)
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 12/20] worktree: make --detach mutually exclusive with -b/-B
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (10 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 11/20] worktree: introduce options container Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 13/20] worktree: make branch creation distinct from worktree population Eric Sunshine
                   ` (7 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

Be consistent with git-checkout which disallows this (not particularly
meaningful) combination.

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

No changes since v1.

 builtin/worktree.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 253382a..cd06bf5 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -296,8 +296,8 @@ static int add(int ac, const char **av, const char *prefix)
 
 	memset(&opts, 0, sizeof(opts));
 	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
-	if (opts.new_branch && new_branch_force)
-		die(_("-b and -B are mutually exclusive"));
+	if (!!opts.detach + !!opts.new_branch + !!new_branch_force > 1)
+		die(_("-b, -B, and --detach are mutually exclusive"));
 	if (ac < 1 || ac > 2)
 		usage_with_options(worktree_usage, options);
 
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 13/20] worktree: make branch creation distinct from worktree population
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (11 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 12/20] worktree: make --detach mutually exclusive with -b/-B Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 14/20] worktree: elucidate environment variables intended for child processes Eric Sunshine
                   ` (6 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

git-worktree currently conflates branch creation, setting of HEAD in the
new worktree, and worktree population into a single sub-invocation of
git-checkout, which requires git-checkout to be specially aware that it
is operating in a newly-created worktree. The goal is to free
git-checkout of that special knowledge, and to do so, git-worktree will
eventually perform those operations separately. Thus, as a first step,
rather than piggybacking on git-checkout's -b/-B ability to create a new
branch at checkout time, make git-worktree responsible for branch
creation itself.

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

Changes since v1: reword commit message.

 builtin/worktree.c | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index cd06bf5..9be1c74 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -314,12 +314,23 @@ static int add(int ac, const char **av, const char *prefix)
 		opts.new_branch = xstrndup(s, n);
 	}
 
+	if (opts.new_branch) {
+		struct child_process cp;
+		memset(&cp, 0, sizeof(cp));
+		cp.git_cmd = 1;
+		argv_array_push(&cp.args, "branch");
+		if (opts.force_new_branch)
+			argv_array_push(&cp.args, "--force");
+		argv_array_push(&cp.args, opts.new_branch);
+		argv_array_push(&cp.args, branch);
+		if (run_command(&cp))
+			return -1;
+		branch = opts.new_branch;
+	}
+
 	argv_array_push(&cmd, "checkout");
 	if (opts.force)
 		argv_array_push(&cmd, "--ignore-other-worktrees");
-	if (opts.new_branch)
-		argv_array_pushl(&cmd, opts.force_new_branch ? "-B" : "-b",
-				 opts.new_branch, NULL);
 	if (opts.detach)
 		argv_array_push(&cmd, "--detach");
 	argv_array_push(&cmd, branch);
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 14/20] worktree: elucidate environment variables intended for child processes
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (12 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 13/20] worktree: make branch creation distinct from worktree population Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 15/20] worktree: add_worktree: construct worktree-population command locally Eric Sunshine
                   ` (5 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

Take advantage of 'struct child_process.env' to make it obvious that
environment variables set by add_worktree() are intended specifically
for sub-commands it invokes to operate in the new worktree.

We assign a local 'struct argv_array' to child_process.env, rather than
utilizing the child_process.env_array 'struct argv_array', because
future patches will make add_worktree() invoke additional sub-commands,
and it's simpler to populate the environment array just once, whereas
child_process.env_array gets cleared after each invocation, thus would
require re-population for each sub-command.

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

New in v2.

 builtin/worktree.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 9be1c74..7ae186f 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -186,6 +186,7 @@ static int add_worktree(const char *path, const char **child_argv,
 	const char *name;
 	struct stat st;
 	struct child_process cp;
+	struct argv_array child_env = ARGV_ARRAY_INIT;
 	int counter = 0, len, ret;
 	unsigned char rev[20];
 
@@ -256,11 +257,12 @@ static int add_worktree(const char *path, const char **child_argv,
 	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);
+	argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
+	argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
 	memset(&cp, 0, sizeof(cp));
 	cp.git_cmd = 1;
 	cp.argv = child_argv;
+	cp.env = child_env.argv;
 	ret = run_command(&cp);
 	if (!ret) {
 		is_junk = 0;
@@ -272,6 +274,7 @@ static int add_worktree(const char *path, const char **child_argv,
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
 	unlink_or_warn(sb.buf);
+	argv_array_clear(&child_env);
 	strbuf_release(&sb);
 	strbuf_release(&sb_repo);
 	strbuf_release(&sb_git);
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 15/20] worktree: add_worktree: construct worktree-population command locally
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (13 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 14/20] worktree: elucidate environment variables intended for child processes Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 16/20] worktree: detect branch-name/detached and error conditions locally Eric Sunshine
                   ` (4 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

The caller of add_worktree() provides it with a command to invoke to
populate the new worktree. This was a useful abstraction during the
conversion of "git checkout --to" functionality to "git worktree add"
since git-checkout and git-worktree constructed the population command
differently. However, now that "git checkout --to" has been retired, and
add_worktree() has access to the options given to "worktree add", this
extra indirection is no longer useful and makes the code a bit
convoluted.

Moreover, the eventual goal is for git-worktree to make setting of HEAD
and worktree population distinct operations, whereas they are currently
conflated into a single git-checkout invocation. As such, add_worktree()
will eventually invoke other commands in addition to the worktree
population command, so it will be doing command construction itself
anyhow.

Therefore, relocate construction of the worktree population command from
add() to add_worktree().

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

Changes since v1: reword commit message.

 builtin/worktree.c | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 7ae186f..28095c1 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -178,7 +178,7 @@ static const char *worktree_basename(const char *path, int *olen)
 	return name;
 }
 
-static int add_worktree(const char *path, const char **child_argv,
+static int add_worktree(const char *path, const char *refname,
 			const struct add_opts *opts)
 {
 	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
@@ -261,7 +261,12 @@ static int add_worktree(const char *path, const char **child_argv,
 	argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
 	memset(&cp, 0, sizeof(cp));
 	cp.git_cmd = 1;
-	cp.argv = child_argv;
+	argv_array_push(&cp.args, "checkout");
+	if (opts->force)
+		argv_array_push(&cp.args, "--ignore-other-worktrees");
+	if (opts->detach)
+		argv_array_push(&cp.args, "--detach");
+	argv_array_push(&cp.args, refname);
 	cp.env = child_env.argv;
 	ret = run_command(&cp);
 	if (!ret) {
@@ -286,7 +291,6 @@ static int add(int ac, const char **av, const char *prefix)
 	struct add_opts opts;
 	const char *new_branch_force = NULL;
 	const char *path, *branch;
-	struct argv_array cmd = ARGV_ARRAY_INIT;
 	struct option options[] = {
 		OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")),
 		OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
@@ -331,14 +335,7 @@ static int add(int ac, const char **av, const char *prefix)
 		branch = opts.new_branch;
 	}
 
-	argv_array_push(&cmd, "checkout");
-	if (opts.force)
-		argv_array_push(&cmd, "--ignore-other-worktrees");
-	if (opts.detach)
-		argv_array_push(&cmd, "--detach");
-	argv_array_push(&cmd, branch);
-
-	return add_worktree(path, cmd.argv, &opts);
+	return add_worktree(path, branch, &opts);
 }
 
 int cmd_worktree(int ac, const char **av, const char *prefix)
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 16/20] worktree: detect branch-name/detached and error conditions locally
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (14 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 15/20] worktree: add_worktree: construct worktree-population command locally Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 17/20] worktree: make setup of new HEAD distinct from worktree population Eric Sunshine
                   ` (3 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

git-worktree currently conflates setting of HEAD in the new worktree
with initial worktree population via a single git-checkout invocation,
which requires git-checkout to have special knowledge that it is
operating in a newly created worktree. The eventual goal is to separate
these operations and rid git-checkout of that overly-intimate knowledge.

Once these operations are separate, git-worktree will no longer be able
to rely upon git-branch to determine the state of the worktree (branch
name or detached), or to check for error conditions, such as the
requested branch already checked out elsewhere, or an invalid reference.
Therefore, imbue git-worktree with the intelligence to determine a
branch name or detached state locally, and to perform error checking on
its own.

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

Changes since v1: don't bother calling ref_exists() for newly created
branch; add a few in-code comments explaining logic; reword commit
message.

 builtin/worktree.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 28095c1..e8f3f30 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -3,6 +3,8 @@
 #include "dir.h"
 #include "parse-options.h"
 #include "argv-array.h"
+#include "branch.h"
+#include "refs.h"
 #include "run-command.h"
 #include "sigchain.h"
 
@@ -188,11 +190,26 @@ static int add_worktree(const char *path, const char *refname,
 	struct child_process cp;
 	struct argv_array child_env = ARGV_ARRAY_INIT;
 	int counter = 0, len, ret;
+	struct strbuf symref = STRBUF_INIT;
+	struct commit *commit = NULL;
 	unsigned char rev[20];
 
 	if (file_exists(path) && !is_empty_dir(path))
 		die(_("'%s' already exists"), path);
 
+	/* is 'refname' a branch or commit? */
+	if (opts->force_new_branch) /* definitely a branch */
+		;
+	else if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
+		 ref_exists(symref.buf)) { /* it's a branch */
+		if (!opts->force)
+			die_if_checked_out(symref.buf);
+	} else { /* must be a commit */
+		commit = lookup_commit_reference_by_name(refname);
+		if (!commit)
+			die(_("invalid reference: %s"), refname);
+	}
+
 	name = worktree_basename(path, &len);
 	strbuf_addstr(&sb_repo,
 		      git_path("worktrees/%.*s", (int)(path + len - name), name));
@@ -281,6 +298,7 @@ static int add_worktree(const char *path, const char *refname,
 	unlink_or_warn(sb.buf);
 	argv_array_clear(&child_env);
 	strbuf_release(&sb);
+	strbuf_release(&symref);
 	strbuf_release(&sb_repo);
 	strbuf_release(&sb_git);
 	return ret;
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 17/20] worktree: make setup of new HEAD distinct from worktree population
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (15 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 16/20] worktree: detect branch-name/detached and error conditions locally Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 18/20] worktree: avoid resolving HEAD unnecessarily Eric Sunshine
                   ` (2 subsequent siblings)
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

git-worktree currently conflates setting of HEAD in the new worktree and
initial worktree population into a single git-checkout invocation which
requires git-checkout to have special knowledge that it is operating on
a newly created worktree. The eventual goal is to rid git-checkout of
that overly-intimate knowledge.

Once these operations are separate, git-worktree will no longer be able
to delegate to git-branch the setting of the new worktree's HEAD to the
desired branch (or commit, if detached). Therefore, make git-worktree
itself responsible for setting up HEAD as either a symbolic reference,
if associated with a branch, or detached, if not.

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

Changes since v1: reword commit message.

 builtin/worktree.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index e8f3f30..59609e3 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -278,12 +278,21 @@ static int add_worktree(const char *path, const char *refname,
 	argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
 	memset(&cp, 0, sizeof(cp));
 	cp.git_cmd = 1;
+
+	if (commit)
+		argv_array_pushl(&cp.args, "update-ref", "HEAD",
+				 sha1_to_hex(commit->object.sha1), NULL);
+	else
+		argv_array_pushl(&cp.args, "symbolic-ref", "HEAD",
+				 symref.buf, NULL);
+	cp.env = child_env.argv;
+	ret = run_command(&cp);
+	if (ret)
+		goto done;
+
+	cp.argv = NULL;
+	argv_array_clear(&cp.args);
 	argv_array_push(&cp.args, "checkout");
-	if (opts->force)
-		argv_array_push(&cp.args, "--ignore-other-worktrees");
-	if (opts->detach)
-		argv_array_push(&cp.args, "--detach");
-	argv_array_push(&cp.args, refname);
 	cp.env = child_env.argv;
 	ret = run_command(&cp);
 	if (!ret) {
@@ -293,6 +302,7 @@ static int add_worktree(const char *path, const char *refname,
 		junk_work_tree = NULL;
 		junk_git_dir = NULL;
 	}
+done:
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
 	unlink_or_warn(sb.buf);
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 18/20] worktree: avoid resolving HEAD unnecessarily
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (16 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 17/20] worktree: make setup of new HEAD distinct from worktree population Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 19/20] worktree: populate via "git reset --hard" rather than "git checkout" Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 20/20] checkout: drop intimate knowledge of newly created worktree Eric Sunshine
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

Now that git-worktree sets HEAD explicitly to its final value via either
git-symbolic-ref or git-update-ref, rather than relying upon
git-checkout to do so, the "hack" for pacifying is_git_directory() with
a temporary HEAD, though still necessary, can be simplified.

Since the real HEAD is now populated with its proper final value, the
value of the temporary HEAD truly no longer matters, and any value which
looks like an object ID is good enough to satisfy is_git_directory().
Therefore, just set the temporary HEAD to a literal value rather than
going through the effort of resolving the current branch's HEAD.

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

No changes since v1.

 builtin/worktree.c | 17 +++++------------
 1 file changed, 5 insertions(+), 12 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 59609e3..54f2f35 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -192,7 +192,6 @@ static int add_worktree(const char *path, const char *refname,
 	int counter = 0, len, ret;
 	struct strbuf symref = STRBUF_INIT;
 	struct commit *commit = NULL;
-	unsigned char rev[20];
 
 	if (file_exists(path) && !is_empty_dir(path))
 		die(_("'%s' already exists"), path);
@@ -253,20 +252,14 @@ static int add_worktree(const char *path, const char *refname,
 		   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. Moreover, HEAD
-	 * in the new worktree must resolve to the same value as HEAD in
-	 * the current tree since the command invoked to populate the new
-	 * worktree will be handed the branch/ref specified by the user.
-	 * For instance, if the user asks for the new worktree to be based
-	 * at HEAD~5, then the resolved HEAD~5 in the new worktree must
-	 * match the resolved HEAD~5 in the current tree in order to match
-	 * the user's expectation.
+	 * or is_git_directory() will reject the directory. Any value which
+	 * looks like an object ID will do since it will be immediately
+	 * replaced by the symbolic-ref or update-ref invocation in the new
+	 * worktree.
 	 */
-	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));
+	write_file(sb.buf, 1, "0000000000000000000000000000000000000000\n");
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
 	write_file(sb.buf, 1, "../..\n");
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 19/20] worktree: populate via "git reset --hard" rather than "git checkout"
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (17 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 18/20] worktree: avoid resolving HEAD unnecessarily Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16  8:20 ` [PATCH v2 20/20] checkout: drop intimate knowledge of newly created worktree Eric Sunshine
  19 siblings, 0 replies; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

Now that git-worktree handles all functionality (--force, --detach,
-b/-B) previously delegated to git-checkout, actual population of the
new worktree can be accomplished more directly and lightweight with
"git reset --hard" in place of "git checkout".

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

Changes since v1: reword commit message.

 builtin/worktree.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 54f2f35..2873064 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -266,7 +266,6 @@ static int add_worktree(const char *path, const char *refname,
 
 	fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
 
-	setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
 	argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
 	argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
 	memset(&cp, 0, sizeof(cp));
@@ -285,7 +284,7 @@ static int add_worktree(const char *path, const char *refname,
 
 	cp.argv = NULL;
 	argv_array_clear(&cp.args);
-	argv_array_push(&cp.args, "checkout");
+	argv_array_pushl(&cp.args, "reset", "--hard", NULL);
 	cp.env = child_env.argv;
 	ret = run_command(&cp);
 	if (!ret) {
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v2 20/20] checkout: drop intimate knowledge of newly created worktree
  2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
                   ` (18 preceding siblings ...)
  2015-07-16  8:20 ` [PATCH v2 19/20] worktree: populate via "git reset --hard" rather than "git checkout" Eric Sunshine
@ 2015-07-16  8:20 ` Eric Sunshine
  2015-07-16 18:13   ` Junio C Hamano
  19 siblings, 1 reply; 25+ messages in thread
From: Eric Sunshine @ 2015-07-16  8:20 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Duy Nguyen, Michael J Gruber, Eric Sunshine

Now that git-worktree no longer relies upon git-checkout for new branch
creation, new worktree HEAD set up, or initial worktree population,
git-checkout no longer needs intimate knowledge that it may be operating
in a newly created worktree. Therefore, drop 'new_worktree_mode' and the
private GIT_CHECKOUT_NEW_WORKTREE environment variable by which
git-worktree communicated to git-checkout that it was being invoked to
manipulate a new worktree.

This reverts the remaining changes to checkout.c by 529fef2 (checkout:
support checking out into a new working directory, 2014-11-30).

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

Changes since v1: reword commit message.

 builtin/checkout.c | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 4ae895c..02d78ba 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -48,8 +48,6 @@ struct checkout_opts {
 	const char *prefix;
 	struct pathspec pathspec;
 	struct tree *source_tree;
-
-	int new_worktree_mode;
 };
 
 static int post_checkout_hook(struct commit *old, struct commit *new,
@@ -491,7 +489,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 			topts.dir->flags |= DIR_SHOW_IGNORED;
 			setup_standard_excludes(topts.dir);
 		}
-		tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ?
+		tree = parse_tree_indirect(old->commit ?
 					   old->commit->object.sha1 :
 					   EMPTY_TREE_SHA1_BIN);
 		init_tree_desc(&trees[0], tree->buffer, tree->size);
@@ -807,8 +805,7 @@ static int switch_branches(const struct checkout_opts *opts,
 		return ret;
 	}
 
-	if (!opts->quiet && !old.path && old.commit &&
-	    new->commit != old.commit && !opts->new_worktree_mode)
+	if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
 		orphaned_commit_warning(old.commit, new->commit);
 
 	update_refs_for_switch(opts, &old, new);
@@ -1151,8 +1148,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
-	opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL;
-
 	if (conflict_style) {
 		opts.merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
-- 
2.5.0.rc2.378.g0af52e8

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v2 06/20] checkout: check_linked_checkout: improve "already checked out" aesthetic
  2015-07-16  8:20 ` [PATCH v2 06/20] checkout: check_linked_checkout: improve "already checked out" aesthetic Eric Sunshine
@ 2015-07-16 17:55   ` Junio C Hamano
  2015-07-17  0:32     ` Eric Sunshine
  0 siblings, 1 reply; 25+ messages in thread
From: Junio C Hamano @ 2015-07-16 17:55 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: git, Duy Nguyen, Michael J Gruber

Eric Sunshine <sunshine@sunshineco.com> writes:

> When check_linked_checkout() discovers that the branch is already
> checked out elsewhere, it emits the diagnostic:
>
>     'blorp' is already checked out at '/some/path/.git'
>
> which is mildly misleading and a bit unsightly due to the trailing
> "/.git". For the user, "/some/path" is significant, whereas "/.git" is
> mere noise, so drop it.

More importantly, when a user hears "a checkout", the word means the
working tree location.  My top-level Makefile is at /some/path/Makefile,
not /some/path/.git/Makefile, so having /.git suffix is wrong.

How does this work with manually configured GIT_DIR environment, by
the way?  I think GIT_DIR=/collection/of/repos/foo.git would be OK,
as strbuf_strip_suffix() would hopefully leave it intact, but I am
more interested in the general working of linked checkout feature,
not just this error message.

In the new world order with GIT_DIR and GIT_COMMON_DIR, does
"$GIT_DIR" always have to be the same as "$GIT_WORK_TREE/.git"?  Do
we need some sanity check if that is the case?  Perhaps: if you have
$GIT_DIR set to $somewhere/.git/worktrees/$name, then

 - $GIT_COMMON_DIR must match $somewhere/.git,

 - $somewhere/.git/worktrees/$name/commondir must point at
   $GIT_COMMON_DIR,

 - $GIT_WORK_TREE/.git must match $GIT_DIR

or something like that?

> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
> ---
>
> New in v2.
>
>  builtin/checkout.c | 1 +
>  1 file changed, 1 insertion(+)
>
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 177ad6a..a331345 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -909,6 +909,7 @@ static void check_linked_checkout(const char *branch, const char *id)
>  	} else
>  		strbuf_addstr(&gitdir, get_git_common_dir());
>  	skip_prefix(branch, "refs/heads/", &branch);
> +	strbuf_strip_suffix(&gitdir, "/.git");

Sick people have '/.git' and run "git add etc/passwd"; do we want to
consider such a use case?

>  	die(_("'%s' is already checked out at '%s'"), branch, gitdir.buf);
>  done:
>  	strbuf_release(&path);

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v2 20/20] checkout: drop intimate knowledge of newly created worktree
  2015-07-16  8:20 ` [PATCH v2 20/20] checkout: drop intimate knowledge of newly created worktree Eric Sunshine
@ 2015-07-16 18:13   ` Junio C Hamano
  0 siblings, 0 replies; 25+ messages in thread
From: Junio C Hamano @ 2015-07-16 18:13 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: git, Duy Nguyen, Michael J Gruber

Eric Sunshine <sunshine@sunshineco.com> writes:

> Now that git-worktree no longer relies upon git-checkout for new branch
> creation, new worktree HEAD set up, or initial worktree population,
> git-checkout no longer needs intimate knowledge that it may be operating
> in a newly created worktree. Therefore, drop 'new_worktree_mode' and the
> private GIT_CHECKOUT_NEW_WORKTREE environment variable by which
> git-worktree communicated to git-checkout that it was being invoked to
> manipulate a new worktree.
>
> This reverts the remaining changes to checkout.c by 529fef2 (checkout:
> support checking out into a new working directory, 2014-11-30).

The diff between 529fef2^ and the version after applying this series
in builtin/checkout.c looks very sensible; essentially it is just
10f102be (checkout: pass whole struct to parse_branchname_arg
instead of individual flags, 2015-01-03).

Thanks.

> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
> ---
>
> Changes since v1: reword commit message.
>
>  builtin/checkout.c | 9 ++-------
>  1 file changed, 2 insertions(+), 7 deletions(-)
>
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 4ae895c..02d78ba 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -48,8 +48,6 @@ struct checkout_opts {
>  	const char *prefix;
>  	struct pathspec pathspec;
>  	struct tree *source_tree;
> -
> -	int new_worktree_mode;
>  };
>  
>  static int post_checkout_hook(struct commit *old, struct commit *new,
> @@ -491,7 +489,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
>  			topts.dir->flags |= DIR_SHOW_IGNORED;
>  			setup_standard_excludes(topts.dir);
>  		}
> -		tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ?
> +		tree = parse_tree_indirect(old->commit ?
>  					   old->commit->object.sha1 :
>  					   EMPTY_TREE_SHA1_BIN);
>  		init_tree_desc(&trees[0], tree->buffer, tree->size);
> @@ -807,8 +805,7 @@ static int switch_branches(const struct checkout_opts *opts,
>  		return ret;
>  	}
>  
> -	if (!opts->quiet && !old.path && old.commit &&
> -	    new->commit != old.commit && !opts->new_worktree_mode)
> +	if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
>  		orphaned_commit_warning(old.commit, new->commit);
>  
>  	update_refs_for_switch(opts, &old, new);
> @@ -1151,8 +1148,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
>  	argc = parse_options(argc, argv, prefix, options, checkout_usage,
>  			     PARSE_OPT_KEEP_DASHDASH);
>  
> -	opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL;
> -
>  	if (conflict_style) {
>  		opts.merge = 1; /* implied */
>  		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v2 06/20] checkout: check_linked_checkout: improve "already checked out" aesthetic
  2015-07-16 17:55   ` Junio C Hamano
@ 2015-07-17  0:32     ` Eric Sunshine
  2015-07-17  1:42       ` Duy Nguyen
  0 siblings, 1 reply; 25+ messages in thread
From: Eric Sunshine @ 2015-07-17  0:32 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git List, Duy Nguyen, Michael J Gruber

On Thu, Jul 16, 2015 at 1:55 PM, Junio C Hamano <gitster@pobox.com> wrote:
> How does this work with manually configured GIT_DIR environment, by
> the way?  I think GIT_DIR=/collection/of/repos/foo.git would be OK,
> as strbuf_strip_suffix() would hopefully leave it intact, but I am
> more interested in the general working of linked checkout feature,
> not just this error message.

I may be misunderstanding your question, but my answer is that
strbuf_strip_suffix() is not applied to $GIT_DIR, but rather to the
content of file $GIT_COMMON_DIR/worktrees/<tag>/gitdir, which is the
path to the .git file in the linked worktree. That is, given:

    git worktree add ../foo HEAD

then the content of 'gitdir' is the absolute path of "../foo/.git",
and strbuf_strip_suffix() operates on that value.

> In the new world order with GIT_DIR and GIT_COMMON_DIR, does
> "$GIT_DIR" always have to be the same as "$GIT_WORK_TREE/.git"?  Do
> we need some sanity check if that is the case?  Perhaps: if you have
> $GIT_DIR set to $somewhere/.git/worktrees/$name, then
>
>  - $GIT_COMMON_DIR must match $somewhere/.git,
>
>  - $somewhere/.git/worktrees/$name/commondir must point at
>    $GIT_COMMON_DIR,
>
>  - $GIT_WORK_TREE/.git must match $GIT_DIR
>
> or something like that?

Duy is probably better suited to answer this, as he would likely have
taken these issues into consideration when implementing the feature.
(I've been poking through documentation and code for quite a while
trying to answer this email but don't yet have a sufficient grasp to
do it justice. I'm not even sure where such a sanity check would be
placed.)

>>       } else
>>               strbuf_addstr(&gitdir, get_git_common_dir());
>>       skip_prefix(branch, "refs/heads/", &branch);
>> +     strbuf_strip_suffix(&gitdir, "/.git");
>
> Sick people have '/.git' and run "git add etc/passwd"; do we want to
> consider such a use case?

I originally implemented this as stripping only ".git" since it felt
natural for the result to have a trailing slash, however, when I
looked back at your report[1], I saw that you suggested stripping
"/.git", so I changed it to strip the slash as well. Given the above
"sick" use-case, we may indeed want to strip only ".git".

[1]: http://article.gmane.org/gmane.comp.version-control.git/274001

>>       die(_("'%s' is already checked out at '%s'"), branch, gitdir.buf);
>>  done:
>>       strbuf_release(&path);

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v2 06/20] checkout: check_linked_checkout: improve "already checked out" aesthetic
  2015-07-17  0:32     ` Eric Sunshine
@ 2015-07-17  1:42       ` Duy Nguyen
  0 siblings, 0 replies; 25+ messages in thread
From: Duy Nguyen @ 2015-07-17  1:42 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Junio C Hamano, Git List, Michael J Gruber

On Fri, Jul 17, 2015 at 7:32 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:
>> In the new world order with GIT_DIR and GIT_COMMON_DIR, does
>> "$GIT_DIR" always have to be the same as "$GIT_WORK_TREE/.git"?  Do
>> we need some sanity check if that is the case?  Perhaps: if you have
>> $GIT_DIR set to $somewhere/.git/worktrees/$name, then
>>
>>  - $GIT_COMMON_DIR must match $somewhere/.git,
>>
>>  - $somewhere/.git/worktrees/$name/commondir must point at
>>    $GIT_COMMON_DIR,
>>
>>  - $GIT_WORK_TREE/.git must match $GIT_DIR
>>
>> or something like that?
>
> Duy is probably better suited to answer this, as he would likely have
> taken these issues into consideration when implementing the feature.
> (I've been poking through documentation and code for quite a while
> trying to answer this email but don't yet have a sufficient grasp to
> do it justice. I'm not even sure where such a sanity check would be
> placed.)

The thing is, we just don't know where the worktree is. All we know is
somewhere there is a .git file sharing this repository. People can
create a linked worktree, then move the actual linked worktree away,
set GIT_DIR/GIT_WORK_TREE to reflect that, and everything must still
work. So, we could say "foo is already checked out at the worktree
that is linked to /some/path/.git" to be technically correct. But
that's not so friendly? We could cache the $GIT_WORK_TREE, when the
user accesses the linked checkout, somewhere in .git/worktrees/foo and
show it instead of /some/path/.git. But that's not always accurate.
-- 
Duy

^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2015-07-17  1:43 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-07-16  8:20 [PATCH v2 00/20] rid git-checkout of too-intimate knowledge of new worktree Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 01/20] checkout: avoid resolving HEAD unnecessarily Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 02/20] checkout: name check_linked_checkouts() more meaningfully Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 03/20] checkout: improve die_if_checked_out() robustness Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 04/20] checkout: die_if_checked_out: simplify strbuf management Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 05/20] checkout: generalize die_if_checked_out() branch name argument Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 06/20] checkout: check_linked_checkout: improve "already checked out" aesthetic Eric Sunshine
2015-07-16 17:55   ` Junio C Hamano
2015-07-17  0:32     ` Eric Sunshine
2015-07-17  1:42       ` Duy Nguyen
2015-07-16  8:20 ` [PATCH v2 07/20] checkout: check_linked_checkout: simplify symref parsing Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 08/20] checkout: teach check_linked_checkout() about symbolic link HEAD Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 09/20] branch: publish die_if_checked_out() Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 10/20] worktree: simplify new branch (-b/-B) option checking Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 11/20] worktree: introduce options container Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 12/20] worktree: make --detach mutually exclusive with -b/-B Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 13/20] worktree: make branch creation distinct from worktree population Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 14/20] worktree: elucidate environment variables intended for child processes Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 15/20] worktree: add_worktree: construct worktree-population command locally Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 16/20] worktree: detect branch-name/detached and error conditions locally Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 17/20] worktree: make setup of new HEAD distinct from worktree population Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 18/20] worktree: avoid resolving HEAD unnecessarily Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 19/20] worktree: populate via "git reset --hard" rather than "git checkout" Eric Sunshine
2015-07-16  8:20 ` [PATCH v2 20/20] checkout: drop intimate knowledge of newly created worktree Eric Sunshine
2015-07-16 18:13   ` Junio C Hamano

Code repositories for project(s) associated with this inbox:

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).