Skip to contentSkip to conversation

possible to checkout same branch in different worktree

PATCH Superseded Reto Hablützel 8 participants 162 messages Jun 16, 2016 – Jun 30, 2016 Documentation

lore
Share & tools
lore
#0 lore raw

Hi,

the checkout command prevents me from checking out a branch in the current worktree if it is already checked out in another worktree.

However, if I rebase the branch in the current worktree onto the branch in the other worktree, I end up in a situation where the same branch is checked out twice in the two worktrees.

- Reto

--

PGP
Key ID = 0x67D30264
Fingerprint = E235 F740 4419 5B4B 29EC 3437 CBF2 2CB7 67D3 0264

#1 lore raw
Quoted 6 lines · Reto Hablützel

the checkout command prevents me from checking out a branch in the current worktree if it is already checked out in another worktree.

However, if I rebase the branch in the current worktree onto the branch in the other worktree, I end up in a situation where the same branch is checked out twice in the two worktrees.

I agree that any end-user facing subcommand like "git rebase", even if it is not "git checkout", should refuse to work on and update a branch that is checked out elsewhere. Otherwise it will end up causing confusion.

Thanks for a report. The "multiple worktree" feature is still highly experimental and those who are working on it would appreciate your reporting issues with it.

#2 lore raw
Quoted 5 lines · Junio C Hamano
Quoted 3 lines · Reto Hablützel

the checkout command prevents me from checking out a branch in the current worktree if it is already checked out in another worktree.

Quoted 8 lines
Quoted 3 lines

However, if I rebase the branch in the current worktree onto the branch in the other worktree, I end up in a situation where the same branch is checked out twice in the two worktrees.

I agree that any end-user facing subcommand like "git rebase", even if it is not "git checkout", should refuse to work on and update a branch that is checked out elsewhere. Otherwise it will end up causing confusion.

I agree. I suppose we need same treatment for git-push? A push can be rejected if the pushed ref is being checked out. Suppose HEAD is in the middle of a rebase (or am), it fails to detect ref name (and thus checkout state) the same way here and we definitely do not want "git rebase" to simply overwrite whatever is updated by the push request.

Signature · 1 line
Duy
#3 lore raw

cover fix checking out a being-rebased branch

First cut. I need opinions on 05/07, which converts wt_status_get_state() to support selecting any worktree. I'm not super happy with leaving "TODO: not supported yet" comments, even though strictly speaking this series does not need it.

The reason it's left unsupported is, in order to add worktree_get_sha1() or similar, resolve_ref() will be touched. But that function is going under some changes for quite some time now, for lmdb backend support, and I don't want to create more code conflicts there, potentially delaying lmdb backend further.

Another option is leave wt_status_get_state() alone, factor out the rebase-detection code and use that for worktree/checkout. We save a few syscalls this way too.

Comments?

[01/07] path.c: add git_common_path() and strbuf_git_common_path() [02/07] worktree.c: store "id" instead of "git_dir"
[03/07] path.c: refactor and add worktree_git_path()
[04/07] worktree.c: add worktree_git_path_..._head()
[05/07] wt-status.c: make wt_status_get_state() support worktree [06/07] worktree.c: avoid referencing to worktrees[i] multiple times [07/07] checkout: prevent checking out a branch being rebased in another worktree

Total 6 files changed, 167 insertions(+), 38 deletions(-)

#4 lore raw

PATCH 1/7 path.c: add git_common_path() and strbuf_git_common_path()

These are mostly convenient functions to reduce code duplication. Most of the time, we should be able to get by with git_path() which handles $GIT_COMMON_DIR internally. However there are a few cases where we need to construct paths manually, for example some paths from a specific worktree. These functions will enable that.

---
 cache.h |  3 +++
 path.c  | 29 +++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)
cache.h +3 -0
index 2711048..c04a17f 100644
@@ -799,11 +799,14 @@ extern void check_repository_format(void);
  */
 extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 
 extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	__attribute__((format (printf, 3, 4)));
 extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 	__attribute__((format (printf, 2, 3)));
+extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
+	__attribute__((format (printf, 2, 3)));
 extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
 	__attribute__((format (printf, 2, 3)));
 extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
path.c +29 -0
index bbaea5a..2ebb23d 100644
@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,
 	va_end(args);
 }
 
+static void do_git_common_path(struct strbuf *buf,
+			       const char *fmt,
+			       va_list args)
+{
+	strbuf_addstr(buf, get_git_common_dir());
+	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
+		strbuf_addch(buf, '/');
+	strbuf_vaddf(buf, fmt, args);
+	strbuf_cleanup_path(buf);
+}
+
+const char *git_common_path(const char *fmt, ...)
+{
+	struct strbuf *pathname = get_pathname();
+	va_list args;
+	va_start(args, fmt);
+	do_git_common_path(pathname, fmt, args);
+	va_end(args);
+	return pathname->buf;
+}
+
+void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+	do_git_common_path(sb, fmt, args);
+	va_end(args);
+}
+
 int validate_headref(const char *path)
 {
 	struct stat st;
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#5 lore raw

PATCH 2/7 worktree.c: store "id" instead of "git_dir"

In reply to Nguyễn Thái Ngọc Duy

We can reconstruct git_dir from id quite easily. It's a bit hackier to do the reverse.

---
 worktree.c | 31 ++++++++++++++++++-------------
 worktree.h |  8 +++++++-
 2 files changed, 25 insertions(+), 14 deletions(-)
worktree.c +18 -13
index 6181a66..5ae54f0 100644
@@ -9,7 +9,7 @@ void free_worktrees(struct worktree **worktrees)
 
 	for (i = 0; worktrees[i]; i++) {
 		free(worktrees[i]->path);
-		free(worktrees[i]->git_dir);
+		free(worktrees[i]->id);
 		free(worktrees[i]->head_ref);
 		free(worktrees[i]);
 	}
@@ -74,13 +74,11 @@ static struct worktree *get_main_worktree(void)
 	struct worktree *worktree = NULL;
 	struct strbuf path = STRBUF_INIT;
 	struct strbuf worktree_path = STRBUF_INIT;
-	struct strbuf gitdir = STRBUF_INIT;
 	struct strbuf head_ref = STRBUF_INIT;
 	int is_bare = 0;
 	int is_detached = 0;
 
-	strbuf_addf(&gitdir, "%s", absolute_path(get_git_common_dir()));
-	strbuf_addbuf(&worktree_path, &gitdir);
+	strbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));
 	is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
 	if (is_bare)
 		strbuf_strip_suffix(&worktree_path, "/.");
@@ -92,7 +90,7 @@ static struct worktree *get_main_worktree(void)
 
 	worktree = xmalloc(sizeof(struct worktree));
 	worktree->path = strbuf_detach(&worktree_path, NULL);
-	worktree->git_dir = strbuf_detach(&gitdir, NULL);
+	worktree->id = NULL;
 	worktree->is_bare = is_bare;
 	worktree->head_ref = NULL;
 	worktree->is_detached = is_detached;
@@ -100,7 +98,6 @@ static struct worktree *get_main_worktree(void)
 
 done:
 	strbuf_release(&path);
-	strbuf_release(&gitdir);
 	strbuf_release(&worktree_path);
 	strbuf_release(&head_ref);
 	return worktree;
@@ -111,16 +108,13 @@ static struct worktree *get_linked_worktree(const char *id)
 	struct worktree *worktree = NULL;
 	struct strbuf path = STRBUF_INIT;
 	struct strbuf worktree_path = STRBUF_INIT;
-	struct strbuf gitdir = STRBUF_INIT;
 	struct strbuf head_ref = STRBUF_INIT;
 	int is_detached = 0;
 
 	if (!id)
 		die("Missing linked worktree name");
 
-	strbuf_addf(&gitdir, "%s/worktrees/%s",
-			absolute_path(get_git_common_dir()), id);
-	strbuf_addf(&path, "%s/gitdir", gitdir.buf);
+	strbuf_git_common_path(&path, "worktrees/%s/gitdir", id);
 	if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
 		/* invalid gitdir file */
 		goto done;
@@ -140,7 +134,7 @@ static struct worktree *get_linked_worktree(const char *id)
 
 	worktree = xmalloc(sizeof(struct worktree));
 	worktree->path = strbuf_detach(&worktree_path, NULL);
-	worktree->git_dir = strbuf_detach(&gitdir, NULL);
+	worktree->id = xstrdup(id);
 	worktree->is_bare = 0;
 	worktree->head_ref = NULL;
 	worktree->is_detached = is_detached;
@@ -148,7 +142,6 @@ static struct worktree *get_linked_worktree(const char *id)
 
 done:
 	strbuf_release(&path);
-	strbuf_release(&gitdir);
 	strbuf_release(&worktree_path);
 	strbuf_release(&head_ref);
 	return worktree;
@@ -188,6 +181,16 @@ struct worktree **get_worktrees(void)
 	return list;
 }
 
+const char *get_worktree_git_dir(const struct worktree *wt)
+{
+	if (!wt)
+		return get_git_dir();
+	else if (!wt->id)
+		return get_git_common_dir();
+	else
+		return git_common_path("worktrees/%s", wt->id);
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
@@ -199,7 +202,9 @@ char *find_shared_symref(const char *symref, const char *target)
 	for (i = 0; worktrees[i]; i++) {
 		strbuf_reset(&path);
 		strbuf_reset(&sb);
-		strbuf_addf(&path, "%s/%s", worktrees[i]->git_dir, symref);
+		strbuf_addf(&path, "%s/%s",
+			    get_worktree_git_dir(worktrees[i]),
+			    symref);
 
 		if (parse_ref(path.buf, &sb, NULL)) {
 			continue;
worktree.h +7 -1
index b4b3dda..3198c8d 100644
@@ -3,7 +3,7 @@
 
 struct worktree {
 	char *path;
-	char *git_dir;
+	char *id;
 	char *head_ref;
 	unsigned char head_sha1[20];
 	int is_detached;
@@ -23,6 +23,12 @@ struct worktree {
 extern struct worktree **get_worktrees(void);
 
 /*
+ * Return git dir of the worktree. Note that the path may be relative.
+ * If wt is NULL, git dir of current worktree is returned.
+ */
+extern const char *get_worktree_git_dir(const struct worktree *wt);
+
+/*
  * Free up the memory for worktree(s)
  */
 extern void free_worktrees(struct worktree **);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#6 lore raw

PATCH 3/7 path.c: refactor and add worktree_git_path()

In reply to Nguyễn Thái Ngọc Duy

do_git_path(), which is the common code for all git_path* functions, is modified to take a worktree struct and can produce paths for any worktree.

worktree_git_path() is the first function that makes use of this. It can be used to write code that can examine any worktree. For example, wt_status_get_state() will be converted using this to take am/rebase/... state of any worktree.

---
 path.c     | 34 ++++++++++++++++++++++++++++------
 worktree.h | 11 +++++++++++
 2 files changed, 39 insertions(+), 6 deletions(-)
path.c +28 -6
index 2ebb23d..c421d37 100644
@@ -5,6 +5,7 @@
 #include "strbuf.h"
 #include "string-list.h"
 #include "dir.h"
+#include "worktree.h"
 
 static int get_st_mode_bits(const char *path, int *mode)
 {
@@ -383,10 +384,11 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 		update_common_dir(buf, git_dir_len, NULL);
 }
 
-static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
+static void do_git_path(const struct worktree *wt, struct strbuf *buf,
+			const char *fmt, va_list args)
 {
 	int gitdir_len;
-	strbuf_addstr(buf, get_git_dir());
+	strbuf_addstr(buf, get_worktree_git_dir(wt));
 	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
 		strbuf_addch(buf, '/');
 	gitdir_len = buf->len;
@@ -400,7 +402,7 @@ char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
 	va_list args;
 	strbuf_reset(buf);
 	va_start(args, fmt);
-	do_git_path(buf, fmt, args);
+	do_git_path(NULL, buf, fmt, args);
 	va_end(args);
 	return buf->buf;
 }
@@ -409,7 +411,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 {
 	va_list args;
 	va_start(args, fmt);
-	do_git_path(sb, fmt, args);
+	do_git_path(NULL, sb, fmt, args);
 	va_end(args);
 }
 
@@ -418,7 +420,7 @@ const char *git_path(const char *fmt, ...)
 	struct strbuf *pathname = get_pathname();
 	va_list args;
 	va_start(args, fmt);
-	do_git_path(pathname, fmt, args);
+	do_git_path(NULL, pathname, fmt, args);
 	va_end(args);
 	return pathname->buf;
 }
@@ -428,7 +430,7 @@ char *git_pathdup(const char *fmt, ...)
 	struct strbuf path = STRBUF_INIT;
 	va_list args;
 	va_start(args, fmt);
-	do_git_path(&path, fmt, args);
+	do_git_path(NULL, &path, fmt, args);
 	va_end(args);
 	return strbuf_detach(&path, NULL);
 }
@@ -454,6 +456,26 @@ const char *mkpath(const char *fmt, ...)
 	return cleanup_path(pathname->buf);
 }
 
+const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)
+{
+	struct strbuf *pathname = get_pathname();
+	va_list args;
+	va_start(args, fmt);
+	do_git_path(wt, pathname, fmt, args);
+	va_end(args);
+	return pathname->buf;
+}
+
+char *worktree_git_pathdup(const struct worktree *wt, const char *fmt, ...)
+{
+	struct strbuf path = STRBUF_INIT;
+	va_list args;
+	va_start(args, fmt);
+	do_git_path(wt, &path, fmt, args);
+	va_end(args);
+	return strbuf_detach(&path, NULL);
+}
+
 static void do_submodule_path(struct strbuf *buf, const char *path,
 			      const char *fmt, va_list args)
 {
worktree.h +11 -0
index 3198c8d..9ae2028 100644
@@ -41,4 +41,15 @@ extern void free_worktrees(struct worktree **);
  */
 extern char *find_shared_symref(const char *symref, const char *target);
 
+/*
+ * Similar to git_path() and git_pathdup() but can produce paths for a
+ * specified worktree instead of current one
+ */
+extern const char *worktree_git_path(const struct worktree *wt,
+				     const char *fmt, ...)
+	__attribute__((format (printf, 2, 3)));
+extern char *worktree_git_pathdup(const struct worktree *wt,
+				  const char *fmt, ...)
+	__attribute__((format (printf, 2, 3)));
+
 #endif
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#7 lore raw

PATCH 4/7 worktree.c: add worktree_git_path_..._head()

In reply to Nguyễn Thái Ngọc Duy

These are a variant of git_path_..._head(), defined with GIT_PATH_FUNC macro but they takes worktree into account. To be used when wt_status_get_state() is converted to be worktree-aware.

---
 worktree.c |  4 ++++
 worktree.h | 13 +++++++++++++
 2 files changed, 17 insertions(+)
worktree.c +4 -0
index 5ae54f0..c5bf583 100644
@@ -222,3 +222,7 @@ char *find_shared_symref(const char *symref, const char *target)
 
 	return existing;
 }
+
+WORKTREE_GIT_PATH_FUNC(merge_head, "MERGE_HEAD")
+WORKTREE_GIT_PATH_FUNC(cherry_pick_head, "CHERRY_PICK_HEAD")
+WORKTREE_GIT_PATH_FUNC(revert_head, "REVERT_HEAD")
worktree.h +13 -0
index 9ae2028..b7c5da0 100644
@@ -52,4 +52,17 @@ extern char *worktree_git_pathdup(const struct worktree *wt,
 				  const char *fmt, ...)
 	__attribute__((format (printf, 2, 3)));
 
+#define WORKTREE_GIT_PATH_FUNC(func, filename) \
+	const char *worktree_git_path_ ## func(const struct worktree *wt) \
+	{ \
+		static char *ret; \
+		if (!ret) \
+			ret = worktree_git_pathdup(wt, filename); \
+		return ret; \
+	}
+
+const char *worktree_git_path_merge_head(const struct worktree *);
+const char *worktree_git_path_cherry_pick_head(const struct worktree *);
+const char *worktree_git_path_revert_head(const struct worktree *);
+
 #endif
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#8 lore raw

PATCH 5/7 wt-status.c: make wt_status_get_state() support worktree

In reply to Nguyễn Thái Ngọc Duy

The function is to be used to detect if a ref is checked out in another worktree. In some cases, parsing "HEAD" alone is not enough because the worktree may be under rebase or bisect.

Note that because get_sha1() cannot take a worktree (and of course will not, we need something like worktree_get_sha1), cherry-pick and revert detection will not work when the examined worktree is not the current one. The same for get_detached_from.

---
 wt-status.c | 42 +++++++++++++++++++++++++-----------------
 wt-status.h |  3 +++
 2 files changed, 28 insertions(+), 17 deletions(-)
wt-status.c +25 -17
index 1ea2ebe..6cca3a6 100644
@@ -15,6 +15,7 @@
 #include "column.h"
 #include "strbuf.h"
 #include "utf8.h"
+#include "worktree.h"
 
 static const char cut_line[] =
 "------------------------ >8 ------------------------\n";
@@ -1262,13 +1263,13 @@ static void show_bisect_in_progress(struct wt_status *s,
 /*
  * Extract branch information from rebase/bisect
  */
-static char *read_and_strip_branch(const char *path)
+static char *read_and_strip_branch(const struct worktree *wt, const char *path)
 {
 	struct strbuf sb = STRBUF_INIT;
 	unsigned char sha1[20];
 	const char *branch_name;
 
-	if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0)
+	if (strbuf_read_file(&sb, worktree_git_path(wt, "%s", path), 0) <= 0)
 		goto got_nothing;
 
 	while (sb.len && sb.buf[sb.len - 1] == '\n')
@@ -1363,45 +1364,52 @@ static void wt_status_get_detached_from(struct wt_status_state *state)
 void wt_status_get_state(struct wt_status_state *state,
 			 int get_detached_from)
 {
+	const struct worktree *wt = state->wt;
 	struct stat st;
 	unsigned char sha1[20];
 
-	if (!stat(git_path_merge_head(), &st)) {
+	if (!stat(worktree_git_path_merge_head(wt), &st)) {
 		state->merge_in_progress = 1;
-	} else if (!stat(git_path("rebase-apply"), &st)) {
-		if (!stat(git_path("rebase-apply/applying"), &st)) {
+	} else if (!stat(worktree_git_path(wt, "rebase-apply"), &st)) {
+		if (!stat(worktree_git_path(wt, "rebase-apply/applying"), &st)) {
 			state->am_in_progress = 1;
-			if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
+			if (!stat(worktree_git_path(wt, "rebase-apply/patch"), &st) && !st.st_size)
 				state->am_empty_patch = 1;
 		} else {
 			state->rebase_in_progress = 1;
-			state->branch = read_and_strip_branch("rebase-apply/head-name");
-			state->onto = read_and_strip_branch("rebase-apply/onto");
+			state->branch = read_and_strip_branch(wt, "rebase-apply/head-name");
+			state->onto = read_and_strip_branch(wt, "rebase-apply/onto");
 		}
-	} else if (!stat(git_path("rebase-merge"), &st)) {
-		if (!stat(git_path("rebase-merge/interactive"), &st))
+	} else if (!stat(worktree_git_path(wt, "rebase-merge"), &st)) {
+		if (!stat(worktree_git_path(wt, "rebase-merge/interactive"), &st))
 			state->rebase_interactive_in_progress = 1;
 		else
 			state->rebase_in_progress = 1;
-		state->branch = read_and_strip_branch("rebase-merge/head-name");
-		state->onto = read_and_strip_branch("rebase-merge/onto");
-	} else if (!stat(git_path_cherry_pick_head(), &st) &&
+		state->branch = read_and_strip_branch(wt, "rebase-merge/head-name");
+		state->onto = read_and_strip_branch(wt, "rebase-merge/onto");
+	} else if (!wt && /* TODO: get_sha1() cannot select worktree yet */
+		   !stat(worktree_git_path_cherry_pick_head(wt), &st) &&
 			!get_sha1("CHERRY_PICK_HEAD", sha1)) {
 		state->cherry_pick_in_progress = 1;
 		hashcpy(state->cherry_pick_head_sha1, sha1);
 	}
-	if (!stat(git_path("BISECT_LOG"), &st)) {
+	if (!stat(worktree_git_path(wt, "BISECT_LOG"), &st)) {
 		state->bisect_in_progress = 1;
-		state->branch = read_and_strip_branch("BISECT_START");
+		state->branch = read_and_strip_branch(wt, "BISECT_START");
 	}
-	if (!stat(git_path_revert_head(), &st) &&
+	if (!wt &&  /* TODO: get_sha1() cannot select worktree yet */
+	    !stat(worktree_git_path_revert_head(wt), &st) &&
 	    !get_sha1("REVERT_HEAD", sha1)) {
 		state->revert_in_progress = 1;
 		hashcpy(state->revert_head_sha1, sha1);
 	}
 
-	if (get_detached_from)
+	if (get_detached_from) {
+		if (wt)
+			die("BUG: %s:%d: non-zero get_detached_from is not supported",
+			    __FILE__, __LINE__);
 		wt_status_get_detached_from(state);
+	}
 }
 
 static void wt_status_print_state(struct wt_status *s,
wt-status.h +3 -0
index c9b3b74..d31db38 100644
@@ -6,6 +6,8 @@
 #include "color.h"
 #include "pathspec.h"
 
+struct worktree;
+
 enum color_wt_status {
 	WT_STATUS_HEADER = 0,
 	WT_STATUS_UPDATED,
@@ -77,6 +79,7 @@ struct wt_status {
 };
 
 struct wt_status_state {
+	struct worktree *wt;
 	int merge_in_progress;
 	int am_in_progress;
 	int am_empty_patch;
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#9 lore raw

PATCH 6/7 worktree.c: avoid referencing to worktrees[i] multiple times

In reply to Nguyễn Thái Ngọc Duy
---
 worktree.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
worktree.c +4 -2
index c5bf583..3b7c6f2 100644
@@ -200,10 +200,12 @@ char *find_shared_symref(const char *symref, const char *target)
 	int i = 0;
 
 	for (i = 0; worktrees[i]; i++) {
+		struct worktree *wt = worktrees[i];
+
 		strbuf_reset(&path);
 		strbuf_reset(&sb);
 		strbuf_addf(&path, "%s/%s",
-			    get_worktree_git_dir(worktrees[i]),
+			    get_worktree_git_dir(wt),
 			    symref);
 
 		if (parse_ref(path.buf, &sb, NULL)) {
@@ -211,7 +213,7 @@ char *find_shared_symref(const char *symref, const char *target)
 		}
 
 		if (!strcmp(sb.buf, target)) {
-			existing = xstrdup(worktrees[i]->path);
+			existing = xstrdup(wt->path);
 			break;
 		}
 	}
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#10 lore raw

PATCH 7/7 checkout: prevent checking out a branch being rebased in another worktree

In reply to Nguyễn Thái Ngọc Duy

We failed to detect this case because the rebase-in-progress worktree has HEAD in detached state. Some more poking around is needed to determine if a rebase is in progress.

Note that even though bisect can also put a worktree in detached state and hide the real branch, we allow to checkout anyway. This is because unlike rebase, bisect does not update the branch. Yes it's still a bit confusing when the user doing the bisecting realizes that the branch is updated elsewhere, but at least we do not lose any valuable ref update.

---
 worktree.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)
worktree.c +23 -0
index 3b7c6f2..6dc40c2 100644
@@ -2,6 +2,7 @@
 #include "refs.h"
 #include "strbuf.h"
 #include "worktree.h"
+#include "wt-status.h"
 
 void free_worktrees(struct worktree **worktrees)
 {
@@ -202,6 +203,28 @@ char *find_shared_symref(const char *symref, const char *target)
 	for (i = 0; worktrees[i]; i++) {
 		struct worktree *wt = worktrees[i];
 
+		if (wt->is_detached) {
+			struct wt_status_state wss;
+			int found;
+
+			memset(&wss, 0, sizeof(wss));
+			wss.wt = wt;
+			wt_status_get_state(&wss, 0);
+			found = (wss.rebase_in_progress ||
+				 wss.rebase_interactive_in_progress) &&
+				wss.branch &&
+				starts_with(target, "refs/heads/") &&
+				!strcmp(wss.branch,
+					target + strlen("refs/heads/"));
+			free(wss.branch);
+			free(wss.onto);
+			free(wss.detached_from);
+			if (found) {
+				existing = xstrdup(wt->path);
+				break;
+			}
+		}
+
 		strbuf_reset(&path);
 		strbuf_reset(&sb);
 		strbuf_addf(&path, "%s/%s",
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#11 lore raw

Re: [PATCH 0/7] fix checking out a being-rebased branch

In reply to Nguyễn Thái Ngọc Duy
Quoted 4 lines · Nguyễn Thái Ngọc Duy

First cut. I need opinions on 05/07, which converts wt_status_get_state() to support selecting any worktree. I'm not super happy with leaving "TODO: not supported yet" comments, even though strictly speaking this series does not need it.

It is a reasonable idea to hook this to wt_status_get_state(), I would say.

Quoted 10 lines

Another option is leave wt_status_get_state() alone, factor out the rebase-detection code and use that for worktree/checkout. We save a few syscalls this way too.

Comments?

[01/07] path.c: add git_common_path() and strbuf_git_common_path() [02/07] worktree.c: store "id" instead of "git_dir" [03/07] path.c: refactor and add worktree_git_path() [04/07] worktree.c: add worktree_git_path_..._head()

I actually wondered how ky/branch-[dm]-worktree topics to avoid "branch -d" and "branch -m" from removing or renaming a branch that is checked out in other worktrees from screwing them up. There may be a missed opportunity to clean them up with using these?

Quoted 5 lines

[05/07] wt-status.c: make wt_status_get_state() support worktree [06/07] worktree.c: avoid referencing to worktrees[i] multiple times [07/07] checkout: prevent checking out a branch being rebased in another worktree

Total 6 files changed, 167 insertions(+), 38 deletions(-)

#12 lore raw

Re: [PATCH 0/7] fix checking out a being-rebased branch

Quoted 11 lines · Junio C Hamano
Quoted 10 lines

Another option is leave wt_status_get_state() alone, factor out the rebase-detection code and use that for worktree/checkout. We save a few syscalls this way too.

Comments?

[01/07] path.c: add git_common_path() and strbuf_git_common_path() [02/07] worktree.c: store "id" instead of "git_dir" [03/07] path.c: refactor and add worktree_git_path() [04/07] worktree.c: add worktree_git_path_..._head()

Quoted 4 lines

I actually wondered how ky/branch-[dm]-worktree topics to avoid "branch -d" and "branch -m" from removing or renaming a branch that is checked out in other worktrees from screwing them up. There may be a missed opportunity to clean them up with using these?

branch-m-worktree uses info populated at get_worktrees() phase. branch-d-worktree uses find_shared_symref() which is modified in this series in order to detect rebase branch. So both topics have the same problem when a branch is being rebased and if I do it right, I'll fix them both without extra code. Actually I may need to check branch-m-worktree again, renaming a branch under rebase might cause problems, I need to have a closer look at git-rebase*.sh.

Signature · 1 line
Duy
#13 lore raw

Re: [PATCH 0/7] fix checking out a being-rebased branch

Quoted 17 lines · Duy Nguyen
Quoted 11 lines · Junio C Hamano
Quoted 10 lines

Another option is leave wt_status_get_state() alone, factor out the rebase-detection code and use that for worktree/checkout. We save a few syscalls this way too.

Comments?

[01/07] path.c: add git_common_path() and strbuf_git_common_path() [02/07] worktree.c: store "id" instead of "git_dir" [03/07] path.c: refactor and add worktree_git_path() [04/07] worktree.c: add worktree_git_path_..._head()

Quoted 4 lines

I actually wondered how ky/branch-[dm]-worktree topics to avoid "branch -d" and "branch -m" from removing or renaming a branch that is checked out in other worktrees from screwing them up. There may be a missed opportunity to clean them up with using these?

Quoted 5 lines

branch-m-worktree uses info populated at get_worktrees() phase. branch-d-worktree uses find_shared_symref() which is modified in this series in order to detect rebase branch. So both topics have the same problem when a branch is being rebased and if I do it right, I'll fix them both without extra code.

Yup, that was exactly why I brought it up.

Quoted 3 lines

Actually I may need to check branch-m-worktree again, renaming a branch under rebase might cause problems, I need to have a closer look at git-rebase*.sh.

Thanks.

#14 lore raw

v2 cover fix checking out a being-rebased branch

In reply to Nguyễn Thái Ngọc Duy

Much happier with this version. This makes

- git checkout refuse if a branch is under rebase or bisect elsewhere

- git worktree add refuse if a branch is under rebase or bisect

- git branch -D refuse if a branch is under rebase or bisect. This applies for single worktree case as well.

- git branch -M refuse if a branch is under rebase or bisect (single worktree case as well)

[01/12] path.c: add git_common_path() and strbuf_git_common_path() [02/12] worktree.c: store "id" instead of "git_dir"
[03/12] worktree.c: make find_shared_symref() return struct worktree * [04/12] worktree.c: mark current worktree
[05/12] path.c: refactor and add worktree_git_path()
[06/12] wt-status.c: split rebase detection out of wt_status_get_state() [07/12] wt-status.c: make wt_status_check_rebase() work on any worktree [08/12] worktree.c: avoid referencing to worktrees[i] multiple times [09/12] worktree.c: test if branch being rebased in another worktree [10/12] wt-status.c: split bisect detection out of wt_status_get_state() [11/12] worktree.c: test if branch being bisected in another worktree [12/12] branch: do not rename a branch under bisect or rebase

Total 13 files changed, 335 insertions(+), 67 deletions(-)

#15 lore raw

PATCH v2 1/12 path.c: add git_common_path() and strbuf_git_common_path()

These are mostly convenient functions to reduce code duplication. Most of the time, we should be able to get by with git_path() which handles $GIT_COMMON_DIR internally. However there are a few cases where we need to construct paths manually, for example some paths from a specific worktree. These functions will enable that.

---
 cache.h |  3 +++
 path.c  | 29 +++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)
cache.h +3 -0
index 2711048..c04a17f 100644
@@ -799,11 +799,14 @@ extern void check_repository_format(void);
  */
 extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 
 extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	__attribute__((format (printf, 3, 4)));
 extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 	__attribute__((format (printf, 2, 3)));
+extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
+	__attribute__((format (printf, 2, 3)));
 extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
 	__attribute__((format (printf, 2, 3)));
 extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
path.c +29 -0
index bbaea5a..2ebb23d 100644
@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,
 	va_end(args);
 }
 
+static void do_git_common_path(struct strbuf *buf,
+			       const char *fmt,
+			       va_list args)
+{
+	strbuf_addstr(buf, get_git_common_dir());
+	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
+		strbuf_addch(buf, '/');
+	strbuf_vaddf(buf, fmt, args);
+	strbuf_cleanup_path(buf);
+}
+
+const char *git_common_path(const char *fmt, ...)
+{
+	struct strbuf *pathname = get_pathname();
+	va_list args;
+	va_start(args, fmt);
+	do_git_common_path(pathname, fmt, args);
+	va_end(args);
+	return pathname->buf;
+}
+
+void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+	do_git_common_path(sb, fmt, args);
+	va_end(args);
+}
+
 int validate_headref(const char *path)
 {
 	struct stat st;
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#16 lore raw

Re: [PATCH v2 01/12] path.c: add git_common_path() and strbuf_git_common_path()

path.c void strbuf_git_path_submodule(struct strbuf *buf, const char *path, · line 510

Quoted 10 lines · Nguyễn Thái Ngọc Duy
path.c +8 -0
@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,
+const char *git_common_path(const char *fmt, ...)
+{
+       struct strbuf *pathname = get_pathname();
+       va_list args;
+       va_start(args, fmt);
+       do_git_common_path(pathname, fmt, args);
+       va_end(args);
+       return pathname->buf;

Is the caller expected to free this value? If not, then shouldn't 'pathname' be static? If so, then perhaps strbuf_detach() would be clearer (and return 'char *' rather than 'const char *').

Quoted 1 line
path.c +1 -0
+}
#17 lore raw

Re: [PATCH v2 01/12] path.c: add git_common_path() and strbuf_git_common_path()

Quoted 15 lines · Eric Sunshine

path.c void strbuf_git_path_submodule(struct strbuf *buf, const char *path, · line 510

Quoted 10 lines · Nguyễn Thái Ngọc Duy
path.c +8 -0
@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,
+const char *git_common_path(const char *fmt, ...)
+{
+       struct strbuf *pathname = get_pathname();
+       va_list args;
+       va_start(args, fmt);
+       do_git_common_path(pathname, fmt, args);
+       va_end(args);
+       return pathname->buf;

Is the caller expected to free this value? If not, then shouldn't 'pathname' be static? If so, then perhaps strbuf_detach() would be clearer (and return 'char *' rather than 'const char *').

get_pathname() actually holds a ring of static buffer. So no, we don't need static pathname, it can be a new buffer next time, mostly to to make printf("%s %s", git_common_path(..), git_common_path(..)) work. And no the caller is not supposed to free it, a little bit more convenient.

Signature · 1 line
Duy
#18 lore raw

PATCH v2 2/12 worktree.c: store "id" instead of "git_dir"

In reply to Nguyễn Thái Ngọc Duy

We can reconstruct git_dir from id quite easily. It's a bit hackier to do the reverse.

---
 branch.c   |  3 ++-
 worktree.c | 31 ++++++++++++++++++-------------
 worktree.h |  8 +++++++-
 3 files changed, 27 insertions(+), 15 deletions(-)
branch.c +2 -1
index 4162443..0674a99 100644
@@ -357,7 +357,8 @@ int replace_each_worktree_head_symref(const char *oldref, const char *newref)
 		if (strcmp(oldref, worktrees[i]->head_ref))
 			continue;
 
-		if (set_worktree_head_symref(worktrees[i]->git_dir, newref)) {
+		if (set_worktree_head_symref(get_worktree_git_dir(worktrees[i]),
+					     newref)) {
 			ret = -1;
 			error(_("HEAD of working tree %s is not updated"),
 			      worktrees[i]->path);
worktree.c +18 -13
index 6181a66..5ae54f0 100644
@@ -9,7 +9,7 @@ void free_worktrees(struct worktree **worktrees)
 
 	for (i = 0; worktrees[i]; i++) {
 		free(worktrees[i]->path);
-		free(worktrees[i]->git_dir);
+		free(worktrees[i]->id);
 		free(worktrees[i]->head_ref);
 		free(worktrees[i]);
 	}
@@ -74,13 +74,11 @@ static struct worktree *get_main_worktree(void)
 	struct worktree *worktree = NULL;
 	struct strbuf path = STRBUF_INIT;
 	struct strbuf worktree_path = STRBUF_INIT;
-	struct strbuf gitdir = STRBUF_INIT;
 	struct strbuf head_ref = STRBUF_INIT;
 	int is_bare = 0;
 	int is_detached = 0;
 
-	strbuf_addf(&gitdir, "%s", absolute_path(get_git_common_dir()));
-	strbuf_addbuf(&worktree_path, &gitdir);
+	strbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));
 	is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
 	if (is_bare)
 		strbuf_strip_suffix(&worktree_path, "/.");
@@ -92,7 +90,7 @@ static struct worktree *get_main_worktree(void)
 
 	worktree = xmalloc(sizeof(struct worktree));
 	worktree->path = strbuf_detach(&worktree_path, NULL);
-	worktree->git_dir = strbuf_detach(&gitdir, NULL);
+	worktree->id = NULL;
 	worktree->is_bare = is_bare;
 	worktree->head_ref = NULL;
 	worktree->is_detached = is_detached;
@@ -100,7 +98,6 @@ static struct worktree *get_main_worktree(void)
 
 done:
 	strbuf_release(&path);
-	strbuf_release(&gitdir);
 	strbuf_release(&worktree_path);
 	strbuf_release(&head_ref);
 	return worktree;
@@ -111,16 +108,13 @@ static struct worktree *get_linked_worktree(const char *id)
 	struct worktree *worktree = NULL;
 	struct strbuf path = STRBUF_INIT;
 	struct strbuf worktree_path = STRBUF_INIT;
-	struct strbuf gitdir = STRBUF_INIT;
 	struct strbuf head_ref = STRBUF_INIT;
 	int is_detached = 0;
 
 	if (!id)
 		die("Missing linked worktree name");
 
-	strbuf_addf(&gitdir, "%s/worktrees/%s",
-			absolute_path(get_git_common_dir()), id);
-	strbuf_addf(&path, "%s/gitdir", gitdir.buf);
+	strbuf_git_common_path(&path, "worktrees/%s/gitdir", id);
 	if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
 		/* invalid gitdir file */
 		goto done;
@@ -140,7 +134,7 @@ static struct worktree *get_linked_worktree(const char *id)
 
 	worktree = xmalloc(sizeof(struct worktree));
 	worktree->path = strbuf_detach(&worktree_path, NULL);
-	worktree->git_dir = strbuf_detach(&gitdir, NULL);
+	worktree->id = xstrdup(id);
 	worktree->is_bare = 0;
 	worktree->head_ref = NULL;
 	worktree->is_detached = is_detached;
@@ -148,7 +142,6 @@ static struct worktree *get_linked_worktree(const char *id)
 
 done:
 	strbuf_release(&path);
-	strbuf_release(&gitdir);
 	strbuf_release(&worktree_path);
 	strbuf_release(&head_ref);
 	return worktree;
@@ -188,6 +181,16 @@ struct worktree **get_worktrees(void)
 	return list;
 }
 
+const char *get_worktree_git_dir(const struct worktree *wt)
+{
+	if (!wt)
+		return get_git_dir();
+	else if (!wt->id)
+		return get_git_common_dir();
+	else
+		return git_common_path("worktrees/%s", wt->id);
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
@@ -199,7 +202,9 @@ char *find_shared_symref(const char *symref, const char *target)
 	for (i = 0; worktrees[i]; i++) {
 		strbuf_reset(&path);
 		strbuf_reset(&sb);
-		strbuf_addf(&path, "%s/%s", worktrees[i]->git_dir, symref);
+		strbuf_addf(&path, "%s/%s",
+			    get_worktree_git_dir(worktrees[i]),
+			    symref);
 
 		if (parse_ref(path.buf, &sb, NULL)) {
 			continue;
worktree.h +7 -1
index b4b3dda..3198c8d 100644
@@ -3,7 +3,7 @@
 
 struct worktree {
 	char *path;
-	char *git_dir;
+	char *id;
 	char *head_ref;
 	unsigned char head_sha1[20];
 	int is_detached;
@@ -23,6 +23,12 @@ struct worktree {
 extern struct worktree **get_worktrees(void);
 
 /*
+ * Return git dir of the worktree. Note that the path may be relative.
+ * If wt is NULL, git dir of current worktree is returned.
+ */
+extern const char *get_worktree_git_dir(const struct worktree *wt);
+
+/*
  * Free up the memory for worktree(s)
  */
 extern void free_worktrees(struct worktree **);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#19 lore raw

PATCH v2 3/12 worktree.c: make find_shared_symref() return struct worktree *

In reply to Nguyễn Thái Ngọc Duy

This gives the caller more information and they can answer things like, "is it the main worktree" or "is it the current worktree". The latter question is needed for the "checkout a rebase branch" case later.

---
 branch.c         |  9 +++++----
 builtin/branch.c |  8 ++++----
 builtin/notes.c  |  8 ++++----
 worktree.c       | 14 +++++++++-----
 worktree.h       |  7 ++++---
 5 files changed, 26 insertions(+), 20 deletions(-)
branch.c +5 -4
index 0674a99..a84fb2c 100644
@@ -336,12 +336,13 @@ void remove_branch_state(void)
 
 void die_if_checked_out(const char *branch)
 {
-	char *existing;
+	const struct worktree *wt;
 
-	existing = find_shared_symref("HEAD", branch);
-	if (existing) {
+	wt = find_shared_symref("HEAD", branch);
+	if (wt) {
 		skip_prefix(branch, "refs/heads/", &branch);
-		die(_("'%s' is already checked out at '%s'"), branch, existing);
+		die(_("'%s' is already checked out at '%s'"),
+		    branch, wt->path);
 	}
 }
 
builtin/branch.c +4 -4
index 0adba62..bcde87d 100644
@@ -220,12 +220,12 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
 		name = mkpathdup(fmt, bname.buf);
 
 		if (kinds == FILTER_REFS_BRANCHES) {
-			char *worktree = find_shared_symref("HEAD", name);
-			if (worktree) {
+			const struct worktree *wt =
+				find_shared_symref("HEAD", name);
+			if (wt) {
 				error(_("Cannot delete branch '%s' "
 					"checked out at '%s'"),
-				      bname.buf, worktree);
-				free(worktree);
+				      bname.buf, wt->path);
 				ret = 1;
 				continue;
 			}
builtin/notes.c +4 -4
index 6fd058d..c65b59a 100644
@@ -847,15 +847,15 @@ static int merge(int argc, const char **argv, const char *prefix)
 		update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
 			   0, UPDATE_REFS_DIE_ON_ERR);
 	else { /* Merge has unresolved conflicts */
-		char *existing;
+		const struct worktree *wt;
 		/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
 		update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
 			   0, UPDATE_REFS_DIE_ON_ERR);
 		/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
-		existing = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
-		if (existing)
+		wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
+		if (wt)
 			die(_("A notes merge into %s is already in-progress at %s"),
-			    default_notes_ref(), existing);
+			    default_notes_ref(), wt->path);
 		if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
 			die("Failed to store link to current notes ref (%s)",
 			    default_notes_ref());
worktree.c +9 -5
index 5ae54f0..360ba41 100644
@@ -191,14 +191,19 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
-char *find_shared_symref(const char *symref, const char *target)
+const struct worktree *find_shared_symref(const char *symref,
+					  const char *target)
 {
-	char *existing = NULL;
+	const struct worktree *existing = NULL;
 	struct strbuf path = STRBUF_INIT;
 	struct strbuf sb = STRBUF_INIT;
-	struct worktree **worktrees = get_worktrees();
+	static struct worktree **worktrees;
 	int i = 0;
 
+	if (worktrees)
+		free_worktrees(worktrees);
+	worktrees = get_worktrees();
+
 	for (i = 0; worktrees[i]; i++) {
 		strbuf_reset(&path);
 		strbuf_reset(&sb);
@@ -211,14 +216,13 @@ char *find_shared_symref(const char *symref, const char *target)
 		}
 
 		if (!strcmp(sb.buf, target)) {
-			existing = xstrdup(worktrees[i]->path);
+			existing = worktrees[i];
 			break;
 		}
 	}
 
 	strbuf_release(&path);
 	strbuf_release(&sb);
-	free_worktrees(worktrees);
 
 	return existing;
 }
worktree.h +4 -3
index 3198c8d..d71d7ec 100644
@@ -36,9 +36,10 @@ extern void free_worktrees(struct worktree **);
 /*
  * Check if a per-worktree symref points to a ref in the main worktree
  * or any linked worktree, and return the path to the exising worktree
- * if it is.  Returns NULL if there is no existing ref.  The caller is
- * responsible for freeing the returned path.
+ * if it is. Returns NULL if there is no existing ref. The result
+ * may be destroyed by the next call.
  */
-extern char *find_shared_symref(const char *symref, const char *target);
+extern const struct worktree *find_shared_symref(const char *symref,
+						 const char *target);
 
 #endif
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#20 lore raw

Re: [PATCH v2 03/12] worktree.c: make find_shared_symref() return struct worktree *

Quoted 3 lines · Nguyễn Thái Ngọc Duy

This gives the caller more information and they can answer things like, "is it the main worktree" or "is it the current worktree". The latter question is needed for the "checkout a rebase branch" case later.

That makes good sense.

worktree.h extern void free_worktrees(struct worktree **); · line 41

Quoted 13 lines
worktree.h +2 -2
index 3198c8d..d71d7ec 100644
@@ -36,9 +36,10 @@ extern void free_worktrees(struct worktree **);
 /*
  * Check if a per-worktree symref points to a ref in the main worktree
  * or any linked worktree, and return the path to the exising worktree
- * if it is.  Returns NULL if there is no existing ref.  The caller is
- * responsible for freeing the returned path.
+ * if it is. Returns NULL if there is no existing ref. The result
+ * may be destroyed by the next call.
  */

To return and keep alive one worktree[] instance (i.e. "existing"), the code holds onto the entire return value from get_worktrees(), if I am not misreading it. Typically you would have only a handful of worktrees so this may not be an issue, though.

#21 lore raw

Re: [PATCH v2 03/12] worktree.c: make find_shared_symref() return struct worktree *

In reply to Nguyễn Thái Ngọc Duy

worktree.h extern void free_worktrees(struct worktree **); · line 38

Quoted 11 lines · Nguyễn Thái Ngọc Duy

This gives the caller more information and they can answer things like, "is it the main worktree" or "is it the current worktree". The latter question is needed for the "checkout a rebase branch" case later.

---

worktree.h +0 -0
@@ -36,9 +36,10 @@ extern void free_worktrees(struct worktree **);
 /*
  * Check if a per-worktree symref points to a ref in the main worktree
  * or any linked worktree, and return the path to the exising worktree

Doesn't "return the path" become outdated with this patch?

Also (not a new problem): s/exising/existing/

Quoted 8 lines
worktree.h +4 -3
- * if it is.  Returns NULL if there is no existing ref.  The caller is
- * responsible for freeing the returned path.
+ * if it is. Returns NULL if there is no existing ref. The result
+ * may be destroyed by the next call.
  */
-extern char *find_shared_symref(const char *symref, const char *target);
+extern const struct worktree *find_shared_symref(const char *symref,
+                                                const char *target);
#22 lore raw

PATCH v2 4/12 worktree.c: mark current worktree

In reply to Nguyễn Thái Ngọc Duy
---
 worktree.c | 18 +++++++++++++++++-
 worktree.h |  1 +
 2 files changed, 18 insertions(+), 1 deletion(-)
worktree.c +17 -1
index 360ba41..452f64a 100644
@@ -2,6 +2,7 @@
 #include "refs.h"
 #include "strbuf.h"
 #include "worktree.h"
+#include "dir.h"
 
 void free_worktrees(struct worktree **worktrees)
 {
@@ -94,6 +95,7 @@ static struct worktree *get_main_worktree(void)
 	worktree->is_bare = is_bare;
 	worktree->head_ref = NULL;
 	worktree->is_detached = is_detached;
+	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
 
 done:
@@ -138,6 +140,7 @@ static struct worktree *get_linked_worktree(const char *id)
 	worktree->is_bare = 0;
 	worktree->head_ref = NULL;
 	worktree->is_detached = is_detached;
+	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
 
 done:
@@ -150,10 +153,11 @@ done:
 struct worktree **get_worktrees(void)
 {
 	struct worktree **list = NULL;
+	struct strbuf git_dir = STRBUF_INIT;
 	struct strbuf path = STRBUF_INIT;
 	DIR *dir;
 	struct dirent *d;
-	int counter = 0, alloc = 2;
+	int i, counter = 0, alloc = 2;
 
 	list = xmalloc(alloc * sizeof(struct worktree *));
 
@@ -178,6 +182,18 @@ struct worktree **get_worktrees(void)
 	}
 	ALLOC_GROW(list, counter + 1, alloc);
 	list[counter] = NULL;
+
+	strbuf_addstr(&git_dir, absolute_path(get_git_dir()));
+	for (i = 0; i < counter; i++) {
+		struct worktree *wt = list[i];
+		strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
+		wt->is_current = !strcmp_icase(git_dir.buf, path.buf);
+		strbuf_reset(&path);
+		if (wt->is_current)
+			break;
+	}
+	strbuf_release(&git_dir);
+	strbuf_release(&path);
 	return list;
 }
 
worktree.h +1 -0
index d71d7ec..625fb8d 100644
@@ -8,6 +8,7 @@ struct worktree {
 	unsigned char head_sha1[20];
 	int is_detached;
 	int is_bare;
+	int is_current;
 };
 
 /* Functions for acting on the information about worktrees. */
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#23 lore raw

Re: [PATCH v2 04/12] worktree.c: mark current worktree

worktree.c struct worktree **get_worktrees(void) · line 190

Quoted 13 lines · Nguyễn Thái Ngọc Duy

---

worktree.c +6 -0
@@ -178,6 +182,18 @@ struct worktree **get_worktrees(void)
        }
        ALLOC_GROW(list, counter + 1, alloc);
        list[counter] = NULL;
+
+       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));
+       for (i = 0; i < counter; i++) {
+               struct worktree *wt = list[i];
+               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
+               wt->is_current = !strcmp_icase(git_dir.buf, path.buf);

Can you talk a bit about why this uses 'icase'? Should it be respecting cache.h:ignore_case?

worktree.c struct worktree **get_worktrees(void) · line 196

Quoted 6 lines
worktree.c +6 -0
+               strbuf_reset(&path);
+               if (wt->is_current)
+                       break;
+       }
+       strbuf_release(&git_dir);
+       strbuf_release(&path);

Minor: Would it make sense to place this new code in its own function -- say, mark_current_worktree() -- to keep get_worktrees() from becoming overlong?

Quoted 2 lines
worktree.c +0 -0
        return list;
 }
#24 lore raw

Re: [PATCH v2 04/12] worktree.c: mark current worktree

Quoted 17 lines · Eric Sunshine

worktree.c struct worktree **get_worktrees(void) · line 190

Quoted 13 lines · Nguyễn Thái Ngọc Duy

---

worktree.c +6 -0
@@ -178,6 +182,18 @@ struct worktree **get_worktrees(void)
        }
        ALLOC_GROW(list, counter + 1, alloc);
        list[counter] = NULL;
+
+       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));
+       for (i = 0; i < counter; i++) {
+               struct worktree *wt = list[i];
+               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
+               wt->is_current = !strcmp_icase(git_dir.buf, path.buf);

Can you talk a bit about why this uses 'icase'? Should it be respecting cache.h:ignore_case?

It does.That function (in dir.c) is just one-liner

    return ignore_case ? strcasecmp(a, b) : strcmp(a, b);

I admit though, the naming does not make that clear.

Quoted 10 lines

worktree.c struct worktree **get_worktrees(void) · line 196

Quoted 6 lines
worktree.c +6 -0
+               strbuf_reset(&path);
+               if (wt->is_current)
+                       break;
+       }
+       strbuf_release(&git_dir);
+       strbuf_release(&path);

Minor: Would it make sense to place this new code in its own function -- say, mark_current_worktree() -- to keep get_worktrees() from becoming overlong?

Good idea. Will do.

Signature · 1 line
Duy
#25 lore raw

Re: [PATCH v2 04/12] worktree.c: mark current worktree

Quoted 19 lines · Duy Nguyen
Quoted 17 lines · Eric Sunshine

worktree.c struct worktree **get_worktrees(void) · line 190

Quoted 13 lines · Nguyễn Thái Ngọc Duy

---

worktree.c +6 -0
@@ -178,6 +182,18 @@ struct worktree **get_worktrees(void)
        }
        ALLOC_GROW(list, counter + 1, alloc);
        list[counter] = NULL;
+
+       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));
+       for (i = 0; i < counter; i++) {
+               struct worktree *wt = list[i];
+               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
+               wt->is_current = !strcmp_icase(git_dir.buf, path.buf);

Can you talk a bit about why this uses 'icase'? Should it be respecting cache.h:ignore_case?

Quoted 5 lines

It does.That function (in dir.c) is just one-liner

    return ignore_case ? strcasecmp(a, b) : strcmp(a, b);

I admit though, the naming does not make that clear.

While we're at it, how about renaming it to pathcmp (and its friend strncmp_icase to pathncmp)?

Signature · 1 line
Duy
#26 lore raw

Re: [PATCH v2 04/12] worktree.c: mark current worktree

Quoted 10 lines · Duy Nguyen
Quoted 9 lines · Duy Nguyen
Quoted 8 lines · Eric Sunshine
Quoted 6 lines · Nguyễn Thái Ngọc Duy
worktree.c +5 -0
+       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));
+       for (i = 0; i < counter; i++) {
+               struct worktree *wt = list[i];
+               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
+               wt->is_current = !strcmp_icase(git_dir.buf, path.buf);
Quoted 8 lines
Quoted 8 lines
Quoted 2 lines

Can you talk a bit about why this uses 'icase'? Should it be respecting cache.h:ignore_case?

It does.That function (in dir.c) is just one-liner

    return ignore_case ? strcasecmp(a, b) : strcmp(a, b);

I admit though, the naming does not make that clear.

Ugh, this is only the fourth patch, yet the second stupid review mistake I've made thus far in this series. For some reason, I kept reading this as a call to strcasecmp() or stricmp() rather than strcmp_icase(). Worse, I had even consulted path.c:strcmp_icase() to see how the issue was handled there.

Quoted 2 lines

While we're at it, how about renaming it to pathcmp (and its friend strncmp_icase to pathncmp)?

Yes, that seems like a good idea. For anyone familiar with strcasecmp() or stricmp(), having "icase" in the name makes it seem as though it's unconditionally case-insensitive, so dropping it from the name would likely be beneficial.

#27 lore raw

Re: [PATCH v2 04/12] worktree.c: mark current worktree

Quoted 7 lines · Eric Sunshine
Quoted 2 lines

While we're at it, how about renaming it to pathcmp (and its friend strncmp_icase to pathncmp)?

Yes, that seems like a good idea. For anyone familiar with strcasecmp() or stricmp(), having "icase" in the name makes it seem as though it's unconditionally case-insensitive, so dropping it from the name would likely be beneficial.

Seconded (thirded?). I have been caught by this confusion in the past, too.

-Peff

#28 lore raw

Re: [PATCH v2 04/12] worktree.c: mark current worktree

Quoted 5 lines · Jeff King
Quoted 3 lines · Eric Sunshine
Quoted 2 lines

While we're at it, how about renaming it to pathcmp (and its friend strncmp_icase to pathncmp)?

Quoted 7 lines
Quoted 4 lines

Yes, that seems like a good idea. For anyone familiar with strcasecmp() or stricmp(), having "icase" in the name makes it seem as though it's unconditionally case-insensitive, so dropping it from the name would likely be beneficial.

Seconded (thirded?). I have been caught by this confusion in the past, too.

I agree that strcmp_icase() gives a false impression that it always ignores case differences, but a new name that does not at all hint that it may do icase comparison as necessary will catch me by an opposite confusion in the future.

I have not yet formed a firm opinion if pathcmp() conveys enough hint.

#29 lore raw

Re: [PATCH v2 04/12] worktree.c: mark current worktree

Quoted 8 lines · Junio C Hamano
Quoted 8 lines
Quoted 7 lines
Quoted 2 lines

While we're at it, how about renaming it to pathcmp (and its friend strncmp_icase to pathncmp)?

Yes, that seems like a good idea. For anyone familiar with strcasecmp() or stricmp(), having "icase" in the name makes it seem as though it's unconditionally case-insensitive, so dropping it from the name would likely be beneficial.

Quoted 7 lines
Quoted 2 lines

Seconded (thirded?). I have been caught by this confusion in the past, too.

I agree that strcmp_icase() gives a false impression that it always ignores case differences, but a new name that does not at all hint that it may do icase comparison as necessary will catch me by an opposite confusion in the future.

To me, the benefit is that you don't have to care about ignore_case. You have asked to compare two paths, and any system-appropriate magic should be applied. That may be icase, or it may be weird unicode normalization.

I think the key thing missing is that this is only about _filesystem_ paths. You would not want to use it for tree-to-tree pathname comparisons. So maybe "fspath" or something would be more descriptive.

-Peff

#30 lore raw

Re: [PATCH v2 04/12] worktree.c: mark current worktree

Quoted 7 lines · Jeff King

To me, the benefit is that you don't have to care about ignore_case. You have asked to compare two paths, and any system-appropriate magic should be applied. That may be icase, or it may be weird unicode normalization.

I think the key thing missing is that this is only about _filesystem_ paths. You would not want to use it for tree-to-tree pathname comparisons. So maybe "fspath" or something would be more descriptive.

Yup, I would be very happy with "fs" in the name.

#31 lore raw

PATCH v2 5/12 path.c: refactor and add worktree_git_path()

In reply to Nguyễn Thái Ngọc Duy

do_git_path(), which is the common code for all git_path* functions, is modified to take a worktree struct and can produce paths for any worktree.

worktree_git_path() is the first function that makes use of this. It can be used to write code that can examine any worktree. For example, wt_status_get_state() will be converted using this to take am/rebase/... state of any worktree.

---
 path.c     | 34 ++++++++++++++++++++++++++++------
 worktree.h | 11 +++++++++++
 2 files changed, 39 insertions(+), 6 deletions(-)
path.c +28 -6
index 2ebb23d..c421d37 100644
@@ -5,6 +5,7 @@
 #include "strbuf.h"
 #include "string-list.h"
 #include "dir.h"
+#include "worktree.h"
 
 static int get_st_mode_bits(const char *path, int *mode)
 {
@@ -383,10 +384,11 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 		update_common_dir(buf, git_dir_len, NULL);
 }
 
-static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
+static void do_git_path(const struct worktree *wt, struct strbuf *buf,
+			const char *fmt, va_list args)
 {
 	int gitdir_len;
-	strbuf_addstr(buf, get_git_dir());
+	strbuf_addstr(buf, get_worktree_git_dir(wt));
 	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
 		strbuf_addch(buf, '/');
 	gitdir_len = buf->len;
@@ -400,7 +402,7 @@ char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
 	va_list args;
 	strbuf_reset(buf);
 	va_start(args, fmt);
-	do_git_path(buf, fmt, args);
+	do_git_path(NULL, buf, fmt, args);
 	va_end(args);
 	return buf->buf;
 }
@@ -409,7 +411,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 {
 	va_list args;
 	va_start(args, fmt);
-	do_git_path(sb, fmt, args);
+	do_git_path(NULL, sb, fmt, args);
 	va_end(args);
 }
 
@@ -418,7 +420,7 @@ const char *git_path(const char *fmt, ...)
 	struct strbuf *pathname = get_pathname();
 	va_list args;
 	va_start(args, fmt);
-	do_git_path(pathname, fmt, args);
+	do_git_path(NULL, pathname, fmt, args);
 	va_end(args);
 	return pathname->buf;
 }
@@ -428,7 +430,7 @@ char *git_pathdup(const char *fmt, ...)
 	struct strbuf path = STRBUF_INIT;
 	va_list args;
 	va_start(args, fmt);
-	do_git_path(&path, fmt, args);
+	do_git_path(NULL, &path, fmt, args);
 	va_end(args);
 	return strbuf_detach(&path, NULL);
 }
@@ -454,6 +456,26 @@ const char *mkpath(const char *fmt, ...)
 	return cleanup_path(pathname->buf);
 }
 
+const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)
+{
+	struct strbuf *pathname = get_pathname();
+	va_list args;
+	va_start(args, fmt);
+	do_git_path(wt, pathname, fmt, args);
+	va_end(args);
+	return pathname->buf;
+}
+
+char *worktree_git_pathdup(const struct worktree *wt, const char *fmt, ...)
+{
+	struct strbuf path = STRBUF_INIT;
+	va_list args;
+	va_start(args, fmt);
+	do_git_path(wt, &path, fmt, args);
+	va_end(args);
+	return strbuf_detach(&path, NULL);
+}
+
 static void do_submodule_path(struct strbuf *buf, const char *path,
 			      const char *fmt, va_list args)
 {
worktree.h +11 -0
index 625fb8d..9d2463e 100644
@@ -43,4 +43,15 @@ extern void free_worktrees(struct worktree **);
 extern const struct worktree *find_shared_symref(const char *symref,
 						 const char *target);
 
+/*
+ * Similar to git_path() and git_pathdup() but can produce paths for a
+ * specified worktree instead of current one
+ */
+extern const char *worktree_git_path(const struct worktree *wt,
+				     const char *fmt, ...)
+	__attribute__((format (printf, 2, 3)));
+extern char *worktree_git_pathdup(const struct worktree *wt,
+				  const char *fmt, ...)
+	__attribute__((format (printf, 2, 3)));
+
 #endif
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#32 lore raw

PATCH v2 6/12 wt-status.c: split rebase detection out of wt_status_get_state()

In reply to Nguyễn Thái Ngọc Duy

worktree.c:find_shared_symref() later needs to know if a branch is being rebased, and only rebased only. Split this code so it can be used independently from other in-progress tests.

---
 wt-status.c | 23 +++++++++++++++++------
 wt-status.h |  1 +
 2 files changed, 18 insertions(+), 6 deletions(-)
wt-status.c +17 -6
index 1ea2ebe..35787ec 100644
@@ -1360,15 +1360,11 @@ static void wt_status_get_detached_from(struct wt_status_state *state)
 	strbuf_release(&cb.buf);
 }
 
-void wt_status_get_state(struct wt_status_state *state,
-			 int get_detached_from)
+int wt_status_check_rebase(struct wt_status_state *state)
 {
 	struct stat st;
-	unsigned char sha1[20];
 
-	if (!stat(git_path_merge_head(), &st)) {
-		state->merge_in_progress = 1;
-	} else if (!stat(git_path("rebase-apply"), &st)) {
+	if (!stat(git_path("rebase-apply"), &st)) {
 		if (!stat(git_path("rebase-apply/applying"), &st)) {
 			state->am_in_progress = 1;
 			if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
@@ -1385,6 +1381,21 @@ void wt_status_get_state(struct wt_status_state *state,
 			state->rebase_in_progress = 1;
 		state->branch = read_and_strip_branch("rebase-merge/head-name");
 		state->onto = read_and_strip_branch("rebase-merge/onto");
+	} else
+		return 0;
+	return 1;
+}
+
+void wt_status_get_state(struct wt_status_state *state,
+			 int get_detached_from)
+{
+	struct stat st;
+	unsigned char sha1[20];
+
+	if (!stat(git_path_merge_head(), &st)) {
+		state->merge_in_progress = 1;
+	} else if (wt_status_check_rebase(state)) {
+		/* all set */
 	} else if (!stat(git_path_cherry_pick_head(), &st) &&
 			!get_sha1("CHERRY_PICK_HEAD", sha1)) {
 		state->cherry_pick_in_progress = 1;
wt-status.h +1 -0
index c9b3b74..b398353 100644
@@ -100,6 +100,7 @@ void wt_status_prepare(struct wt_status *s);
 void wt_status_print(struct wt_status *s);
 void wt_status_collect(struct wt_status *s);
 void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
+int wt_status_check_rebase(struct wt_status_state *state);
 
 void wt_shortstatus_print(struct wt_status *s);
 void wt_porcelain_print(struct wt_status *s);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#33 lore raw

Re: [PATCH v2 06/12] wt-status.c: split rebase detection out of wt_status_get_state()

Quoted 2 lines · Nguyễn Thái Ngọc Duy

worktree.c:find_shared_symref() later needs to know if a branch is being rebased, and only rebased only. Split this code so it can be used

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Err ... what?

ATB,
Ramsay Jones

#34 lore raw

Re: [PATCH v2 06/12] wt-status.c: split rebase detection out of wt_status_get_state()

Quoted 7 lines · Ramsay Jones
Quoted 2 lines · Nguyễn Thái Ngọc Duy

worktree.c:find_shared_symref() later needs to know if a branch is being rebased, and only rebased only. Split this code so it can be used

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Err ... what?

wt_status_get_state() detects more than rebase, it does bisect, cherry-pick, detached head as well. I "only" too much there though.

Signature · 1 line
Duy
#35 lore raw

PATCH v2 7/12 wt-status.c: make wt_status_check_rebase() work on any worktree

In reply to Nguyễn Thái Ngọc Duy

This is a preparation step for find_shared_symref() to detect if any worktree is being rebased.

---
 wt-status.c | 33 ++++++++++++++++++++-------------
 wt-status.h |  5 ++++-
 2 files changed, 24 insertions(+), 14 deletions(-)
wt-status.c +20 -13
index 35787ec..2295682 100644
@@ -15,6 +15,7 @@
 #include "column.h"
 #include "strbuf.h"
 #include "utf8.h"
+#include "worktree.h"
 
 static const char cut_line[] =
 "------------------------ >8 ------------------------\n";
@@ -1262,13 +1263,13 @@ static void show_bisect_in_progress(struct wt_status *s,
 /*
  * Extract branch information from rebase/bisect
  */
-static char *read_and_strip_branch(const char *path)
+static char *get_branch(const struct worktree *wt, const char *path)
 {
 	struct strbuf sb = STRBUF_INIT;
 	unsigned char sha1[20];
 	const char *branch_name;
 
-	if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0)
+	if (strbuf_read_file(&sb, worktree_git_path(wt, "%s", path), 0) <= 0)
 		goto got_nothing;
 
 	while (sb.len && sb.buf[sb.len - 1] == '\n')
@@ -1295,6 +1296,11 @@ got_nothing:
 	return NULL;
 }
 
+static char *read_and_strip_branch(const char *path)
+{
+	return get_branch(NULL, path);
+}
+
 struct grab_1st_switch_cbdata {
 	struct strbuf buf;
 	unsigned char nsha1[20];
@@ -1360,27 +1366,28 @@ static void wt_status_get_detached_from(struct wt_status_state *state)
 	strbuf_release(&cb.buf);
 }
 
-int wt_status_check_rebase(struct wt_status_state *state)
+int wt_status_check_rebase(const struct worktree *wt,
+			   struct wt_status_state *state)
 {
 	struct stat st;
 
-	if (!stat(git_path("rebase-apply"), &st)) {
-		if (!stat(git_path("rebase-apply/applying"), &st)) {
+	if (!stat(worktree_git_path(wt, "rebase-apply"), &st)) {
+		if (!stat(worktree_git_path(wt, "rebase-apply/applying"), &st)) {
 			state->am_in_progress = 1;
-			if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
+			if (!stat(worktree_git_path(wt, "rebase-apply/patch"), &st) && !st.st_size)
 				state->am_empty_patch = 1;
 		} else {
 			state->rebase_in_progress = 1;
-			state->branch = read_and_strip_branch("rebase-apply/head-name");
-			state->onto = read_and_strip_branch("rebase-apply/onto");
+			state->branch = get_branch(wt, "rebase-apply/head-name");
+			state->onto = get_branch(wt, "rebase-apply/onto");
 		}
-	} else if (!stat(git_path("rebase-merge"), &st)) {
-		if (!stat(git_path("rebase-merge/interactive"), &st))
+	} else if (!stat(worktree_git_path(wt, "rebase-merge"), &st)) {
+		if (!stat(worktree_git_path(wt, "rebase-merge/interactive"), &st))
 			state->rebase_interactive_in_progress = 1;
 		else
 			state->rebase_in_progress = 1;
-		state->branch = read_and_strip_branch("rebase-merge/head-name");
-		state->onto = read_and_strip_branch("rebase-merge/onto");
+		state->branch = get_branch(wt, "rebase-merge/head-name");
+		state->onto = get_branch(wt, "rebase-merge/onto");
 	} else
 		return 0;
 	return 1;
@@ -1394,7 +1401,7 @@ void wt_status_get_state(struct wt_status_state *state,
 
 	if (!stat(git_path_merge_head(), &st)) {
 		state->merge_in_progress = 1;
-	} else if (wt_status_check_rebase(state)) {
+	} else if (wt_status_check_rebase(NULL, state)) {
 		/* all set */
 	} else if (!stat(git_path_cherry_pick_head(), &st) &&
 			!get_sha1("CHERRY_PICK_HEAD", sha1)) {
wt-status.h +4 -1
index b398353..c4ddcad 100644
@@ -6,6 +6,8 @@
 #include "color.h"
 #include "pathspec.h"
 
+struct worktree;
+
 enum color_wt_status {
 	WT_STATUS_HEADER = 0,
 	WT_STATUS_UPDATED,
@@ -100,7 +102,8 @@ void wt_status_prepare(struct wt_status *s);
 void wt_status_print(struct wt_status *s);
 void wt_status_collect(struct wt_status *s);
 void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
-int wt_status_check_rebase(struct wt_status_state *state);
+int wt_status_check_rebase(const struct worktree *wt,
+			   struct wt_status_state *state);
 
 void wt_shortstatus_print(struct wt_status *s);
 void wt_porcelain_print(struct wt_status *s);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#36 lore raw

PATCH v2 8/12 worktree.c: avoid referencing to worktrees[i] multiple times

In reply to Nguyễn Thái Ngọc Duy
---
 worktree.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
worktree.c +4 -2
index 452f64a..b5ca78f 100644
@@ -221,10 +221,12 @@ const struct worktree *find_shared_symref(const char *symref,
 	worktrees = get_worktrees();
 
 	for (i = 0; worktrees[i]; i++) {
+		struct worktree *wt = worktrees[i];
+
 		strbuf_reset(&path);
 		strbuf_reset(&sb);
 		strbuf_addf(&path, "%s/%s",
-			    get_worktree_git_dir(worktrees[i]),
+			    get_worktree_git_dir(wt),
 			    symref);
 
 		if (parse_ref(path.buf, &sb, NULL)) {
@@ -232,7 +234,7 @@ const struct worktree *find_shared_symref(const char *symref,
 		}
 
 		if (!strcmp(sb.buf, target)) {
-			existing = worktrees[i];
+			existing = wt;
 			break;
 		}
 	}
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#37 lore raw

PATCH v2 9/12 worktree.c: test if branch being rebased in another worktree

In reply to Nguyễn Thái Ngọc Duy

This function find_shared_symref() is used in a couple places:

1) in builtin/branch.c: it's used to detect if a branch is checked out elsewhere and refuse to delete the branch.

2) in builtin/notes.c: it's used to detect if a note is being merged in another worktree

3) in branch.c, the function die_if_checked_out() is actually used by "git checkout" and "git worktree add" to see if a branch is already checked out elsewhere and refuse the operation.

In cases 1 and 3, if a rebase is ongoing, "HEAD" will be in detached mode, find_shared_symref() fails to detect it and declares "no branch is checked out here", which is incorrect.

This patch tightens the test. If the given symref is "HEAD", we try to detect if rebase is ongoing. If so return the branch being rebased. This makes checkout and branch delete operations safer because you can't checkout a branch being rebased in another place, or delete it.

Special case for checkout. If the current branch is being rebased, git-rebase.sh may use "git checkout" to abort and return back to the original branch. The updated test in find_shared_symref() will prevent that and "git rebase --abort" will fail as a result. find_shared_symref() and die_if_checked_out() have to learn a new option ignore_current_worktree to loose the test a bit.

---
 branch.c                |  4 ++--
 branch.h                |  2 +-
 builtin/branch.c        |  2 +-
 builtin/checkout.c      |  2 +-
 builtin/notes.c         |  2 +-
 builtin/worktree.c      |  4 ++--
 t/t2025-worktree-add.sh | 38 ++++++++++++++++++++++++++++++++++++++
 worktree.c              | 32 +++++++++++++++++++++++++++++++-
 worktree.h              |  3 ++-
 9 files changed, 79 insertions(+), 10 deletions(-)
branch.c +2 -2
index a84fb2c..8e323d3 100644
@@ -334,11 +334,11 @@ void remove_branch_state(void)
 	unlink(git_path_squash_msg());
 }
 
-void die_if_checked_out(const char *branch)
+void die_if_checked_out(const char *branch, int ignore_current_worktree)
 {
 	const struct worktree *wt;
 
-	wt = find_shared_symref("HEAD", branch);
+	wt = find_shared_symref("HEAD", branch, ignore_current_worktree);
 	if (wt) {
 		skip_prefix(branch, "refs/heads/", &branch);
 		die(_("'%s' is already checked out at '%s'"),
branch.h +1 -1
index d69163d..b2f9649 100644
@@ -58,7 +58,7 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);
  * worktree and die (with a message describing its checkout location) if
  * it is.
  */
-extern void die_if_checked_out(const char *branch);
+extern void die_if_checked_out(const char *branch, int ignore_current_worktree);
 
 /*
  * Update all per-worktree HEADs pointing at the old ref to point the new ref.
builtin/branch.c +1 -1
index bcde87d..bf91bbd 100644
@@ -221,7 +221,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
 
 		if (kinds == FILTER_REFS_BRANCHES) {
 			const struct worktree *wt =
-				find_shared_symref("HEAD", name);
+				find_shared_symref("HEAD", name, 0);
 			if (wt) {
 				error(_("Cannot delete branch '%s' "
 					"checked out at '%s'"),
builtin/checkout.c +1 -1
index efcbd8f..6041718 100644
@@ -1111,7 +1111,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->path);
+			die_if_checked_out(new->path, 1);
 		free(head_ref);
 	}
 
builtin/notes.c +1 -1
index c65b59a..f154a69 100644
@@ -852,7 +852,7 @@ static int merge(int argc, const char **argv, const char *prefix)
 		update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
 			   0, UPDATE_REFS_DIE_ON_ERR);
 		/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
-		wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
+		wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref(), 0);
 		if (wt)
 			die(_("A notes merge into %s is already in-progress at %s"),
 			    default_notes_ref(), wt->path);
builtin/worktree.c +2 -2
index d8e3795..12c0af7 100644
@@ -205,7 +205,7 @@ static int add_worktree(const char *path, const char *refname,
 	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);
+			die_if_checked_out(symref.buf, 0);
 	} else { /* must be a commit */
 		commit = lookup_commit_reference_by_name(refname);
 		if (!commit)
@@ -349,7 +349,7 @@ static int add(int ac, const char **av, const char *prefix)
 		if (!opts.force &&
 		    !strbuf_check_branch_ref(&symref, opts.new_branch) &&
 		    ref_exists(symref.buf))
-			die_if_checked_out(symref.buf);
+			die_if_checked_out(symref.buf, 0);
 		strbuf_release(&symref);
 	}
 
t/t2025-worktree-add.sh +38 -0
index 3acb992..da54327 100755
@@ -4,6 +4,8 @@ test_description='test git worktree add'
 
 . ./test-lib.sh
 
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
 test_expect_success 'setup' '
 	test_commit init
 '
@@ -225,4 +227,40 @@ test_expect_success '"add" worktree with --checkout' '
 	test_cmp init.t swamp2/init.t
 '
 
+test_expect_success 'put a worktree under rebase' '
+	git worktree add under-rebase &&
+	(
+		cd under-rebase &&
+		set_fake_editor &&
+		FAKE_LINES="edit 1" git rebase -i HEAD^ &&
+		git worktree list | grep "under-rebase.*detached HEAD"
+	)
+'
+
+test_expect_success 'add a worktree, checking out a rebased branch' '
+	test_must_fail git worktree add new-rebase under-rebase &&
+	! test -d new-rebase
+'
+
+test_expect_success 'checking out a rebased branch from another worktree' '
+	git worktree add new-place &&
+	test_must_fail git -C new-place checkout under-rebase
+'
+
+test_expect_success 'not allow to delete a branch under rebase' '
+	(
+		cd under-rebase &&
+		test_must_fail git branch -D under-rebase
+	)
+'
+
+test_expect_success 'check out from current worktree branch ok' '
+	(
+		cd under-rebase &&
+		git checkout under-rebase &&
+		git checkout - &&
+		git rebase --abort
+	)
+'
+
 test_done
worktree.c +31 -1
index b5ca78f..dc380a2 100644
@@ -3,6 +3,7 @@
 #include "strbuf.h"
 #include "worktree.h"
 #include "dir.h"
+#include "wt-status.h"
 
 void free_worktrees(struct worktree **worktrees)
 {
@@ -207,8 +208,27 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
+static int is_worktree_being_rebased(const struct worktree *wt,
+				     const char *target)
+{
+	struct wt_status_state state;
+	int found_rebase;
+
+	memset(&state, 0, sizeof(state));
+	found_rebase = wt_status_check_rebase(wt, &state) &&
+		((state.rebase_in_progress ||
+		  state.rebase_interactive_in_progress) &&
+		 state.branch &&
+		 starts_with(target, "refs/heads/") &&
+		 !strcmp(state.branch, target + strlen("refs/heads/")));
+	free(state.branch);
+	free(state.onto);
+	return found_rebase;
+}
+
 const struct worktree *find_shared_symref(const char *symref,
-					  const char *target)
+					  const char *target,
+					  int ignore_current_worktree)
 {
 	const struct worktree *existing = NULL;
 	struct strbuf path = STRBUF_INIT;
@@ -223,6 +243,16 @@ const struct worktree *find_shared_symref(const char *symref,
 	for (i = 0; worktrees[i]; i++) {
 		struct worktree *wt = worktrees[i];
 
+		if (ignore_current_worktree && wt->is_current)
+			continue;
+
+		if (wt->is_detached && !strcmp(symref, "HEAD")) {
+			if (is_worktree_being_rebased(wt, target)) {
+				existing = wt;
+				break;
+			}
+		}
+
 		strbuf_reset(&path);
 		strbuf_reset(&sb);
 		strbuf_addf(&path, "%s/%s",
worktree.h +2 -1
index 9d2463e..fb9f5cc 100644
@@ -41,7 +41,8 @@ extern void free_worktrees(struct worktree **);
  * may be destroyed by the next call.
  */
 extern const struct worktree *find_shared_symref(const char *symref,
-						 const char *target);
+						 const char *target,
+						 int ignore_current_worktree);
 
 /*
  * Similar to git_path() and git_pathdup() but can produce paths for a
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#38 lore raw

Re: [PATCH v2 09/12] worktree.c: test if branch being rebased in another worktree

Quoted 1 line · Nguyễn Thái Ngọc Duy

Subject: Re: [PATCH v2 09/12] worktree.c: test if branch being rebased in another worktree

Lacks the verb? Perhaps s/being/is/ is sufficient.

Quoted 8 lines

This function find_shared_symref() is used in a couple places:

1) in builtin/branch.c: it's used to detect if a branch is checked out elsewhere and refuse to delete the branch.

2) in builtin/notes.c: it's used to detect if a note is being merged in another worktree

Quoted 7 lines

3) in branch.c, the function die_if_checked_out() is actually used by "git checkout" and "git worktree add" to see if a branch is already checked out elsewhere and refuse the operation.

In cases 1 and 3, if a rebase is ongoing, "HEAD" will be in detached mode, find_shared_symref() fails to detect it and declares "no branch is checked out here", which is incorrect.

which is technically correct but is not what we want to check.

Quoted 4 lines

This patch tightens the test. If the given symref is "HEAD", we try to detect if rebase is ongoing. If so return the branch being rebased. This makes checkout and branch delete operations safer because you can't checkout a branch being rebased in another place, or delete it.

Is rebase the only thing that tentatively detach before working on the real branch? It may be currently the case, but I would imagine that we want to makefind-shared-symref to be responsible for detecting new cases other than rebase that we may introduce in the future, in which case we may want to leave come comment near the function to describe that expectation.

Quoted 6 lines

Special case for checkout. If the current branch is being rebased, git-rebase.sh may use "git checkout" to abort and return back to the original branch. The updated test in find_shared_symref() will prevent that and "git rebase --abort" will fail as a result. find_shared_symref() and die_if_checked_out() have to learn a new option ignore_current_worktree to loose the test a bit.

s/loose/&n/

Quoted 14 lines
branch.c +2 -1
+void die_if_checked_out(const char *branch, int ignore_current_worktree)
 {
 	const struct worktree *wt;
 
-	wt = find_shared_symref("HEAD", branch);
+	wt = find_shared_symref("HEAD", branch, ignore_current_worktree);
 	if (wt) {
 		skip_prefix(branch, "refs/heads/", &branch);
 		die(_("'%s' is already checked out at '%s'"),
builtin/checkout.c +0 -0
index efcbd8f..6041718 100644

builtin/checkout.c static int checkout_branch(struct checkout_opts *opts, · line 1116

Quoted 7 lines
builtin/checkout.c +1 -1
@@ -1111,7 +1111,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->path);
+			die_if_checked_out(new->path, 1);
 		free(head_ref);
 	}

So the idea is "if the branch is checked out (or "being worked on" even if technically the HEAD is detached, like with 'rebase') anywhere, callers of die-if-checked-out in general want to die; but for this caller, it is OK if the place the branch is checked out or being worked on is in this repository"?

I understand die_if_checked_out() taking that "ignore this one" bit may be sensible, but I do not understand why find_shared_symref() needs to be told about it. The change makes the meaning of the find_shared_symref() function unclear. It used to mean "This symbolic ref cannot point at the same ref in different worktrees, so for a given pair of a symbolic ref and a concrete ref, there can be at most one worktree in which the symbolic ref points at that ref". That is already a mouthful. As the worktree structure already have "Am I the current worktree?" bit, "ignore" logic can easily be done inside die_if_checked_out() and that would help find_shared_symref() stay simpler and more focused function, no?

#39 lore raw

Re: [PATCH v2 09/12] worktree.c: test if branch being rebased in another worktree

Quoted 19 lines · Junio C Hamano

builtin/checkout.c static int checkout_branch(struct checkout_opts *opts, · line 1116

Quoted 12 lines
builtin/checkout.c +1 -1
index efcbd8f..6041718 100644
@@ -1111,7 +1111,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->path);
+                     die_if_checked_out(new->path, 1);
              free(head_ref);
      }

So the idea is "if the branch is checked out (or "being worked on" even if technically the HEAD is detached, like with 'rebase') anywhere, callers of die-if-checked-out in general want to die; but for this caller, it is OK if the place the branch is checked out or being worked on is in this repository"?

Quoted 11 lines

I understand die_if_checked_out() taking that "ignore this one" bit may be sensible, but I do not understand why find_shared_symref() needs to be told about it. The change makes the meaning of the find_shared_symref() function unclear. It used to mean "This symbolic ref cannot point at the same ref in different worktrees, so for a given pair of a symbolic ref and a concrete ref, there can be at most one worktree in which the symbolic ref points at that ref". That is already a mouthful. As the worktree structure already have "Am I the current worktree?" bit, "ignore" logic can easily be done inside die_if_checked_out() and that would help find_shared_symref() stay simpler and more focused function, no?

That was the intention when I made find_shared_symref() return struct worktree * instead of char *. I forget why I changed my mind and not do so. The only case when find_shared_symref should do this is when a ref is shared twice, then we need to ignore current worktree from inside the loop. But that can't happen. Will move this ignore-current-worktree test to die_if_checked_out().

Signature · 1 line
Duy
#40 lore raw

PATCH v2 10/12 wt-status.c: split bisect detection out of wt_status_get_state()

In reply to Nguyễn Thái Ngọc Duy

And make it work with any given worktree, in preparation for (again) find_shared_symref(). read_and_strip_branch() is deleted because it's no longer used.

---
 wt-status.c | 23 ++++++++++++++---------
 wt-status.h |  2 ++
 2 files changed, 16 insertions(+), 9 deletions(-)
wt-status.c +14 -9
index 2295682..36c85f8 100644
@@ -1296,11 +1296,6 @@ got_nothing:
 	return NULL;
 }
 
-static char *read_and_strip_branch(const char *path)
-{
-	return get_branch(NULL, path);
-}
-
 struct grab_1st_switch_cbdata {
 	struct strbuf buf;
 	unsigned char nsha1[20];
@@ -1393,6 +1388,19 @@ int wt_status_check_rebase(const struct worktree *wt,
 	return 1;
 }
 
+int wt_status_check_bisect(const struct worktree *wt,
+			   struct wt_status_state *state)
+{
+	struct stat st;
+
+	if (!stat(worktree_git_path(wt, "BISECT_LOG"), &st)) {
+		state->bisect_in_progress = 1;
+		state->branch = get_branch(wt, "BISECT_START");
+		return 1;
+	}
+	return 0;
+}
+
 void wt_status_get_state(struct wt_status_state *state,
 			 int get_detached_from)
 {
@@ -1408,10 +1416,7 @@ void wt_status_get_state(struct wt_status_state *state,
 		state->cherry_pick_in_progress = 1;
 		hashcpy(state->cherry_pick_head_sha1, sha1);
 	}
-	if (!stat(git_path("BISECT_LOG"), &st)) {
-		state->bisect_in_progress = 1;
-		state->branch = read_and_strip_branch("BISECT_START");
-	}
+	wt_status_check_bisect(NULL, state);
 	if (!stat(git_path_revert_head(), &st) &&
 	    !get_sha1("REVERT_HEAD", sha1)) {
 		state->revert_in_progress = 1;
wt-status.h +2 -0
index c4ddcad..2ca93f6 100644
@@ -104,6 +104,8 @@ void wt_status_collect(struct wt_status *s);
 void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
 int wt_status_check_rebase(const struct worktree *wt,
 			   struct wt_status_state *state);
+int wt_status_check_bisect(const struct worktree *wt,
+			   struct wt_status_state *state);
 
 void wt_shortstatus_print(struct wt_status *s);
 void wt_porcelain_print(struct wt_status *s);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#41 lore raw

PATCH v2 11/12 worktree.c: test if branch being bisected in another worktree

In reply to Nguyễn Thái Ngọc Duy

Similar to the rebase case, we want to detect if "HEAD" in some worktree is being bisected because

1) we do not want to checkout this branch in another worktree, after bisect is done it will want to go back to this branch

2) we do not want to delete the branch is either or git bisect will fail to return to the (long gone) branch

---
 t/t2025-worktree-add.sh | 13 +++++++++++++
 worktree.c              | 19 +++++++++++++++++++
 2 files changed, 32 insertions(+)
t/t2025-worktree-add.sh +13 -0
index da54327..8f53944 100755
@@ -263,4 +263,17 @@ test_expect_success 'check out from current worktree branch ok' '
 	)
 '
 
+test_expect_success 'checkout a branch under bisect' '
+	git worktree add under-bisect &&
+	(
+		cd under-bisect &&
+		git bisect start &&
+		git bisect bad &&
+		git bisect good HEAD~2 &&
+		git worktree list | grep "under-bisect.*detached HEAD" &&
+		test_must_fail git worktree add new-bisect under-bisect &&
+		! test -d new-bisect
+	)
+'
+
 test_done
worktree.c +19 -0
index dc380a2..7b66071 100644
@@ -226,6 +226,21 @@ static int is_worktree_being_rebased(const struct worktree *wt,
 	return found_rebase;
 }
 
+static int is_worktree_being_bisected(const struct worktree *wt,
+				      const char *target)
+{
+	struct wt_status_state state;
+	int found_rebase;
+
+	memset(&state, 0, sizeof(state));
+	found_rebase = wt_status_check_bisect(wt, &state) &&
+		state.branch &&
+		starts_with(target, "refs/heads/") &&
+		!strcmp(state.branch, target + strlen("refs/heads/"));
+	free(state.branch);
+	return found_rebase;
+}
+
 const struct worktree *find_shared_symref(const char *symref,
 					  const char *target,
 					  int ignore_current_worktree)
@@ -251,6 +266,10 @@ const struct worktree *find_shared_symref(const char *symref,
 				existing = wt;
 				break;
 			}
+			if (is_worktree_being_bisected(wt, target)) {
+				existing = wt;
+				break;
+			}
 		}
 
 		strbuf_reset(&path);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#42 lore raw

PATCH v2 12/12 branch: do not rename a branch under bisect or rebase

In reply to Nguyễn Thái Ngọc Duy

The branch name in that case could be saved in rebase's head_name or bisect's BISECT_START files. Ideally we should try to update them as well. But it's trickier (*). Let's play safe and see if the user complains about inconveniences before doing that.

(*) If we do it, bisect and rebase need to provide an API to rename branches. We can't do it in worktree.c or builtin/branch.c because when other people change rebase/bisect code, they may not be aware of this code and accidentally break it (e.g. rename the branch file, or refer to the branch in new files). It's a lot more work.

---
 builtin/branch.c        | 25 +++++++++++++++++++++++++
 t/t2025-worktree-add.sh |  8 ++++++++
 worktree.c              |  8 ++++----
 worktree.h              |  3 +++
 4 files changed, 40 insertions(+), 4 deletions(-)
builtin/branch.c +25 -0
index bf91bbd..3a2eceb 100644
@@ -524,6 +524,29 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
 	ref_array_clear(&array);
 }
 
+static void reject_rebase_or_bisect_branch(const char *target)
+{
+	struct worktree **worktrees = get_worktrees();
+	int i;
+
+	for (i = 0; worktrees[i]; i++) {
+		struct worktree *wt = worktrees[i];
+
+		if (!wt->is_detached)
+			continue;
+
+		if (is_worktree_being_rebased(wt, target))
+			die(_("Branch %s is being rebased at %s"),
+			    target, wt->path);
+
+		if (is_worktree_being_bisected(wt, target))
+			die(_("Branch %s is being bisected at %s"),
+			    target, wt->path);
+	}
+
+	free_worktrees(worktrees);
+}
+
 static void rename_branch(const char *oldname, const char *newname, int force)
 {
 	struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
@@ -553,6 +576,8 @@ static void rename_branch(const char *oldname, const char *newname, int force)
 
 	validate_new_branchname(newname, &newref, force, clobber_head_ok);
 
+	reject_rebase_or_bisect_branch(oldref.buf);
+
 	strbuf_addf(&logmsg, "Branch: renamed %s to %s",
 		 oldref.buf, newref.buf);
 
t/t2025-worktree-add.sh +8 -0
index 8f53944..3a22fc5 100755
@@ -254,6 +254,10 @@ test_expect_success 'not allow to delete a branch under rebase' '
 	)
 '
 
+test_expect_success 'rename a branch under rebase not allowed' '
+	test_must_fail git branch -M under-rebase rebase-with-new-name
+'
+
 test_expect_success 'check out from current worktree branch ok' '
 	(
 		cd under-rebase &&
@@ -276,4 +280,8 @@ test_expect_success 'checkout a branch under bisect' '
 	)
 '
 
+test_expect_success 'rename a branch under bisect not allowed' '
+	test_must_fail git branch -M under-bisect bisect-with-new-name
+'
+
 test_done
worktree.c +4 -4
index 7b66071..8a3d394 100644
@@ -208,8 +208,8 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
-static int is_worktree_being_rebased(const struct worktree *wt,
-				     const char *target)
+int is_worktree_being_rebased(const struct worktree *wt,
+			      const char *target)
 {
 	struct wt_status_state state;
 	int found_rebase;
@@ -226,8 +226,8 @@ static int is_worktree_being_rebased(const struct worktree *wt,
 	return found_rebase;
 }
 
-static int is_worktree_being_bisected(const struct worktree *wt,
-				      const char *target)
+int is_worktree_being_bisected(const struct worktree *wt,
+			       const char *target)
 {
 	struct wt_status_state state;
 	int found_rebase;
worktree.h +3 -0
index fb9f5cc..d4a3534 100644
@@ -44,6 +44,9 @@ extern const struct worktree *find_shared_symref(const char *symref,
 						 const char *target,
 						 int ignore_current_worktree);
 
+int is_worktree_being_rebased(const struct worktree *wt, const char *target);
+int is_worktree_being_bisected(const struct worktree *wt, const char *target);
+
 /*
  * Similar to git_path() and git_pathdup() but can produce paths for a
  * specified worktree instead of current one
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#43 lore raw

Re: [PATCH v2 00/12] fix checking out a being-rebased branch

In reply to Nguyễn Thái Ngọc Duy
Quoted 5 lines · Nguyễn Thái Ngọc Duy

Much happier with this version. This makes

- git checkout refuse if a branch is under rebase or bisect elsewhere

Quoted 7 lines

- git worktree add refuse if a branch is under rebase or bisect

- git branch -D refuse if a branch is under rebase or bisect. This applies for single worktree case as well.

- git branch -M refuse if a branch is under rebase or bisect (single worktree case as well)

I agree that this reads much nicer than v1, especially with the abstraction around find_shared_symref() that returns the actual worktree.

Quoted 14 lines

[01/12] path.c: add git_common_path() and strbuf_git_common_path() [02/12] worktree.c: store "id" instead of "git_dir"
[03/12] worktree.c: make find_shared_symref() return struct worktree * [04/12] worktree.c: mark current worktree
[05/12] path.c: refactor and add worktree_git_path()
[06/12] wt-status.c: split rebase detection out of wt_status_get_state() [07/12] wt-status.c: make wt_status_check_rebase() work on any worktree [08/12] worktree.c: avoid referencing to worktrees[i] multiple times [09/12] worktree.c: test if branch being rebased in another worktree [10/12] wt-status.c: split bisect detection out of wt_status_get_state() [11/12] worktree.c: test if branch being bisected in another worktree [12/12] branch: do not rename a branch under bisect or rebase

Total 13 files changed, 335 insertions(+), 67 deletions(-)

#44 lore raw

v3 cover nd/worktree-various-heads

In reply to Nguyễn Thái Ngọc Duy

v3 fixes all the things found in v2, deletes unused stuff and adds a new patch 01/13 that renames str(n)cmp_icase to fspath(n)cmp.

Interdiff

branch.c +6 -6
index 8e323d3..a5a8dcb 100644
@@ -338,12 +338,12 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree)
 {
 	const struct worktree *wt;
 
-	wt = find_shared_symref("HEAD", branch, ignore_current_worktree);
-	if (wt) {
-		skip_prefix(branch, "refs/heads/", &branch);
-		die(_("'%s' is already checked out at '%s'"),
-		    branch, wt->path);
-	}
+	wt = find_shared_symref("HEAD", branch);
+	if (!wt || (ignore_current_worktree && wt->is_current))
+		return;
+	skip_prefix(branch, "refs/heads/", &branch);
+	die(_("'%s' is already checked out at '%s'"),
+	    branch, wt->path);
 }
 
 int replace_each_worktree_head_symref(const char *oldref, const char *newref)
builtin/branch.c +1 -1
index 3a2eceb..b488c3f 100644
@@ -221,7 +221,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
 
 		if (kinds == FILTER_REFS_BRANCHES) {
 			const struct worktree *wt =
-				find_shared_symref("HEAD", name, 0);
+				find_shared_symref("HEAD", name);
 			if (wt) {
 				error(_("Cannot delete branch '%s' "
 					"checked out at '%s'"),
builtin/notes.c +1 -1
index f154a69..c65b59a 100644
@@ -852,7 +852,7 @@ static int merge(int argc, const char **argv, const char *prefix)
 		update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
 			   0, UPDATE_REFS_DIE_ON_ERR);
 		/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
-		wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref(), 0);
+		wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
 		if (wt)
 			die(_("A notes merge into %s is already in-progress at %s"),
 			    default_notes_ref(), wt->path);
dir.c +6 -7
index 996653b..f04bd3b 100644
@@ -53,13 +53,12 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
 	int check_only, const struct path_simplify *simplify);
 static int get_dtype(struct dirent *de, const char *path, int len);
 
-/* helper string functions with support for the ignore_case flag */
-int strcmp_icase(const char *a, const char *b)
+int fspathcmp(const char *a, const char *b)
 {
 	return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
 }
 
-int strncmp_icase(const char *a, const char *b, size_t count)
+int fspathncmp(const char *a, const char *b, size_t count)
 {
 	return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
 }
@@ -802,12 +801,12 @@ int match_basename(const char *basename, int basenamelen,
 {
 	if (prefix == patternlen) {
 		if (patternlen == basenamelen &&
-		    !strncmp_icase(pattern, basename, basenamelen))
+		    !fspathncmp(pattern, basename, basenamelen))
 			return 1;
 	} else if (flags & EXC_FLAG_ENDSWITH) {
 		/* "*literal" matching against "fooliteral" */
 		if (patternlen - 1 <= basenamelen &&
-		    !strncmp_icase(pattern + 1,
+		    !fspathncmp(pattern + 1,
 				   basename + basenamelen - (patternlen - 1),
 				   patternlen - 1))
 			return 1;
@@ -844,7 +843,7 @@ int match_pathname(const char *pathname, int pathlen,
 	 */
 	if (pathlen < baselen + 1 ||
 	    (baselen && pathname[baselen] != '/') ||
-	    strncmp_icase(pathname, base, baselen))
+	    fspathncmp(pathname, base, baselen))
 		return 0;
 
 	namelen = baselen ? pathlen - baselen - 1 : pathlen;
@@ -858,7 +857,7 @@ int match_pathname(const char *pathname, int pathlen,
 		if (prefix > namelen)
 			return 0;
 
-		if (strncmp_icase(pattern, name, prefix))
+		if (fspathncmp(pattern, name, prefix))
 			return 0;
 		pattern += prefix;
 		patternlen -= prefix;
dir.h +2 -2
index 301b737..e34d555 100644
@@ -270,8 +270,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag);
 /* tries to remove the path with empty directories along it, ignores ENOENT */
 extern int remove_path(const char *path);
 
-extern int strcmp_icase(const char *a, const char *b);
-extern int strncmp_icase(const char *a, const char *b, size_t count);
+extern int fspathcmp(const char *a, const char *b);
+extern int fspathncmp(const char *a, const char *b, size_t count);
 extern int fnmatch_icase(const char *pattern, const char *string, int flags);
 
 /*
fast-import.c +3 -3
index 9fc7093..339cd38 100644
@@ -1512,7 +1512,7 @@ static int tree_content_set(
 	t = root->tree;
 	for (i = 0; i < t->entry_count; i++) {
 		e = t->entries[i];
-		if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
+		if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
 			if (!*slash1) {
 				if (!S_ISDIR(mode)
 						&& e->versions[1].mode == mode
@@ -1602,7 +1602,7 @@ static int tree_content_remove(
 	t = root->tree;
 	for (i = 0; i < t->entry_count; i++) {
 		e = t->entries[i];
-		if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
+		if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
 			if (*slash1 && !S_ISDIR(e->versions[1].mode))
 				/*
 				 * If p names a file in some subdirectory, and a
@@ -1669,7 +1669,7 @@ static int tree_content_get(
 	t = root->tree;
 	for (i = 0; i < t->entry_count; i++) {
 		e = t->entries[i];
-		if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
+		if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
 			if (!*slash1)
 				goto found_entry;
 			if (!S_ISDIR(e->versions[1].mode))
path.c +0 -10
index c421d37..8fdd187 100644
@@ -466,16 +466,6 @@ const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)
 	return pathname->buf;
 }
 
-char *worktree_git_pathdup(const struct worktree *wt, const char *fmt, ...)
-{
-	struct strbuf path = STRBUF_INIT;
-	va_list args;
-	va_start(args, fmt);
-	do_git_path(wt, &path, fmt, args);
-	va_end(args);
-	return strbuf_detach(&path, NULL);
-}
-
 static void do_submodule_path(struct strbuf *buf, const char *path,
 			      const char *fmt, va_list args)
 {
sha1_file.c +1 -1
index d0f2aa0..ea6381b 100644
@@ -301,7 +301,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,
 			return -1;
 		}
 	}
-	if (!strcmp_icase(ent->base, normalized_objdir)) {
+	if (!fspathcmp(ent->base, normalized_objdir)) {
 		free(ent);
 		return -1;
 	}
worktree.c +28 -18

77 lines not on this page · raw message

worktree.h +5 -10
index d4a3534..1394909 100644
@@ -36,26 +36,21 @@ extern void free_worktrees(struct worktree **);
 
 /*
  * Check if a per-worktree symref points to a ref in the main worktree
- * or any linked worktree, and return the path to the exising worktree
- * if it is. Returns NULL if there is no existing ref. The result
- * may be destroyed by the next call.
+ * or any linked worktree, and return the worktree that holds the ref,
+ * or NULL otherwise. The result may be destroyed by the next call.
  */
 extern const struct worktree *find_shared_symref(const char *symref,
-						 const char *target,
-						 int ignore_current_worktree);
+						 const char *target);
 
 int is_worktree_being_rebased(const struct worktree *wt, const char *target);
 int is_worktree_being_bisected(const struct worktree *wt, const char *target);
 
 /*
- * Similar to git_path() and git_pathdup() but can produce paths for a
- * specified worktree instead of current one
+ * Similar to git_path() but can produce paths for a specified
+ * worktree instead of current one
  */
 extern const char *worktree_git_path(const struct worktree *wt,
 				     const char *fmt, ...)
 	__attribute__((format (printf, 2, 3)));
-extern char *worktree_git_pathdup(const struct worktree *wt,
-				  const char *fmt, ...)
-	__attribute__((format (printf, 2, 3)));
 
 #endif
wt-status.c +1 -1
index 971e071..0032ef5 100644
@@ -1410,7 +1410,7 @@ void wt_status_get_state(struct wt_status_state *state,
 	if (!stat(git_path_merge_head(), &st)) {
 		state->merge_in_progress = 1;
 	} else if (wt_status_check_rebase(NULL, state)) {
-		; /* all set */
+		;		/* all set */
 	} else if (!stat(git_path_cherry_pick_head(), &st) &&
 			!get_sha1("CHERRY_PICK_HEAD", sha1)) {
 		state->cherry_pick_in_progress = 1;

[01/13] dir.c: rename str(n)cmp_icase to fspath(n)cmp [02/13] path.c: add git_common_path() and strbuf_git_common_path() [03/13] worktree.c: store "id" instead of "git_dir"
[04/13] worktree.c: make find_shared_symref() return struct worktree * [05/13] worktree.c: mark current worktree
[06/13] path.c: refactor and add worktree_git_path()
[07/13] wt-status.c: split rebase detection out of wt_status_get_state() [08/13] wt-status.c: make wt_status_check_rebase() work on any worktree [09/13] worktree.c: avoid referencing to worktrees[i] multiple times [10/13] worktree.c: check whether branch is rebased in another worktree [11/13] wt-status.c: split bisect detection out of wt_status_get_state() [12/13] worktree.c: check whether branch is bisected in another worktree [13/13] branch: do not rename a branch under bisect or rebase

Total 17 files changed, 344 insertions(+), 82 deletions(-) --
Duy

#45 lore raw

PATCH v3 1/13 dir.c: rename str(n)cmp_icase to fspath(n)cmp

These functions compare two paths that are taken from file system. Depending on the running file system, paths may need to be compared case-sensitively or not, and maybe even something else in future. The current names do not convey that well.

---
 dir.c         | 13 ++++++-------
 dir.h         |  4 ++--
 fast-import.c |  6 +++---
 sha1_file.c   |  2 +-
 4 files changed, 12 insertions(+), 13 deletions(-)
dir.c +6 -7
index 996653b..f04bd3b 100644
@@ -53,13 +53,12 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
 	int check_only, const struct path_simplify *simplify);
 static int get_dtype(struct dirent *de, const char *path, int len);
 
-/* helper string functions with support for the ignore_case flag */
-int strcmp_icase(const char *a, const char *b)
+int fspathcmp(const char *a, const char *b)
 {
 	return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
 }
 
-int strncmp_icase(const char *a, const char *b, size_t count)
+int fspathncmp(const char *a, const char *b, size_t count)
 {
 	return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
 }
@@ -802,12 +801,12 @@ int match_basename(const char *basename, int basenamelen,
 {
 	if (prefix == patternlen) {
 		if (patternlen == basenamelen &&
-		    !strncmp_icase(pattern, basename, basenamelen))
+		    !fspathncmp(pattern, basename, basenamelen))
 			return 1;
 	} else if (flags & EXC_FLAG_ENDSWITH) {
 		/* "*literal" matching against "fooliteral" */
 		if (patternlen - 1 <= basenamelen &&
-		    !strncmp_icase(pattern + 1,
+		    !fspathncmp(pattern + 1,
 				   basename + basenamelen - (patternlen - 1),
 				   patternlen - 1))
 			return 1;
@@ -844,7 +843,7 @@ int match_pathname(const char *pathname, int pathlen,
 	 */
 	if (pathlen < baselen + 1 ||
 	    (baselen && pathname[baselen] != '/') ||
-	    strncmp_icase(pathname, base, baselen))
+	    fspathncmp(pathname, base, baselen))
 		return 0;
 
 	namelen = baselen ? pathlen - baselen - 1 : pathlen;
@@ -858,7 +857,7 @@ int match_pathname(const char *pathname, int pathlen,
 		if (prefix > namelen)
 			return 0;
 
-		if (strncmp_icase(pattern, name, prefix))
+		if (fspathncmp(pattern, name, prefix))
 			return 0;
 		pattern += prefix;
 		patternlen -= prefix;
dir.h +2 -2
index 301b737..e34d555 100644
@@ -270,8 +270,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag);
 /* tries to remove the path with empty directories along it, ignores ENOENT */
 extern int remove_path(const char *path);
 
-extern int strcmp_icase(const char *a, const char *b);
-extern int strncmp_icase(const char *a, const char *b, size_t count);
+extern int fspathcmp(const char *a, const char *b);
+extern int fspathncmp(const char *a, const char *b, size_t count);
 extern int fnmatch_icase(const char *pattern, const char *string, int flags);
 
 /*
fast-import.c +3 -3
index 9fc7093..339cd38 100644
@@ -1512,7 +1512,7 @@ static int tree_content_set(
 	t = root->tree;
 	for (i = 0; i < t->entry_count; i++) {
 		e = t->entries[i];
-		if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
+		if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
 			if (!*slash1) {
 				if (!S_ISDIR(mode)
 						&& e->versions[1].mode == mode
@@ -1602,7 +1602,7 @@ static int tree_content_remove(
 	t = root->tree;
 	for (i = 0; i < t->entry_count; i++) {
 		e = t->entries[i];
-		if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
+		if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
 			if (*slash1 && !S_ISDIR(e->versions[1].mode))
 				/*
 				 * If p names a file in some subdirectory, and a
@@ -1669,7 +1669,7 @@ static int tree_content_get(
 	t = root->tree;
 	for (i = 0; i < t->entry_count; i++) {
 		e = t->entries[i];
-		if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
+		if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
 			if (!*slash1)
 				goto found_entry;
 			if (!S_ISDIR(e->versions[1].mode))
sha1_file.c +1 -1
index d0f2aa0..ea6381b 100644
@@ -301,7 +301,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,
 			return -1;
 		}
 	}
-	if (!strcmp_icase(ent->base, normalized_objdir)) {
+	if (!fspathcmp(ent->base, normalized_objdir)) {
 		free(ent);
 		return -1;
 	}
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#46 lore raw

PATCH v3 2/13 path.c: add git_common_path() and strbuf_git_common_path()

In reply to Nguyễn Thái Ngọc Duy

These are mostly convenient functions to reduce code duplication. Most of the time, we should be able to get by with git_path() which handles $GIT_COMMON_DIR internally. However there are a few cases where we need to construct paths manually, for example some paths from a specific worktree. These functions will enable that.

---
 cache.h |  3 +++
 path.c  | 29 +++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)
cache.h +3 -0
index 2711048..c04a17f 100644
@@ -799,11 +799,14 @@ extern void check_repository_format(void);
  */
 extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 
 extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	__attribute__((format (printf, 3, 4)));
 extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 	__attribute__((format (printf, 2, 3)));
+extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
+	__attribute__((format (printf, 2, 3)));
 extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
 	__attribute__((format (printf, 2, 3)));
 extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
path.c +29 -0
index bbaea5a..2ebb23d 100644
@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,
 	va_end(args);
 }
 
+static void do_git_common_path(struct strbuf *buf,
+			       const char *fmt,
+			       va_list args)
+{
+	strbuf_addstr(buf, get_git_common_dir());
+	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
+		strbuf_addch(buf, '/');
+	strbuf_vaddf(buf, fmt, args);
+	strbuf_cleanup_path(buf);
+}
+
+const char *git_common_path(const char *fmt, ...)
+{
+	struct strbuf *pathname = get_pathname();
+	va_list args;
+	va_start(args, fmt);
+	do_git_common_path(pathname, fmt, args);
+	va_end(args);
+	return pathname->buf;
+}
+
+void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+	do_git_common_path(sb, fmt, args);
+	va_end(args);
+}
+
 int validate_headref(const char *path)
 {
 	struct stat st;
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#47 lore raw

PATCH v3 3/13 worktree.c: store "id" instead of "git_dir"

In reply to Nguyễn Thái Ngọc Duy

We can reconstruct git_dir from id quite easily. It's a bit hackier to do the reverse.

---
 branch.c   |  3 ++-
 worktree.c | 31 ++++++++++++++++++-------------
 worktree.h |  8 +++++++-
 3 files changed, 27 insertions(+), 15 deletions(-)
branch.c +2 -1
index 4162443..0674a99 100644
@@ -357,7 +357,8 @@ int replace_each_worktree_head_symref(const char *oldref, const char *newref)
 		if (strcmp(oldref, worktrees[i]->head_ref))
 			continue;
 
-		if (set_worktree_head_symref(worktrees[i]->git_dir, newref)) {
+		if (set_worktree_head_symref(get_worktree_git_dir(worktrees[i]),
+					     newref)) {
 			ret = -1;
 			error(_("HEAD of working tree %s is not updated"),
 			      worktrees[i]->path);
worktree.c +18 -13
index 6181a66..5ae54f0 100644
@@ -9,7 +9,7 @@ void free_worktrees(struct worktree **worktrees)
 
 	for (i = 0; worktrees[i]; i++) {
 		free(worktrees[i]->path);
-		free(worktrees[i]->git_dir);
+		free(worktrees[i]->id);
 		free(worktrees[i]->head_ref);
 		free(worktrees[i]);
 	}
@@ -74,13 +74,11 @@ static struct worktree *get_main_worktree(void)
 	struct worktree *worktree = NULL;
 	struct strbuf path = STRBUF_INIT;
 	struct strbuf worktree_path = STRBUF_INIT;
-	struct strbuf gitdir = STRBUF_INIT;
 	struct strbuf head_ref = STRBUF_INIT;
 	int is_bare = 0;
 	int is_detached = 0;
 
-	strbuf_addf(&gitdir, "%s", absolute_path(get_git_common_dir()));
-	strbuf_addbuf(&worktree_path, &gitdir);
+	strbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));
 	is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
 	if (is_bare)
 		strbuf_strip_suffix(&worktree_path, "/.");
@@ -92,7 +90,7 @@ static struct worktree *get_main_worktree(void)
 
 	worktree = xmalloc(sizeof(struct worktree));
 	worktree->path = strbuf_detach(&worktree_path, NULL);
-	worktree->git_dir = strbuf_detach(&gitdir, NULL);
+	worktree->id = NULL;
 	worktree->is_bare = is_bare;
 	worktree->head_ref = NULL;
 	worktree->is_detached = is_detached;
@@ -100,7 +98,6 @@ static struct worktree *get_main_worktree(void)
 
 done:
 	strbuf_release(&path);
-	strbuf_release(&gitdir);
 	strbuf_release(&worktree_path);
 	strbuf_release(&head_ref);
 	return worktree;
@@ -111,16 +108,13 @@ static struct worktree *get_linked_worktree(const char *id)
 	struct worktree *worktree = NULL;
 	struct strbuf path = STRBUF_INIT;
 	struct strbuf worktree_path = STRBUF_INIT;
-	struct strbuf gitdir = STRBUF_INIT;
 	struct strbuf head_ref = STRBUF_INIT;
 	int is_detached = 0;
 
 	if (!id)
 		die("Missing linked worktree name");
 
-	strbuf_addf(&gitdir, "%s/worktrees/%s",
-			absolute_path(get_git_common_dir()), id);
-	strbuf_addf(&path, "%s/gitdir", gitdir.buf);
+	strbuf_git_common_path(&path, "worktrees/%s/gitdir", id);
 	if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
 		/* invalid gitdir file */
 		goto done;
@@ -140,7 +134,7 @@ static struct worktree *get_linked_worktree(const char *id)
 
 	worktree = xmalloc(sizeof(struct worktree));
 	worktree->path = strbuf_detach(&worktree_path, NULL);
-	worktree->git_dir = strbuf_detach(&gitdir, NULL);
+	worktree->id = xstrdup(id);
 	worktree->is_bare = 0;
 	worktree->head_ref = NULL;
 	worktree->is_detached = is_detached;
@@ -148,7 +142,6 @@ static struct worktree *get_linked_worktree(const char *id)
 
 done:
 	strbuf_release(&path);
-	strbuf_release(&gitdir);
 	strbuf_release(&worktree_path);
 	strbuf_release(&head_ref);
 	return worktree;
@@ -188,6 +181,16 @@ struct worktree **get_worktrees(void)
 	return list;
 }
 
+const char *get_worktree_git_dir(const struct worktree *wt)
+{
+	if (!wt)
+		return get_git_dir();
+	else if (!wt->id)
+		return get_git_common_dir();
+	else
+		return git_common_path("worktrees/%s", wt->id);
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
@@ -199,7 +202,9 @@ char *find_shared_symref(const char *symref, const char *target)
 	for (i = 0; worktrees[i]; i++) {
 		strbuf_reset(&path);
 		strbuf_reset(&sb);
-		strbuf_addf(&path, "%s/%s", worktrees[i]->git_dir, symref);
+		strbuf_addf(&path, "%s/%s",
+			    get_worktree_git_dir(worktrees[i]),
+			    symref);
 
 		if (parse_ref(path.buf, &sb, NULL)) {
 			continue;
worktree.h +7 -1
index b4b3dda..3198c8d 100644
@@ -3,7 +3,7 @@
 
 struct worktree {
 	char *path;
-	char *git_dir;
+	char *id;
 	char *head_ref;
 	unsigned char head_sha1[20];
 	int is_detached;
@@ -23,6 +23,12 @@ struct worktree {
 extern struct worktree **get_worktrees(void);
 
 /*
+ * Return git dir of the worktree. Note that the path may be relative.
+ * If wt is NULL, git dir of current worktree is returned.
+ */
+extern const char *get_worktree_git_dir(const struct worktree *wt);
+
+/*
  * Free up the memory for worktree(s)
  */
 extern void free_worktrees(struct worktree **);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#48 lore raw

PATCH v3 4/13 worktree.c: make find_shared_symref() return struct worktree *

In reply to Nguyễn Thái Ngọc Duy

This gives the caller more information and they can answer things like, "is it the main worktree" or "is it the current worktree". The latter question is needed for the "checkout a rebase branch" case later.

---
 branch.c         | 13 +++++++------
 builtin/branch.c |  8 ++++----
 builtin/notes.c  |  8 ++++----
 worktree.c       | 14 +++++++++-----
 worktree.h       |  8 ++++----
 5 files changed, 28 insertions(+), 23 deletions(-)
branch.c +7 -6
index 0674a99..1f1fbf5 100644
@@ -336,13 +336,14 @@ void remove_branch_state(void)
 
 void die_if_checked_out(const char *branch)
 {
-	char *existing;
+	const struct worktree *wt;
 
-	existing = find_shared_symref("HEAD", branch);
-	if (existing) {
-		skip_prefix(branch, "refs/heads/", &branch);
-		die(_("'%s' is already checked out at '%s'"), branch, existing);
-	}
+	wt = find_shared_symref("HEAD", branch);
+	if (!wt)
+		return;
+	skip_prefix(branch, "refs/heads/", &branch);
+	die(_("'%s' is already checked out at '%s'"),
+	    branch, wt->path);
 }
 
 int replace_each_worktree_head_symref(const char *oldref, const char *newref)
builtin/branch.c +4 -4
index 0adba62..bcde87d 100644
@@ -220,12 +220,12 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
 		name = mkpathdup(fmt, bname.buf);
 
 		if (kinds == FILTER_REFS_BRANCHES) {
-			char *worktree = find_shared_symref("HEAD", name);
-			if (worktree) {
+			const struct worktree *wt =
+				find_shared_symref("HEAD", name);
+			if (wt) {
 				error(_("Cannot delete branch '%s' "
 					"checked out at '%s'"),
-				      bname.buf, worktree);
-				free(worktree);
+				      bname.buf, wt->path);
 				ret = 1;
 				continue;
 			}
builtin/notes.c +4 -4
index 6fd058d..c65b59a 100644
@@ -847,15 +847,15 @@ static int merge(int argc, const char **argv, const char *prefix)
 		update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
 			   0, UPDATE_REFS_DIE_ON_ERR);
 	else { /* Merge has unresolved conflicts */
-		char *existing;
+		const struct worktree *wt;
 		/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
 		update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
 			   0, UPDATE_REFS_DIE_ON_ERR);
 		/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
-		existing = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
-		if (existing)
+		wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
+		if (wt)
 			die(_("A notes merge into %s is already in-progress at %s"),
-			    default_notes_ref(), existing);
+			    default_notes_ref(), wt->path);
 		if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
 			die("Failed to store link to current notes ref (%s)",
 			    default_notes_ref());
worktree.c +9 -5
index 5ae54f0..360ba41 100644
@@ -191,14 +191,19 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
-char *find_shared_symref(const char *symref, const char *target)
+const struct worktree *find_shared_symref(const char *symref,
+					  const char *target)
 {
-	char *existing = NULL;
+	const struct worktree *existing = NULL;
 	struct strbuf path = STRBUF_INIT;
 	struct strbuf sb = STRBUF_INIT;
-	struct worktree **worktrees = get_worktrees();
+	static struct worktree **worktrees;
 	int i = 0;
 
+	if (worktrees)
+		free_worktrees(worktrees);
+	worktrees = get_worktrees();
+
 	for (i = 0; worktrees[i]; i++) {
 		strbuf_reset(&path);
 		strbuf_reset(&sb);
@@ -211,14 +216,13 @@ char *find_shared_symref(const char *symref, const char *target)
 		}
 
 		if (!strcmp(sb.buf, target)) {
-			existing = xstrdup(worktrees[i]->path);
+			existing = worktrees[i];
 			break;
 		}
 	}
 
 	strbuf_release(&path);
 	strbuf_release(&sb);
-	free_worktrees(worktrees);
 
 	return existing;
 }
worktree.h +4 -4
index 3198c8d..ca50e73 100644
@@ -35,10 +35,10 @@ extern void free_worktrees(struct worktree **);
 
 /*
  * Check if a per-worktree symref points to a ref in the main worktree
- * or any linked worktree, and return the path to the exising worktree
- * if it is.  Returns NULL if there is no existing ref.  The caller is
- * responsible for freeing the returned path.
+ * or any linked worktree, and return the worktree that holds the ref,
+ * or NULL otherwise. The result may be destroyed by the next call.
  */
-extern char *find_shared_symref(const char *symref, const char *target);
+extern const struct worktree *find_shared_symref(const char *symref,
+						 const char *target);
 
 #endif
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#49 lore raw

PATCH v3 5/13 worktree.c: mark current worktree

In reply to Nguyễn Thái Ngọc Duy
---
 worktree.c | 24 ++++++++++++++++++++++++
 worktree.h |  1 +
 2 files changed, 25 insertions(+)
worktree.c +24 -0
index 360ba41..dc092a7 100644
@@ -2,6 +2,7 @@
 #include "refs.h"
 #include "strbuf.h"
 #include "worktree.h"
+#include "dir.h"
 
 void free_worktrees(struct worktree **worktrees)
 {
@@ -94,6 +95,7 @@ static struct worktree *get_main_worktree(void)
 	worktree->is_bare = is_bare;
 	worktree->head_ref = NULL;
 	worktree->is_detached = is_detached;
+	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
 
 done:
@@ -138,6 +140,7 @@ static struct worktree *get_linked_worktree(const char *id)
 	worktree->is_bare = 0;
 	worktree->head_ref = NULL;
 	worktree->is_detached = is_detached;
+	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
 
 done:
@@ -147,6 +150,25 @@ done:
 	return worktree;
 }
 
+static void mark_current_worktree(struct worktree **worktrees)
+{
+	struct strbuf git_dir = STRBUF_INIT;
+	struct strbuf path = STRBUF_INIT;
+	int i;
+
+	strbuf_addstr(&git_dir, absolute_path(get_git_dir()));
+	for (i = 0; worktrees[i]; i++) {
+		struct worktree *wt = worktrees[i];
+		strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
+		wt->is_current = !fspathcmp(git_dir.buf, path.buf);
+		strbuf_reset(&path);
+		if (wt->is_current)
+			break;
+	}
+	strbuf_release(&git_dir);
+	strbuf_release(&path);
+}
+
 struct worktree **get_worktrees(void)
 {
 	struct worktree **list = NULL;
@@ -178,6 +200,8 @@ struct worktree **get_worktrees(void)
 	}
 	ALLOC_GROW(list, counter + 1, alloc);
 	list[counter] = NULL;
+
+	mark_current_worktree(list);
 	return list;
 }
 
worktree.h +1 -0
index ca50e73..ccdf69a 100644
@@ -8,6 +8,7 @@ struct worktree {
 	unsigned char head_sha1[20];
 	int is_detached;
 	int is_bare;
+	int is_current;
 };
 
 /* Functions for acting on the information about worktrees. */
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#50 lore raw

Re: [PATCH v3 05/13] worktree.c: mark current worktree

worktree.c done: · line 156

Quoted 11 lines · Nguyễn Thái Ngọc Duy

---

worktree.c +7 -0
@@ -147,6 +150,25 @@ done:
+static void mark_current_worktree(struct worktree **worktrees)
+{
+       struct strbuf git_dir = STRBUF_INIT;
+       struct strbuf path = STRBUF_INIT;
+       int i;
+
+       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));

This could also just be:

    char *git_dir = xstrdup(absolute_path(...));

with a corresponding 'free(git_dir)' below.

worktree.c done: · line 163

Quoted 4 lines
worktree.c +4 -0
+       for (i = 0; worktrees[i]; i++) {
+               struct worktree *wt = worktrees[i];
+               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
+               wt->is_current = !fspathcmp(git_dir.buf, path.buf);

Similarly, it looks like 'path' doesn't need to be a strbuf at all since the result of absolute_path() should remain valid long enough for fspathcmp(). It could just be:

    const char *path = absolute_path(...);
    wt->is_current = !fspathcmp(git_dir, path);

But these are very minor; probably not worth a re-roll.

Quoted 7 lines
worktree.c +7 -0
+               strbuf_reset(&path);
+               if (wt->is_current)
+                       break;
+       }
+       strbuf_release(&git_dir);
+       strbuf_release(&path);
+}
#51 lore raw

Re: [PATCH v3 05/13] worktree.c: mark current worktree

Quoted 24 lines · Eric Sunshine

worktree.c done: · line 156

Quoted 11 lines · Nguyễn Thái Ngọc Duy

---

worktree.c +7 -0
@@ -147,6 +150,25 @@ done:
+static void mark_current_worktree(struct worktree **worktrees)
+{
+       struct strbuf git_dir = STRBUF_INIT;
+       struct strbuf path = STRBUF_INIT;
+       int i;
+
+       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));

This could also just be:

    char *git_dir = xstrdup(absolute_path(...));

with a corresponding 'free(git_dir)' below.

worktree.c done: · line 163

Quoted 4 lines
worktree.c +4 -0
+       for (i = 0; worktrees[i]; i++) {
+               struct worktree *wt = worktrees[i];
+               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
+               wt->is_current = !fspathcmp(git_dir.buf, path.buf);
Quoted 8 lines

Similarly, it looks like 'path' doesn't need to be a strbuf at all since the result of absolute_path() should remain valid long enough for fspathcmp(). It could just be:

    const char *path = absolute_path(...);
    wt->is_current = !fspathcmp(git_dir, path);

But these are very minor; probably not worth a re-roll.

Yeah. I think the use of strbuf is influenced by the code in get_worktrees(). But since this code is now in a separate function, it makes little sense to go with the strbuf hammer. If there's no big change in this series, I'll just do this as a cleanup step in my next series, worktree-move.

Signature · 1 line
Duy
#52 lore raw

Re: [PATCH v3 05/13] worktree.c: mark current worktree

Quoted 7 lines · Duy Nguyen
Quoted 7 lines

Similarly, it looks like 'path' doesn't need to be a strbuf at all since the result of absolute_path() should remain valid long enough for fspathcmp(). It could just be:

    const char *path = absolute_path(...);
    wt->is_current = !fspathcmp(git_dir, path);
Quoted 7 lines
Quoted 1 line

But these are very minor; probably not worth a re-roll.

Yeah. I think the use of strbuf is influenced by the code in get_worktrees(). But since this code is now in a separate function, it makes little sense to go with the strbuf hammer. If there's no big change in this series, I'll just do this as a cleanup step in my next series, worktree-move.

On second thought, why hold patches back, lengthen the worktree-move series and make it a pain to review? I moved a few patches from worktree-move into this series and I took two other out to create nd/error-errno. So I'm going to take more patches out of it, creating two bite-sized series, to be sent shortly.

The first one is purely cleanup (ok, 1/7 is not exactly cleanup)

[1/7] completion: support git-worktree
[2/7] worktree.c: rewrite mark_current_worktree() to avoid [3/7] git-worktree.txt: keep subcommand listing in alphabetical [4/7] worktree.c: use is_dot_or_dotdot()
[5/7] worktree.c: add clear_worktree()
[6/7] worktree: avoid 0{40}, too many zeroes, hard to read [7/7] worktree: simplify prefixing paths

And the second one adds "git worktree lock" and "git worktree unlock". This series is built on top of the first one, it needs 1/7.

[1/5] worktree.c: add find_worktree_by_path()
[2/5] worktree.c: add is_main_worktree()
[3/5] worktree.c: add is_worktree_locked()
[4/5] worktree: add "lock" command
[5/5] worktree: add "unlock" command

After this, worktree-move becomes ~10 patch series.

Signature · 1 line
Duy
#53 lore raw

PATCH 1/7 completion: support git-worktree

---
 contrib/completion/git-completion.bash | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)
contrib/completion/git-completion.bash +23 -0
index 3402475..d3ac391 100644
@@ -2595,6 +2595,29 @@ _git_whatchanged ()
 	_git_log
 }
 
+_git_worktree ()
+{
+	local subcommands="add list prune"
+	local subcommand="$(__git_find_on_cmdline "$subcommands")"
+	if [ -z "$subcommand" ]; then
+		__gitcomp "$subcommands"
+	else
+		case "$subcommand,$cur" in
+		add,--*)
+			__gitcomp "--detach --force"
+			;;
+		list,--*)
+			__gitcomp "--porcelain"
+			;;
+		prune,--*)
+			__gitcomp "--dry-run --expire --verbose"
+			;;
+		*)
+			;;
+		esac
+	fi
+}
+
 __git_main ()
 {
 	local i c=1 command __git_dir
Signature · 1 line
2.8.2.524.g6ff3d78
#54 lore raw

PATCH 2/7 worktree.c: rewrite mark_current_worktree() to avoid strbuf

strbuf is a bit overkill for this function. What we need is call absolute_path() twice and make sure the second call does not destroy the result of the first. One buffer allocation is enough.

---
 worktree.c | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)
worktree.c +7 -9
index 4817d60..6a11611 100644
@@ -153,21 +153,19 @@ done:
 
 static void mark_current_worktree(struct worktree **worktrees)
 {
-	struct strbuf git_dir = STRBUF_INIT;
-	struct strbuf path = STRBUF_INIT;
+	char *git_dir = xstrdup(absolute_path(get_git_dir()));
 	int i;
 
-	strbuf_addstr(&git_dir, absolute_path(get_git_dir()));
 	for (i = 0; worktrees[i]; i++) {
 		struct worktree *wt = worktrees[i];
-		strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
-		wt->is_current = !fspathcmp(git_dir.buf, path.buf);
-		strbuf_reset(&path);
-		if (wt->is_current)
+		const char *wt_git_dir = get_worktree_git_dir(wt);
+
+		if (!fspathcmp(git_dir, absolute_path(wt_git_dir))) {
+			wt->is_current = 1;
 			break;
+		}
 	}
-	strbuf_release(&git_dir);
-	strbuf_release(&path);
+	free(git_dir);
 }
 
 struct worktree **get_worktrees(void)
Signature · 1 line
2.8.2.524.g6ff3d78
#55 lore raw

Re: [PATCH 2/7] worktree.c: rewrite mark_current_worktree() to avoid strbuf

Quoted 1 line · Nguyễn Thái Ngọc Duy

strbuf is a bit overkill for this function. What we need is call

s/is call/is to call/

Quoted 2 lines

absolute_path() twice and make sure the second call does not destroy the result of the first. One buffer allocation is enough.

The patch itself looks good.

Quoted 29 lines

---

worktree.c +7 -9
@@ -153,21 +153,19 @@ done:
 static void mark_current_worktree(struct worktree **worktrees)
 {
-       struct strbuf git_dir = STRBUF_INIT;
-       struct strbuf path = STRBUF_INIT;
+       char *git_dir = xstrdup(absolute_path(get_git_dir()));
        int i;
 
-       strbuf_addstr(&git_dir, absolute_path(get_git_dir()));
        for (i = 0; worktrees[i]; i++) {
                struct worktree *wt = worktrees[i];
-               strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
-               wt->is_current = !fspathcmp(git_dir.buf, path.buf);
-               strbuf_reset(&path);
-               if (wt->is_current)
+               const char *wt_git_dir = get_worktree_git_dir(wt);
+
+               if (!fspathcmp(git_dir, absolute_path(wt_git_dir))) {
+                       wt->is_current = 1;
                        break;
+               }
        }
-       strbuf_release(&git_dir);
-       strbuf_release(&path);
+       free(git_dir);
 }
#56 lore raw

PATCH 3/7 git-worktree.txt: keep subcommand listing in alphabetical order

In reply to Nguyễn Thái Ngọc Duy

This is probably not the best order. But it makes it no-brainer to know where to insert new commands. At some point we might want to reorder at least the synopsis part again, grouping commonly use subcommands together.

---
 Documentation/git-worktree.txt | 10 +++++-----
 builtin/worktree.c             |  2 +-
 2 files changed, 6 insertions(+), 6 deletions(-)
Documentation/git-worktree.txt +5 -5
index c622345..27feff6 100644
@@ -10,8 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]
-'git worktree prune' [-n] [-v] [--expire <expire>]
 'git worktree list' [--porcelain]
+'git worktree prune' [-n] [-v] [--expire <expire>]
 
 DESCRIPTION
 -----------
@@ -54,10 +54,6 @@ If `<branch>` is omitted and neither `-b` nor `-B` nor `--detached` used,
 then, as a convenience, a new branch based at HEAD is created automatically,
 as if `-b $(basename <path>)` was specified.
 
-prune::
-
-Prune working tree information in $GIT_DIR/worktrees.
-
 list::
 
 List details of each worktree.  The main worktree is listed first, followed by
@@ -65,6 +61,10 @@ each of the linked worktrees.  The output details include if the worktree is
 bare, the revision currently checked out, and the branch currently checked out
 (or 'detached HEAD' if none).
 
+prune::
+
+Prune working tree information in $GIT_DIR/worktrees.
+
 OPTIONS
 -------
 
builtin/worktree.c +1 -1
index 12c0af7..bf80111 100644
@@ -13,8 +13,8 @@
 
 static const char * const worktree_usage[] = {
 	N_("git worktree add [<options>] <path> [<branch>]"),
-	N_("git worktree prune [<options>]"),
 	N_("git worktree list [<options>]"),
+	N_("git worktree prune [<options>]"),
 	NULL
 };
 
Signature · 1 line
2.8.2.524.g6ff3d78
#57 lore raw

PATCH 4/7 worktree.c: use is_dot_or_dotdot()

In reply to Nguyễn Thái Ngọc Duy
---
 builtin/worktree.c | 2 +-
 worktree.c         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
builtin/worktree.c +1 -1
index bf80111..aaee0e2 100644
@@ -95,7 +95,7 @@ static void prune_worktrees(void)
 	if (!dir)
 		return;
 	while ((d = readdir(dir)) != NULL) {
-		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+		if (is_dot_or_dotdot(d->d_name))
 			continue;
 		strbuf_reset(&reason);
 		if (!prune_worktree(d->d_name, &reason))
worktree.c +1 -1
index 6a11611..f4a4f38 100644
@@ -187,7 +187,7 @@ struct worktree **get_worktrees(void)
 	if (dir) {
 		while ((d = readdir(dir)) != NULL) {
 			struct worktree *linked = NULL;
-			if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+			if (is_dot_or_dotdot(d->d_name))
 				continue;
 
 			if ((linked = get_linked_worktree(d->d_name))) {
Signature · 1 line
2.8.2.524.g6ff3d78
#58 lore raw

PATCH 5/7 worktree.c: add clear_worktree()

In reply to Nguyễn Thái Ngọc Duy

The use case is keep some worktree and discard the rest of the worktree list.

---
 worktree.c | 14 +++++++++++---
 worktree.h |  5 +++++
 2 files changed, 16 insertions(+), 3 deletions(-)
worktree.c +11 -3
index f4a4f38..335c58f 100644
@@ -5,14 +5,22 @@
 #include "dir.h"
 #include "wt-status.h"
 
+void clear_worktree(struct worktree *wt)
+{
+	if (!wt)
+		return;
+	free(wt->path);
+	free(wt->id);
+	free(wt->head_ref);
+	memset(wt, 0, sizeof(*wt));
+}
+
 void free_worktrees(struct worktree **worktrees)
 {
 	int i = 0;
 
 	for (i = 0; worktrees[i]; i++) {
-		free(worktrees[i]->path);
-		free(worktrees[i]->id);
-		free(worktrees[i]->head_ref);
+		clear_worktree(worktrees[i]);
 		free(worktrees[i]);
 	}
 	free (worktrees);
worktree.h +5 -0
index 1394909..7430a4e 100644
@@ -29,6 +29,11 @@ extern struct worktree **get_worktrees(void);
  */
 extern const char *get_worktree_git_dir(const struct worktree *wt);
 
+/*
+ * Free up the memory for worktree
+ */
+extern void clear_worktree(struct worktree *);
+
 /*
  * Free up the memory for worktree(s)
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#59 lore raw

Re: [PATCH 5/7] worktree.c: add clear_worktree()

Quoted 2 lines · Nguyễn Thái Ngọc Duy

The use case is keep some worktree and discard the rest of the worktree list.

So, you're saying that rather than a client freeing the entire worktree list like this:

    free_worktrees(worktrees);

it might instead want to keep some element ('n') and free all others plus the list itself, like this:

    struct worktree *keep = worktrees[n];
    struct worktree **p = worktrees;
    for (; *p; p++)
        if (*p != keep)
            clear_worktree(*p);
    free(worktrees);

Is that correct?

If so, then doesn't this require the client to have far too intimate knowledge of the internals of free_worktrees(). Or, was your idea that the client would simply leak the 'worktrees' array after freeing the unwanted elements?

In either case, a cleaner approach might be to provide a function for copying a worktree element. Perhaps:

    struct worktree *copy_worktree(const struct worktree *);

or something?

Quoted 50 lines

---

worktree.c +11 -3
index f4a4f38..335c58f 100644
@@ -5,14 +5,22 @@
 #include "dir.h"
 #include "wt-status.h"
 
+void clear_worktree(struct worktree *wt)
+{
+       if (!wt)
+               return;
+       free(wt->path);
+       free(wt->id);
+       free(wt->head_ref);
+       memset(wt, 0, sizeof(*wt));
+}
+
 void free_worktrees(struct worktree **worktrees)
 {
        int i = 0;
 
        for (i = 0; worktrees[i]; i++) {
-               free(worktrees[i]->path);
-               free(worktrees[i]->id);
-               free(worktrees[i]->head_ref);
+               clear_worktree(worktrees[i]);
                free(worktrees[i]);
        }
        free (worktrees);
worktree.h +5 -1
index 1394909..7430a4e 100644
@@ -29,6 +29,11 @@ extern struct worktree **get_worktrees(void);
  */
 extern const char *get_worktree_git_dir(const struct worktree *wt);
 
+/*
+ * Free up the memory for worktree
+ */
+extern void clear_worktree(struct worktree *);
+
 /*
  * Free up the memory for worktree(s)
  */
--

2.8.2.524.g6ff3d78

#60 lore raw

Re: [PATCH 5/7] worktree.c: add clear_worktree()

Quoted 27 lines · Eric Sunshine
Quoted 2 lines · Nguyễn Thái Ngọc Duy

The use case is keep some worktree and discard the rest of the worktree list.

So, you're saying that rather than a client freeing the entire worktree list like this:

    free_worktrees(worktrees);

it might instead want to keep some element ('n') and free all others plus the list itself, like this:

    struct worktree *keep = worktrees[n];
    struct worktree **p = worktrees;
    for (; *p; p++)
        if (*p != keep)
            clear_worktree(*p);
    free(worktrees);

Is that correct?

If so, then doesn't this require the client to have far too intimate knowledge of the internals of free_worktrees(). Or, was your idea that the client would simply leak the 'worktrees' array after freeing the unwanted elements?

Quoted 4 lines

In either case, a cleaner approach might be to provide a function for copying a worktree element. Perhaps:

    struct worktree *copy_worktree(const struct worktree *);

Of course, you'd still need a clear_worktree() or free_worktree() function to dispose of the copy.

#61 lore raw

Re: [PATCH 5/7] worktree.c: add clear_worktree()

In reply to Eric Sunshine
Quoted 20 lines · Eric Sunshine
Quoted 2 lines · Nguyễn Thái Ngọc Duy

The use case is keep some worktree and discard the rest of the worktree list.

So, you're saying that rather than a client freeing the entire worktree list like this:

    free_worktrees(worktrees);

it might instead want to keep some element ('n') and free all others plus the list itself, like this:

    struct worktree *keep = worktrees[n];
    struct worktree **p = worktrees;
    for (; *p; p++)
        if (*p != keep)
            clear_worktree(*p);
    free(worktrees);
Quoted 6 lines

Is that correct?

If so, then doesn't this require the client to have far too intimate knowledge of the internals of free_worktrees(). Or, was your idea that the client would simply leak the 'worktrees' array after freeing the unwanted elements?

I looked back in my trees to see why I needed it in the first place. It turns out my explanation was a lie. I needed it for refactoring builtin/worktree.c:add_worktree(), where I initialized struct worktree separately, not from get_worktrees() [1]. Because it's not clear of the exact use case for this commit, and because that commit I mentioned may take even longer time to be here (it adds "git worktree move --repository", a lot more complex operation than a "git worktree move"), I'll take this commit out of the series for now.

[1] https://github.com/pclouds/git/commit/528d81ce5609b5fbbe1ba193389f7aeecc83d992

Quoted 6 lines

In either case, a cleaner approach might be to provide a function for copying a worktree element. Perhaps:

    struct worktree *copy_worktree(const struct worktree *);

or something?

Signature · 1 line
Duy
#62 lore raw

PATCH 6/7 worktree: avoid 0{40}, too many zeroes, hard to read

In reply to Nguyễn Thái Ngọc Duy
---
 builtin/worktree.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
builtin/worktree.c +1 -1
index aaee0e2..b53f802 100644
@@ -262,7 +262,7 @@ static int add_worktree(const char *path, const char *refname,
 	 */
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
-	write_file(sb.buf, "0000000000000000000000000000000000000000");
+	write_file(sb.buf, sha1_to_hex(null_sha1));
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
 	write_file(sb.buf, "../..");
Signature · 1 line
2.8.2.524.g6ff3d78
#63 lore raw

PATCH 7/7 worktree: simplify prefixing paths

In reply to Nguyễn Thái Ngọc Duy
---
 builtin/worktree.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
builtin/worktree.c +3 -1
index b53f802..f9dac37 100644
@@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)
 	if (ac < 1 || ac > 2)
 		usage_with_options(worktree_usage, options);
 
-	path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
+	path = prefix_filename(prefix, strlen(prefix), av[0]);
 	branch = ac < 2 ? "HEAD" : av[1];
 
 	opts.force_new_branch = !!new_branch_force;
@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 
 	if (ac < 2)
 		usage_with_options(worktree_usage, options);
+	if (!prefix)
+		prefix = "";
 	if (!strcmp(av[1], "add"))
 		return add(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "prune"))
Signature · 1 line
2.8.2.524.g6ff3d78
#64 lore raw

Re: [PATCH 7/7] worktree: simplify prefixing paths

Quoted 2 lines · Nguyễn Thái Ngọc Duy

---

This changes semantics, doesn't it? prefix_filename() seems to do a lot more than just strbuf_vadd("%s%s", prefix, filename); would do.

It may be a good change (e.g. turn '\' into '/' on Windows), but this is way more than "simplify prefixing". It is something else whose effect needs to be explained.

Quoted 25 lines
 builtin/worktree.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
builtin/worktree.c +3 -1
index b53f802..f9dac37 100644
@@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)
 	if (ac < 1 || ac > 2)
 		usage_with_options(worktree_usage, options);
 
-	path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
+	path = prefix_filename(prefix, strlen(prefix), av[0]);
 	branch = ac < 2 ? "HEAD" : av[1];
 
 	opts.force_new_branch = !!new_branch_force;
@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 
 	if (ac < 2)
 		usage_with_options(worktree_usage, options);
+	if (!prefix)
+		prefix = "";
 	if (!strcmp(av[1], "add"))
 		return add(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "prune"))
#65 lore raw

Re: [PATCH 7/7] worktree: simplify prefixing paths

Quoted 11 lines · Junio C Hamano
Quoted 2 lines · Nguyễn Thái Ngọc Duy

---

This changes semantics, doesn't it? prefix_filename() seems to do a lot more than just strbuf_vadd("%s%s", prefix, filename); would do.

It may be a good change (e.g. turn '\' into '/' on Windows), but this is way more than "simplify prefixing". It is something else whose effect needs to be explained.

Quoted 11 lines · Junio C Hamano
Quoted 2 lines · Nguyễn Thái Ngọc Duy

---

This changes semantics, doesn't it? prefix_filename() seems to do a lot more than just strbuf_vadd("%s%s", prefix, filename); would do.

It may be a good change (e.g. turn '\' into '/' on Windows), but this is way more than "simplify prefixing". It is something else whose effect needs to be explained.

I admit I forgot about Windows part in prefix_filename(). For non-Windows code, it's exactly the same behavior. Maybe we should do this to emphasize that prefix_filename() is no-op when pfx_len is zero? The same pattern is used elsewhere too (or I'm spreading it in my local tree..) Unless of course if convert_slashes() is a good thing to always do, then I need to update my commit message (+Johannes for this question).

abspath.c +4 -1
index 2825de8..bf454e0 100644
@@ -160,8 +160,11 @@ const char *absolute_path(const char *path)
 const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
 {
  static struct strbuf path = STRBUF_INIT;
+
+ if (!pfx_len)
+ return arg;
 #ifndef GIT_WINDOWS_NATIVE
- if (!pfx_len || is_absolute_path(arg))
+ if (is_absolute_path(arg))
  return arg;
  strbuf_reset(&path);
  strbuf_add(&path, pfx, pfx_len);
Quoted 26 lines
Quoted 26 lines
 builtin/worktree.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
builtin/worktree.c +3 -1
index b53f802..f9dac37 100644
@@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)
      if (ac < 1 || ac > 2)
              usage_with_options(worktree_usage, options);
 
-     path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
+     path = prefix_filename(prefix, strlen(prefix), av[0]);
      branch = ac < 2 ? "HEAD" : av[1];
 
      opts.force_new_branch = !!new_branch_force;
@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 
      if (ac < 2)
              usage_with_options(worktree_usage, options);
+     if (!prefix)
+             prefix = "";
      if (!strcmp(av[1], "add"))
              return add(ac - 1, av + 1, prefix);
      if (!strcmp(av[1], "prune"))
Signature · 1 line
Duy
#66 lore raw

Re: [PATCH 1/7] completion: support git-worktree

In reply to Nguyễn Thái Ngọc Duy
Quoted 1 line · Nguyễn Thái Ngọc Duy

completion: support git-worktree

See [1] and, especially, [2] for previous attempts...

[1]: http://thread.gmane.org/gmane.comp.version-control.git/274526
[2]: http://thread.gmane.org/gmane.comp.version-control.git/274526/focus=276333

Quoted 38 lines

---

contrib/completion/git-completion.bash +23 -1
index 3402475..d3ac391 100644
@@ -2595,6 +2595,29 @@ _git_whatchanged ()
        _git_log
 }
 
+_git_worktree ()
+{
+       local subcommands="add list prune"
+       local subcommand="$(__git_find_on_cmdline "$subcommands")"
+       if [ -z "$subcommand" ]; then
+               __gitcomp "$subcommands"
+       else
+               case "$subcommand,$cur" in
+               add,--*)
+                       __gitcomp "--detach --force"
+                       ;;
+               list,--*)
+                       __gitcomp "--porcelain"
+                       ;;
+               prune,--*)
+                       __gitcomp "--dry-run --expire --verbose"
+                       ;;
+               *)
+                       ;;
+               esac
+       fi
+}
+
 __git_main ()
 {
        local i c=1 command __git_dir
--

2.8.2.524.g6ff3d78

#67 lore raw

PATCH v2 1/5 worktree.c: add find_worktree_by_path()

In reply to Duy Nguyen

So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. There are of course other options for identifying a worktree, for example by branch or even by internal id. They may be added later if proved useful.

---
 worktree.c | 16 ++++++++++++++++
 worktree.h |  6 ++++++
 2 files changed, 22 insertions(+)
worktree.c +16 -0
index 335c58f..a6d1ad1 100644
@@ -222,6 +222,22 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
+struct worktree *find_worktree_by_path(struct worktree **list,
+				       const char *path_)
+{
+	char *path = xstrdup(real_path(path_));
+	struct worktree *wt = NULL;
+
+	while (*list) {
+		wt = *list++;
+		if (!fspathcmp(path, real_path(wt->path)))
+			break;
+		wt = NULL;
+	}
+	free(path);
+	return wt;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +6 -0
index 7430a4e..7775d1b 100644
@@ -29,6 +29,12 @@ extern struct worktree **get_worktrees(void);
  */
 extern const char *get_worktree_git_dir(const struct worktree *wt);
 
+/*
+ * Search a worktree by its path. Paths are normalized internally.
+ */
+extern struct worktree *find_worktree_by_path(struct worktree **list,
+					      const char *path_);
+
 /*
  * Free up the memory for worktree
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#68 lore raw

PATCH v2 2/5 worktree.c: add is_main_worktree()

Main worktree _is_ different. You can lock a linked worktree but not the main one, for example. Provide an API for checking that.

---
 worktree.c | 5 +++++
 worktree.h | 5 +++++
 2 files changed, 10 insertions(+)
worktree.c +5 -0
index a6d1ad1..37fea3d 100644
@@ -238,6 +238,11 @@ struct worktree *find_worktree_by_path(struct worktree **list,
 	return wt;
 }
 
+int is_main_worktree(const struct worktree *wt)
+{
+	return wt && !wt->id;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +5 -0
index 7775d1b..77a9e09 100644
@@ -35,6 +35,11 @@ extern const char *get_worktree_git_dir(const struct worktree *wt);
 extern struct worktree *find_worktree_by_path(struct worktree **list,
 					      const char *path_);
 
+/*
+ * Return true if the given worktree is the main one.
+ */
+extern int is_main_worktree(const struct worktree *wt);
+
 /*
  * Free up the memory for worktree
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#69 lore raw

Re: [PATCH 2/5] worktree.c: add is_main_worktree()

worktree.c struct worktree *find_worktree_by_path(struct worktree **list, · line 241

Quoted 11 lines · Nguyễn Thái Ngọc Duy

Main worktree _is_ different. You can lock a linked worktree but not the main one, for example. Provide an API for checking that.

---

worktree.c +4 -0
@@ -238,6 +238,11 @@ struct worktree *find_worktree_by_path(struct worktree **list,
+int is_main_worktree(const struct worktree *wt)
+{
+       return wt && !wt->id;
+}

Is or will there ever be a case when it is valid for 'wt' to be NULL? If not, perhaps assert() or die() if given NULL?

#70 lore raw

PATCH v2 3/5 worktree.c: add is_worktree_locked()

In reply to Nguyễn Thái Ngọc Duy

This provides an API for checking if a worktree is locked. We need to check this to avoid double locking a worktree, or try to unlock one when it's not even locked.

---
 worktree.c | 18 ++++++++++++++++++
 worktree.h |  6 ++++++
 2 files changed, 24 insertions(+)
worktree.c +18 -0
index 37fea3d..6805deb 100644
@@ -243,6 +243,24 @@ int is_main_worktree(const struct worktree *wt)
 	return wt && !wt->id;
 }
 
+const char *is_worktree_locked(const struct worktree *wt)
+{
+	static struct strbuf sb = STRBUF_INIT;
+
+	if (!file_exists(git_common_path("worktrees/%s/locked", wt->id)))
+		return NULL;
+
+	strbuf_reset(&sb);
+	if (strbuf_read_file(&sb,
+			     git_common_path("worktrees/%s/locked", wt->id),
+			     0) < 0)
+		die_errno(_("failed to read '%s'"),
+			  git_common_path("worktrees/%s/locked", wt->id));
+
+	strbuf_rtrim(&sb);
+	return sb.buf;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +6 -0
index 77a9e09..12906be 100644
@@ -40,6 +40,12 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,
  */
 extern int is_main_worktree(const struct worktree *wt);
 
+/*
+ * Return the reason string if the given worktree is locked. Return
+ * NULL otherwise.
+ */
+extern const char *is_worktree_locked(const struct worktree *wt);
+
 /*
  * Free up the memory for worktree
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#71 lore raw

Re: [PATCH 3/5] worktree.c: add is_worktree_locked()

Quoted 8 lines · Nguyễn Thái Ngọc Duy

This provides an API for checking if a worktree is locked. We need to check this to avoid double locking a worktree, or try to unlock one when it's not even locked.

---

worktree.c int is_main_worktree(const struct worktree *wt) · line 248

Quoted 6 lines
worktree.c +6 -0
@@ -243,6 +243,24 @@ int is_main_worktree(const struct worktree *wt)
+const char *is_worktree_locked(const struct worktree *wt)
+{
+       static struct strbuf sb = STRBUF_INIT;
+
+       if (!file_exists(git_common_path("worktrees/%s/locked", wt->id)))
+               return NULL;

The git_common_path(...) invocation is textually lengthy and repeated three times in this function. If you instead assign the result to a variable (possibly xstrdup'ing it if needed), then the below strbuf_read_file() would likely fit on one line, thus be easier to read.

worktree.c int is_main_worktree(const struct worktree *wt) · line 256

Quoted 5 lines
worktree.c +5 -0
+
+       strbuf_reset(&sb);
+       if (strbuf_read_file(&sb,
+                            git_common_path("worktrees/%s/locked", wt->id),
+                            0) < 0)

It's too bad that strbuf_read_file() doesn't guarantee anything about 'errno', such as indicating that the file did not exist, in which case the !file_exists() check would not be needed, and a bit of raciness eliminated, but that's outside the scope of this series.

worktree.c int is_main_worktree(const struct worktree *wt) · line 260

Quoted 4 lines
worktree.c +4 -0
+               die_errno(_("failed to read '%s'"),
+                         git_common_path("worktrees/%s/locked", wt->id));
+
+       strbuf_rtrim(&sb);

Since this file is presumably human-editable (historically and at this point in the series) in order to specify the lock reason, should this be doing a full trim() rather than only rtrim()?

worktree.h extern struct worktree *find_worktree_by_path(struct worktree **list, · line 43

Quoted 8 lines
worktree.c +2 -0
+       return sb.buf;
+}
worktree.h +4 -0
@@ -40,6 +40,12 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,
+/*
+ * Return the reason string if the given worktree is locked. Return
+ * NULL otherwise.
+ */

Does this need to mention that the returned "locked reason" string is only guaranteed valid until the next invocation?

Actually, I recall that when I suggested the idea of 'struct worktree' and get_worktrees() to Mike that it would be natural for the structure to have a 'locked' (or 'locked_reason') field, in which case the reason could be stored there instead of in this static strbuf. In fact, my idea at the time was that get_worktrees() would populate that field automatically, rather than having to do so via a separate on-demand function call as in this patch.

worktree.h extern struct worktree *find_worktree_by_path(struct worktree **list, · line 47

Quoted 1 line
worktree.h +1 -0
+extern const char *is_worktree_locked(const struct worktree *wt);

I was wondering if builtin/worktree.c:prune_worktree() should be updated to invoke this new function instead of consulting "worktrees/<id>/locked" manually, but I see that the entire "prune worktrees" functionality in builting/worktree.c first needs to be updated to the get_worktrees() API for that to happen.

#72 lore raw

Re: [PATCH 3/5] worktree.c: add is_worktree_locked()

Quoted 7 lines · Eric Sunshine

Actually, I recall that when I suggested the idea of 'struct worktree' and get_worktrees() to Mike that it would be natural for the structure to have a 'locked' (or 'locked_reason') field, in which case the reason could be stored there instead of in this static strbuf. In fact, my idea at the time was that get_worktrees() would populate that field automatically, rather than having to do so via a separate on-demand function call as in this patch.

I'm keeping this as a separate function for now. I don't like get_worktrees() doing extra work unless it has to. We soon will see the complete picture of "git worktree" then we can merge it back to get_worktrees() if it turns out checking "locked" is the common operation. is_worktree_locked() then may become a thin wrapper to access this "locked" field.

Quoted 7 lines
Quoted 1 line

+extern const char *is_worktree_locked(const struct worktree *wt);

I was wondering if builtin/worktree.c:prune_worktree() should be updated to invoke this new function instead of consulting "worktrees/<id>/locked" manually, but I see that the entire "prune worktrees" functionality in builting/worktree.c first needs to be updated to the get_worktrees() API for that to happen.

I thought about updating prune too. But it is in a bit special situation where it may need to consider invalid (or partly invalid) worktrees as well. So far worktree api is about valid worktrees only if I'm not mistaken and we probably should keep it that way, otherwise all callers may need to check "is this worktree valid" all over the place.

Signature · 1 line
Duy
#73 lore raw

Re: [PATCH 3/5] worktree.c: add is_worktree_locked()

Quoted 9 lines · Duy Nguyen
Quoted 7 lines · Eric Sunshine

Actually, I recall that when I suggested the idea of 'struct worktree' and get_worktrees() to Mike that it would be natural for the structure to have a 'locked' (or 'locked_reason') field, in which case the reason could be stored there instead of in this static strbuf. In fact, my idea at the time was that get_worktrees() would populate that field automatically, rather than having to do so via a separate on-demand function call as in this patch.

Quoted 6 lines

I'm keeping this as a separate function for now. I don't like get_worktrees() doing extra work unless it has to. We soon will see the complete picture of "git worktree" then we can merge it back to get_worktrees() if it turns out checking "locked" is the common operation. is_worktree_locked() then may become a thin wrapper to access this "locked" field.

is_worktree_locked() could also just go away since the 'struct worktree::locked' field would be non-NULL for locked, else NULL.

Quoted 8 lines
Quoted 7 lines
Quoted 1 line

+extern const char *is_worktree_locked(const struct worktree *wt);

I was wondering if builtin/worktree.c:prune_worktree() should be updated to invoke this new function instead of consulting "worktrees/<id>/locked" manually, but I see that the entire "prune worktrees" functionality in builting/worktree.c first needs to be updated to the get_worktrees() API for that to happen.

Quoted 6 lines

I thought about updating prune too. But it is in a bit special situation where it may need to consider invalid (or partly invalid) worktrees as well. So far worktree api is about valid worktrees only if I'm not mistaken and we probably should keep it that way, otherwise all callers may need to check "is this worktree valid" all over the place.

Yes and no. In addition to suggesting to Mike that 'struct worktree' should have a 'locked' field, I also suggested a 'prunable' field which would indicate whether a worktree was a candidate for pruning. In fact, the field would be a 'const char *' where non-NULL would mean prunable and give a reason (one of the reasons already determined by builtin/worktree.c:prune_worktree()). Alternately it could be an enum. Like the 'locked' (or 'lock_reason') field, 'prunable' (or 'prune_reason') is likely the sort of information which should be presented by "git worktree list". And, as with 'locked', I think it makes sense for the 'prunable' field to be populated automatically by get_worktrees().

As for your concern about clients having to take care with valid vs. invalid worktrees, get_worktrees() could be updated to return all worktrees or only valid worktrees (for some definition of "valid"). "git worktree list" is an example of a client which would want to see all worktrees assuming its updated to show 'locked' and 'prunable' status.

#74 lore raw

PATCH v2 4/5 worktree: add "lock" command

In reply to Nguyễn Thái Ngọc Duy
---
 Documentation/git-worktree.txt         | 12 ++++++++--
 builtin/worktree.c                     | 41 ++++++++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  5 ++++-
 t/t2028-worktree-move.sh (new +x)      | 34 ++++++++++++++++++++++++++++
 4 files changed, 89 insertions(+), 3 deletions(-)
 create mode 100755 t/t2028-worktree-move.sh
Documentation/git-worktree.txt +10 -2
index 27feff6..74583c1 100644
@@ -11,6 +11,7 @@ SYNOPSIS
 [verse]
 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]
 'git worktree list' [--porcelain]
+'git worktree lock' [--reason <string>] <path>
 'git worktree prune' [-n] [-v] [--expire <expire>]
 
 DESCRIPTION
@@ -61,6 +62,12 @@ each of the linked worktrees.  The output details include if the worktree is
 bare, the revision currently checked out, and the branch currently checked out
 (or 'detached HEAD' if none).
 
+lock::
+
+When a worktree is locked, it cannot be pruned, moved or deleted. For
+example, if the worktree is on portable device that is not available
+when "git worktree <command>" is executed.
+
 prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
@@ -110,6 +117,9 @@ OPTIONS
 --expire <time>::
 	With `prune`, only expire unused working trees older than <time>.
 
+--reason <string>:
+	An explanation why the worktree is locked.
+
 DETAILS
 -------
 Each linked working tree has a private sub-directory in the repository's
@@ -226,8 +236,6 @@ performed manually, such as:
 - `remove` to remove a linked working tree and its administrative files (and
   warn if the working tree is dirty)
 - `mv` to move or rename a working tree and update its administrative files
-- `lock` to prevent automatic pruning of administrative files (for instance,
-  for a working tree on a portable device)
 
 GIT
 ---
builtin/worktree.c +41 -0

58 lines not on this page · raw message

contrib/completion/git-completion.bash +4 -1
index d3ac391..6c321aa 100644
@@ -2597,7 +2597,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list prune"
+	local subcommands="add list lock prune"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
@@ -2609,6 +2609,9 @@ _git_worktree ()
 		list,--*)
 			__gitcomp "--porcelain"
 			;;
+		lock,--*)
+			__gitcomp "--reason"
+			;;
 		prune,--*)
 			__gitcomp "--dry-run --expire --verbose"
 			;;
t/t2028-worktree-move.sh new file +34 -0
new file mode 100755
index 0000000..97434be
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+test_description='test git worktree move, remove, lock and unlock'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit init &&
+	git worktree add source &&
+	git worktree list --porcelain | grep "^worktree" >actual &&
+	cat <<-EOF >expected &&
+	worktree $TRASH_DIRECTORY
+	worktree $TRASH_DIRECTORY/source
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success 'lock main worktree' '
+	test_must_fail git worktree lock .
+'
+
+test_expect_success 'lock linked worktree' '
+	git worktree lock --reason hahaha source &&
+	echo hahaha >expected &&
+	test_cmp expected .git/worktrees/source/locked
+'
+
+test_expect_success 'lock worktree twice' '
+	test_must_fail git worktree lock source &&
+	echo hahaha >expected &&
+	test_cmp expected .git/worktrees/source/locked
+'
+
+test_done
Signature · 1 line
2.8.2.524.g6ff3d78
#75 lore raw

Re: [PATCH 4/5] worktree: add "lock" command

Quoted 11 lines · Nguyễn Thái Ngọc Duy
builtin/worktree.c +11 -0
+static int lock_worktree(int ac, const char **av, const char *prefix)
+{
+       const char *reason = "", *old_reason;
+       struct option options[] = {
+               OPT_STRING(0, "reason", &reason, N_("string"),
+                          N_("reason for locking")),
+               OPT_END()
+       };
+       struct worktree **worktrees, *wt;
+       struct strbuf dst = STRBUF_INIT;
+

builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 483

Quoted 10 lines
builtin/worktree.c +10 -0
+       ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+       if (ac != 1)
+               usage_with_options(worktree_usage, options);
+
+       strbuf_addstr(&dst, prefix_filename(prefix,
+                                           strlen(prefix),
+                                           av[0]));
+
+       worktrees = get_worktrees();
+       wt = find_worktree_by_path(worktrees, dst.buf);

Oh, I forgot to mention in my previous email that this is leaking 'strbuf dst'. It could be released right here at its point of final use.

Quoted 16 lines
builtin/worktree.c +16 -0
+       if (!wt)
+               die(_("'%s' is not a working directory"), av[0]);
+       if (is_main_worktree(wt))
+               die(_("'%s' is a main working directory"), av[0]);
+
+       old_reason = is_worktree_locked(wt);
+       if (old_reason) {
+               if (*old_reason)
+                       die(_("already locked, reason: %s"), old_reason);
+               die(_("already locked, no reason"));
+       }
+
+       write_file(git_common_path("worktrees/%s/locked", wt->id),
+                  "%s", reason);
+       return 0;
+}
#76 lore raw

Re: [PATCH 4/5] worktree: add "lock" command

In reply to Nguyễn Thái Ngọc Duy

Documentation/git-worktree.txt SYNOPSIS · line 17

Quoted 11 lines · Nguyễn Thái Ngọc Duy

---

Documentation/git-worktree.txt +1 -0
@@ -11,6 +11,7 @@ SYNOPSIS
 [verse]
 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]
 'git worktree list' [--porcelain]
+'git worktree lock' [--reason <string>] <path>
 'git worktree prune' [-n] [-v] [--expire <expire>]
 
 DESCRIPTION

This forgets to update the paragraph in DESCRIPTION which currently talks about manually creating the 'locked' file. Perhaps it can be rewritten like this:

If a linked working tree is stored on a portable device or network share which is not always mounted, you can prevent its administrative files from being pruned by issuing the git worktree lock command, optionally specifying --reason to explain why the working tree is locked.

The DETAILS section might also need a small update. It currently says:

    ...add a file named 'locked'...

but perhaps it should instead say:

...use the git worktree lock command, which adds a file named locked,...

Documentation/git-worktree.txt each of the linked worktrees. The output details include if the worktree is · line 66

Quoted 6 lines
Documentation/git-worktree.txt +5 -0
@@ -61,6 +62,12 @@ each of the linked worktrees.  The output details include if the worktree is
+lock::
+
+When a worktree is locked, it cannot be pruned, moved or deleted. For
+example, if the worktree is on portable device that is not available
+when "git worktree <command>" is executed.

The really important reason for locking a worktree is to prevent its administrative files from being pruned *automatically*. This description doesn't properly emphasize that point.

Also, bc48328 (Documentation/git-worktree: consistently use term "linked working tree", 2015-07-20) comprehensively replaced the term "worktree" with "working tree" and, although some new instances slipped in with bb9c03b (worktree: add 'list' command, 2015-10-08), we should probably avoid adding more.

Perhaps the description of "lock" can be rewritten like this:

If a working tree is on a portable device or network share which is not always mounted, lock it to prevent its administrative files from being pruned automatically. This also prevents it from being moved or deleted. Optionally, specify a reason for the lock with --reason.

I guess preventing it from being deleted when locked is a safety measure?

Documentation/git-worktree.txt OPTIONS · line 121

Quoted 6 lines
Documentation/git-worktree.txt +2 -0
@@ -110,6 +117,9 @@ OPTIONS
 --expire <time>::
        With `prune`, only expire unused working trees older than <time>.
 
+--reason <string>:
+       An explanation why the worktree is locked.

For consistency with other option descriptions, perhaps:

With lock, an explanation ...

though I don't feel strongly about it.

Quoted 24 lines
builtin/worktree.c +22 -0
@@ -459,6 +460,44 @@ static int list(int ac, const char **av, const char *prefix)
+static int lock_worktree(int ac, const char **av, const char *prefix)
+{
+       const char *reason = "", *old_reason;
+       struct option options[] = {
+               OPT_STRING(0, "reason", &reason, N_("string"),
+                          N_("reason for locking")),
+               OPT_END()
+       };
+       struct worktree **worktrees, *wt;
+       struct strbuf dst = STRBUF_INIT;
+
+       ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+       if (ac != 1)
+               usage_with_options(worktree_usage, options);
+
+       strbuf_addstr(&dst, prefix_filename(prefix,
+                                           strlen(prefix),
+                                           av[0]));
+
+       worktrees = get_worktrees();
+       wt = find_worktree_by_path(worktrees, dst.buf);
+       if (!wt)

builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 491

Quoted 10 lines
builtin/worktree.c +10 -0
+               die(_("'%s' is not a working directory"), av[0]);
+       if (is_main_worktree(wt))
+               die(_("'%s' is a main working directory"), av[0]);
+
+       old_reason = is_worktree_locked(wt);
+       if (old_reason) {
+               if (*old_reason)
+                       die(_("already locked, reason: %s"), old_reason);
+               die(_("already locked, no reason"));
+       }

As a convenience, would it make sense to allow a worktree to be re-locked with a different reason rather than erroring out?

Also, the "no reason" in the error message might be confusing. Perhaps it would be better to say merely "already locked" in this case.

builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 499

Quoted 4 lines
builtin/worktree.c +4 -0
+       write_file(git_common_path("worktrees/%s/locked", wt->id),
+                  "%s", reason);
+       return 0;
+}

This is a tangent, but it would be nice for the "git worktree list" command to show whether a worktree is locked, and the reason (if available), both in pretty and porcelain formats. (That was another reason why I suggested to Mike, back when he was adding the "list" command, that 'struct worktree' should have a 'locked' field and get_worktrees() should populate it automatically.)

Quoted 18 lines
t/t2028-worktree-move.sh +16 -0
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+test_description='test git worktree move, remove, lock and unlock'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit init &&
+       git worktree add source &&
+       git worktree list --porcelain | grep "^worktree" >actual &&
+       cat <<-EOF >expected &&
+       worktree $TRASH_DIRECTORY
+       worktree $TRASH_DIRECTORY/source
+       EOF
+       test_cmp expected actual
+'

t/t2028-worktree-move.sh line 26

Quoted 10 lines
t/t2028-worktree-move.sh +10 -0
+
+test_expect_success 'lock main worktree' '
+       test_must_fail git worktree lock .
+'
+
+test_expect_success 'lock linked worktree' '
+       git worktree lock --reason hahaha source &&
+       echo hahaha >expected &&
+       test_cmp expected .git/worktrees/source/locked
+'

Would it make sense to also add tests of locking from within the worktree being locked, and from within a worktree other than the one being locked (and other than the main one)?

Quoted 7 lines
t/t2028-worktree-move.sh +7 -0
+test_expect_success 'lock worktree twice' '
+       test_must_fail git worktree lock source &&
+       echo hahaha >expected &&
+       test_cmp expected .git/worktrees/source/locked
+'
+
+test_done
#77 lore raw

Re: [PATCH 4/5] worktree: add "lock" command

(the answer to rest of your questions is "yes you're right, will fix" or something along that line so I will not quote them here)

Quoted 9 lines · Eric Sunshine

builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 494

Quoted 6 lines
builtin/worktree.c +6 -0
+       old_reason = is_worktree_locked(wt);
+       if (old_reason) {
+               if (*old_reason)
+                       die(_("already locked, reason: %s"), old_reason);
+               die(_("already locked, no reason"));
+       }

As a convenience, would it make sense to allow a worktree to be re-locked with a different reason rather than erroring out?

For now I think the user can just unlock then relock, after examining the previous reason and maybe incorporating it to the new reason. Yes there is a race but for these operations I don't think it really matters. If it's done often enough, we can lift this restriction.

Quoted 11 lines

builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 499

Quoted 4 lines
builtin/worktree.c +4 -0
+       write_file(git_common_path("worktrees/%s/locked", wt->id),
+                  "%s", reason);
+       return 0;
+}

This is a tangent, but it would be nice for the "git worktree list" command to show whether a worktree is locked, and the reason (if available), both in pretty and porcelain formats. (That was another reason why I suggested to Mike, back when he was adding the "list" command, that 'struct worktree' should have a 'locked' field and get_worktrees() should populate it automatically.)

Yes. And a good reason for get_worktrees() to read lock status automatically too. I will not do it right away though or at least until after I'm done with move/remove and the shared config file problem.

Signature · 1 line
Duy
#78 lore raw

Re: [PATCH 4/5] worktree: add "lock" command

Quoted 8 lines · Duy Nguyen
Quoted 7 lines · Eric Sunshine
Quoted 6 lines
builtin/worktree.c +6 -0
+       old_reason = is_worktree_locked(wt);
+       if (old_reason) {
+               if (*old_reason)
+                       die(_("already locked, reason: %s"), old_reason);
+               die(_("already locked, no reason"));
+       }
Quoted 7 lines
Quoted 2 lines

As a convenience, would it make sense to allow a worktree to be re-locked with a different reason rather than erroring out?

For now I think the user can just unlock then relock, after examining the previous reason and maybe incorporating it to the new reason. Yes there is a race but for these operations I don't think it really matters. If it's done often enough, we can lift this restriction.

Agreed. It's easy to imagine someone wanting to change the lock message to correct a typo, but that's likely a rare occurrence, and other conceivable reasons for wanting to change the message are probably even more unusual, thus not worth worrying about now.

Quoted 12 lines
Quoted 11 lines

builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 499

Quoted 4 lines
builtin/worktree.c +4 -0
+       write_file(git_common_path("worktrees/%s/locked", wt->id),
+                  "%s", reason);
+       return 0;
+}

This is a tangent, but it would be nice for the "git worktree list" command to show whether a worktree is locked, and the reason (if available), both in pretty and porcelain formats. (That was another reason why I suggested to Mike, back when he was adding the "list" command, that 'struct worktree' should have a 'locked' field and get_worktrees() should populate it automatically.)

Quoted 4 lines

Yes. And a good reason for get_worktrees() to read lock status automatically too. I will not do it right away though or at least until after I'm done with move/remove and the shared config file problem.

For the record (for future readers of this thread), [1] itemized various useful bits of information that would be handy as fields in 'struct worktree' and populated automatically by get_worktrees(), all of which (I think) could be nice to have in the output of "git worktree list". Excerpt:

For git-worktree commands such as "lock", "mv", "remove", it likely will be nice to allow people to specify the linked worktree not only by path, but also by tag, and possibly even by $(basename $path) if not ambiguous. Therefore, to support such usage, at minimum, I think you also want to show the worktree's tag (d->d_name) in addition to the path.

Other information which would be nice to display for each worktree (possibly controlled by a --verbose flag):

* the checked out branch or detached head
* whether it is locked
- the lock reason (if available)
- and whether the worktree is currently accessible
* whether it can be pruned
- and the prune reason if so

The prune reason could be obtained by factoring out the reason-determination code from worktree.c:prune_worktree() to make it re-usable.

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

#79 lore raw

PATCH v2 5/5 worktree: add "unlock" command

In reply to Nguyễn Thái Ngọc Duy
---
 Documentation/git-worktree.txt         |  5 +++++
 builtin/worktree.c                     | 31 +++++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  2 +-
 t/t2028-worktree-move.sh               | 14 ++++++++++++++
 4 files changed, 51 insertions(+), 1 deletion(-)
Documentation/git-worktree.txt +5 -0
index 74583c1..9ac1129 100644
@@ -13,6 +13,7 @@ SYNOPSIS
 'git worktree list' [--porcelain]
 'git worktree lock' [--reason <string>] <path>
 'git worktree prune' [-n] [-v] [--expire <expire>]
+'git worktree unlock' <path>
 
 DESCRIPTION
 -----------
@@ -72,6 +73,10 @@ prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
 
+unlock::
+
+Unlock a worktree, allowing it to be pruned, moved or deleted.
+
 OPTIONS
 -------
 
builtin/worktree.c +31 -0
index 51fa103..53e5f5a 100644
@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {
 	N_("git worktree list [<options>]"),
 	N_("git worktree lock [<options>] <path>"),
 	N_("git worktree prune [<options>]"),
+	N_("git worktree unlock <path>"),
 	NULL
 };
 
@@ -498,6 +499,34 @@ static int lock_worktree(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static int unlock_worktree(int ac, const char **av, const char *prefix)
+{
+	struct option options[] = {
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+	struct strbuf dst = STRBUF_INIT;
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (ac != 1)
+		usage_with_options(worktree_usage, options);
+
+	strbuf_addstr(&dst, prefix_filename(prefix,
+					    strlen(prefix),
+					    av[0]));
+
+	worktrees = get_worktrees();
+	wt = find_worktree_by_path(worktrees, dst.buf);
+	if (!wt)
+		die(_("'%s' is not a working directory"), av[0]);
+	if (is_main_worktree(wt))
+		die(_("'%s' is a main working directory"), av[0]);
+	if (!is_worktree_locked(wt))
+		die(_("not locked"));
+
+	return unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -516,5 +545,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 		return list(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "lock"))
 		return lock_worktree(ac - 1, av + 1, prefix);
+	if (!strcmp(av[1], "unlock"))
+		return unlock_worktree(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
 }
contrib/completion/git-completion.bash +1 -1
index 6c321aa..db4b0e7 100644
@@ -2597,7 +2597,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list lock prune"
+	local subcommands="add list lock prune unlock"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
t/t2028-worktree-move.sh +14 -0
index 97434be..f4b2816 100755
@@ -31,4 +31,18 @@ test_expect_success 'lock worktree twice' '
 	test_cmp expected .git/worktrees/source/locked
 '
 
+test_expect_success 'unlock main worktree' '
+	test_must_fail git worktree unlock .
+'
+
+test_expect_success 'unlock linked worktree' '
+	git worktree unlock source &&
+	test_path_is_missing .git/worktrees/source/locked
+'
+
+test_expect_success 'unlock worktree twice' '
+	test_must_fail git worktree unlock source &&
+	test_path_is_missing .git/worktrees/source/locked
+'
+
 test_done
Signature · 1 line
2.8.2.524.g6ff3d78
#80 lore raw

Re: [PATCH 5/5] worktree: add "unlock" command

Quoted 12 lines · Nguyễn Thái Ngọc Duy

---

builtin/worktree.c +8 -0
@@ -498,6 +499,34 @@ static int lock_worktree(int ac, const char **av, const char *prefix)
+static int unlock_worktree(int ac, const char **av, const char *prefix)
+{
+       struct option options[] = {
+               OPT_END()
+       };
+       struct worktree **worktrees, *wt;
+       struct strbuf dst = STRBUF_INIT;
+

builtin/worktree.c static int lock_worktree(int ac, const char **av, const char *prefix) · line 516

Quoted 10 lines
builtin/worktree.c +10 -0
+       ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+       if (ac != 1)
+               usage_with_options(worktree_usage, options);
+
+       strbuf_addstr(&dst, prefix_filename(prefix,
+                                           strlen(prefix),
+                                           av[0]));
+
+       worktrees = get_worktrees();
+       wt = find_worktree_by_path(worktrees, dst.buf);

strbuf_release(&dst);

Quoted 9 lines
builtin/worktree.c +9 -0
+       if (!wt)
+               die(_("'%s' is not a working directory"), av[0]);
+       if (is_main_worktree(wt))
+               die(_("'%s' is a main working directory"), av[0]);
+       if (!is_worktree_locked(wt))
+               die(_("not locked"));
+
+       return unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
+}
#81 lore raw

Re: [PATCH 1/5] worktree.c: add find_worktree_by_path()

In reply to Nguyễn Thái Ngọc Duy
Quoted 14 lines · Nguyễn Thái Ngọc Duy

So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. There are of course other options for identifying a worktree, for example by branch or even by internal id. They may be added later if proved useful.

---

worktree.c +5 -0
@@ -222,6 +222,22 @@ const char *get_worktree_git_dir(const struct worktree *wt)
+struct worktree *find_worktree_by_path(struct worktree **list,
+                                      const char *path_)
+{
+       char *path = xstrdup(real_path(path_));
+       struct worktree *wt = NULL;

worktree.c const char *get_worktree_git_dir(const struct worktree *wt) · line 236

Quoted 10 lines
worktree.c +10 -0
+
+       while (*list) {
+               wt = *list++;
+               if (!fspathcmp(path, real_path(wt->path)))
+                       break;
+               wt = NULL;
+       }
+       free(path);
+       return wt;
+}

Very slightly shorter and perhaps idiomatic (but not at all worth a re-roll):

    for (; *list; list++)
        if (!fspathcmp(path, real_path((*list)->path)))
            break;
    free(path);
    return *list;
#82 lore raw

Re: [PATCH v3 05/13] worktree.c: mark current worktree

In reply to Duy Nguyen
Quoted 19 lines · Duy Nguyen

On second thought, why hold patches back, lengthen the worktree-move series and make it a pain to review? I moved a few patches from worktree-move into this series and I took two other out to create nd/error-errno. So I'm going to take more patches out of it, creating two bite-sized series, to be sent shortly.

The first one is purely cleanup (ok, 1/7 is not exactly cleanup)

[1/7] completion: support git-worktree
[2/7] worktree.c: rewrite mark_current_worktree() to avoid [3/7] git-worktree.txt: keep subcommand listing in alphabetical [4/7] worktree.c: use is_dot_or_dotdot()
[5/7] worktree.c: add clear_worktree()
[6/7] worktree: avoid 0{40}, too many zeroes, hard to read [7/7] worktree: simplify prefixing paths

And the second one adds "git worktree lock" and "git worktree unlock". This series is built on top of the first one, it needs 1/7.

Quoted 7 lines

[1/5] worktree.c: add find_worktree_by_path()
[2/5] worktree.c: add is_main_worktree()
[3/5] worktree.c: add is_worktree_locked()
[4/5] worktree: add "lock" command
[5/5] worktree: add "unlock" command

After this, worktree-move becomes ~10 patch series.

Yay. Thanks; will take a look.

#83 lore raw

Re: [PATCH v3 05/13] worktree.c: mark current worktree

In reply to Duy Nguyen
Quoted 8 lines · Duy Nguyen

On second thought, why hold patches back, lengthen the worktree-move series and make it a pain to review? I moved a few patches from worktree-move into this series and I took two other out to create nd/error-errno. So I'm going to take more patches out of it, creating two bite-sized series, to be sent shortly.

The first one is purely cleanup (ok, 1/7 is not exactly cleanup)

Quoted 7 lines

[1/7] completion: support git-worktree
[2/7] worktree.c: rewrite mark_current_worktree() to avoid [3/7] git-worktree.txt: keep subcommand listing in alphabetical [4/7] worktree.c: use is_dot_or_dotdot()
[5/7] worktree.c: add clear_worktree()
[6/7] worktree: avoid 0{40}, too many zeroes, hard to read [7/7] worktree: simplify prefixing paths

Where are these patches designed to apply?

It appears that this depends on something in 'next' (probably nd/worktree-various-heads topic?)

Quoted 11 lines

And the second one adds "git worktree lock" and "git worktree unlock". This series is built on top of the first one, it needs 1/7.

[1/5] worktree.c: add find_worktree_by_path()
[2/5] worktree.c: add is_main_worktree()
[3/5] worktree.c: add is_worktree_locked()
[4/5] worktree: add "lock" command
[5/5] worktree: add "unlock" command

After this, worktree-move becomes ~10 patch series.

#84 lore raw

Re: [PATCH v3 05/13] worktree.c: mark current worktree

Quoted 18 lines · Junio C Hamano
Quoted 8 lines · Duy Nguyen

On second thought, why hold patches back, lengthen the worktree-move series and make it a pain to review? I moved a few patches from worktree-move into this series and I took two other out to create nd/error-errno. So I'm going to take more patches out of it, creating two bite-sized series, to be sent shortly.

The first one is purely cleanup (ok, 1/7 is not exactly cleanup)

Quoted 7 lines

[1/7] completion: support git-worktree
[2/7] worktree.c: rewrite mark_current_worktree() to avoid [3/7] git-worktree.txt: keep subcommand listing in alphabetical [4/7] worktree.c: use is_dot_or_dotdot()
[5/7] worktree.c: add clear_worktree()
[6/7] worktree: avoid 0{40}, too many zeroes, hard to read [7/7] worktree: simplify prefixing paths

Quoted 4 lines

Where are these patches designed to apply?

It appears that this depends on something in 'next' (probably nd/worktree-various-heads topic?)

Yes. Sorry I forgot to mention that. Though if you move 2/7 to nd/worktree-various-heads (and deal with some conflicts in worktree.c) then it may become independent.

Signature · 1 line
Duy
#85 lore raw

v2 cover nd/worktree-cleanup-post-head-protection update

In reply to Duy Nguyen

This is mostly commit message update plus

- dropping "--force" from the completion patch (a comment from Szeder Gabor in a previous attempt of worktree completion support).

- the removal of clear_worktree() patch.

I keep the completion patch anyway even though two versions of it were posted by Eric and Szeder (and didn't get discussed much) because I needed this for new commands and because I didn't aim high. It's meant to provide very basic completion support. Fancy stuff like ref completion can be added later by people who are more experienced in bash completion stuff.

Anyway, interdiff

-- 8< --

contrib/completion/git-completion.bash +1 -1
index d3ac391..951a186 100644
@@ -2604,7 +2604,7 @@ _git_worktree ()
 	else
 		case "$subcommand,$cur" in
 		add,--*)
-			__gitcomp "--detach --force"
+			__gitcomp "--detach"
 			;;
 		list,--*)
 			__gitcomp "--porcelain"
worktree.c +3 -11
index 335c58f..f4a4f38 100644
@@ -5,22 +5,14 @@
 #include "dir.h"
 #include "wt-status.h"
 
-void clear_worktree(struct worktree *wt)
-{
-	if (!wt)
-		return;
-	free(wt->path);
-	free(wt->id);
-	free(wt->head_ref);
-	memset(wt, 0, sizeof(*wt));
-}
-
 void free_worktrees(struct worktree **worktrees)
 {
 	int i = 0;
 
 	for (i = 0; worktrees[i]; i++) {
-		clear_worktree(worktrees[i]);
+		free(worktrees[i]->path);
+		free(worktrees[i]->id);
+		free(worktrees[i]->head_ref);
 		free(worktrees[i]);
 	}
 	free (worktrees);
worktree.h +0 -5
index 7430a4e..1394909 100644
@@ -29,11 +29,6 @@ extern struct worktree **get_worktrees(void);
  */
 extern const char *get_worktree_git_dir(const struct worktree *wt);
 
-/*
- * Free up the memory for worktree
- */
-extern void clear_worktree(struct worktree *);
-
 /*
  * Free up the memory for worktree(s)
  */

-- 8< --

Nguyễn Thái Ngọc Duy (6):
completion: support git-worktree
worktree.c: rewrite mark_current_worktree() to avoid strbuf git-worktree.txt: keep subcommand listing in alphabetical order worktree.c: use is_dot_or_dotdot()
worktree: avoid 0{40}, too many zeroes, hard to read
worktree: simplify prefixing paths

 Documentation/git-worktree.txt         | 10 +++++-----
 builtin/worktree.c                     | 10 ++++++----
 contrib/completion/git-completion.bash | 23 +++++++++++++++++++++++
 worktree.c                             | 18 ++++++++----------
 4 files changed, 42 insertions(+), 19 deletions(-)
Signature · 1 line
2.8.2.524.g6ff3d78
#86 lore raw

PATCH v2 1/6 completion: support git-worktree

This adds bare-bone completion support for git-worktree. More advanced completion (e.g. ref completion in git-worktree-add) can be added later.

--force completion in "worktree add" is left out because that option should be handled with care.

---
 contrib/completion/git-completion.bash | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)
contrib/completion/git-completion.bash +23 -0
index 3402475..951a186 100644
@@ -2595,6 +2595,29 @@ _git_whatchanged ()
 	_git_log
 }
 
+_git_worktree ()
+{
+	local subcommands="add list prune"
+	local subcommand="$(__git_find_on_cmdline "$subcommands")"
+	if [ -z "$subcommand" ]; then
+		__gitcomp "$subcommands"
+	else
+		case "$subcommand,$cur" in
+		add,--*)
+			__gitcomp "--detach"
+			;;
+		list,--*)
+			__gitcomp "--porcelain"
+			;;
+		prune,--*)
+			__gitcomp "--dry-run --expire --verbose"
+			;;
+		*)
+			;;
+		esac
+	fi
+}
+
 __git_main ()
 {
 	local i c=1 command __git_dir
Signature · 1 line
2.8.2.524.g6ff3d78
#87 lore raw

PATCH v2 2/6 worktree.c: rewrite mark_current_worktree() to avoid strbuf

In reply to Nguyễn Thái Ngọc Duy

strbuf is a bit overkill for this function. What we need is to call absolute_path() twice and make sure the second call does not destroy the result of the first. One buffer allocation is enough.

---
 worktree.c | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)
worktree.c +7 -9
index 4817d60..6a11611 100644
@@ -153,21 +153,19 @@ done:
 
 static void mark_current_worktree(struct worktree **worktrees)
 {
-	struct strbuf git_dir = STRBUF_INIT;
-	struct strbuf path = STRBUF_INIT;
+	char *git_dir = xstrdup(absolute_path(get_git_dir()));
 	int i;
 
-	strbuf_addstr(&git_dir, absolute_path(get_git_dir()));
 	for (i = 0; worktrees[i]; i++) {
 		struct worktree *wt = worktrees[i];
-		strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
-		wt->is_current = !fspathcmp(git_dir.buf, path.buf);
-		strbuf_reset(&path);
-		if (wt->is_current)
+		const char *wt_git_dir = get_worktree_git_dir(wt);
+
+		if (!fspathcmp(git_dir, absolute_path(wt_git_dir))) {
+			wt->is_current = 1;
 			break;
+		}
 	}
-	strbuf_release(&git_dir);
-	strbuf_release(&path);
+	free(git_dir);
 }
 
 struct worktree **get_worktrees(void)
Signature · 1 line
2.8.2.524.g6ff3d78
#88 lore raw

PATCH v2 3/6 git-worktree.txt: keep subcommand listing in alphabetical order

In reply to Nguyễn Thái Ngọc Duy

This is probably not the best order. But it makes it no-brainer to know where to insert new commands. At some point we might want to reorder at least the synopsis part again, grouping commonly use subcommands together.

---
 Documentation/git-worktree.txt | 10 +++++-----
 builtin/worktree.c             |  2 +-
 2 files changed, 6 insertions(+), 6 deletions(-)
Documentation/git-worktree.txt +5 -5
index c622345..27feff6 100644
@@ -10,8 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]
-'git worktree prune' [-n] [-v] [--expire <expire>]
 'git worktree list' [--porcelain]
+'git worktree prune' [-n] [-v] [--expire <expire>]
 
 DESCRIPTION
 -----------
@@ -54,10 +54,6 @@ If `<branch>` is omitted and neither `-b` nor `-B` nor `--detached` used,
 then, as a convenience, a new branch based at HEAD is created automatically,
 as if `-b $(basename <path>)` was specified.
 
-prune::
-
-Prune working tree information in $GIT_DIR/worktrees.
-
 list::
 
 List details of each worktree.  The main worktree is listed first, followed by
@@ -65,6 +61,10 @@ each of the linked worktrees.  The output details include if the worktree is
 bare, the revision currently checked out, and the branch currently checked out
 (or 'detached HEAD' if none).
 
+prune::
+
+Prune working tree information in $GIT_DIR/worktrees.
+
 OPTIONS
 -------
 
builtin/worktree.c +1 -1
index 12c0af7..bf80111 100644
@@ -13,8 +13,8 @@
 
 static const char * const worktree_usage[] = {
 	N_("git worktree add [<options>] <path> [<branch>]"),
-	N_("git worktree prune [<options>]"),
 	N_("git worktree list [<options>]"),
+	N_("git worktree prune [<options>]"),
 	NULL
 };
 
Signature · 1 line
2.8.2.524.g6ff3d78
#89 lore raw

PATCH v2 4/6 worktree.c: use is_dot_or_dotdot()

In reply to Nguyễn Thái Ngọc Duy
---
 builtin/worktree.c | 2 +-
 worktree.c         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
builtin/worktree.c +1 -1
index bf80111..aaee0e2 100644
@@ -95,7 +95,7 @@ static void prune_worktrees(void)
 	if (!dir)
 		return;
 	while ((d = readdir(dir)) != NULL) {
-		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+		if (is_dot_or_dotdot(d->d_name))
 			continue;
 		strbuf_reset(&reason);
 		if (!prune_worktree(d->d_name, &reason))
worktree.c +1 -1
index 6a11611..f4a4f38 100644
@@ -187,7 +187,7 @@ struct worktree **get_worktrees(void)
 	if (dir) {
 		while ((d = readdir(dir)) != NULL) {
 			struct worktree *linked = NULL;
-			if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+			if (is_dot_or_dotdot(d->d_name))
 				continue;
 
 			if ((linked = get_linked_worktree(d->d_name))) {
Signature · 1 line
2.8.2.524.g6ff3d78
#90 lore raw

PATCH v2 5/6 worktree: avoid 0{40}, too many zeroes, hard to read

In reply to Nguyễn Thái Ngọc Duy
---
 builtin/worktree.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
builtin/worktree.c +1 -1
index aaee0e2..b53f802 100644
@@ -262,7 +262,7 @@ static int add_worktree(const char *path, const char *refname,
 	 */
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
-	write_file(sb.buf, "0000000000000000000000000000000000000000");
+	write_file(sb.buf, sha1_to_hex(null_sha1));
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
 	write_file(sb.buf, "../..");
Signature · 1 line
2.8.2.524.g6ff3d78
#91 lore raw

PATCH v2 6/6 worktree: simplify prefixing paths

In reply to Nguyễn Thái Ngọc Duy

This also makes slash conversion always happen on Windows (a side effect of prefix_filename). Which is a good thing.

---
 builtin/worktree.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
builtin/worktree.c +3 -1
index b53f802..f9dac37 100644
@@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)
 	if (ac < 1 || ac > 2)
 		usage_with_options(worktree_usage, options);
 
-	path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
+	path = prefix_filename(prefix, strlen(prefix), av[0]);
 	branch = ac < 2 ? "HEAD" : av[1];
 
 	opts.force_new_branch = !!new_branch_force;
@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 
 	if (ac < 2)
 		usage_with_options(worktree_usage, options);
+	if (!prefix)
+		prefix = "";
 	if (!strcmp(av[1], "add"))
 		return add(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "prune"))
Signature · 1 line
2.8.2.524.g6ff3d78
#92 lore raw

Re: [PATCH v2 6/6] worktree: simplify prefixing paths

Quoted 2 lines · Nguyễn Thái Ngọc Duy

This also makes slash conversion always happen on Windows (a side effect of prefix_filename). Which is a good thing.

Selling this patch as a mere simplification seems misguided (especially as it's subjective whether this is indeed simpler). I'd find it more easy to buy this change if it was described primarily as benefiting Windows, in the same vein as[1].

More below...

[1]: http://git.661346.n2.nabble.com/PATCH-Do-not-skip-prefix-filename-when-there-is-no-prefix-tp7657038.html

Quoted 13 lines

---

builtin/worktree.c +1 -1
@@ -337,7 +337,7 @@ static int add(int ac, const char **av, const char *prefix)
        if (ac < 1 || ac > 2)
                usage_with_options(worktree_usage, options);
 
-       path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
+       path = prefix_filename(prefix, strlen(prefix), av[0]);
        branch = ac < 2 ? "HEAD" : av[1];
 
        opts.force_new_branch = !!new_branch_force;

builtin/worktree.c int cmd_worktree(int ac, const char **av, const char *prefix) · line 471

Quoted 5 lines
builtin/worktree.c +2 -0
@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 
        if (ac < 2)
                usage_with_options(worktree_usage, options);
+       if (!prefix)
+               prefix = "";

Considering that all the other existing git-worktree subcommands already handle NULL prefix acceptably, it makes me a bit uncomfortable that this "fix", which could be local to add(), leaks into all the other subcommands, thus making the assumption that they (or other new subcommands) will never care about the distinction between NULL and "".

Not a big objection; just a minor niggle, probably not worth a re-roll.

Quoted 3 lines
builtin/worktree.c +0 -0
        if (!strcmp(av[1], "add"))
                return add(ac - 1, av + 1, prefix);
        if (!strcmp(av[1], "prune"))
#93 lore raw

Re: [PATCH v2 6/6] worktree: simplify prefixing paths

Quoted 14 lines · Eric Sunshine

builtin/worktree.c int cmd_worktree(int ac, const char **av, const char *prefix) · line 470

Quoted 5 lines · Nguyễn Thái Ngọc Duy
builtin/worktree.c +2 -0
@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
        if (ac < 2)
                usage_with_options(worktree_usage, options);
+       if (!prefix)
+               prefix = "";

Considering that all the other existing git-worktree subcommands already handle NULL prefix acceptably, it makes me a bit uncomfortable that this "fix", which could be local to add(), leaks into all the other subcommands, thus making the assumption that they (or other new subcommands) will never care about the distinction between NULL and "".

Quoted 1 line

Not a big objection; just a minor niggle, probably not worth a re-roll.

Okay, I see that you're taking advantage of the prefix="" in patch 5/6, as well, so it's not benefitting only add().

#94 lore raw

Re: [PATCH v2 6/6] worktree: simplify prefixing paths

Quoted 15 lines · Eric Sunshine
Quoted 14 lines · Eric Sunshine

builtin/worktree.c int cmd_worktree(int ac, const char **av, const char *prefix) · line 470

Quoted 5 lines · Nguyễn Thái Ngọc Duy
builtin/worktree.c +2 -0
@@ -467,6 +467,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
        if (ac < 2)
                usage_with_options(worktree_usage, options);
+       if (!prefix)
+               prefix = "";

Considering that all the other existing git-worktree subcommands already handle NULL prefix acceptably, it makes me a bit uncomfortable that this "fix", which could be local to add(), leaks into all the other subcommands, thus making the assumption that they (or other new subcommands) will never care about the distinction between NULL and "".

Quoted 4 lines
Quoted 1 line

Not a big objection; just a minor niggle, probably not worth a re-roll.

Okay, I see that you're taking advantage of the prefix="" in patch 5/6, as well, so it's not benefitting only add().

Yep. 'add' so far is the only command taking paths. The next four, lock, unlock, move and delete, all deal with paths and need to deal with NULL prefix otherwise.

Signature · 1 line
Duy
#95 lore raw

Re: [PATCH v2 0/6] nd/worktree-cleanup-post-head-protection update

In reply to Nguyễn Thái Ngọc Duy
Quoted 5 lines · Nguyễn Thái Ngọc Duy

This is mostly commit message update plus

- dropping "--force" from the completion patch (a comment from Szeder Gabor in a previous attempt of worktree completion support).

Quoted 8 lines

- the removal of clear_worktree() patch.

I keep the completion patch anyway even though two versions of it were posted by Eric and Szeder (and didn't get discussed much) because I needed this for new commands and because I didn't aim high. It's meant to provide very basic completion support. Fancy stuff like ref completion can be added later by people who are more experienced in bash completion stuff.

Thanks. Dropping the clear_worktree() patch does indeed seem like a good idea. I've re-read the entire series, and aside from my minor comments on 6/6, everything looks fine and the series is:

Reviewed by: Eric Sunshine <[email protected]>

#96 lore raw

v2 cover worktree lock/unlock

In reply to Duy Nguyen

This should address all of Eric's comments (thanks!). An extra change I made is free_worktrees() at the end of {,un}lock_worktree() to avoid leaking. This series depends on nd/worktree-cleanup-post-head-protection.

Interdiff
-- 8< --

Documentation/git-worktree.txt +10 -8
index 9ac1129..e0f2605 100644
@@ -40,9 +40,8 @@ section "DETAILS" for more information.
 
 If a linked working tree is stored on a portable device or network share
 which is not always mounted, you can prevent its administrative files from
-being pruned by creating a file named 'locked' alongside the other
-administrative files, optionally containing a plain text reason that
-pruning should be suppressed. See section "DETAILS" for more information.
+being pruned by issuing the `git worktree lock` command, optionally
+specifying `--reason` to explain why the working tree is locked.
 
 COMMANDS
 --------
@@ -65,9 +64,11 @@ bare, the revision currently checked out, and the branch currently checked out
 
 lock::
 
-When a worktree is locked, it cannot be pruned, moved or deleted. For
-example, if the worktree is on portable device that is not available
-when "git worktree <command>" is executed.
+If a working tree is on a portable device or network share which
+is not always mounted, lock it to prevent its administrative
+files from being pruned automatically. This also prevents it from
+being moved or deleted. Optionally, specify a reason for the lock
+with `--reason`.
 
 prune::
 
@@ -123,7 +124,7 @@ OPTIONS
 	With `prune`, only expire unused working trees older than <time>.
 
 --reason <string>:
-	An explanation why the worktree is locked.
+	With `lock`, an explanation why the worktree is locked.
 
 DETAILS
 -------
@@ -165,7 +166,8 @@ instead.
 
 To prevent a $GIT_DIR/worktrees entry 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
+entry's working tree is stored on a portable device), use the
+`git worktree lock` comamnd, which adds 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
builtin/worktree.c +8 -3
index 53e5f5a..da9f771 100644
@@ -482,6 +482,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix)
 
 	worktrees = get_worktrees();
 	wt = find_worktree_by_path(worktrees, dst.buf);
+	strbuf_release(&dst);
 	if (!wt)
 		die(_("'%s' is not a working directory"), av[0]);
 	if (is_main_worktree(wt))
@@ -491,11 +492,12 @@ static int lock_worktree(int ac, const char **av, const char *prefix)
 	if (old_reason) {
 		if (*old_reason)
 			die(_("already locked, reason: %s"), old_reason);
-		die(_("already locked, no reason"));
+		die(_("already locked"));
 	}
 
 	write_file(git_common_path("worktrees/%s/locked", wt->id),
 		   "%s", reason);
+	free_worktrees(worktrees);
 	return 0;
 }
 
@@ -506,6 +508,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)
 	};
 	struct worktree **worktrees, *wt;
 	struct strbuf dst = STRBUF_INIT;
+	int ret;
 
 	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
 	if (ac != 1)
@@ -517,14 +520,16 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)
 
 	worktrees = get_worktrees();
 	wt = find_worktree_by_path(worktrees, dst.buf);
+	strbuf_release(&dst);
 	if (!wt)
 		die(_("'%s' is not a working directory"), av[0]);
 	if (is_main_worktree(wt))
 		die(_("'%s' is a main working directory"), av[0]);
 	if (!is_worktree_locked(wt))
 		die(_("not locked"));
-
-	return unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
+	ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
+	free_worktrees(worktrees);
+	return ret;
 }
 
 int cmd_worktree(int ac, const char **av, const char *prefix)
t/t2028-worktree-move.sh +20 -0
index f4b2816..1927537 100755
@@ -25,12 +25,32 @@ test_expect_success 'lock linked worktree' '
 	test_cmp expected .git/worktrees/source/locked
 '
 
+test_expect_success 'lock linked worktree from another worktree' '
+	rm .git/worktrees/source/locked &&
+	git worktree add elsewhere &&
+	(
+		cd elsewhere &&
+		git worktree lock --reason hahaha ../source
+	) &&
+	echo hahaha >expected &&
+	test_cmp expected .git/worktrees/source/locked
+'
+
 test_expect_success 'lock worktree twice' '
 	test_must_fail git worktree lock source &&
 	echo hahaha >expected &&
 	test_cmp expected .git/worktrees/source/locked
 '
 
+test_expect_success 'lock worktree twice (from the locked worktree)' '
+	(
+		cd source &&
+		test_must_fail git worktree lock .
+	) &&
+	echo hahaha >expected &&
+	test_cmp expected .git/worktrees/source/locked
+'
+
 test_expect_success 'unlock main worktree' '
 	test_must_fail git worktree unlock .
 '
worktree.c +14 -15
index a9cfbb3..cb30af3 100644
@@ -218,38 +218,37 @@ struct worktree *find_worktree_by_path(struct worktree **list,
 				       const char *path_)
 {
 	char *path = xstrdup(real_path(path_));
-	struct worktree *wt = NULL;
 
-	while (*list) {
-		wt = *list++;
-		if (!fspathcmp(path, real_path(wt->path)))
+	for (; *list; list++)
+		if (!fspathcmp(path, real_path((*list)->path)))
 			break;
-		wt = NULL;
-	}
 	free(path);
-	return wt;
+	return *list;
 }
 
 int is_main_worktree(const struct worktree *wt)
 {
-	return wt && !wt->id;
+	return !wt->id;
 }
 
 const char *is_worktree_locked(const struct worktree *wt)
 {
 	static struct strbuf sb = STRBUF_INIT;
+	struct strbuf path = STRBUF_INIT;
+
+	strbuf_git_common_path(&path, "worktrees/%s/locked", wt->id);
 
-	if (!file_exists(git_common_path("worktrees/%s/locked", wt->id)))
+	if (!file_exists(path.buf)) {
+		strbuf_release(&path);
 		return NULL;
+	}
 
 	strbuf_reset(&sb);
-	if (strbuf_read_file(&sb,
-			     git_common_path("worktrees/%s/locked", wt->id),
-			     0) < 0)
-		die_errno(_("failed to read '%s'"),
-			  git_common_path("worktrees/%s/locked", wt->id));
+	if (strbuf_read_file(&sb, path.buf, 0) < 0)
+		die_errno(_("failed to read '%s'"), path.buf);
+	strbuf_release(&path);
 
-	strbuf_rtrim(&sb);
+	strbuf_trim(&sb);
 	return sb.buf;
 }
 
worktree.h +1 -2
index 19a6790..3a780c2 100644
@@ -42,7 +42,7 @@ extern int is_main_worktree(const struct worktree *wt);
 
 /*
  * Return the reason string if the given worktree is locked. Return
- * NULL otherwise.
+ * NULL otherwise. Return value is only valid until the next invocation.
  */
 extern const char *is_worktree_locked(const struct worktree *wt);
-- 8< --

Nguyễn Thái Ngọc Duy (5):
worktree.c: add find_worktree_by_path()
worktree.c: add is_main_worktree()
worktree.c: add is_worktree_locked()
worktree: add "lock" command
worktree: add "unlock" command

 Documentation/git-worktree.txt         | 27 +++++++++---
 builtin/worktree.c                     | 77 ++++++++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  5 ++-
 t/t2028-worktree-move.sh (new +x)      | 68 ++++++++++++++++++++++++++++++
 worktree.c                             | 38 +++++++++++++++++
 worktree.h                             | 17 ++++++++
 6 files changed, 225 insertions(+), 7 deletions(-)
 create mode 100755 t/t2028-worktree-move.sh
Signature · 1 line
2.8.2.524.g6ff3d78
#97 lore raw

PATCH v2 1/5 worktree.c: add find_worktree_by_path()

So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. There are of course other options for identifying a worktree, for example by branch or even by internal id. They may be added later if proved useful.

---
 worktree.c | 12 ++++++++++++
 worktree.h |  6 ++++++
 2 files changed, 18 insertions(+)
worktree.c +12 -0
index f4a4f38..c5d45a6 100644
@@ -214,6 +214,18 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
+struct worktree *find_worktree_by_path(struct worktree **list,
+				       const char *path_)
+{
+	char *path = xstrdup(real_path(path_));
+
+	for (; *list; list++)
+		if (!fspathcmp(path, real_path((*list)->path)))
+			break;
+	free(path);
+	return *list;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +6 -0
index 1394909..91627fd 100644
@@ -29,6 +29,12 @@ extern struct worktree **get_worktrees(void);
  */
 extern const char *get_worktree_git_dir(const struct worktree *wt);
 
+/*
+ * Search a worktree by its path. Paths are normalized internally.
+ */
+extern struct worktree *find_worktree_by_path(struct worktree **list,
+					      const char *path_);
+
 /*
  * Free up the memory for worktree(s)
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#98 lore raw

Re: [PATCH v2 1/5] worktree.c: add find_worktree_by_path()

Quoted 10 lines · Nguyễn Thái Ngọc Duy

So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. There are of course other options for identifying a worktree, for example by branch or even by internal id. They may be added later if proved useful.

---

worktree.h extern struct worktree **get_worktrees(void); · line 34

Quoted 6 lines
worktree.h +6 -0
@@ -29,6 +29,12 @@ extern struct worktree **get_worktrees(void);
+/*
+ * Search a worktree by its path. Paths are normalized internally.
+ */
+extern struct worktree *find_worktree_by_path(struct worktree **list,
+                                             const char *path_);
+

Sorry for not noticing last time, but I'd probably have named this argument 'path' rather than 'path_' here in the header.

Not worth a re-roll, of course.

#99 lore raw

Re: [PATCH v2 1/5] worktree.c: add find_worktree_by_path()

In reply to Nguyễn Thái Ngọc Duy
Quoted 4 lines · Nguyễn Thái Ngọc Duy

So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. There are of course other options for identifying a worktree, for example by branch or even by internal id. They may be added later if proved useful.

Beyond the above methods for specifying a worktree, [1] adds $(basename $path) as a possibility if not ambiguous. Excerpt:

For git-worktree commands such as "lock", "mv", "remove", it likely will be nice to allow people to specify the linked worktree not only by path, but also by tag, and possibly even by $(basename $path) if not ambiguous.

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

Quoted 2 lines
#100 lore raw

Re: [PATCH v2 1/5] worktree.c: add find_worktree_by_path()

Quoted 6 lines · Eric Sunshine
Quoted 4 lines · Nguyễn Thái Ngọc Duy

So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. There are of course other options for identifying a worktree, for example by branch or even by internal id. They may be added later if proved useful.

Quoted 7 lines

Beyond the above methods for specifying a worktree, [1] adds $(basename $path) as a possibility if not ambiguous. Excerpt:

For git-worktree commands such as "lock", "mv", "remove", it likely will be nice to allow people to specify the linked worktree not only by path, but also by tag, and possibly even by $(basename $path) if not ambiguous.

Thanks. This changes things. Turns out my "find_by_path" API cannot be easily extended to support identifying worktrees some other way. I will need to pass the command line argument as-is, so that this "find_worktree" (by any means) has enough info to decide (especially about ambiguation).

Signature · 1 line
Duy
#101 lore raw

PATCH v2 2/5 worktree.c: add is_main_worktree()

In reply to Nguyễn Thái Ngọc Duy

Main worktree _is_ different. You can lock a linked worktree but not the main one, for example. Provide an API for checking that.

---
 worktree.c | 5 +++++
 worktree.h | 5 +++++
 2 files changed, 10 insertions(+)
worktree.c +5 -0
index c5d45a6..6b3015b 100644
@@ -226,6 +226,11 @@ struct worktree *find_worktree_by_path(struct worktree **list,
 	return *list;
 }
 
+int is_main_worktree(const struct worktree *wt)
+{
+	return !wt->id;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +5 -0
index 91627fd..39f1aa2 100644
@@ -35,6 +35,11 @@ extern const char *get_worktree_git_dir(const struct worktree *wt);
 extern struct worktree *find_worktree_by_path(struct worktree **list,
 					      const char *path_);
 
+/*
+ * Return true if the given worktree is the main one.
+ */
+extern int is_main_worktree(const struct worktree *wt);
+
 /*
  * Free up the memory for worktree(s)
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#102 lore raw

PATCH v2 3/5 worktree.c: add is_worktree_locked()

In reply to Nguyễn Thái Ngọc Duy

This provides an API for checking if a worktree is locked. We need to check this to avoid double locking a worktree, or try to unlock one when it's not even locked.

---
 worktree.c | 21 +++++++++++++++++++++
 worktree.h |  6 ++++++
 2 files changed, 27 insertions(+)
worktree.c +21 -0
index 6b3015b..cb30af3 100644
@@ -231,6 +231,27 @@ int is_main_worktree(const struct worktree *wt)
 	return !wt->id;
 }
 
+const char *is_worktree_locked(const struct worktree *wt)
+{
+	static struct strbuf sb = STRBUF_INIT;
+	struct strbuf path = STRBUF_INIT;
+
+	strbuf_git_common_path(&path, "worktrees/%s/locked", wt->id);
+
+	if (!file_exists(path.buf)) {
+		strbuf_release(&path);
+		return NULL;
+	}
+
+	strbuf_reset(&sb);
+	if (strbuf_read_file(&sb, path.buf, 0) < 0)
+		die_errno(_("failed to read '%s'"), path.buf);
+	strbuf_release(&path);
+
+	strbuf_trim(&sb);
+	return sb.buf;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +6 -0
index 39f1aa2..3a780c2 100644
@@ -40,6 +40,12 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,
  */
 extern int is_main_worktree(const struct worktree *wt);
 
+/*
+ * Return the reason string if the given worktree is locked. Return
+ * NULL otherwise. Return value is only valid until the next invocation.
+ */
+extern const char *is_worktree_locked(const struct worktree *wt);
+
 /*
  * Free up the memory for worktree(s)
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#103 lore raw

PATCH v2 4/5 worktree: add "lock" command

In reply to Nguyễn Thái Ngọc Duy
---
 Documentation/git-worktree.txt         | 22 ++++++++++----
 builtin/worktree.c                     | 43 +++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  5 +++-
 t/t2028-worktree-move.sh (new +x)      | 54 ++++++++++++++++++++++++++++++++++
 4 files changed, 117 insertions(+), 7 deletions(-)
 create mode 100755 t/t2028-worktree-move.sh
Documentation/git-worktree.txt +16 -6
index 27feff6..004d770 100644
@@ -11,6 +11,7 @@ SYNOPSIS
 [verse]
 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]
 'git worktree list' [--porcelain]
+'git worktree lock' [--reason <string>] <path>
 'git worktree prune' [-n] [-v] [--expire <expire>]
 
 DESCRIPTION
@@ -38,9 +39,8 @@ section "DETAILS" for more information.
 
 If a linked working tree is stored on a portable device or network share
 which is not always mounted, you can prevent its administrative files from
-being pruned by creating a file named 'locked' alongside the other
-administrative files, optionally containing a plain text reason that
-pruning should be suppressed. See section "DETAILS" for more information.
+being pruned by issuing the `git worktree lock` command, optionally
+specifying `--reason` to explain why the working tree is locked.
 
 COMMANDS
 --------
@@ -61,6 +61,14 @@ each of the linked worktrees.  The output details include if the worktree is
 bare, the revision currently checked out, and the branch currently checked out
 (or 'detached HEAD' if none).
 
+lock::
+
+If a working tree is on a portable device or network share which
+is not always mounted, lock it to prevent its administrative
+files from being pruned automatically. This also prevents it from
+being moved or deleted. Optionally, specify a reason for the lock
+with `--reason`.
+
 prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
@@ -110,6 +118,9 @@ OPTIONS
 --expire <time>::
 	With `prune`, only expire unused working trees older than <time>.
 
+--reason <string>:
+	With `lock`, an explanation why the worktree is locked.
+
 DETAILS
 -------
 Each linked working tree has a private sub-directory in the repository's
@@ -150,7 +161,8 @@ instead.
 
 To prevent a $GIT_DIR/worktrees entry 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
+entry's working tree is stored on a portable device), use the
+`git worktree lock` comamnd, which adds 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
@@ -226,8 +238,6 @@ performed manually, such as:
 - `remove` to remove a linked working tree and its administrative files (and
   warn if the working tree is dirty)
 - `mv` to move or rename a working tree and update its administrative files
-- `lock` to prevent automatic pruning of administrative files (for instance,
-  for a working tree on a portable device)
 
 GIT
 ---
builtin/worktree.c +43 -0

60 lines not on this page · raw message

contrib/completion/git-completion.bash +4 -1
index 951a186..f88727d 100644
@@ -2597,7 +2597,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list prune"
+	local subcommands="add list lock prune"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
@@ -2609,6 +2609,9 @@ _git_worktree ()
 		list,--*)
 			__gitcomp "--porcelain"
 			;;
+		lock,--*)
+			__gitcomp "--reason"
+			;;
 		prune,--*)
 			__gitcomp "--dry-run --expire --verbose"
 			;;
t/t2028-worktree-move.sh new file +54 -0

54 lines not on this page · raw message

Signature · 1 line
2.8.2.524.g6ff3d78
#104 lore raw

Re: [PATCH v2 4/5] worktree: add "lock" command

Documentation/git-worktree.txt instead. · line 165

Quoted 11 lines · Nguyễn Thái Ngọc Duy

---

Documentation/git-worktree.txt +2 -1
@@ -150,7 +161,8 @@ instead.
 
 To prevent a $GIT_DIR/worktrees entry 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
+entry's working tree is stored on a portable device), use the
+`git worktree lock` comamnd, which adds a file named

s/comamnd/command/

t/t2028-worktree-move.sh line 6

Quoted 11 lines
Documentation/git-worktree.txt +0 -0
 '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
t/t2028-worktree-move.sh +6 -0
@@ -0,0 +1,54 @@
+test_expect_success 'lock linked worktree from another worktree' '
+       rm .git/worktrees/source/locked &&
+       git worktree add elsewhere &&
+       (
+               cd elsewhere &&
+               git worktree lock --reason hahaha ../source

Could use "git -C elsewhere worktree lock ..." and drop the subshell, but not worth a re-roll.

t/t2028-worktree-move.sh line 48

Quoted 9 lines
t/t2028-worktree-move.sh +9 -0
+       ) &&
+       echo hahaha >expected &&
+       test_cmp expected .git/worktrees/source/locked
+'
+
+test_expect_success 'lock worktree twice (from the locked worktree)' '
+       (
+               cd source &&
+               test_must_fail git worktree lock .

Likewise, -C.

Quoted 4 lines
t/t2028-worktree-move.sh +4 -0
+       ) &&
+       echo hahaha >expected &&
+       test_cmp expected .git/worktrees/source/locked
+'
#105 lore raw

PATCH v2 5/5 worktree: add "unlock" command

In reply to Nguyễn Thái Ngọc Duy
---
 Documentation/git-worktree.txt         |  5 +++++
 builtin/worktree.c                     | 34 ++++++++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  2 +-
 t/t2028-worktree-move.sh               | 14 ++++++++++++++
 4 files changed, 54 insertions(+), 1 deletion(-)
Documentation/git-worktree.txt +5 -0
index 004d770..e0f2605 100644
@@ -13,6 +13,7 @@ SYNOPSIS
 'git worktree list' [--porcelain]
 'git worktree lock' [--reason <string>] <path>
 'git worktree prune' [-n] [-v] [--expire <expire>]
+'git worktree unlock' <path>
 
 DESCRIPTION
 -----------
@@ -73,6 +74,10 @@ prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
 
+unlock::
+
+Unlock a worktree, allowing it to be pruned, moved or deleted.
+
 OPTIONS
 -------
 
builtin/worktree.c +34 -0
index 9008dcd..da9f771 100644
@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {
 	N_("git worktree list [<options>]"),
 	N_("git worktree lock [<options>] <path>"),
 	N_("git worktree prune [<options>]"),
+	N_("git worktree unlock <path>"),
 	NULL
 };
 
@@ -500,6 +501,37 @@ static int lock_worktree(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static int unlock_worktree(int ac, const char **av, const char *prefix)
+{
+	struct option options[] = {
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+	struct strbuf dst = STRBUF_INIT;
+	int ret;
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (ac != 1)
+		usage_with_options(worktree_usage, options);
+
+	strbuf_addstr(&dst, prefix_filename(prefix,
+					    strlen(prefix),
+					    av[0]));
+
+	worktrees = get_worktrees();
+	wt = find_worktree_by_path(worktrees, dst.buf);
+	strbuf_release(&dst);
+	if (!wt)
+		die(_("'%s' is not a working directory"), av[0]);
+	if (is_main_worktree(wt))
+		die(_("'%s' is a main working directory"), av[0]);
+	if (!is_worktree_locked(wt))
+		die(_("not locked"));
+	ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
+	free_worktrees(worktrees);
+	return ret;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -518,5 +550,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 		return list(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "lock"))
 		return lock_worktree(ac - 1, av + 1, prefix);
+	if (!strcmp(av[1], "unlock"))
+		return unlock_worktree(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
 }
contrib/completion/git-completion.bash +1 -1
index f88727d..0e3841d 100644
@@ -2597,7 +2597,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list lock prune"
+	local subcommands="add list lock prune unlock"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
t/t2028-worktree-move.sh +14 -0
index aeac848..1927537 100755
@@ -51,4 +51,18 @@ test_expect_success 'lock worktree twice (from the locked worktree)' '
 	test_cmp expected .git/worktrees/source/locked
 '
 
+test_expect_success 'unlock main worktree' '
+	test_must_fail git worktree unlock .
+'
+
+test_expect_success 'unlock linked worktree' '
+	git worktree unlock source &&
+	test_path_is_missing .git/worktrees/source/locked
+'
+
+test_expect_success 'unlock worktree twice' '
+	test_must_fail git worktree unlock source &&
+	test_path_is_missing .git/worktrees/source/locked
+'
+
 test_done
Signature · 1 line
2.8.2.524.g6ff3d78
#106 lore raw

Re: [PATCH v2 5/5] worktree: add "unlock" command

Quoted 5 lines · Nguyễn Thái Ngọc Duy

---

builtin/worktree.c +1 -0
@@ -500,6 +501,37 @@ static int lock_worktree(int ac, const char **av, const char *prefix)
+static int unlock_worktree(int ac, const char **av, const char *prefix)

builtin/worktree.c static int lock_worktree(int ac, const char **av, const char *prefix) · line 511

Quoted 10 lines
builtin/worktree.c +10 -0
+{
+       [...]
+       wt = find_worktree_by_path(worktrees, dst.buf);
+       strbuf_release(&dst);
+       if (!wt)
+               die(_("'%s' is not a working directory"), av[0]);
+       if (is_main_worktree(wt))
+               die(_("'%s' is a main working directory"), av[0]);
+       if (!is_worktree_locked(wt))
+               die(_("not locked"));

Could be _("'%s' is not locked"), but not worth a re-roll.

Quoted 4 lines
builtin/worktree.c +4 -0
+       ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
+       free_worktrees(worktrees);
+       return ret;
+}
Reviewed-by on the v2 series
#107 lore raw

Re: [PATCH v2 0/5] worktree lock/unlock

In reply to Nguyễn Thái Ngọc Duy
Quoted 3 lines · Nguyễn Thái Ngọc Duy

This should address all of Eric's comments (thanks!). An extra change I made is free_worktrees() at the end of {,un}lock_worktree() to avoid leaking. This series depends on nd/worktree-cleanup-post-head-protection.

Thanks, this addresses all my comments from the previous round (aside from the suggestion to add a 'locked' field to 'struct worktree' and populate it automatically, which you elected to defer for the present).

I re-read the entire series and, aside from the typo in the documentation update of patch 4/5, everything looks fine, and the series is:

#108 lore raw

Re: [PATCH v2 0/5] worktree lock/unlock

Quoted 9 lines · Eric Sunshine
Quoted 3 lines · Nguyễn Thái Ngọc Duy

This should address all of Eric's comments (thanks!). An extra change I made is free_worktrees() at the end of {,un}lock_worktree() to avoid leaking. This series depends on nd/worktree-cleanup-post-head-protection.

Thanks, this addresses all my comments from the previous round (aside from the suggestion to add a 'locked' field to 'struct worktree' and populate it automatically, which you elected to defer for the present).

If there's another re-roll (likely so), I'm tempted to do that too as it's quite clear now that "locked" belongs in struct worktree.

Signature · 1 line
Duy
#109 lore raw

v3 cover worktree lock/unlock

In reply to Nguyễn Thái Ngọc Duy

v3 changes almost all patches so I didn't bother with interdiff

[1/6] worktree.c: add find_worktree()

This replaces the old find_wortree_by_path(). It now takes both prefix and command line argument so it can do some intelligent things with them if needed.

[2/6] worktree.c: find_worktree() learns to identify worktrees by basename

This is from Eric's suggestion. Here we only care about the "argument" argument and ignore the "prefix" argument from 1/6

[3/6] worktree.c: add is_main_worktree()

This is the only unchanged patch since v2!

[4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()

This used to be is_worktree_locked()

[5/6] worktree: add "lock" command

Besides Eric's comments in v2, the syntax line "worktree lock <path>" is changed to "worktree lock <worktree>", where <worktree> could be either a path, or a base name, or something more in future. I'm aware that we don't like showing "worktrees" in user documents, favoring working trees. But I think <worktree> is ok in this case.

[6/6] worktree: add "unlock" command

Pretty much addressing comments from v2 and some more changes because of the new 1/6 and 4/6.

 Documentation/git-worktree.txt         | 33 +++++++++++++----
 builtin/worktree.c                     | 66 ++++++++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  5 ++-
 t/t2028-worktree-move.sh (new +x)      | 62 ++++++++++++++++++++++++++++++++
 worktree.c                             | 53 +++++++++++++++++++++++++++
 worktree.h                             | 14 ++++++++
 6 files changed, 226 insertions(+), 7 deletions(-)
 create mode 100755 t/t2028-worktree-move.sh
Signature · 1 line
2.8.2.524.g6ff3d78
#110 lore raw

PATCH v3 1/6 worktree.c: add find_worktree()

So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. The current implementation identifies worktrees by path (*). In future, the function could learn to identify by $(basename $path) or tags...

(*) We could probably go cheaper with comparing inode number (and probably more reliable than paths when unicode enters the game). But not all systems have good inode that so let's stick to something simple for now.

---
 worktree.c | 15 +++++++++++++++
 worktree.h |  8 ++++++++
 2 files changed, 23 insertions(+)
worktree.c +15 -0
index f4a4f38..0782e00 100644
@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
+struct worktree *find_worktree(struct worktree **list,
+			       const char *prefix,
+			       const char *arg)
+{
+	char *path;
+
+	arg = prefix_filename(prefix, strlen(prefix), arg);
+	path = xstrdup(real_path(arg));
+	for (; *list; list++)
+		if (!fspathcmp(path, real_path((*list)->path)))
+			break;
+	free(path);
+	return *list;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +8 -0
index 1394909..7ad15da 100644
@@ -29,6 +29,14 @@ extern struct worktree **get_worktrees(void);
  */
 extern const char *get_worktree_git_dir(const struct worktree *wt);
 
+/*
+ * Search a worktree that can be unambiguously identified by
+ * "arg". "prefix" must not be NULL.
+ */
+extern struct worktree *find_worktree(struct worktree **list,
+				      const char *prefix,
+				      const char *arg);
+
 /*
  * Free up the memory for worktree(s)
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#111 lore raw

PATCH v3 2/6 worktree.c: find_worktree() learns to identify worktrees by basename

In reply to Nguyễn Thái Ngọc Duy

This allows the user to do something like "worktree lock foo" instead of "worktree lock <path/to/foo>". With completion support it could be quite convenient. While this base name search can be done in the same worktree iteration loop, the code is split into a separate function for clarity.

---
 worktree.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
worktree.c +21 -0
index 0782e00..4dd7b77 100644
@@ -214,12 +214,33 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
+static struct worktree *find_worktree_by_basename(struct worktree **list,
+						  const char *base_name)
+{
+	struct worktree *found = NULL;
+	int nr_found = 0;
+
+	for (; *list && nr_found < 2; list++) {
+		char *path = xstrdup((*list)->path);
+		if (!fspathcmp(base_name, basename(path))) {
+			found = *list;
+			nr_found++;
+		}
+		free(path);
+	}
+	return nr_found == 1 ? found : NULL;
+}
+
 struct worktree *find_worktree(struct worktree **list,
 			       const char *prefix,
 			       const char *arg)
 {
+	struct worktree *wt;
 	char *path;
 
+	if ((wt = find_worktree_by_basename(list, arg)))
+		return wt;
+
 	arg = prefix_filename(prefix, strlen(prefix), arg);
 	path = xstrdup(real_path(arg));
 	for (; *list; list++)
Signature · 1 line
2.8.2.524.g6ff3d78
#112 lore raw

Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename

Quoted 4 lines · Nguyễn Thái Ngọc Duy

This allows the user to do something like "worktree lock foo" instead of "worktree lock <path/to/foo>". With completion support it could be quite convenient. While this base name search can be done in the same worktree iteration loop, the code is split into a separate function for clarity.

Makes me wonder if you want to do this without calling basename(3) function at all. I do not think such a feature would cost that much over what we see in this patch.

That is, wouldn't you rather see "worktree lock to/foo" work when 'foo' is ambiguous but 'to/foo' is not?

Quoted 45 lines
---
 worktree.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
worktree.c +21 -0
index 0782e00..4dd7b77 100644
@@ -214,12 +214,33 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
+static struct worktree *find_worktree_by_basename(struct worktree **list,
+						  const char *base_name)
+{
+	struct worktree *found = NULL;
+	int nr_found = 0;
+
+	for (; *list && nr_found < 2; list++) {
+		char *path = xstrdup((*list)->path);
+		if (!fspathcmp(base_name, basename(path))) {
+			found = *list;
+			nr_found++;
+		}
+		free(path);
+	}
+	return nr_found == 1 ? found : NULL;
+}
+
 struct worktree *find_worktree(struct worktree **list,
 			       const char *prefix,
 			       const char *arg)
 {
+	struct worktree *wt;
 	char *path;
 
+	if ((wt = find_worktree_by_basename(list, arg)))
+		return wt;
+
 	arg = prefix_filename(prefix, strlen(prefix), arg);
 	path = xstrdup(real_path(arg));
 	for (; *list; list++)
#113 lore raw

Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename

Quoted 7 lines · Junio C Hamano
Quoted 4 lines · Nguyễn Thái Ngọc Duy

This allows the user to do something like "worktree lock foo" instead of "worktree lock <path/to/foo>". With completion support it could be quite convenient. While this base name search can be done in the same worktree iteration loop, the code is split into a separate function for clarity.

Quoted 6 lines

Makes me wonder if you want to do this without calling basename(3) function at all. I do not think such a feature would cost that much over what we see in this patch.

That is, wouldn't you rather see "worktree lock to/foo" work when 'foo' is ambiguous but 'to/foo' is not?

I don't know. I suppose if people have to make basename $path the same because of some weird build settings, e.g. abc/git and def/git, then this basename selection becomes useless.

I had similar thought though, if you only have a worktree named "foo" then "fo" or even "f" should be unambiguous and can also identify a worktree, similar to short sha-1. But I discarded that idea because of the higher chances that typos can select a wrong worktree. We would need to convert or match both '/' and '\' in "to/foo" case because of Windows, so it's not much easier than basename().

Signature · 1 line
Duy
#114 lore raw

Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename

Quoted 11 lines · Duy Nguyen
Quoted 2 lines

That is, wouldn't you rather see "worktree lock to/foo" work when 'foo' is ambiguous but 'to/foo' is not?

I don't know. I suppose if people have to make basename $path the same because of some weird build settings, e.g. abc/git and def/git, then this basename selection becomes useless.

I had similar thought though, if you only have a worktree named "foo" then "fo" or even "f" should be unambiguous and can also identify a worktree, similar to short sha-1. But I discarded that idea because of the higher chances that typos can select a wrong worktree.

I do not think abbreviation of "foo" down to "fo" is sensible at all. I would instead suggest "path-component-wise tail match", i.e. "to/foo" would match "path/to/foo", "path/TO/FOO" (on fold-case filesystems), but not "pathto/foo".

Quoted 3 lines

We would
need to convert or match both '/' and '\' in "to/foo" case because of Windows, so it's not much easier than basename().

I never said "easier to implement". But can this codepath get backslashed paths in the first place? I somehow thought that normalization would happen a lot before the control reaches here.

You'll be calling into fspathcmp() anyway; shouldn't the function know that '/' and '\' are equivalent on some platforms, or is it legal to only call fspathcmp() on a single path component without directory separator?

#115 lore raw

Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename

Quoted 4 lines · Junio C Hamano
Quoted 3 lines

We would
need to convert or match both '/' and '\' in "to/foo" case because of Windows, so it's not much easier than basename().

Quoted 8 lines

I never said "easier to implement". But can this codepath get backslashed paths in the first place? I somehow thought that normalization would happen a lot before the control reaches here.

You'll be calling into fspathcmp() anyway; shouldn't the function know that '/' and '\' are equivalent on some platforms, or is it legal to only call fspathcmp() on a single path component without directory separator?

We still need to calculate the length to compare, which could be problematic when utf-8 is involved, or some other encoding. If we always split at '/' boundary though (e.g. "abc/def/ghi", "def/ghi" or "ghi" but never "ef/ghi") then it should be ok.

Signature · 1 line
Duy
#116 lore raw

Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename

Quoted 9 lines · Duy Nguyen
Quoted 8 lines · Junio C Hamano
Quoted 3 lines

We would
need to convert or match both '/' and '\' in "to/foo" case because of Windows, so it's not much easier than basename().

I never said "easier to implement". But can this codepath get backslashed paths in the first place? I somehow thought that normalization would happen a lot before the control reaches here.

Quoted 7 lines
Quoted 4 lines

You'll be calling into fspathcmp() anyway; shouldn't the function know that '/' and '\' are equivalent on some platforms, or is it legal to only call fspathcmp() on a single path component without directory separator?

We still need to calculate the length to compare, which could be problematic when utf-8 is involved, or some other encoding.

Hmph. I was unaware that fspathcmp() used here does more than byte-for-byte processing, which would cause problems due to encoding issues when you hand code the comparison.

+static struct worktree *find_worktree_by_basename(struct worktree **list,
+ const char *base_name)
+{
+ struct worktree *found = NULL;
+ int nr_found = 0;
+
+ for (; *list && nr_found < 2; list++) {
+ char *path = xstrdup((*list)->path);
+ if (!fspathcmp(base_name, basename(path))) {
+ found = *list;
+ nr_found++;
+ }
+ free(path);
+ }
+ return nr_found == 1 ? found : NULL;
+}

Quoted 2 lines

If we always split at '/' boundary though (e.g. "abc/def/ghi", "def/ghi" or "ghi" but never "ef/ghi") then it should be ok.

Does "basename()" used here know '/' and '\' can both be a directory separator, or does worktree->path have a normalized representation of the path, i.e. '/' is the only directory separator?

#117 lore raw

Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename

Quoted 18 lines · Junio C Hamano
worktree.c +16 -0
+static struct worktree *find_worktree_by_basename(struct worktree **list,
+                                                 const char *base_name)
+{
+       struct worktree *found = NULL;
+       int nr_found = 0;
+
+       for (; *list && nr_found < 2; list++) {
+               char *path = xstrdup((*list)->path);
+               if (!fspathcmp(base_name, basename(path))) {
+                       found = *list;
+                       nr_found++;
+               }
+               free(path);
+       }
+       return nr_found == 1 ? found : NULL;
+}
Quoted 6 lines
Quoted 2 lines

If we always split at '/' boundary though (e.g. "abc/def/ghi", "def/ghi" or "ghi" but never "ef/ghi") then it should be ok.

Does "basename()" used here know '/' and '\' can both be a directory separator, or does worktree->path have a normalized representation of the path, i.e. '/' is the only directory separator?

basename() does (or I think so because Windows has its own version). worktree->path always uses '/' but the command line option can come with either '/' or '\'. Probably safest to accept both.

Signature · 1 line
Duy
#118 lore raw

Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename

Quoted 3 lines · Duy Nguyen

basename() does (or I think so because Windows has its own version). worktree->path always uses '/' but the command line option can come with either '/' or '\'. Probably safest to accept both.

OK. Going beyond basename() was merely "This might be an easy nice-to-have enhancement", not "basename() is not sufficient because of such and such issues", so the patch is fine as-is.

Thanks.

#119 lore raw

Re: [PATCH v3 2/6] worktree.c: find_worktree() learns to identify worktrees by basename

In reply to Duy Nguyen

Hi,

Quoted 9 lines · Duy Nguyen
Quoted 3 lines · Junio C Hamano

Does "basename()" used here know '/' and '\' can both be a directory separator, or does worktree->path have a normalized representation of the path, i.e. '/' is the only directory separator?

basename() does (or I think so because Windows has its own version). worktree->path always uses '/' but the command line option can come with either '/' or '\'. Probably safest to accept both.

It is very easy to see what the Windows version of basename() does by looking at our very own test code:

	https://github.com/git/git/blob/v2.8.3/test-path-utils.c#L71-L110

Short answer: basename() knows that both '/' and '\' are directory separators.

Ciao,
Dscho

#120 lore raw

PATCH v3 3/6 worktree.c: add is_main_worktree()

In reply to Nguyễn Thái Ngọc Duy

Main worktree _is_ different. You can lock a linked worktree but not the main one, for example. Provide an API for checking that.

---
 worktree.c | 5 +++++
 worktree.h | 5 +++++
 2 files changed, 10 insertions(+)
worktree.c +5 -0
index 4dd7b77..6432eec 100644
@@ -250,6 +250,11 @@ struct worktree *find_worktree(struct worktree **list,
 	return *list;
 }
 
+int is_main_worktree(const struct worktree *wt)
+{
+	return !wt->id;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +5 -0
index 7ad15da..e1c4715 100644
@@ -37,6 +37,11 @@ extern struct worktree *find_worktree(struct worktree **list,
 				      const char *prefix,
 				      const char *arg);
 
+/*
+ * Return true if the given worktree is the main one.
+ */
+extern int is_main_worktree(const struct worktree *wt);
+
 /*
  * Free up the memory for worktree(s)
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#121 lore raw

Re: [PATCH v3 3/6] worktree.c: add is_main_worktree()

Quoted 2 lines · Nguyễn Thái Ngọc Duy

Main worktree _is_ different. You can lock a linked worktree but not the main one, for example. Provide an API for checking that.

You haven't defined what it means to "lock" a worktree, so that example does not sound convincing at all, until the reader sees what happens later in the series. I'll just take your word for it and keep reading for now ;-)

Quoted 39 lines
---
 worktree.c | 5 +++++
 worktree.h | 5 +++++
 2 files changed, 10 insertions(+)
worktree.c +5 -0
index 4dd7b77..6432eec 100644
@@ -250,6 +250,11 @@ struct worktree *find_worktree(struct worktree **list,
 	return *list;
 }
 
+int is_main_worktree(const struct worktree *wt)
+{
+	return !wt->id;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +5 -0
index 7ad15da..e1c4715 100644
@@ -37,6 +37,11 @@ extern struct worktree *find_worktree(struct worktree **list,
 				      const char *prefix,
 				      const char *arg);
 
+/*
+ * Return true if the given worktree is the main one.
+ */
+extern int is_main_worktree(const struct worktree *wt);
+
 /*
  * Free up the memory for worktree(s)
  */
#122 lore raw

PATCH v3 4/6 worktree.c: retrieve lock status (and optionally reason) in get_worktrees()

In reply to Nguyễn Thái Ngọc Duy

We need this later to avoid double locking a worktree, or unlocking one when it's not even locked.

---
 worktree.c | 12 ++++++++++++
 worktree.h |  1 +
 2 files changed, 13 insertions(+)
worktree.c +12 -0
index 6432eec..c6a64b1 100644
@@ -98,6 +98,7 @@ static struct worktree *get_main_worktree(void)
 	worktree->is_detached = is_detached;
 	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
+	worktree->lock_reason = NULL;
 
 done:
 	strbuf_release(&path);
@@ -144,6 +145,17 @@ static struct worktree *get_linked_worktree(const char *id)
 	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
 
+	strbuf_reset(&path);
+	strbuf_addf(&path, "%s/worktrees/%s/locked", get_git_common_dir(), id);
+	if (file_exists(path.buf)) {
+		struct strbuf lock_reason = STRBUF_INIT;
+		if (strbuf_read_file(&lock_reason, path.buf, 0) < 0)
+			die_errno(_("failed to read '%s'"), path.buf);
+		strbuf_trim(&lock_reason);
+		worktree->lock_reason = strbuf_detach(&lock_reason, NULL);
+	} else
+		worktree->lock_reason = NULL;
+
 done:
 	strbuf_release(&path);
 	strbuf_release(&worktree_path);
worktree.h +1 -0
index e1c4715..9932710 100644
@@ -5,6 +5,7 @@ struct worktree {
 	char *path;
 	char *id;
 	char *head_ref;
+	char *lock_reason;	/* NULL means not locked */
 	unsigned char head_sha1[20];
 	int is_detached;
 	int is_bare;
Signature · 1 line
2.8.2.524.g6ff3d78
#123 lore raw

Re: [PATCH v3 4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()

Quoted 2 lines · Nguyễn Thái Ngọc Duy

We need this later to avoid double locking a worktree, or unlocking one when it's not even locked.

Shouldn't this be done lazily?

If a user is working in worktree B and is not doing anything funky, she would not care why worktree A and C are locked, even though she might care the fact that they are locked.

Quoted 48 lines
---
 worktree.c | 12 ++++++++++++
 worktree.h |  1 +
 2 files changed, 13 insertions(+)
worktree.c +12 -0
index 6432eec..c6a64b1 100644
@@ -98,6 +98,7 @@ static struct worktree *get_main_worktree(void)
 	worktree->is_detached = is_detached;
 	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
+	worktree->lock_reason = NULL;
 
 done:
 	strbuf_release(&path);
@@ -144,6 +145,17 @@ static struct worktree *get_linked_worktree(const char *id)
 	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
 
+	strbuf_reset(&path);
+	strbuf_addf(&path, "%s/worktrees/%s/locked", get_git_common_dir(), id);
+	if (file_exists(path.buf)) {
+		struct strbuf lock_reason = STRBUF_INIT;
+		if (strbuf_read_file(&lock_reason, path.buf, 0) < 0)
+			die_errno(_("failed to read '%s'"), path.buf);
+		strbuf_trim(&lock_reason);
+		worktree->lock_reason = strbuf_detach(&lock_reason, NULL);
+	} else
+		worktree->lock_reason = NULL;
+
 done:
 	strbuf_release(&path);
 	strbuf_release(&worktree_path);
worktree.h +1 -0
index e1c4715..9932710 100644
@@ -5,6 +5,7 @@ struct worktree {
 	char *path;
 	char *id;
 	char *head_ref;
+	char *lock_reason;	/* NULL means not locked */
 	unsigned char head_sha1[20];
 	int is_detached;
 	int is_bare;
#124 lore raw

Re: [PATCH v3 4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()

Quoted 10 lines · Junio C Hamano
Quoted 2 lines · Nguyễn Thái Ngọc Duy

We need this later to avoid double locking a worktree, or unlocking one when it's not even locked.

Shouldn't this be done lazily?

If a user is working in worktree B and is not doing anything funky, she would not care why worktree A and C are locked, even though she might care the fact that they are locked.

You and Eric will have to settle this. It was done lazily in v2, but Eric convinced me this lock thing will be needed for many worktree commands (list, lock, unlock, move, remove and maybe even prune) that it makes more sense to make it a field in struct worktree instead. For a dozen worktrees, it does not really matter. But get_worktrees() is also being used outside "git worktree" command, some of those new callers may get picky. Maybe a common ground is adding a flag to get_worktrees() to select what fields to be filled?

Signature · 1 line
Duy
#125 lore raw

Re: [PATCH v3 4/6] worktree.c: retrieve lock status (and optionally reason) in get_worktrees()

Quoted 12 lines · Duy Nguyen
Quoted 10 lines · Junio C Hamano
Quoted 2 lines · Nguyễn Thái Ngọc Duy

We need this later to avoid double locking a worktree, or unlocking one when it's not even locked.

Shouldn't this be done lazily?

If a user is working in worktree B and is not doing anything funky, she would not care why worktree A and C are locked, even though she might care the fact that they are locked.

Quoted 8 lines

You and Eric will have to settle this. It was done lazily in v2, but Eric convinced me this lock thing will be needed for many worktree commands (list, lock, unlock, move, remove and maybe even prune) that it makes more sense to make it a field in struct worktree instead. For a dozen worktrees, it does not really matter. But get_worktrees() is also being used outside "git worktree" command, some of those new callers may get picky. Maybe a common ground is adding a flag to get_worktrees() to select what fields to be filled?

NO.

When you tell callers that they have DIRECT access to fields, then they need to keep track of what flag they (or their callers) passed when get_worktrees() was called, which is a road to insanity.

By "lazily", I didn't mean "do not have a field there; instead every time read from the file and return the value". A lazy interface would give a function to read the "is it locked and if so why" information as the sole interface, whose implementation would _not_ do the reading when a worktree structure is instantiated, the first call would do the reading, and it is up to the implementation to cache the result of that first call's work in the structure and answer the subsequent calls from the cache, or do the same reading for subsequent calls.

#126 lore raw

PATCH v3 5/6 worktree: add "lock" command

In reply to Nguyễn Thái Ngọc Duy
---
 Documentation/git-worktree.txt         | 28 +++++++++++++++-----
 builtin/worktree.c                     | 38 +++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  5 +++-
 t/t2028-worktree-move.sh (new +x)      | 48 ++++++++++++++++++++++++++++++++++
 4 files changed, 112 insertions(+), 7 deletions(-)
 create mode 100755 t/t2028-worktree-move.sh
Documentation/git-worktree.txt +22 -6
index 27feff6..6fca6cf 100644
@@ -11,6 +11,7 @@ SYNOPSIS
 [verse]
 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]
 'git worktree list' [--porcelain]
+'git worktree lock' [--reason <string>] <worktree>
 'git worktree prune' [-n] [-v] [--expire <expire>]
 
 DESCRIPTION
@@ -38,9 +39,8 @@ section "DETAILS" for more information.
 
 If a linked working tree is stored on a portable device or network share
 which is not always mounted, you can prevent its administrative files from
-being pruned by creating a file named 'locked' alongside the other
-administrative files, optionally containing a plain text reason that
-pruning should be suppressed. See section "DETAILS" for more information.
+being pruned by issuing the `git worktree lock` command, optionally
+specifying `--reason` to explain why the working tree is locked.
 
 COMMANDS
 --------
@@ -61,6 +61,14 @@ each of the linked worktrees.  The output details include if the worktree is
 bare, the revision currently checked out, and the branch currently checked out
 (or 'detached HEAD' if none).
 
+lock::
+
+If a working tree is on a portable device or network share which
+is not always mounted, lock it to prevent its administrative
+files from being pruned automatically. This also prevents it from
+being moved or deleted. Optionally, specify a reason for the lock
+with `--reason`.
+
 prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
@@ -110,6 +118,15 @@ OPTIONS
 --expire <time>::
 	With `prune`, only expire unused working trees older than <time>.
 
+--reason <string>:
+	With `lock`, an explanation why the worktree is locked.
+
+<worktree>::
+	Working trees can be identified by path, either relative or
+	absolute. Alternatively, if the last component in the
+	worktree's path is unique among working trees, it can be used
+	as well.
+
 DETAILS
 -------
 Each linked working tree has a private sub-directory in the repository's
@@ -150,7 +167,8 @@ instead.
 
 To prevent a $GIT_DIR/worktrees entry 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
+entry's working tree is stored on a portable device), use the
+`git worktree lock` command, which adds 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
@@ -226,8 +244,6 @@ performed manually, such as:
 - `remove` to remove a linked working tree and its administrative files (and
   warn if the working tree is dirty)
 - `mv` to move or rename a working tree and update its administrative files
-- `lock` to prevent automatic pruning of administrative files (for instance,
-  for a working tree on a portable device)
 
 GIT
 ---
builtin/worktree.c +38 -0
index f9dac37..84fe63b 100644
@@ -14,6 +14,7 @@
 static const char * const worktree_usage[] = {
 	N_("git worktree add [<options>] <path> [<branch>]"),
 	N_("git worktree list [<options>]"),
+	N_("git worktree lock [<options>] <path>"),
 	N_("git worktree prune [<options>]"),
 	NULL
 };
@@ -459,6 +460,41 @@ static int list(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static int lock_worktree(int ac, const char **av, const char *prefix)
+{
+	const char *reason = "", *old_reason;
+	struct option options[] = {
+		OPT_STRING(0, "reason", &reason, N_("string"),
+			   N_("reason for locking")),
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (ac != 1)
+		usage_with_options(worktree_usage, options);
+
+	worktrees = get_worktrees();
+	wt = find_worktree(worktrees, prefix, av[0]);
+	if (!wt)
+		die(_("'%s' is not a working directory"), av[0]);
+	if (is_main_worktree(wt))
+		die(_("'%s' is a main working directory"), av[0]);
+
+	old_reason = wt->lock_reason;
+	if (old_reason) {
+		if (*old_reason)
+			die(_("'%s' is already locked, reason: %s"),
+			    av[0], old_reason);
+		die(_("'%s' is already locked"), av[0]);
+	}
+
+	write_file(git_common_path("worktrees/%s/locked", wt->id),
+		   "%s", reason);
+	free_worktrees(worktrees);
+	return 0;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -475,5 +511,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 		return prune(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "list"))
 		return list(ac - 1, av + 1, prefix);
+	if (!strcmp(av[1], "lock"))
+		return lock_worktree(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
 }
contrib/completion/git-completion.bash +4 -1
index 951a186..f88727d 100644
@@ -2597,7 +2597,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list prune"
+	local subcommands="add list lock prune"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
@@ -2609,6 +2609,9 @@ _git_worktree ()
 		list,--*)
 			__gitcomp "--porcelain"
 			;;
+		lock,--*)
+			__gitcomp "--reason"
+			;;
 		prune,--*)
 			__gitcomp "--dry-run --expire --verbose"
 			;;
t/t2028-worktree-move.sh new file +48 -0

48 lines not on this page · raw message

Signature · 1 line
2.8.2.524.g6ff3d78
#127 lore raw

Re: [PATCH v3 5/6] worktree: add "lock" command

Quoted 28 lines · Nguyễn Thái Ngọc Duy
builtin/worktree.c +13 -0
index f9dac37..84fe63b 100644
@@ -14,6 +14,7 @@
 static const char * const worktree_usage[] = {
 	N_("git worktree add [<options>] <path> [<branch>]"),
 	N_("git worktree list [<options>]"),
+	N_("git worktree lock [<options>] <path>"),
 	N_("git worktree prune [<options>]"),
 	NULL
 };
@@ -459,6 +460,41 @@ static int list(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static int lock_worktree(int ac, const char **av, const char *prefix)
+{
+	const char *reason = "", *old_reason;
+	struct option options[] = {
+		OPT_STRING(0, "reason", &reason, N_("string"),
+			   N_("reason for locking")),
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (ac != 1)

builtin/worktree.c static int list(int ac, const char **av, const char *prefix) · line 484

Quoted 10 lines
builtin/worktree.c +10 -0
+		usage_with_options(worktree_usage, options);
+
+	worktrees = get_worktrees();
+	wt = find_worktree(worktrees, prefix, av[0]);
+	if (!wt)
+		die(_("'%s' is not a working directory"), av[0]);
+	if (is_main_worktree(wt))
+		die(_("'%s' is a main working directory"), av[0]);
+
+	old_reason = wt->lock_reason;

This use pattern suggests that the reading of lock_reason should be done lazily, doesn't it? The other user of the "is it locked?" information, which is builtin/worktree.c::prune_worktree(), does not even use the get_worktrees() interface and instead it just checks if the lock marker git_path("worktrees/*/locked") exists.

Perhaps you want one of these as a public interface:

	int worktree_is_locked(const char *id, const char **reason);
	int worktree_is_locked(struct worktree *wt, const char **reason);

where the caller can learn if a worktree is locked, and optionally why?

#128 lore raw

Re: [PATCH v3 5/6] worktree: add "lock" command

In reply to Nguyễn Thái Ngọc Duy

Documentation/git-worktree.txt SYNOPSIS · line 15

Quoted 6 lines · Nguyễn Thái Ngọc Duy
Documentation/git-worktree.txt +1 -0
@@ -11,6 +11,7 @@ SYNOPSIS
 [verse]
 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]
 'git worktree list' [--porcelain]
+'git worktree lock' [--reason <string>] <worktree>
 'git worktree prune' [-n] [-v] [--expire <expire>]

This seems to be based on an unseen version that lists 'list' before 'prune' here and other places. I'd appreciate it if you either make sure a series that introduces a new feature applies cleanly to my tree before sending it out, or clarify to what state the series is designed to apply, when you send a patch series next time.

Documentation/git-worktree.txt OPTIONS · line 121

Quoted 5 lines
Documentation/git-worktree.txt +1 -0
@@ -110,6 +118,15 @@ OPTIONS
 --expire <time>::
 	With `prune`, only expire unused working trees older than <time>.
 
+--reason <string>:

Double-colon, I think (locally fixable).

#129 lore raw

PATCH v3 6/6 worktree: add "unlock" command

In reply to Nguyễn Thái Ngọc Duy
---
 Documentation/git-worktree.txt         |  5 +++++
 builtin/worktree.c                     | 28 ++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  2 +-
 t/t2028-worktree-move.sh               | 14 ++++++++++++++
 4 files changed, 48 insertions(+), 1 deletion(-)
Documentation/git-worktree.txt +5 -0
index 6fca6cf..6a5ff47 100644
@@ -13,6 +13,7 @@ SYNOPSIS
 'git worktree list' [--porcelain]
 'git worktree lock' [--reason <string>] <worktree>
 'git worktree prune' [-n] [-v] [--expire <expire>]
+'git worktree unlock' <worktree>
 
 DESCRIPTION
 -----------
@@ -73,6 +74,10 @@ prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
 
+unlock::
+
+Unlock a worktree, allowing it to be pruned, moved or deleted.
+
 OPTIONS
 -------
 
builtin/worktree.c +28 -0
index 84fe63b..5fe9323 100644
@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {
 	N_("git worktree list [<options>]"),
 	N_("git worktree lock [<options>] <path>"),
 	N_("git worktree prune [<options>]"),
+	N_("git worktree unlock <path>"),
 	NULL
 };
 
@@ -495,6 +496,31 @@ static int lock_worktree(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static int unlock_worktree(int ac, const char **av, const char *prefix)
+{
+	struct option options[] = {
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+	int ret;
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (ac != 1)
+		usage_with_options(worktree_usage, options);
+
+	worktrees = get_worktrees();
+	wt = find_worktree(worktrees, prefix, av[0]);
+	if (!wt)
+		die(_("'%s' is not a working directory"), av[0]);
+	if (is_main_worktree(wt))
+		die(_("'%s' is a main working directory"), av[0]);
+	if (!wt->lock_reason)
+		die(_("'%s' is not locked"), av[0]);
+	ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
+	free_worktrees(worktrees);
+	return ret;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -513,5 +539,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 		return list(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "lock"))
 		return lock_worktree(ac - 1, av + 1, prefix);
+	if (!strcmp(av[1], "unlock"))
+		return unlock_worktree(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
 }
contrib/completion/git-completion.bash +1 -1
index f88727d..0e3841d 100644
@@ -2597,7 +2597,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list lock prune"
+	local subcommands="add list lock prune unlock"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
t/t2028-worktree-move.sh +14 -0
index 87afc2e..68d3fe8 100755
@@ -45,4 +45,18 @@ test_expect_success 'lock worktree twice (from the locked worktree)' '
 	test_cmp expected .git/worktrees/source/locked
 '
 
+test_expect_success 'unlock main worktree' '
+	test_must_fail git worktree unlock .
+'
+
+test_expect_success 'unlock linked worktree' '
+	git worktree unlock source &&
+	test_path_is_missing .git/worktrees/source/locked
+'
+
+test_expect_success 'unlock worktree twice' '
+	test_must_fail git worktree unlock source &&
+	test_path_is_missing .git/worktrees/source/locked
+'
+
 test_done
Signature · 1 line
2.8.2.524.g6ff3d78
#130 lore raw

Re: [PATCH v3 6/6] worktree: add "unlock" command

Quoted 13 lines · Nguyễn Thái Ngọc Duy
---
 Documentation/git-worktree.txt         |  5 +++++
 builtin/worktree.c                     | 28 ++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  2 +-
 t/t2028-worktree-move.sh               | 14 ++++++++++++++
 4 files changed, 48 insertions(+), 1 deletion(-)
Documentation/git-worktree.txt +0 -0
index 6fca6cf..6a5ff47 100644

Documentation/git-worktree.txt SYNOPSIS · line 16

Quoted 4 lines
Documentation/git-worktree.txt +1 -0
@@ -13,6 +13,7 @@ SYNOPSIS
 'git worktree list' [--porcelain]
 'git worktree lock' [--reason <string>] <worktree>
 'git worktree prune' [-n] [-v] [--expire <expire>]
+'git worktree unlock' <worktree>

Again, this seems to be built on an unseen version. I think I fixed these up (and also the "missing second colon in the doc"), but please double check what I push out later today.

Thanks.

#131 lore raw

Re: [PATCH v3 6/6] worktree: add "unlock" command

Quoted 3 lines · Junio C Hamano

Again, this seems to be built on an unseen version. I think I fixed these up (and also the "missing second colon in the doc"), but please double check what I push out later today.

Ah, this comes on top of 0409e0b6 (worktree: simplify prefixing paths, 2016-05-22). I do not necessarily agree with 7b722d9 (I think "add, list, prune followed by lock, unlock" would be a more sensible order), but I'll leave them as-is.

Thanks. Queued.

#132 lore raw

Re: [PATCH v3 6/6] worktree: add "unlock" command

In reply to Nguyễn Thái Ngọc Duy

builtin/worktree.c static int lock_worktree(int ac, const char **av, const char *prefix) · line 518

Quoted 4 lines · Nguyễn Thái Ngọc Duy
builtin/worktree.c +4 -0
+	if (is_main_worktree(wt))
+		die(_("'%s' is a main working directory"), av[0]);
+	if (!wt->lock_reason)
+		die(_("'%s' is not locked"), av[0]);

Exactly the same comment about the lack of need for lock_reason field as 5/6 appiles here. Also, as "lock" does not allow you to lock the primary tree, do you even need is_main_worktree() check?

That is:

	if (!worktree_is_locked(wt, &reason))
        	die(_("'%s' is not locked), av[0]);

should be sufficient, no?

#133 lore raw

Re: [PATCH v3 6/6] worktree: add "unlock" command

Quoted 11 lines · Junio C Hamano

builtin/worktree.c static int lock_worktree(int ac, const char **av, const char *prefix) · line 518

Quoted 4 lines · Nguyễn Thái Ngọc Duy
builtin/worktree.c +4 -0
+     if (is_main_worktree(wt))
+             die(_("'%s' is a main working directory"), av[0]);
+     if (!wt->lock_reason)
+             die(_("'%s' is not locked"), av[0]);

Exactly the same comment about the lack of need for lock_reason field as 5/6 appiles here. Also, as "lock" does not allow you to lock the primary tree, do you even need is_main_worktree() check?

Quoted 6 lines

That is:

if (!worktree_is_locked(wt, &reason))
die(_("'%s' is not locked), av[0]);

should be sufficient, no?

It crossed my mind but I went for separate checks anyway because it gives better explanation.

Assigning either lock or unlock status to main worktree does not work well either. If you assume worktree is always unlocked, you still need to check main worktree in lock, move and remove as all of them would only proceed if it's an unlocked linked worktree. If you assume the opposite, you need is_main_worktree() check here and "worktree list" may print the confusing "locked" status on main worktree (I assume we may add that later).

Signature · 1 line
Duy
#134 lore raw

v4 cover worktree lock/unlock

In reply to Nguyễn Thái Ngọc Duy

v4

- fixes some documentation bugs in v3

- updates is_main_worktree commit message a bit

- brings back is_worktree_locked() as API with value lazily obtained and cached in worktree->lock_reason and fixes a memory leak

- extends basename search to path suffix search. I moved this patch to the bottom so that the relevant documentation part is included in the patch (previously it was part of the "add lock command" patch).

This series is based on nd/worktree-cleanup-post-head-protection

Nguyễn Thái Ngọc Duy (6):
worktree.c: add find_worktree() worktree.c: add is_main_worktree() worktree.c: add is_worktree_locked()
worktree: add "lock" command
worktree: add "unlock" command worktree.c: find_worktree() search by path suffix

 Documentation/git-worktree.txt         | 36 +++++++++++++---
 builtin/worktree.c                     | 66 ++++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  5 ++-
 t/t2028-worktree-move.sh (new +x)      | 62 ++++++++++++++++++++++++++++
 worktree.c                             | 75 ++++++++++++++++++++++++++++++++++
 worktree.h                             | 20 +++++++++
 6 files changed, 257 insertions(+), 7 deletions(-)
 create mode 100755 t/t2028-worktree-move.sh
Signature · 1 line
2.8.2.524.g6ff3d78
#135 lore raw

PATCH v4 1/6 worktree.c: add find_worktree()

So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. The current implementation identifies worktrees by path (*). In future, the function could learn to identify by $(basename $path) or tags...

(*) We could probably go cheaper with comparing inode number (and probably more reliable than paths when unicode enters the game). But not all systems have good inode that so let's stick to something simple for now.

---
 worktree.c | 15 +++++++++++++++
 worktree.h |  8 ++++++++
 2 files changed, 23 insertions(+)
worktree.c +15 -0
index e2a94e0..554f566 100644
@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
+struct worktree *find_worktree(struct worktree **list,
+			       const char *prefix,
+			       const char *arg)
+{
+	char *path;
+
+	arg = prefix_filename(prefix, strlen(prefix), arg);
+	path = xstrdup(real_path(arg));
+	for (; *list; list++)
+		if (!fspathcmp(path, real_path((*list)->path)))
+			break;
+	free(path);
+	return *list;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +8 -0
index 1394909..7ad15da 100644
@@ -29,6 +29,14 @@ extern struct worktree **get_worktrees(void);
  */
 extern const char *get_worktree_git_dir(const struct worktree *wt);
 
+/*
+ * Search a worktree that can be unambiguously identified by
+ * "arg". "prefix" must not be NULL.
+ */
+extern struct worktree *find_worktree(struct worktree **list,
+				      const char *prefix,
+				      const char *arg);
+
 /*
  * Free up the memory for worktree(s)
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#136 lore raw

Re: [PATCH v4 1/6] worktree.c: add find_worktree()

Quoted 25 lines · Nguyễn Thái Ngọc Duy

So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. The current implementation identifies worktrees by path (*). In future, the function could learn to identify by $(basename $path) or tags...

(*) We could probably go cheaper with comparing inode number (and probably more reliable than paths when unicode enters the game). But not all systems have good inode that so let's stick to something simple for now.

---
 worktree.c | 15 +++++++++++++++
 worktree.h |  8 ++++++++
 2 files changed, 23 insertions(+)
worktree.c +0 -0
index e2a94e0..554f566 100644
@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 

worktree.c const char *get_worktree_git_dir(const struct worktree *wt) · line 226

Quoted 10 lines
worktree.c +10 -0
+struct worktree *find_worktree(struct worktree **list,
+			       const char *prefix,
+			       const char *arg)
+{
+	char *path;
+
+	arg = prefix_filename(prefix, strlen(prefix), arg);
+	path = xstrdup(real_path(arg));
+	for (; *list; list++)
+		if (!fspathcmp(path, real_path((*list)->path)))

The results of the call to real_path() should probably be cached in the worktree structure, since real_path() is relatively expensive (it calls chdir(), lstat(), readlink() etc.), so you don't want to re-compute the same result time-after-time ...

worktree.c const char *get_worktree_git_dir(const struct worktree *wt) · line 231

Quoted 5 lines
worktree.c +5 -0
+			break;
+	free(path);
+	return *list;
+}
+

ATB,
Ramsay Jones

#137 lore raw

Re: [PATCH v4 1/6] worktree.c: add find_worktree()

Quoted 28 lines · Ramsay Jones
Quoted 25 lines · Nguyễn Thái Ngọc Duy

So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. The current implementation identifies worktrees by path (*). In future, the function could learn to identify by $(basename $path) or tags...

(*) We could probably go cheaper with comparing inode number (and probably more reliable than paths when unicode enters the game). But not all systems have good inode that so let's stick to something simple for now.

---
 worktree.c | 15 +++++++++++++++
 worktree.h |  8 ++++++++
 2 files changed, 23 insertions(+)
worktree.c +0 -0
index e2a94e0..554f566 100644
@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)
              return git_common_path("worktrees/%s", wt->id);
 }
 
Quoted 15 lines

worktree.c const char *get_worktree_git_dir(const struct worktree *wt) · line 226

Quoted 10 lines
worktree.c +10 -0
+struct worktree *find_worktree(struct worktree **list,
+                            const char *prefix,
+                            const char *arg)
+{
+     char *path;
+
+     arg = prefix_filename(prefix, strlen(prefix), arg);
+     path = xstrdup(real_path(arg));
+     for (; *list; list++)
+             if (!fspathcmp(path, real_path((*list)->path)))

The results of the call to real_path() should probably be cached in the worktree structure, since real_path() is relatively expensive (it calls chdir(), lstat(), readlink() etc.), so you don't want to re-compute the same result time-after-time ...

Urgh.. I missed this after sending out v5. Because find_worktree is probably called once or twice per process, I don't think we need to optimize this yet. If nr. worktrees goes up to hundreds then this is one of many items we need to do to make worktree list fast.

Signature · 1 line
Duy
#138 lore raw

PATCH v4 2/6 worktree.c: add is_main_worktree()

In reply to Nguyễn Thái Ngọc Duy

Main worktree _is_ different. You can lock (*) a linked worktree but not the main one, for example. Provide an API for checking that.

(*) Add the file $GIT_DIR/worktrees/xxx/locked to avoid worktree xxx from being removed or moved.

---
 worktree.c | 5 +++++
 worktree.h | 5 +++++
 2 files changed, 10 insertions(+)
worktree.c +5 -0
index 554f566..eb3aaaa 100644
@@ -229,6 +229,11 @@ struct worktree *find_worktree(struct worktree **list,
 	return *list;
 }
 
+int is_main_worktree(const struct worktree *wt)
+{
+	return !wt->id;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +5 -0
index 7ad15da..e1c4715 100644
@@ -37,6 +37,11 @@ extern struct worktree *find_worktree(struct worktree **list,
 				      const char *prefix,
 				      const char *arg);
 
+/*
+ * Return true if the given worktree is the main one.
+ */
+extern int is_main_worktree(const struct worktree *wt);
+
 /*
  * Free up the memory for worktree(s)
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#139 lore raw

PATCH v4 3/6 worktree.c: add is_worktree_locked()

In reply to Nguyễn Thái Ngọc Duy

We need this later to avoid double locking a worktree, or unlocking one when it's not even locked.

---
 worktree.c | 26 ++++++++++++++++++++++++++
 worktree.h |  7 +++++++
 2 files changed, 33 insertions(+)
worktree.c +26 -0
index eb3aaaa..ee592a4 100644
@@ -5,6 +5,8 @@
 #include "dir.h"
 #include "wt-status.h"
 
+static const char *lock_field_uninitialized = "value is not important";
+
 void free_worktrees(struct worktree **worktrees)
 {
 	int i = 0;
@@ -13,6 +15,8 @@ void free_worktrees(struct worktree **worktrees)
 		free(worktrees[i]->path);
 		free(worktrees[i]->id);
 		free(worktrees[i]->head_ref);
+		if (worktrees[i]->lock_reason != lock_field_uninitialized)
+			free(worktrees[i]->lock_reason);
 		free(worktrees[i]);
 	}
 	free (worktrees);
@@ -98,6 +102,7 @@ static struct worktree *get_main_worktree(void)
 	worktree->is_detached = is_detached;
 	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
+	worktree->lock_reason = (char *)lock_field_uninitialized;
 
 done:
 	strbuf_release(&path);
@@ -143,6 +148,7 @@ static struct worktree *get_linked_worktree(const char *id)
 	worktree->is_detached = is_detached;
 	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
+	worktree->lock_reason = (char *)lock_field_uninitialized;
 
 done:
 	strbuf_release(&path);
@@ -234,6 +240,26 @@ int is_main_worktree(const struct worktree *wt)
 	return !wt->id;
 }
 
+const char *is_worktree_locked(struct worktree *wt)
+{
+	if (wt->lock_reason == lock_field_uninitialized) {
+		struct strbuf path = STRBUF_INIT;
+
+		strbuf_addstr(&path, worktree_git_path(wt, "locked"));
+		if (file_exists(path.buf)) {
+			struct strbuf lock_reason = STRBUF_INIT;
+			if (strbuf_read_file(&lock_reason, path.buf, 0) < 0)
+				die_errno(_("failed to read '%s'"), path.buf);
+			strbuf_trim(&lock_reason);
+			wt->lock_reason = strbuf_detach(&lock_reason, NULL);
+		} else
+			wt->lock_reason = NULL;
+		strbuf_release(&path);
+	}
+
+	return wt->lock_reason;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +7 -0
index e1c4715..263b61d 100644
@@ -5,6 +5,7 @@ struct worktree {
 	char *path;
 	char *id;
 	char *head_ref;
+	char *lock_reason;	/* internal use */
 	unsigned char head_sha1[20];
 	int is_detached;
 	int is_bare;
@@ -42,6 +43,12 @@ extern struct worktree *find_worktree(struct worktree **list,
  */
 extern int is_main_worktree(const struct worktree *wt);
 
+/*
+ * Return the reason string if the given worktree is locked or NULL
+ * otherwise.
+ */
+extern const char *is_worktree_locked(struct worktree *wt);
+
 /*
  * Free up the memory for worktree(s)
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#140 lore raw

PATCH v4 4/6 worktree: add "lock" command

In reply to Nguyễn Thái Ngọc Duy
---
 Documentation/git-worktree.txt         | 26 +++++++++++++-----
 builtin/worktree.c                     | 38 +++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  5 +++-
 t/t2028-worktree-move.sh (new +x)      | 48 ++++++++++++++++++++++++++++++++++
 4 files changed, 110 insertions(+), 7 deletions(-)
 create mode 100755 t/t2028-worktree-move.sh
Documentation/git-worktree.txt +20 -6
index 7c4cfb0..d233d61 100644
@@ -11,6 +11,7 @@ SYNOPSIS
 [verse]
 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]
 'git worktree list' [--porcelain]
+'git worktree lock' [--reason <string>] <worktree>
 'git worktree prune' [-n] [-v] [--expire <expire>]
 
 DESCRIPTION
@@ -38,9 +39,8 @@ section "DETAILS" for more information.
 
 If a linked working tree is stored on a portable device or network share
 which is not always mounted, you can prevent its administrative files from
-being pruned by creating a file named 'locked' alongside the other
-administrative files, optionally containing a plain text reason that
-pruning should be suppressed. See section "DETAILS" for more information.
+being pruned by issuing the `git worktree lock` command, optionally
+specifying `--reason` to explain why the working tree is locked.
 
 COMMANDS
 --------
@@ -62,6 +62,14 @@ each of the linked worktrees.  The output details include if the worktree is
 bare, the revision currently checked out, and the branch currently checked out
 (or 'detached HEAD' if none).
 
+lock::
+
+If a working tree is on a portable device or network share which
+is not always mounted, lock it to prevent its administrative
+files from being pruned automatically. This also prevents it from
+being moved or deleted. Optionally, specify a reason for the lock
+with `--reason`.
+
 prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
@@ -111,6 +119,13 @@ OPTIONS
 --expire <time>::
 	With `prune`, only expire unused working trees older than <time>.
 
+--reason <string>::
+	With `lock`, an explanation why the working tree is locked.
+
+<worktree>::
+	Working trees can be identified by path, either relative or
+	absolute.
+
 DETAILS
 -------
 Each linked working tree has a private sub-directory in the repository's
@@ -151,7 +166,8 @@ instead.
 
 To prevent a $GIT_DIR/worktrees entry 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
+entry's working tree is stored on a portable device), use the
+`git worktree lock` command, which adds 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
@@ -227,8 +243,6 @@ performed manually, such as:
 - `remove` to remove a linked working tree and its administrative files (and
   warn if the working tree is dirty)
 - `mv` to move or rename a working tree and update its administrative files
-- `lock` to prevent automatic pruning of administrative files (for instance,
-  for a working tree on a portable device)
 
 GIT
 ---
builtin/worktree.c +38 -0
index e866844..b686c7f 100644
@@ -14,6 +14,7 @@
 static const char * const worktree_usage[] = {
 	N_("git worktree add [<options>] <path> [<branch>]"),
 	N_("git worktree list [<options>]"),
+	N_("git worktree lock [<options>] <path>"),
 	N_("git worktree prune [<options>]"),
 	NULL
 };
@@ -462,6 +463,41 @@ static int list(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static int lock_worktree(int ac, const char **av, const char *prefix)
+{
+	const char *reason = "", *old_reason;
+	struct option options[] = {
+		OPT_STRING(0, "reason", &reason, N_("string"),
+			   N_("reason for locking")),
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (ac != 1)
+		usage_with_options(worktree_usage, options);
+
+	worktrees = get_worktrees();
+	wt = find_worktree(worktrees, prefix, av[0]);
+	if (!wt)
+		die(_("'%s' is not a working directory"), av[0]);
+	if (is_main_worktree(wt))
+		die(_("'%s' is a main working directory"), av[0]);
+
+	old_reason = is_worktree_locked(wt);
+	if (old_reason) {
+		if (*old_reason)
+			die(_("'%s' is already locked, reason: %s"),
+			    av[0], old_reason);
+		die(_("'%s' is already locked"), av[0]);
+	}
+
+	write_file(git_common_path("worktrees/%s/locked", wt->id),
+		   "%s", reason);
+	free_worktrees(worktrees);
+	return 0;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -478,5 +514,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 		return prune(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "list"))
 		return list(ac - 1, av + 1, prefix);
+	if (!strcmp(av[1], "lock"))
+		return lock_worktree(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
 }
contrib/completion/git-completion.bash +4 -1
index fb0f6e3..61ef18d 100644
@@ -2598,7 +2598,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list prune"
+	local subcommands="add list lock prune"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
@@ -2610,6 +2610,9 @@ _git_worktree ()
 		list,--*)
 			__gitcomp "--porcelain"
 			;;
+		lock,--*)
+			__gitcomp "--reason"
+			;;
 		prune,--*)
 			__gitcomp "--dry-run --expire --verbose"
 			;;
t/t2028-worktree-move.sh new file +48 -0

48 lines not on this page · raw message

Signature · 1 line
2.8.2.524.g6ff3d78
#141 lore raw

PATCH v4 5/6 worktree: add "unlock" command

In reply to Nguyễn Thái Ngọc Duy
---
 Documentation/git-worktree.txt         |  5 +++++
 builtin/worktree.c                     | 28 ++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  2 +-
 t/t2028-worktree-move.sh               | 14 ++++++++++++++
 4 files changed, 48 insertions(+), 1 deletion(-)
Documentation/git-worktree.txt +5 -0
index d233d61..317c7d2 100644
@@ -13,6 +13,7 @@ SYNOPSIS
 'git worktree list' [--porcelain]
 'git worktree lock' [--reason <string>] <worktree>
 'git worktree prune' [-n] [-v] [--expire <expire>]
+'git worktree unlock' <worktree>
 
 DESCRIPTION
 -----------
@@ -74,6 +75,10 @@ prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
 
+unlock::
+
+Unlock a working tree, allowing it to be pruned, moved or deleted.
+
 OPTIONS
 -------
 
builtin/worktree.c +28 -0
index b686c7f..6d30c55 100644
@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {
 	N_("git worktree list [<options>]"),
 	N_("git worktree lock [<options>] <path>"),
 	N_("git worktree prune [<options>]"),
+	N_("git worktree unlock <path>"),
 	NULL
 };
 
@@ -498,6 +499,31 @@ static int lock_worktree(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static int unlock_worktree(int ac, const char **av, const char *prefix)
+{
+	struct option options[] = {
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+	int ret;
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (ac != 1)
+		usage_with_options(worktree_usage, options);
+
+	worktrees = get_worktrees();
+	wt = find_worktree(worktrees, prefix, av[0]);
+	if (!wt)
+		die(_("'%s' is not a working directory"), av[0]);
+	if (is_main_worktree(wt))
+		die(_("'%s' is a main working directory"), av[0]);
+	if (!is_worktree_locked(wt))
+		die(_("'%s' is not locked"), av[0]);
+	ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
+	free_worktrees(worktrees);
+	return ret;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -516,5 +542,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 		return list(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "lock"))
 		return lock_worktree(ac - 1, av + 1, prefix);
+	if (!strcmp(av[1], "unlock"))
+		return unlock_worktree(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
 }
contrib/completion/git-completion.bash +1 -1
index 61ef18d..d90e571 100644
@@ -2598,7 +2598,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list lock prune"
+	local subcommands="add list lock prune unlock"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
t/t2028-worktree-move.sh +14 -0
index 87afc2e..68d3fe8 100755
@@ -45,4 +45,18 @@ test_expect_success 'lock worktree twice (from the locked worktree)' '
 	test_cmp expected .git/worktrees/source/locked
 '
 
+test_expect_success 'unlock main worktree' '
+	test_must_fail git worktree unlock .
+'
+
+test_expect_success 'unlock linked worktree' '
+	git worktree unlock source &&
+	test_path_is_missing .git/worktrees/source/locked
+'
+
+test_expect_success 'unlock worktree twice' '
+	test_must_fail git worktree unlock source &&
+	test_path_is_missing .git/worktrees/source/locked
+'
+
 test_done
Signature · 1 line
2.8.2.524.g6ff3d78
#142 lore raw

PATCH v4 6/6 worktree.c: find_worktree() search by path suffix

In reply to Nguyễn Thái Ngọc Duy

This allows the user to do something like "worktree lock foo" or "worktree lock to/foo" instead of "worktree lock /long/path/to/foo" if it's unambiguous. With completion support it could be quite convenient.

---
 Documentation/git-worktree.txt |  5 +++++
 worktree.c                     | 29 +++++++++++++++++++++++++++++
 2 files changed, 34 insertions(+)
Documentation/git-worktree.txt +5 -0
index 317c7d2..0aeb020 100644
@@ -130,6 +130,11 @@ OPTIONS
 <worktree>::
 	Working trees can be identified by path, either relative or
 	absolute.
++
+If the last path components in the working tree's path is unique among
+working trees, it can be used to identify worktrees. For example if
+you only have to working trees at "/abc/def/ghi" and "/abc/def/ggg",
+then "ghi" or "def/ghi" is enough to point to the former working tree.
 
 DETAILS
 -------
worktree.c +29 -0
index ee592a4..fd5c768 100644
@@ -220,12 +220,41 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
+static struct worktree *find_worktree_by_suffix(struct worktree **list,
+						const char *suffix)
+{
+	struct worktree *found = NULL;
+	int nr_found = 0, suffixlen;
+
+	suffixlen = strlen(suffix);
+	if (!suffixlen)
+		return NULL;
+
+	for (; *list && nr_found < 2; list++) {
+		const char	*path	 = (*list)->path;
+		int		 pathlen = strlen(path);
+		int		 start	 = pathlen - suffixlen;
+
+		/* suffix must start at directory boundary */
+		if ((!start || (start > 0 && is_dir_sep(path[start - 1]))) &&
+		    !fspathcmp(suffix, path + start)) {
+			found = *list;
+			nr_found++;
+		}
+	}
+	return nr_found == 1 ? found : NULL;
+}
+
 struct worktree *find_worktree(struct worktree **list,
 			       const char *prefix,
 			       const char *arg)
 {
+	struct worktree *wt;
 	char *path;
 
+	if ((wt = find_worktree_by_suffix(list, arg)))
+		return wt;
+
 	arg = prefix_filename(prefix, strlen(prefix), arg);
 	path = xstrdup(real_path(arg));
 	for (; *list; list++)
Signature · 1 line
2.8.2.524.g6ff3d78
#143 lore raw

Re: [PATCH v4 0/6] worktree lock/unlock

In reply to Nguyễn Thái Ngọc Duy
Quoted 9 lines · Nguyễn Thái Ngọc Duy

v4

- fixes some documentation bugs in v3

- updates is_main_worktree commit message a bit

- brings back is_worktree_locked() as API with value lazily obtained and cached in worktree->lock_reason and fixes a memory leak

Quoted 6 lines

- extends basename search to path suffix search. I moved this patch to the bottom so that the relevant documentation part is included in the patch (previously it was part of the "add lock command" patch).

This series is based on nd/worktree-cleanup-post-head-protection

Will replace the one in 'pu'. A few things I noticed:

* Documentation and messages talk about "working tree" and "working directory" when they refer to "worktree", which is not a normal "working tree". I think this was done as an attempt to avoid being too precise and technical in phrasing, but I suspect that is a false reader-friendliness. At least, "working directory" in messages should be "worktree", e.g.

	wt = find_worktree(...);
	if (!wt)
        	die("'%s' is not a working directory", av[0]);

does not make much sense (imagine "git wt lock ." and being told that '.' is not a working directory).

* You are probably trying to be frugal by not introducing a separate bit for is_lock_checked to struct worktre and instead using a sentinel value for lock_reason, but I think that is a false economy, after seeing that you have to cast away constness in number of places and having to be extra careful not to free that sentinel value.

Thanks.

#144 lore raw

v5 cover worktree lock/unlock

In reply to Nguyễn Thái Ngọc Duy

v5 fixes some error messages mentioning "working directory" instead of "working tree" and split the double use of "lock_reason" field in "struct worktree". This series depends on nd/worktree-cleanup-post-head-protection.

Diff from v4

-- 8< --

builtin/worktree.c +4 -4
index cb5026d..4877421 100644
@@ -478,9 +478,9 @@ static int lock_worktree(int ac, const char **av, const char *prefix)
 	worktrees = get_worktrees();
 	wt = find_worktree(worktrees, prefix, av[0]);
 	if (!wt)
-		die(_("'%s' is not a working directory"), av[0]);
+		die(_("'%s' is not a working tree"), av[0]);
 	if (is_main_worktree(wt))
-		die(_("'%s' is a main working directory"), av[0]);
+		die(_("The main working tree cannot be locked or unlocked"));
 
 	old_reason = is_worktree_locked(wt);
 	if (old_reason) {
@@ -511,9 +511,9 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)
 	worktrees = get_worktrees();
 	wt = find_worktree(worktrees, prefix, av[0]);
 	if (!wt)
-		die(_("'%s' is not a working directory"), av[0]);
+		die(_("'%s' is not a working tree"), av[0]);
 	if (is_main_worktree(wt))
-		die(_("'%s' is a main working directory"), av[0]);
+		die(_("The main working tree cannot be locked or unlocked"));
 	if (!is_worktree_locked(wt))
 		die(_("'%s' is not locked"), av[0]);
 	ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
worktree.c +9 -7
index b16262b..2107c06 100644
@@ -5,8 +5,6 @@
 #include "dir.h"
 #include "wt-status.h"
 
-static const char *lock_field_uninitialized = "value is not important";
-
 void free_worktrees(struct worktree **worktrees)
 {
 	int i = 0;
@@ -15,8 +13,7 @@ void free_worktrees(struct worktree **worktrees)
 		free(worktrees[i]->path);
 		free(worktrees[i]->id);
 		free(worktrees[i]->head_ref);
-		if (worktrees[i]->lock_reason != lock_field_uninitialized)
-			free(worktrees[i]->lock_reason);
+		free(worktrees[i]->lock_reason);
 		free(worktrees[i]);
 	}
 	free (worktrees);
@@ -102,7 +99,8 @@ static struct worktree *get_main_worktree(void)
 	worktree->is_detached = is_detached;
 	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
-	worktree->lock_reason = (char *)lock_field_uninitialized;
+	worktree->lock_reason = NULL;
+	worktree->lock_reason_valid = 0;
 
 done:
 	strbuf_release(&path);
@@ -148,7 +146,8 @@ static struct worktree *get_linked_worktree(const char *id)
 	worktree->is_detached = is_detached;
 	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
-	worktree->lock_reason = (char *)lock_field_uninitialized;
+	worktree->lock_reason = NULL;
+	worktree->lock_reason_valid = 0;
 
 done:
 	strbuf_release(&path);
@@ -271,7 +270,9 @@ int is_main_worktree(const struct worktree *wt)
 
 const char *is_worktree_locked(struct worktree *wt)
 {
-	if (wt->lock_reason == lock_field_uninitialized) {
+	assert(!is_main_worktree(wt));
+
+	if (!wt->lock_reason_valid) {
 		struct strbuf path = STRBUF_INIT;
 
 		strbuf_addstr(&path, worktree_git_path(wt, "locked"));
@@ -283,6 +284,7 @@ const char *is_worktree_locked(struct worktree *wt)
 			wt->lock_reason = strbuf_detach(&lock_reason, NULL);
 		} else
 			wt->lock_reason = NULL;
+		wt->lock_reason_valid = 1;
 		strbuf_release(&path);
 	}
 
worktree.h +1 -0
index 263b61d..90e1311 100644
@@ -10,6 +10,7 @@ struct worktree {
 	int is_detached;
 	int is_bare;
 	int is_current;
+	int lock_reason_valid;
 };
 
 /* Functions for acting on the information about worktrees. */

-- >8 --

Nguyễn Thái Ngọc Duy (6):
worktree.c: add find_worktree() worktree.c: add is_main_worktree() worktree.c: add is_worktree_locked()
worktree: add "lock" command
worktree: add "unlock" command worktree.c: find_worktree() search by path suffix

 Documentation/git-worktree.txt         | 36 +++++++++++++---
 builtin/worktree.c                     | 66 +++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  5 ++-
 t/t2028-worktree-move.sh (new +x)      | 62 +++++++++++++++++++++++++++
 worktree.c                             | 77 ++++++++++++++++++++++++++++++++++
 worktree.h                             | 21 ++++++++++
 6 files changed, 260 insertions(+), 7 deletions(-)
 create mode 100755 t/t2028-worktree-move.sh
Signature · 1 line
2.8.2.524.g6ff3d78
#145 lore raw

PATCH v5 1/6 worktree.c: add find_worktree()

So far we haven't needed to identify an existing worktree from command line. Future commands such as lock or move will need it. The current implementation identifies worktrees by path (*). In future, the function could learn to identify by $(basename $path) or tags...

(*) We could probably go cheaper with comparing inode number (and probably more reliable than paths when unicode enters the game). But not all systems have good inode that so let's stick to something simple for now.

---
 worktree.c | 15 +++++++++++++++
 worktree.h |  8 ++++++++
 2 files changed, 23 insertions(+)
worktree.c +15 -0
index f4a4f38..0782e00 100644
@@ -214,6 +214,21 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
+struct worktree *find_worktree(struct worktree **list,
+			       const char *prefix,
+			       const char *arg)
+{
+	char *path;
+
+	arg = prefix_filename(prefix, strlen(prefix), arg);
+	path = xstrdup(real_path(arg));
+	for (; *list; list++)
+		if (!fspathcmp(path, real_path((*list)->path)))
+			break;
+	free(path);
+	return *list;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +8 -0
index 1394909..7ad15da 100644
@@ -29,6 +29,14 @@ extern struct worktree **get_worktrees(void);
  */
 extern const char *get_worktree_git_dir(const struct worktree *wt);
 
+/*
+ * Search a worktree that can be unambiguously identified by
+ * "arg". "prefix" must not be NULL.
+ */
+extern struct worktree *find_worktree(struct worktree **list,
+				      const char *prefix,
+				      const char *arg);
+
 /*
  * Free up the memory for worktree(s)
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#146 lore raw

PATCH v5 2/6 worktree.c: add is_main_worktree()

In reply to Nguyễn Thái Ngọc Duy

Main worktree _is_ different. You can lock (*) a linked worktree but not the main one, for example. Provide an API for checking that.

(*) Add the file $GIT_DIR/worktrees/xxx/locked to avoid worktree xxx from being removed or moved.

---
 worktree.c | 5 +++++
 worktree.h | 5 +++++
 2 files changed, 10 insertions(+)
worktree.c +5 -0
index 0782e00..12a766a 100644
@@ -229,6 +229,11 @@ struct worktree *find_worktree(struct worktree **list,
 	return *list;
 }
 
+int is_main_worktree(const struct worktree *wt)
+{
+	return !wt->id;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +5 -0
index 7ad15da..e1c4715 100644
@@ -37,6 +37,11 @@ extern struct worktree *find_worktree(struct worktree **list,
 				      const char *prefix,
 				      const char *arg);
 
+/*
+ * Return true if the given worktree is the main one.
+ */
+extern int is_main_worktree(const struct worktree *wt);
+
 /*
  * Free up the memory for worktree(s)
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#147 lore raw

PATCH v5 3/6 worktree.c: add is_worktree_locked()

In reply to Nguyễn Thái Ngọc Duy

We need this later to avoid double locking a worktree, or unlocking one when it's not even locked.

---
 worktree.c | 28 ++++++++++++++++++++++++++++
 worktree.h |  8 ++++++++
 2 files changed, 36 insertions(+)
worktree.c +28 -0
index 12a766a..2bcfff3 100644
@@ -13,6 +13,7 @@ void free_worktrees(struct worktree **worktrees)
 		free(worktrees[i]->path);
 		free(worktrees[i]->id);
 		free(worktrees[i]->head_ref);
+		free(worktrees[i]->lock_reason);
 		free(worktrees[i]);
 	}
 	free (worktrees);
@@ -98,6 +99,8 @@ static struct worktree *get_main_worktree(void)
 	worktree->is_detached = is_detached;
 	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
+	worktree->lock_reason = NULL;
+	worktree->lock_reason_valid = 0;
 
 done:
 	strbuf_release(&path);
@@ -143,6 +146,8 @@ static struct worktree *get_linked_worktree(const char *id)
 	worktree->is_detached = is_detached;
 	worktree->is_current = 0;
 	add_head_info(&head_ref, worktree);
+	worktree->lock_reason = NULL;
+	worktree->lock_reason_valid = 0;
 
 done:
 	strbuf_release(&path);
@@ -234,6 +239,29 @@ int is_main_worktree(const struct worktree *wt)
 	return !wt->id;
 }
 
+const char *is_worktree_locked(struct worktree *wt)
+{
+	assert(!is_main_worktree(wt));
+
+	if (!wt->lock_reason_valid) {
+		struct strbuf path = STRBUF_INIT;
+
+		strbuf_addstr(&path, worktree_git_path(wt, "locked"));
+		if (file_exists(path.buf)) {
+			struct strbuf lock_reason = STRBUF_INIT;
+			if (strbuf_read_file(&lock_reason, path.buf, 0) < 0)
+				die_errno(_("failed to read '%s'"), path.buf);
+			strbuf_trim(&lock_reason);
+			wt->lock_reason = strbuf_detach(&lock_reason, NULL);
+		} else
+			wt->lock_reason = NULL;
+		wt->lock_reason_valid = 1;
+		strbuf_release(&path);
+	}
+
+	return wt->lock_reason;
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
 			      const char *target)
 {
worktree.h +8 -0
index e1c4715..90e1311 100644
@@ -5,10 +5,12 @@ struct worktree {
 	char *path;
 	char *id;
 	char *head_ref;
+	char *lock_reason;	/* internal use */
 	unsigned char head_sha1[20];
 	int is_detached;
 	int is_bare;
 	int is_current;
+	int lock_reason_valid;
 };
 
 /* Functions for acting on the information about worktrees. */
@@ -42,6 +44,12 @@ extern struct worktree *find_worktree(struct worktree **list,
  */
 extern int is_main_worktree(const struct worktree *wt);
 
+/*
+ * Return the reason string if the given worktree is locked or NULL
+ * otherwise.
+ */
+extern const char *is_worktree_locked(struct worktree *wt);
+
 /*
  * Free up the memory for worktree(s)
  */
Signature · 1 line
2.8.2.524.g6ff3d78
#148 lore raw

PATCH v5 4/6 worktree: add "lock" command

In reply to Nguyễn Thái Ngọc Duy
---
 Documentation/git-worktree.txt         | 26 +++++++++++++-----
 builtin/worktree.c                     | 38 +++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  5 +++-
 t/t2028-worktree-move.sh (new +x)      | 48 ++++++++++++++++++++++++++++++++++
 4 files changed, 110 insertions(+), 7 deletions(-)
 create mode 100755 t/t2028-worktree-move.sh
Documentation/git-worktree.txt +20 -6
index 27feff6..b49b25b 100644
@@ -11,6 +11,7 @@ SYNOPSIS
 [verse]
 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]
 'git worktree list' [--porcelain]
+'git worktree lock' [--reason <string>] <worktree>
 'git worktree prune' [-n] [-v] [--expire <expire>]
 
 DESCRIPTION
@@ -38,9 +39,8 @@ section "DETAILS" for more information.
 
 If a linked working tree is stored on a portable device or network share
 which is not always mounted, you can prevent its administrative files from
-being pruned by creating a file named 'locked' alongside the other
-administrative files, optionally containing a plain text reason that
-pruning should be suppressed. See section "DETAILS" for more information.
+being pruned by issuing the `git worktree lock` command, optionally
+specifying `--reason` to explain why the working tree is locked.
 
 COMMANDS
 --------
@@ -61,6 +61,14 @@ each of the linked worktrees.  The output details include if the worktree is
 bare, the revision currently checked out, and the branch currently checked out
 (or 'detached HEAD' if none).
 
+lock::
+
+If a working tree is on a portable device or network share which
+is not always mounted, lock it to prevent its administrative
+files from being pruned automatically. This also prevents it from
+being moved or deleted. Optionally, specify a reason for the lock
+with `--reason`.
+
 prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
@@ -110,6 +118,13 @@ OPTIONS
 --expire <time>::
 	With `prune`, only expire unused working trees older than <time>.
 
+--reason <string>::
+	With `lock`, an explanation why the working tree is locked.
+
+<worktree>::
+	Working trees can be identified by path, either relative or
+	absolute.
+
 DETAILS
 -------
 Each linked working tree has a private sub-directory in the repository's
@@ -150,7 +165,8 @@ instead.
 
 To prevent a $GIT_DIR/worktrees entry 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
+entry's working tree is stored on a portable device), use the
+`git worktree lock` command, which adds 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
@@ -226,8 +242,6 @@ performed manually, such as:
 - `remove` to remove a linked working tree and its administrative files (and
   warn if the working tree is dirty)
 - `mv` to move or rename a working tree and update its administrative files
-- `lock` to prevent automatic pruning of administrative files (for instance,
-  for a working tree on a portable device)
 
 GIT
 ---
builtin/worktree.c +38 -0
index f9dac37..3b9220f 100644
@@ -14,6 +14,7 @@
 static const char * const worktree_usage[] = {
 	N_("git worktree add [<options>] <path> [<branch>]"),
 	N_("git worktree list [<options>]"),
+	N_("git worktree lock [<options>] <path>"),
 	N_("git worktree prune [<options>]"),
 	NULL
 };
@@ -459,6 +460,41 @@ static int list(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static int lock_worktree(int ac, const char **av, const char *prefix)
+{
+	const char *reason = "", *old_reason;
+	struct option options[] = {
+		OPT_STRING(0, "reason", &reason, N_("string"),
+			   N_("reason for locking")),
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (ac != 1)
+		usage_with_options(worktree_usage, options);
+
+	worktrees = get_worktrees();
+	wt = find_worktree(worktrees, prefix, av[0]);
+	if (!wt)
+		die(_("'%s' is not a working tree"), av[0]);
+	if (is_main_worktree(wt))
+		die(_("The main working tree cannot be locked or unlocked"));
+
+	old_reason = is_worktree_locked(wt);
+	if (old_reason) {
+		if (*old_reason)
+			die(_("'%s' is already locked, reason: %s"),
+			    av[0], old_reason);
+		die(_("'%s' is already locked"), av[0]);
+	}
+
+	write_file(git_common_path("worktrees/%s/locked", wt->id),
+		   "%s", reason);
+	free_worktrees(worktrees);
+	return 0;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -475,5 +511,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 		return prune(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "list"))
 		return list(ac - 1, av + 1, prefix);
+	if (!strcmp(av[1], "lock"))
+		return lock_worktree(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
 }
contrib/completion/git-completion.bash +4 -1
index 951a186..f88727d 100644
@@ -2597,7 +2597,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list prune"
+	local subcommands="add list lock prune"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
@@ -2609,6 +2609,9 @@ _git_worktree ()
 		list,--*)
 			__gitcomp "--porcelain"
 			;;
+		lock,--*)
+			__gitcomp "--reason"
+			;;
 		prune,--*)
 			__gitcomp "--dry-run --expire --verbose"
 			;;
t/t2028-worktree-move.sh new file +48 -0

48 lines not on this page · raw message

Signature · 1 line
2.8.2.524.g6ff3d78
#149 lore raw

PATCH v5 5/6 worktree: add "unlock" command

In reply to Nguyễn Thái Ngọc Duy
---
 Documentation/git-worktree.txt         |  5 +++++
 builtin/worktree.c                     | 28 ++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  2 +-
 t/t2028-worktree-move.sh               | 14 ++++++++++++++
 4 files changed, 48 insertions(+), 1 deletion(-)
Documentation/git-worktree.txt +5 -0
index b49b25b..27330c5 100644
@@ -13,6 +13,7 @@ SYNOPSIS
 'git worktree list' [--porcelain]
 'git worktree lock' [--reason <string>] <worktree>
 'git worktree prune' [-n] [-v] [--expire <expire>]
+'git worktree unlock' <worktree>
 
 DESCRIPTION
 -----------
@@ -73,6 +74,10 @@ prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
 
+unlock::
+
+Unlock a working tree, allowing it to be pruned, moved or deleted.
+
 OPTIONS
 -------
 
builtin/worktree.c +28 -0
index 3b9220f..4877421 100644
@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {
 	N_("git worktree list [<options>]"),
 	N_("git worktree lock [<options>] <path>"),
 	N_("git worktree prune [<options>]"),
+	N_("git worktree unlock <path>"),
 	NULL
 };
 
@@ -495,6 +496,31 @@ static int lock_worktree(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static int unlock_worktree(int ac, const char **av, const char *prefix)
+{
+	struct option options[] = {
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+	int ret;
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (ac != 1)
+		usage_with_options(worktree_usage, options);
+
+	worktrees = get_worktrees();
+	wt = find_worktree(worktrees, prefix, av[0]);
+	if (!wt)
+		die(_("'%s' is not a working tree"), av[0]);
+	if (is_main_worktree(wt))
+		die(_("The main working tree cannot be locked or unlocked"));
+	if (!is_worktree_locked(wt))
+		die(_("'%s' is not locked"), av[0]);
+	ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
+	free_worktrees(worktrees);
+	return ret;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -513,5 +539,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 		return list(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "lock"))
 		return lock_worktree(ac - 1, av + 1, prefix);
+	if (!strcmp(av[1], "unlock"))
+		return unlock_worktree(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
 }
contrib/completion/git-completion.bash +1 -1
index f88727d..0e3841d 100644
@@ -2597,7 +2597,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list lock prune"
+	local subcommands="add list lock prune unlock"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
t/t2028-worktree-move.sh +14 -0
index 87afc2e..68d3fe8 100755
@@ -45,4 +45,18 @@ test_expect_success 'lock worktree twice (from the locked worktree)' '
 	test_cmp expected .git/worktrees/source/locked
 '
 
+test_expect_success 'unlock main worktree' '
+	test_must_fail git worktree unlock .
+'
+
+test_expect_success 'unlock linked worktree' '
+	git worktree unlock source &&
+	test_path_is_missing .git/worktrees/source/locked
+'
+
+test_expect_success 'unlock worktree twice' '
+	test_must_fail git worktree unlock source &&
+	test_path_is_missing .git/worktrees/source/locked
+'
+
 test_done
Signature · 1 line
2.8.2.524.g6ff3d78
#150 lore raw

PATCH v5 6/6 worktree.c: find_worktree() search by path suffix

In reply to Nguyễn Thái Ngọc Duy

This allows the user to do something like "worktree lock foo" or "worktree lock to/foo" instead of "worktree lock /long/path/to/foo" if it's unambiguous.

With completion support it could be quite convenient. While this base name search can be done in the same worktree iteration loop, the code is split into a separate function for clarity.

---
 Documentation/git-worktree.txt |  5 +++++
 worktree.c                     | 29 +++++++++++++++++++++++++++++
 2 files changed, 34 insertions(+)
Documentation/git-worktree.txt +5 -0
index 27330c5..7850dee 100644
@@ -129,6 +129,11 @@ OPTIONS
 <worktree>::
 	Working trees can be identified by path, either relative or
 	absolute.
++
+If the last path components in the working tree's path is unique among
+working trees, it can be used to identify worktrees. For example if
+you only have to working trees at "/abc/def/ghi" and "/abc/def/ggg",
+then "ghi" or "def/ghi" is enough to point to the former working tree.
 
 DETAILS
 -------
worktree.c +29 -0
index 2bcfff3..2107c06 100644
@@ -219,12 +219,41 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
+static struct worktree *find_worktree_by_suffix(struct worktree **list,
+						const char *suffix)
+{
+	struct worktree *found = NULL;
+	int nr_found = 0, suffixlen;
+
+	suffixlen = strlen(suffix);
+	if (!suffixlen)
+		return NULL;
+
+	for (; *list && nr_found < 2; list++) {
+		const char	*path	 = (*list)->path;
+		int		 pathlen = strlen(path);
+		int		 start	 = pathlen - suffixlen;
+
+		/* suffix must start at directory boundary */
+		if ((!start || (start > 0 && is_dir_sep(path[start - 1]))) &&
+		    !fspathcmp(suffix, path + start)) {
+			found = *list;
+			nr_found++;
+		}
+	}
+	return nr_found == 1 ? found : NULL;
+}
+
 struct worktree *find_worktree(struct worktree **list,
 			       const char *prefix,
 			       const char *arg)
 {
+	struct worktree *wt;
 	char *path;
 
+	if ((wt = find_worktree_by_suffix(list, arg)))
+		return wt;
+
 	arg = prefix_filename(prefix, strlen(prefix), arg);
 	path = xstrdup(real_path(arg));
 	for (; *list; list++)
Signature · 1 line
2.8.2.524.g6ff3d78
#151 lore raw

Re: [PATCH v4 0/6] worktree lock/unlock

In reply to Nguyễn Thái Ngọc Duy
Quoted 1 line · Nguyễn Thái Ngọc Duy

Minor problem:
t2028 fails, when the test is run from a directory that is

a softlink.
(In my case
/Users/tb/projects/git/git.pu
is a softlink to
/Users/tb/NoBackup/projects/git/git.pu/

[master (root-commit) 2519212] init
Author: A U Thor <[email protected]>

 1 file changed, 1 insertion(+)
 create mode 100644 init.t

Preparing source (identifier source)
HEAD is now at 2519212 init

actual 2016-06-27 09:50:19.000000000 +0000 renamed from expected 2016-06-27 09:50:19.000000000 +0000 +2 -2
@@ -1,2 +1,2 @@
-worktree /Users/tb/projects/git/git.pu/t/trash directory.t2028-worktree-move
-worktree /Users/tb/projects/git/git.pu/t/trash directory.t2028-worktree-move/source
+worktree /Users/tb/NoBackup/projects/git/git.pu/t/trash directory.t2028-worktree-move
+worktree /Users/tb/NoBackup/projects/git/git.pu/t/trash directory.t2028-worktree-move/source

not ok 1 - setup
#

#152 lore raw

PATCH 1/1 fixup! worktree: add "lock" command

---
Torsten, this seems to fix the symlink problem for me. How many times have I got similar reports from you and still managed to forget ...

 t/t2028-worktree-move.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
t/t2028-worktree-move.sh +2 -2
index 68d3fe8..8298aaf 100755
@@ -9,8 +9,8 @@ test_expect_success 'setup' '
 	git worktree add source &&
 	git worktree list --porcelain | grep "^worktree" >actual &&
 	cat <<-EOF >expected &&
-	worktree $TRASH_DIRECTORY
-	worktree $TRASH_DIRECTORY/source
+	worktree $(pwd)
+	worktree $(pwd)/source
 	EOF
 	test_cmp expected actual
 '
Signature · 1 line
2.8.2.531.gd073806
#153 lore raw

PATCH v3 6/13 path.c: refactor and add worktree_git_path()

In reply to Nguyễn Thái Ngọc Duy

do_git_path(), which is the common code for all git_path* functions, is modified to take a worktree struct and can produce paths for any worktree.

worktree_git_path() is the first function that makes use of this. It can be used to write code that can examine any worktree. For example, wt_status_get_state() will be converted using this to take am/rebase/... state of any worktree.

---
 path.c     | 24 ++++++++++++++++++------
 worktree.h |  8 ++++++++
 2 files changed, 26 insertions(+), 6 deletions(-)
path.c +18 -6
index 2ebb23d..8fdd187 100644
@@ -5,6 +5,7 @@
 #include "strbuf.h"
 #include "string-list.h"
 #include "dir.h"
+#include "worktree.h"
 
 static int get_st_mode_bits(const char *path, int *mode)
 {
@@ -383,10 +384,11 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 		update_common_dir(buf, git_dir_len, NULL);
 }
 
-static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
+static void do_git_path(const struct worktree *wt, struct strbuf *buf,
+			const char *fmt, va_list args)
 {
 	int gitdir_len;
-	strbuf_addstr(buf, get_git_dir());
+	strbuf_addstr(buf, get_worktree_git_dir(wt));
 	if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
 		strbuf_addch(buf, '/');
 	gitdir_len = buf->len;
@@ -400,7 +402,7 @@ char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
 	va_list args;
 	strbuf_reset(buf);
 	va_start(args, fmt);
-	do_git_path(buf, fmt, args);
+	do_git_path(NULL, buf, fmt, args);
 	va_end(args);
 	return buf->buf;
 }
@@ -409,7 +411,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 {
 	va_list args;
 	va_start(args, fmt);
-	do_git_path(sb, fmt, args);
+	do_git_path(NULL, sb, fmt, args);
 	va_end(args);
 }
 
@@ -418,7 +420,7 @@ const char *git_path(const char *fmt, ...)
 	struct strbuf *pathname = get_pathname();
 	va_list args;
 	va_start(args, fmt);
-	do_git_path(pathname, fmt, args);
+	do_git_path(NULL, pathname, fmt, args);
 	va_end(args);
 	return pathname->buf;
 }
@@ -428,7 +430,7 @@ char *git_pathdup(const char *fmt, ...)
 	struct strbuf path = STRBUF_INIT;
 	va_list args;
 	va_start(args, fmt);
-	do_git_path(&path, fmt, args);
+	do_git_path(NULL, &path, fmt, args);
 	va_end(args);
 	return strbuf_detach(&path, NULL);
 }
@@ -454,6 +456,16 @@ const char *mkpath(const char *fmt, ...)
 	return cleanup_path(pathname->buf);
 }
 
+const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)
+{
+	struct strbuf *pathname = get_pathname();
+	va_list args;
+	va_start(args, fmt);
+	do_git_path(wt, pathname, fmt, args);
+	va_end(args);
+	return pathname->buf;
+}
+
 static void do_submodule_path(struct strbuf *buf, const char *path,
 			      const char *fmt, va_list args)
 {
worktree.h +8 -0
index ccdf69a..0da8c1f 100644
@@ -42,4 +42,12 @@ extern void free_worktrees(struct worktree **);
 extern const struct worktree *find_shared_symref(const char *symref,
 						 const char *target);
 
+/*
+ * Similar to git_path() but can produce paths for a specified
+ * worktree instead of current one
+ */
+extern const char *worktree_git_path(const struct worktree *wt,
+				     const char *fmt, ...)
+	__attribute__((format (printf, 2, 3)));
+
 #endif
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#154 lore raw

PATCH v3 7/13 wt-status.c: split rebase detection out of wt_status_get_state()

In reply to Nguyễn Thái Ngọc Duy

worktree.c:find_shared_symref() later needs to know if a branch is being rebased, and only rebase, no cherry-pick, do detached branch... Split this code so it can be used independently from other in-progress tests.

---
 wt-status.c | 23 +++++++++++++++++------
 wt-status.h |  1 +
 2 files changed, 18 insertions(+), 6 deletions(-)
wt-status.c +17 -6
index 1ea2ebe..ec9240d 100644
@@ -1360,15 +1360,11 @@ static void wt_status_get_detached_from(struct wt_status_state *state)
 	strbuf_release(&cb.buf);
 }
 
-void wt_status_get_state(struct wt_status_state *state,
-			 int get_detached_from)
+int wt_status_check_rebase(struct wt_status_state *state)
 {
 	struct stat st;
-	unsigned char sha1[20];
 
-	if (!stat(git_path_merge_head(), &st)) {
-		state->merge_in_progress = 1;
-	} else if (!stat(git_path("rebase-apply"), &st)) {
+	if (!stat(git_path("rebase-apply"), &st)) {
 		if (!stat(git_path("rebase-apply/applying"), &st)) {
 			state->am_in_progress = 1;
 			if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
@@ -1385,6 +1381,21 @@ void wt_status_get_state(struct wt_status_state *state,
 			state->rebase_in_progress = 1;
 		state->branch = read_and_strip_branch("rebase-merge/head-name");
 		state->onto = read_and_strip_branch("rebase-merge/onto");
+	} else
+		return 0;
+	return 1;
+}
+
+void wt_status_get_state(struct wt_status_state *state,
+			 int get_detached_from)
+{
+	struct stat st;
+	unsigned char sha1[20];
+
+	if (!stat(git_path_merge_head(), &st)) {
+		state->merge_in_progress = 1;
+	} else if (wt_status_check_rebase(state)) {
+		;		/* all set */
 	} else if (!stat(git_path_cherry_pick_head(), &st) &&
 			!get_sha1("CHERRY_PICK_HEAD", sha1)) {
 		state->cherry_pick_in_progress = 1;
wt-status.h +1 -0
index c9b3b74..b398353 100644
@@ -100,6 +100,7 @@ void wt_status_prepare(struct wt_status *s);
 void wt_status_print(struct wt_status *s);
 void wt_status_collect(struct wt_status *s);
 void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
+int wt_status_check_rebase(struct wt_status_state *state);
 
 void wt_shortstatus_print(struct wt_status *s);
 void wt_porcelain_print(struct wt_status *s);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#155 lore raw

PATCH v3 8/13 wt-status.c: make wt_status_check_rebase() work on any worktree

In reply to Nguyễn Thái Ngọc Duy

This is a preparation step for find_shared_symref() to detect if any worktree is being rebased.

---
 wt-status.c | 33 ++++++++++++++++++++-------------
 wt-status.h |  5 ++++-
 2 files changed, 24 insertions(+), 14 deletions(-)
wt-status.c +20 -13
index ec9240d..ce5080c 100644
@@ -15,6 +15,7 @@
 #include "column.h"
 #include "strbuf.h"
 #include "utf8.h"
+#include "worktree.h"
 
 static const char cut_line[] =
 "------------------------ >8 ------------------------\n";
@@ -1262,13 +1263,13 @@ static void show_bisect_in_progress(struct wt_status *s,
 /*
  * Extract branch information from rebase/bisect
  */
-static char *read_and_strip_branch(const char *path)
+static char *get_branch(const struct worktree *wt, const char *path)
 {
 	struct strbuf sb = STRBUF_INIT;
 	unsigned char sha1[20];
 	const char *branch_name;
 
-	if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0)
+	if (strbuf_read_file(&sb, worktree_git_path(wt, "%s", path), 0) <= 0)
 		goto got_nothing;
 
 	while (sb.len && sb.buf[sb.len - 1] == '\n')
@@ -1295,6 +1296,11 @@ got_nothing:
 	return NULL;
 }
 
+static char *read_and_strip_branch(const char *path)
+{
+	return get_branch(NULL, path);
+}
+
 struct grab_1st_switch_cbdata {
 	struct strbuf buf;
 	unsigned char nsha1[20];
@@ -1360,27 +1366,28 @@ static void wt_status_get_detached_from(struct wt_status_state *state)
 	strbuf_release(&cb.buf);
 }
 
-int wt_status_check_rebase(struct wt_status_state *state)
+int wt_status_check_rebase(const struct worktree *wt,
+			   struct wt_status_state *state)
 {
 	struct stat st;
 
-	if (!stat(git_path("rebase-apply"), &st)) {
-		if (!stat(git_path("rebase-apply/applying"), &st)) {
+	if (!stat(worktree_git_path(wt, "rebase-apply"), &st)) {
+		if (!stat(worktree_git_path(wt, "rebase-apply/applying"), &st)) {
 			state->am_in_progress = 1;
-			if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
+			if (!stat(worktree_git_path(wt, "rebase-apply/patch"), &st) && !st.st_size)
 				state->am_empty_patch = 1;
 		} else {
 			state->rebase_in_progress = 1;
-			state->branch = read_and_strip_branch("rebase-apply/head-name");
-			state->onto = read_and_strip_branch("rebase-apply/onto");
+			state->branch = get_branch(wt, "rebase-apply/head-name");
+			state->onto = get_branch(wt, "rebase-apply/onto");
 		}
-	} else if (!stat(git_path("rebase-merge"), &st)) {
-		if (!stat(git_path("rebase-merge/interactive"), &st))
+	} else if (!stat(worktree_git_path(wt, "rebase-merge"), &st)) {
+		if (!stat(worktree_git_path(wt, "rebase-merge/interactive"), &st))
 			state->rebase_interactive_in_progress = 1;
 		else
 			state->rebase_in_progress = 1;
-		state->branch = read_and_strip_branch("rebase-merge/head-name");
-		state->onto = read_and_strip_branch("rebase-merge/onto");
+		state->branch = get_branch(wt, "rebase-merge/head-name");
+		state->onto = get_branch(wt, "rebase-merge/onto");
 	} else
 		return 0;
 	return 1;
@@ -1394,7 +1401,7 @@ void wt_status_get_state(struct wt_status_state *state,
 
 	if (!stat(git_path_merge_head(), &st)) {
 		state->merge_in_progress = 1;
-	} else if (wt_status_check_rebase(state)) {
+	} else if (wt_status_check_rebase(NULL, state)) {
 		;		/* all set */
 	} else if (!stat(git_path_cherry_pick_head(), &st) &&
 			!get_sha1("CHERRY_PICK_HEAD", sha1)) {
wt-status.h +4 -1
index b398353..c4ddcad 100644
@@ -6,6 +6,8 @@
 #include "color.h"
 #include "pathspec.h"
 
+struct worktree;
+
 enum color_wt_status {
 	WT_STATUS_HEADER = 0,
 	WT_STATUS_UPDATED,
@@ -100,7 +102,8 @@ void wt_status_prepare(struct wt_status *s);
 void wt_status_print(struct wt_status *s);
 void wt_status_collect(struct wt_status *s);
 void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
-int wt_status_check_rebase(struct wt_status_state *state);
+int wt_status_check_rebase(const struct worktree *wt,
+			   struct wt_status_state *state);
 
 void wt_shortstatus_print(struct wt_status *s);
 void wt_porcelain_print(struct wt_status *s);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#156 lore raw

PATCH v3 9/13 worktree.c: avoid referencing to worktrees[i] multiple times

In reply to Nguyễn Thái Ngọc Duy
---
 worktree.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
worktree.c +4 -2
index dc092a7..927905b 100644
@@ -229,10 +229,12 @@ const struct worktree *find_shared_symref(const char *symref,
 	worktrees = get_worktrees();
 
 	for (i = 0; worktrees[i]; i++) {
+		struct worktree *wt = worktrees[i];
+
 		strbuf_reset(&path);
 		strbuf_reset(&sb);
 		strbuf_addf(&path, "%s/%s",
-			    get_worktree_git_dir(worktrees[i]),
+			    get_worktree_git_dir(wt),
 			    symref);
 
 		if (parse_ref(path.buf, &sb, NULL)) {
@@ -240,7 +242,7 @@ const struct worktree *find_shared_symref(const char *symref,
 		}
 
 		if (!strcmp(sb.buf, target)) {
-			existing = worktrees[i];
+			existing = wt;
 			break;
 		}
 	}
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#157 lore raw

PATCH v3 10/13 worktree.c: check whether branch is rebased in another worktree

In reply to Nguyễn Thái Ngọc Duy

This function find_shared_symref() is used in a couple places:

1) in builtin/branch.c: it's used to detect if a branch is checked out elsewhere and refuse to delete the branch.

2) in builtin/notes.c: it's used to detect if a note is being merged in another worktree

3) in branch.c, the function die_if_checked_out() is actually used by "git checkout" and "git worktree add" to see if a branch is already checked out elsewhere and refuse the operation.

In cases 1 and 3, if a rebase is ongoing, "HEAD" will be in detached mode, find_shared_symref() fails to detect it and declares "no branch is checked out here", which is not really what we want.

This patch tightens the test. If the given symref is "HEAD", we try to detect if rebase is ongoing. If so return the branch being rebased. This makes checkout and branch delete operations safer because you can't checkout a branch being rebased in another place, or delete it.

Special case for checkout. If the current branch is being rebased, git-rebase.sh may use "git checkout" to abort and return back to the original branch. The updated test in find_shared_symref() will prevent that and "git rebase --abort" will fail as a result. find_shared_symref() and die_if_checked_out() have to learn a new option ignore_current_worktree to loosen the test a bit.

---
 branch.c                |  4 ++--
 branch.h                |  2 +-
 builtin/checkout.c      |  2 +-
 builtin/worktree.c      |  4 ++--
 t/t2025-worktree-add.sh | 38 ++++++++++++++++++++++++++++++++++++++
 worktree.c              | 32 ++++++++++++++++++++++++++++++++
 6 files changed, 76 insertions(+), 6 deletions(-)
branch.c +2 -2
index 1f1fbf5..a5a8dcb 100644
@@ -334,12 +334,12 @@ void remove_branch_state(void)
 	unlink(git_path_squash_msg());
 }
 
-void die_if_checked_out(const char *branch)
+void die_if_checked_out(const char *branch, int ignore_current_worktree)
 {
 	const struct worktree *wt;
 
 	wt = find_shared_symref("HEAD", branch);
-	if (!wt)
+	if (!wt || (ignore_current_worktree && wt->is_current))
 		return;
 	skip_prefix(branch, "refs/heads/", &branch);
 	die(_("'%s' is already checked out at '%s'"),
branch.h +1 -1
index d69163d..b2f9649 100644
@@ -58,7 +58,7 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);
  * worktree and die (with a message describing its checkout location) if
  * it is.
  */
-extern void die_if_checked_out(const char *branch);
+extern void die_if_checked_out(const char *branch, int ignore_current_worktree);
 
 /*
  * Update all per-worktree HEADs pointing at the old ref to point the new ref.
builtin/checkout.c +1 -1
index efcbd8f..6041718 100644
@@ -1111,7 +1111,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->path);
+			die_if_checked_out(new->path, 1);
 		free(head_ref);
 	}
 
builtin/worktree.c +2 -2
index d8e3795..12c0af7 100644
@@ -205,7 +205,7 @@ static int add_worktree(const char *path, const char *refname,
 	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);
+			die_if_checked_out(symref.buf, 0);
 	} else { /* must be a commit */
 		commit = lookup_commit_reference_by_name(refname);
 		if (!commit)
@@ -349,7 +349,7 @@ static int add(int ac, const char **av, const char *prefix)
 		if (!opts.force &&
 		    !strbuf_check_branch_ref(&symref, opts.new_branch) &&
 		    ref_exists(symref.buf))
-			die_if_checked_out(symref.buf);
+			die_if_checked_out(symref.buf, 0);
 		strbuf_release(&symref);
 	}
 
t/t2025-worktree-add.sh +38 -0
index 3acb992..da54327 100755
@@ -4,6 +4,8 @@ test_description='test git worktree add'
 
 . ./test-lib.sh
 
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
 test_expect_success 'setup' '
 	test_commit init
 '
@@ -225,4 +227,40 @@ test_expect_success '"add" worktree with --checkout' '
 	test_cmp init.t swamp2/init.t
 '
 
+test_expect_success 'put a worktree under rebase' '
+	git worktree add under-rebase &&
+	(
+		cd under-rebase &&
+		set_fake_editor &&
+		FAKE_LINES="edit 1" git rebase -i HEAD^ &&
+		git worktree list | grep "under-rebase.*detached HEAD"
+	)
+'
+
+test_expect_success 'add a worktree, checking out a rebased branch' '
+	test_must_fail git worktree add new-rebase under-rebase &&
+	! test -d new-rebase
+'
+
+test_expect_success 'checking out a rebased branch from another worktree' '
+	git worktree add new-place &&
+	test_must_fail git -C new-place checkout under-rebase
+'
+
+test_expect_success 'not allow to delete a branch under rebase' '
+	(
+		cd under-rebase &&
+		test_must_fail git branch -D under-rebase
+	)
+'
+
+test_expect_success 'check out from current worktree branch ok' '
+	(
+		cd under-rebase &&
+		git checkout under-rebase &&
+		git checkout - &&
+		git rebase --abort
+	)
+'
+
 test_done
worktree.c +32 -0
index 927905b..5043756 100644
@@ -3,6 +3,7 @@
 #include "strbuf.h"
 #include "worktree.h"
 #include "dir.h"
+#include "wt-status.h"
 
 void free_worktrees(struct worktree **worktrees)
 {
@@ -215,6 +216,30 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
+static int is_worktree_being_rebased(const struct worktree *wt,
+				     const char *target)
+{
+	struct wt_status_state state;
+	int found_rebase;
+
+	memset(&state, 0, sizeof(state));
+	found_rebase = wt_status_check_rebase(wt, &state) &&
+		((state.rebase_in_progress ||
+		  state.rebase_interactive_in_progress) &&
+		 state.branch &&
+		 starts_with(target, "refs/heads/") &&
+		 !strcmp(state.branch, target + strlen("refs/heads/")));
+	free(state.branch);
+	free(state.onto);
+	return found_rebase;
+}
+
+/*
+ * note: this function should be able to detect shared symref even if
+ * HEAD is temporarily detached (e.g. in the middle of rebase or
+ * bisect). New commands that do similar things should update this
+ * function as well.
+ */
 const struct worktree *find_shared_symref(const char *symref,
 					  const char *target)
 {
@@ -231,6 +256,13 @@ const struct worktree *find_shared_symref(const char *symref,
 	for (i = 0; worktrees[i]; i++) {
 		struct worktree *wt = worktrees[i];
 
+		if (wt->is_detached && !strcmp(symref, "HEAD")) {
+			if (is_worktree_being_rebased(wt, target)) {
+				existing = wt;
+				break;
+			}
+		}
+
 		strbuf_reset(&path);
 		strbuf_reset(&sb);
 		strbuf_addf(&path, "%s/%s",
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#158 lore raw

PATCH v3 11/13 wt-status.c: split bisect detection out of wt_status_get_state()

In reply to Nguyễn Thái Ngọc Duy

And make it work with any given worktree, in preparation for (again) find_shared_symref(). read_and_strip_branch() is deleted because it's no longer used.

---
 wt-status.c | 23 ++++++++++++++---------
 wt-status.h |  2 ++
 2 files changed, 16 insertions(+), 9 deletions(-)
wt-status.c +14 -9
index ce5080c..0032ef5 100644
@@ -1296,11 +1296,6 @@ got_nothing:
 	return NULL;
 }
 
-static char *read_and_strip_branch(const char *path)
-{
-	return get_branch(NULL, path);
-}
-
 struct grab_1st_switch_cbdata {
 	struct strbuf buf;
 	unsigned char nsha1[20];
@@ -1393,6 +1388,19 @@ int wt_status_check_rebase(const struct worktree *wt,
 	return 1;
 }
 
+int wt_status_check_bisect(const struct worktree *wt,
+			   struct wt_status_state *state)
+{
+	struct stat st;
+
+	if (!stat(worktree_git_path(wt, "BISECT_LOG"), &st)) {
+		state->bisect_in_progress = 1;
+		state->branch = get_branch(wt, "BISECT_START");
+		return 1;
+	}
+	return 0;
+}
+
 void wt_status_get_state(struct wt_status_state *state,
 			 int get_detached_from)
 {
@@ -1408,10 +1416,7 @@ void wt_status_get_state(struct wt_status_state *state,
 		state->cherry_pick_in_progress = 1;
 		hashcpy(state->cherry_pick_head_sha1, sha1);
 	}
-	if (!stat(git_path("BISECT_LOG"), &st)) {
-		state->bisect_in_progress = 1;
-		state->branch = read_and_strip_branch("BISECT_START");
-	}
+	wt_status_check_bisect(NULL, state);
 	if (!stat(git_path_revert_head(), &st) &&
 	    !get_sha1("REVERT_HEAD", sha1)) {
 		state->revert_in_progress = 1;
wt-status.h +2 -0
index c4ddcad..2ca93f6 100644
@@ -104,6 +104,8 @@ void wt_status_collect(struct wt_status *s);
 void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
 int wt_status_check_rebase(const struct worktree *wt,
 			   struct wt_status_state *state);
+int wt_status_check_bisect(const struct worktree *wt,
+			   struct wt_status_state *state);
 
 void wt_shortstatus_print(struct wt_status *s);
 void wt_porcelain_print(struct wt_status *s);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#159 lore raw

PATCH v3 12/13 worktree.c: check whether branch is bisected in another worktree

In reply to Nguyễn Thái Ngọc Duy

Similar to the rebase case, we want to detect if "HEAD" in some worktree is being bisected because

1) we do not want to checkout this branch in another worktree, after bisect is done it will want to go back to this branch

2) we do not want to delete the branch is either or git bisect will fail to return to the (long gone) branch

---
 t/t2025-worktree-add.sh | 13 +++++++++++++
 worktree.c              | 19 +++++++++++++++++++
 2 files changed, 32 insertions(+)
t/t2025-worktree-add.sh +13 -0
index da54327..8f53944 100755
@@ -263,4 +263,17 @@ test_expect_success 'check out from current worktree branch ok' '
 	)
 '
 
+test_expect_success 'checkout a branch under bisect' '
+	git worktree add under-bisect &&
+	(
+		cd under-bisect &&
+		git bisect start &&
+		git bisect bad &&
+		git bisect good HEAD~2 &&
+		git worktree list | grep "under-bisect.*detached HEAD" &&
+		test_must_fail git worktree add new-bisect under-bisect &&
+		! test -d new-bisect
+	)
+'
+
 test_done
worktree.c +19 -0
index 5043756..aab4b95 100644
@@ -234,6 +234,21 @@ static int is_worktree_being_rebased(const struct worktree *wt,
 	return found_rebase;
 }
 
+static int is_worktree_being_bisected(const struct worktree *wt,
+				      const char *target)
+{
+	struct wt_status_state state;
+	int found_rebase;
+
+	memset(&state, 0, sizeof(state));
+	found_rebase = wt_status_check_bisect(wt, &state) &&
+		state.branch &&
+		starts_with(target, "refs/heads/") &&
+		!strcmp(state.branch, target + strlen("refs/heads/"));
+	free(state.branch);
+	return found_rebase;
+}
+
 /*
  * note: this function should be able to detect shared symref even if
  * HEAD is temporarily detached (e.g. in the middle of rebase or
@@ -261,6 +276,10 @@ const struct worktree *find_shared_symref(const char *symref,
 				existing = wt;
 				break;
 			}
+			if (is_worktree_being_bisected(wt, target)) {
+				existing = wt;
+				break;
+			}
 		}
 
 		strbuf_reset(&path);
Signature · 1 line
2.8.0.rc0.210.gd302cd2
#160 lore raw

Re: [PATCH v3 12/13] worktree.c: check whether branch is bisected in another worktree

Quoted 8 lines · Nguyễn Thái Ngọc Duy

Similar to the rebase case, we want to detect if "HEAD" in some worktree is being bisected because

1) we do not want to checkout this branch in another worktree, after bisect is done it will want to go back to this branch

2) we do not want to delete the branch is either or git bisect will fail to return to the (long gone) branch

I'm very far behind with my reviews and I see that this series is already in 'next', so perhaps these comments are too late...

t/t2025-worktree-add.sh test_expect_success 'check out from current worktree branch ok' ' · line 271

Quoted 13 lines

---

t/t2025-worktree-add.sh +9 -0
@@ -263,4 +263,17 @@ test_expect_success 'check out from current worktree branch ok' '
+test_expect_success 'checkout a branch under bisect' '
+       git worktree add under-bisect &&
+       (
+               cd under-bisect &&
+               git bisect start &&
+               git bisect bad &&
+               git bisect good HEAD~2 &&
+               git worktree list | grep "under-bisect.*detached HEAD" &&
+               test_must_fail git worktree add new-bisect under-bisect &&

Nit: I realize that the checking 'worktree add' is sufficient, but it's a bit confusing to read in the commit message about how deleting the branch would be bad, but then see it testing only 'add'.

worktree.c static int is_worktree_being_rebased(const struct worktree *wt, · line 238

Quoted 10 lines
t/t2025-worktree-add.sh +3 -0
+               ! test -d new-bisect
+       )
+'
worktree.c +5 -0
@@ -234,6 +234,21 @@ static int is_worktree_being_rebased(const struct worktree *wt,
+static int is_worktree_being_bisected(const struct worktree *wt,
+                                     const char *target)
+{
+       struct wt_status_state state;
+       int found_rebase;

s/rebase/bisect/ perhaps?

Quoted 8 lines
worktree.c +8 -0
+       memset(&state, 0, sizeof(state));
+       found_rebase = wt_status_check_bisect(wt, &state) &&
+               state.branch &&
+               starts_with(target, "refs/heads/") &&
+               !strcmp(state.branch, target + strlen("refs/heads/"));
+       free(state.branch);
+       return found_rebase;
+}
#161 lore raw

PATCH v3 13/13 branch: do not rename a branch under bisect or rebase

In reply to Nguyễn Thái Ngọc Duy

The branch name in that case could be saved in rebase's head_name or bisect's BISECT_START files. Ideally we should try to update them as well. But it's trickier (*). Let's play safe and see if the user complains about inconveniences before doing that.

(*) If we do it, bisect and rebase need to provide an API to rename branches. We can't do it in worktree.c or builtin/branch.c because when other people change rebase/bisect code, they may not be aware of this code and accidentally break it (e.g. rename the branch file, or refer to the branch in new files). It's a lot more work.

---
 builtin/branch.c        | 25 +++++++++++++++++++++++++
 t/t2025-worktree-add.sh |  8 ++++++++
 worktree.c              |  8 ++++----
 worktree.h              |  3 +++
 4 files changed, 40 insertions(+), 4 deletions(-)
builtin/branch.c +25 -0
index bcde87d..b488c3f 100644
@@ -524,6 +524,29 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
 	ref_array_clear(&array);
 }
 
+static void reject_rebase_or_bisect_branch(const char *target)
+{
+	struct worktree **worktrees = get_worktrees();
+	int i;
+
+	for (i = 0; worktrees[i]; i++) {
+		struct worktree *wt = worktrees[i];
+
+		if (!wt->is_detached)
+			continue;
+
+		if (is_worktree_being_rebased(wt, target))
+			die(_("Branch %s is being rebased at %s"),
+			    target, wt->path);
+
+		if (is_worktree_being_bisected(wt, target))
+			die(_("Branch %s is being bisected at %s"),
+			    target, wt->path);
+	}
+
+	free_worktrees(worktrees);
+}
+
 static void rename_branch(const char *oldname, const char *newname, int force)
 {
 	struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
@@ -553,6 +576,8 @@ static void rename_branch(const char *oldname, const char *newname, int force)
 
 	validate_new_branchname(newname, &newref, force, clobber_head_ok);
 
+	reject_rebase_or_bisect_branch(oldref.buf);
+
 	strbuf_addf(&logmsg, "Branch: renamed %s to %s",
 		 oldref.buf, newref.buf);
 
t/t2025-worktree-add.sh +8 -0
index 8f53944..3a22fc5 100755
@@ -254,6 +254,10 @@ test_expect_success 'not allow to delete a branch under rebase' '
 	)
 '
 
+test_expect_success 'rename a branch under rebase not allowed' '
+	test_must_fail git branch -M under-rebase rebase-with-new-name
+'
+
 test_expect_success 'check out from current worktree branch ok' '
 	(
 		cd under-rebase &&
@@ -276,4 +280,8 @@ test_expect_success 'checkout a branch under bisect' '
 	)
 '
 
+test_expect_success 'rename a branch under bisect not allowed' '
+	test_must_fail git branch -M under-bisect bisect-with-new-name
+'
+
 test_done
worktree.c +4 -4
index aab4b95..4817d60 100644
@@ -216,8 +216,8 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return git_common_path("worktrees/%s", wt->id);
 }
 
-static int is_worktree_being_rebased(const struct worktree *wt,
-				     const char *target)
+int is_worktree_being_rebased(const struct worktree *wt,
+			      const char *target)
 {
 	struct wt_status_state state;
 	int found_rebase;
@@ -234,8 +234,8 @@ static int is_worktree_being_rebased(const struct worktree *wt,
 	return found_rebase;
 }
 
-static int is_worktree_being_bisected(const struct worktree *wt,
-				      const char *target)
+int is_worktree_being_bisected(const struct worktree *wt,
+			       const char *target)
 {
 	struct wt_status_state state;
 	int found_rebase;
worktree.h +3 -0
index 0da8c1f..1394909 100644
@@ -42,6 +42,9 @@ extern void free_worktrees(struct worktree **);
 extern const struct worktree *find_shared_symref(const char *symref,
 						 const char *target);
 
+int is_worktree_being_rebased(const struct worktree *wt, const char *target);
+int is_worktree_being_bisected(const struct worktree *wt, const char *target);
+
 /*
  * Similar to git_path() but can produce paths for a specified
  * worktree instead of current one
Signature · 1 line
2.8.0.rc0.210.gd302cd2