All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/20] "git worktree move" preview
@ 2016-02-03  9:35 Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 01/20] usage.c: move format processing out of die_errno() Nguyễn Thái Ngọc Duy
                   ` (20 more replies)
  0 siblings, 21 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

This is the rough first cut of "git worktree move" for early
feedback. The series adds "git worktree move" which can

 - move a linked worktree to another place
 - move .git repository away

The first use case is obvious. The second less so.

Main worktree has always been an odd ball because it does not always
behave like a linked worktree. You can't move it (because you'll be
moving the repo along), you don't know where it is from 'worktree
list' because it's not tracked the same way. The move of .git dir will
automatically convert the main worktree to linked one, making it equal
to all other worktrees.

At the code level, it should be noted that I import copy_file() from
busybox for whole directory copy that preserves file permission and
stuff. Either that or I need to rely on POSIX command "cp" or "mv".
But that would make Windows adaptation harder later on.

A little off topic. The first two patches adds sys_error() that, like
die_errno(), automatically do strerror(). There's a 38 patches series
to convert error("...: %s", ..., strerror(errno)) to use it. I think
it's a good idea. So unless somebody objects, I'll post it some time
later.

Nguyễn Thái Ngọc Duy (20):
  usage.c: move format processing out of die_errno()
  usage.c: add sys_error() that prints strerror() automatically
  path.c: add git_common_path() and strbuf_git_common_path()
  path.c: add is_git_path_shared()
  copy.c: add copy_dir_recursively()
  worktree.c: use is_dot_or_dotdot()
  worktree.c: store "id" instead of "git_dir"
  worktree.c: add clear_worktree()
  worktree.c: add find_worktree_by_path()
  worktree.c: add is_main_worktree()
  worktree.c: recognize no main worktree
  worktree.c: add update_worktree_location()
  worktree.c: add update_worktree_gitfile()
  worktree.c: add collect_per_worktree_git_paths()
  worktree: avoid 0{40}, too many zeroes, hard to read
  worktree: simplify prefixing paths
  worktree: add "move" commmand
  worktree: refactor add_worktree()
  worktree: move repo, simple case
  worktree: move repo, convert main worktree

 builtin/worktree.c                | 297 ++++++++++++++++++++++++------
 cache.h                           |   6 +
 copy.c                            | 371 ++++++++++++++++++++++++++++++++++++++
 git-compat-util.h                 |   1 +
 path.c                            |  56 +++++-
 t/t2028-worktree-move.sh (new +x) |  77 ++++++++
 usage.c                           |  35 +++-
 worktree.c                        | 161 +++++++++++++++--
 worktree.h                        |  42 ++++-
 9 files changed, 963 insertions(+), 83 deletions(-)
 create mode 100755 t/t2028-worktree-move.sh

-- 
2.7.0.377.g4cd97dd

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

* [PATCH 01/20] usage.c: move format processing out of die_errno()
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 02/20] usage.c: add sys_error() that prints strerror() automatically Nguyễn Thái Ngọc Duy
                   ` (19 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 usage.c | 25 +++++++++++++++----------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/usage.c b/usage.c
index 82ff131..0dba0c5 100644
--- a/usage.c
+++ b/usage.c
@@ -109,19 +109,12 @@ void NORETURN die(const char *err, ...)
 	va_end(params);
 }
 
-void NORETURN die_errno(const char *fmt, ...)
+static const char *fmt_with_err(const char *fmt)
 {
-	va_list params;
-	char fmt_with_err[1024];
+	static char fmt_with_err[1024];
 	char str_error[256], *err;
 	int i, j;
 
-	if (die_is_recursing()) {
-		fputs("fatal: recursion detected in die_errno handler\n",
-			stderr);
-		exit(128);
-	}
-
 	err = strerror(errno);
 	for (i = j = 0; err[i] && j < sizeof(str_error) - 1; ) {
 		if ((str_error[j++] = err[i++]) != '%')
@@ -137,9 +130,21 @@ void NORETURN die_errno(const char *fmt, ...)
 	}
 	str_error[j] = 0;
 	snprintf(fmt_with_err, sizeof(fmt_with_err), "%s: %s", fmt, str_error);
+	return fmt_with_err;
+}
+
+void NORETURN die_errno(const char *fmt, ...)
+{
+	va_list params;
+
+	if (die_is_recursing()) {
+		fputs("fatal: recursion detected in die_errno handler\n",
+			stderr);
+		exit(128);
+	}
 
 	va_start(params, fmt);
-	die_routine(fmt_with_err, params);
+	die_routine(fmt_with_err(fmt), params);
 	va_end(params);
 }
 
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 02/20] usage.c: add sys_error() that prints strerror() automatically
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 01/20] usage.c: move format processing out of die_errno() Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 03/20] path.c: add git_common_path() and strbuf_git_common_path() Nguyễn Thái Ngọc Duy
                   ` (18 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-compat-util.h |  1 +
 usage.c           | 10 ++++++++++
 2 files changed, 11 insertions(+)

diff --git a/git-compat-util.h b/git-compat-util.h
index e8f2867..f2bd775 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -398,6 +398,7 @@ extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf,
 extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern NORETURN void die_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
+extern int sys_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
 
 #ifndef NO_OPENSSL
diff --git a/usage.c b/usage.c
index 0dba0c5..e7c37f2 100644
--- a/usage.c
+++ b/usage.c
@@ -148,6 +148,16 @@ void NORETURN die_errno(const char *fmt, ...)
 	va_end(params);
 }
 
+int sys_error(const char *fmt, ...)
+{
+	va_list params;
+
+	va_start(params, fmt);
+	error_routine(fmt_with_err(fmt), params);
+	va_end(params);
+	return -1;
+}
+
 #undef error
 int error(const char *err, ...)
 {
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 03/20] path.c: add git_common_path() and strbuf_git_common_path()
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 01/20] usage.c: move format processing out of die_errno() Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 02/20] usage.c: add sys_error() that prints strerror() automatically Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 04/20] path.c: add is_git_path_shared() Nguyễn Thái Ngọc Duy
                   ` (17 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 cache.h |  3 +++
 path.c  | 29 +++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)

diff --git a/cache.h b/cache.h
index dfc459c..ec71181 100644
--- a/cache.h
+++ b/cache.h
@@ -747,11 +747,14 @@ extern int 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,
diff --git a/path.c b/path.c
index 3cd155e..08ff346 100644
--- a/path.c
+++ b/path.c
@@ -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;
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 04/20] path.c: add is_git_path_shared()
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (2 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 03/20] path.c: add git_common_path() and strbuf_git_common_path() Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 05/20] copy.c: add copy_dir_recursively() Nguyễn Thái Ngọc Duy
                   ` (16 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 cache.h |  2 ++
 path.c  | 27 ++++++++++++++++++++++-----
 2 files changed, 24 insertions(+), 5 deletions(-)

diff --git a/cache.h b/cache.h
index ec71181..c75d13f 100644
--- a/cache.h
+++ b/cache.h
@@ -748,6 +748,8 @@ extern int 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 int is_git_path_shared(const char *path);
+
 
 extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	__attribute__((format (printf, 3, 4)));
diff --git a/path.c b/path.c
index 08ff346..9e350e7 100644
--- a/path.c
+++ b/path.c
@@ -335,15 +335,18 @@ static int check_common(const char *unmatched, void *value, void *baton)
 	return 0;
 }
 
-static void update_common_dir(struct strbuf *buf, int git_dir_len,
-			      const char *common_dir)
+static int update_common_dir(struct strbuf *buf, int git_dir_len,
+			     const char *common_dir)
 {
 	char *base = buf->buf + git_dir_len;
 	init_common_trie();
 	if (!common_dir)
 		common_dir = get_git_common_dir();
-	if (trie_find(&common_trie, base, check_common, NULL) > 0)
+	if (trie_find(&common_trie, base, check_common, NULL) > 0) {
 		replace_dir(buf, git_dir_len, common_dir);
+		return 1;
+	}
+	return 0;
 }
 
 void report_linked_checkout_garbage(void)
@@ -368,7 +371,7 @@ void report_linked_checkout_garbage(void)
 	strbuf_release(&sb);
 }
 
-static void adjust_git_path(struct strbuf *buf, int git_dir_len)
+static int adjust_git_path(struct strbuf *buf, int git_dir_len)
 {
 	const char *base = buf->buf + git_dir_len;
 	if (git_graft_env && is_dir_file(base, "info", "grafts"))
@@ -380,7 +383,10 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 	else if (git_db_env && dir_prefix(base, "objects"))
 		replace_dir(buf, git_dir_len + 7, get_object_directory());
 	else if (git_common_dir_env)
-		update_common_dir(buf, git_dir_len, NULL);
+		return update_common_dir(buf, git_dir_len, NULL);
+	else
+		return 0;
+	return 1;
 }
 
 static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
@@ -532,6 +538,17 @@ void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
 	va_end(args);
 }
 
+int is_git_path_shared(const char *path)
+{
+	struct strbuf buf = STRBUF_INIT;
+	int ret;
+
+	strbuf_addf(&buf, ".git/%s", path);
+	ret = adjust_git_path(&buf, 5);
+	strbuf_release(&buf);
+	return ret;
+}
+
 int validate_headref(const char *path)
 {
 	struct stat st;
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 05/20] copy.c: add copy_dir_recursively()
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (3 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 04/20] path.c: add is_git_path_shared() Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 06/20] worktree.c: use is_dot_or_dotdot() Nguyễn Thái Ngọc Duy
                   ` (15 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

This is busybox's copy_file() [1] modified to fit in Git. Because this
is busybox, the code is likely POSIX-y (or even Linux-y). Windows
support may not be there yet.

[1] in libbb/copy_file.c from the GPL2+ commit
    f2c043acfcf9dad9fd3d65821b81f89986bbe54e (busybox: fix
    uninitialized memory when displaying IPv6 addresses - 2016-01-18)

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 cache.h |   1 +
 copy.c  | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 372 insertions(+)

diff --git a/cache.h b/cache.h
index c75d13f..3fbb38d 100644
--- a/cache.h
+++ b/cache.h
@@ -1638,6 +1638,7 @@ extern void fprintf_or_die(FILE *, const char *fmt, ...);
 extern int copy_fd(int ifd, int ofd);
 extern int copy_file(const char *dst, const char *src, int mode);
 extern int copy_file_with_time(const char *dst, const char *src, int mode);
+extern int copy_dir_recursively(const char *source, const char *dest);
 
 extern void write_or_die(int fd, const void *buf, size_t count);
 extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
diff --git a/copy.c b/copy.c
index 574fa1f..c99d6e5 100644
--- a/copy.c
+++ b/copy.c
@@ -1,4 +1,6 @@
 #include "cache.h"
+#include "dir.h"
+#include "hashmap.h"
 
 int copy_fd(int ifd, int ofd)
 {
@@ -65,3 +67,372 @@ int copy_file_with_time(const char *dst, const char *src, int mode)
 		return copy_times(dst, src);
 	return status;
 }
+
+struct inode_key {
+	struct hashmap_entry entry;
+	ino_t ino;
+	dev_t dev;
+	/*
+	 * Reportedly, on cramfs a file and a dir can have same ino.
+	 * Need to also remember "file/dir" bit:
+	 */
+	char isdir; /* bool */
+};
+
+struct inode_value {
+	struct inode_key key;
+	char name[FLEX_ARRAY];
+};
+
+#define HASH_SIZE      311u   /* Should be prime */
+static inline unsigned hash_inode(ino_t i)
+{
+	return i % HASH_SIZE;
+}
+
+static int inode_cmp(const void *entry, const void *entry_or_key,
+		     const void *keydata)
+{
+	const struct inode_value *inode = entry;
+	const struct inode_key   *key   = entry_or_key;
+
+	return !(inode->key.ino   == key->ino &&
+		 inode->key.dev   == key->dev &&
+		 inode->key.isdir == key->isdir);
+}
+
+static const char *is_in_ino_dev_hashtable(const struct hashmap *map,
+					   const struct stat *st)
+{
+	struct inode_key key;
+	struct inode_value *value;
+
+	key.entry.hash = hash_inode(st->st_ino);
+	key.ino	       = st->st_ino;
+	key.dev	       = st->st_dev;
+	key.isdir      = !!S_ISDIR(st->st_mode);
+	value	       = hashmap_get(map, &key, NULL);
+	return value ? value->name : NULL;
+}
+
+static void add_to_ino_dev_hashtable(struct hashmap *map,
+				     const struct stat *st,
+				     const char *path)
+{
+	struct inode_value *v;
+	int len = strlen(path);
+
+	v = xmalloc(offsetof(struct inode_value, name) + len + 1);
+	v->key.entry.hash = hash_inode(st->st_ino);
+	v->key.ino	  = st->st_ino;
+	v->key.dev	  = st->st_dev;
+	v->key.isdir      = !!S_ISDIR(st->st_mode);
+	memcpy(v->name, path, len + 1);
+	hashmap_add(map, v);
+}
+
+/*
+ * Find out if the last character of a string matches the one given.
+ * Don't underrun the buffer if the string length is 0.
+ */
+static inline char *last_char_is(const char *s, int c)
+{
+	if (s && *s) {
+		size_t sz = strlen(s) - 1;
+		s += sz;
+		if ( (unsigned char)*s == c)
+			return (char*)s;
+	}
+	return NULL;
+}
+
+static inline char *concat_path_file(const char *path, const char *filename)
+{
+	struct strbuf sb = STRBUF_INIT;
+	char *lc;
+
+	if (!path)
+		path = "";
+	lc = last_char_is(path, '/');
+	while (*filename == '/')
+		filename++;
+	strbuf_addf(&sb, "%s%s%s", path, (lc==NULL ? "/" : ""), filename);
+	return strbuf_detach(&sb, NULL);
+}
+
+static char *concat_subpath_file(const char *path, const char *f)
+{
+	if (f && is_dot_or_dotdot(f))
+		return NULL;
+	return concat_path_file(path, f);
+}
+
+static int do_unlink(const char *dest)
+{
+	int e = errno;
+
+	if (unlink(dest) < 0) {
+		errno = e; /* do not use errno from unlink */
+		return sys_error(_("can't create '%s'"), dest);
+	}
+	return 0;
+}
+
+/* See busybox.git, libbb/copy_file.c for the original implementation */
+static int copy_dir_1(struct hashmap *inode_map,
+		      const char *source,
+		      const char *dest)
+{
+	/* This is a recursive function, try to minimize stack usage */
+	struct stat source_stat;
+	struct stat dest_stat;
+	int retval = 0;
+	int dest_exists = 0;
+	int ovr;
+
+	if (stat(source, &source_stat) < 0)
+		return sys_error(_("can't stat '%s'"), source);
+
+	if (lstat(dest, &dest_stat) < 0) {
+		if (errno != ENOENT)
+			return sys_error(_("can't stat '%s'"), dest);
+	} else {
+		if (source_stat.st_dev == dest_stat.st_dev &&
+		    source_stat.st_ino == dest_stat.st_ino)
+			return sys_error(_("'%s' and '%s' are the same file"), source, dest);
+		dest_exists = 1;
+	}
+
+	if (S_ISDIR(source_stat.st_mode)) {
+		DIR *dp;
+		const char *tp;
+		struct dirent *d;
+		mode_t saved_umask = 0;
+
+		/* Did we ever create source ourself before? */
+		tp = is_in_ino_dev_hashtable(inode_map, &source_stat);
+		if (tp)
+			/* We did! it's a recursion! man the lifeboats... */
+			return error(_("recursion detected, omitting directory '%s'"),
+				     source);
+
+		if (dest_exists) {
+			if (!S_ISDIR(dest_stat.st_mode))
+				return sys_error(_("target '%s' is not a directory"), dest);
+			/*
+			 * race here: user can substitute a symlink between
+			 * this check and actual creation of files inside dest
+			 */
+		} else {
+			/* Create DEST */
+			mode_t mode;
+			saved_umask = umask(0);
+
+			mode = source_stat.st_mode;
+			/* Allow owner to access new dir (at least for now) */
+			mode |= S_IRWXU;
+			if (mkdir(dest, mode) < 0) {
+				umask(saved_umask);
+				return sys_error(_("can't create directory '%s'"), dest);
+			}
+			umask(saved_umask);
+			/* need stat info for add_to_ino_dev_hashtable */
+			if (lstat(dest, &dest_stat) < 0)
+				return sys_error(_("can't stat '%s'"), dest);
+		}
+
+		/*
+		 * remember (dev,inode) of each created dir. name is
+		 * not remembered
+		 */
+		add_to_ino_dev_hashtable(inode_map, &dest_stat, "");
+
+		/* Recursively copy files in SOURCE */
+		dp = opendir(source);
+		if (!dp) {
+			retval = -1;
+			goto preserve_mode_ugid_time;
+		}
+
+		while ((d = readdir(dp))) {
+			char *new_source, *new_dest;
+
+			new_source = concat_subpath_file(source, d->d_name);
+			if (!new_source)
+				continue;
+			new_dest = concat_path_file(dest, d->d_name);
+			if (copy_dir_1(inode_map, new_source, new_dest) < 0)
+				retval = -1;
+			free(new_source);
+			free(new_dest);
+		}
+		closedir(dp);
+
+		if (!dest_exists &&
+		    chmod(dest, source_stat.st_mode & ~saved_umask) < 0) {
+			sys_error(_("can't preserve permissions of '%s'"), dest);
+			/* retval = -1; - WRONG! copy *WAS* made */
+		}
+		goto preserve_mode_ugid_time;
+	}
+
+	/* "cp [-opts] regular_file thing2" */
+	if (S_ISREG(source_stat.st_mode)) {
+		const char *link_target;
+		int src_fd;
+		int dst_fd;
+		mode_t new_mode;
+
+		if (S_ISLNK(source_stat.st_mode)) {
+			/* "cp -d symlink dst": create a link */
+			goto dont_cat;
+		}
+
+		link_target = is_in_ino_dev_hashtable(inode_map, &source_stat);
+		if (link_target) {
+			if (link(link_target, dest) < 0) {
+				ovr = do_unlink(dest);
+				if (ovr < 0)
+					return ovr;
+				if (link(link_target, dest) < 0) {
+					sys_error(_("can't create link '%s'"), dest);
+					return -1;
+				}
+			}
+			return 0;
+		}
+		add_to_ino_dev_hashtable(inode_map, &source_stat, dest);
+
+		src_fd = open(source, O_RDONLY);
+		if (src_fd < 0)
+			return sys_error(_("can't open '%s'"), source);
+
+		/* Do not try to open with weird mode fields */
+		new_mode = source_stat.st_mode;
+		if (!S_ISREG(source_stat.st_mode))
+			new_mode = 0666;
+
+		dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
+		if (dst_fd == -1) {
+			ovr = do_unlink(dest);
+			if (ovr < 0) {
+				close(src_fd);
+				return ovr;
+			}
+			/* It shouldn't exist. If it exists, do not open (symlink attack?) */
+			dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
+			if (dst_fd < 0) {
+				close(src_fd);
+				return sys_error(_("can't open '%s'"), dest);
+			}
+		}
+
+		switch (copy_fd(src_fd, dst_fd)) {
+		case COPY_READ_ERROR:
+			error(_("copy-fd: read returned %s"), strerror(errno));
+			retval = -1;
+			break;
+		case COPY_WRITE_ERROR:
+			error(_("copy-fd: write returned %s"), strerror(errno));
+			retval = -1;
+			break;
+		}
+
+		/* Careful with writing... */
+		if (close(dst_fd) < 0)
+			retval = sys_error(_("error writing to '%s'"), dest);
+		/* ...but read size is already checked by bb_copyfd_eof */
+		close(src_fd);
+		/*
+		 * "cp /dev/something new_file" should not
+		 * copy mode of /dev/something
+		 */
+		if (!S_ISREG(source_stat.st_mode))
+			return retval;
+		goto preserve_mode_ugid_time;
+	}
+dont_cat:
+
+	/* Source is a symlink or a special file */
+	/* We are lazy here, a bit lax with races... */
+	if (dest_exists) {
+		errno = EEXIST;
+		ovr = do_unlink(dest);
+		if (ovr < 0)
+			return ovr;
+	}
+	if (S_ISLNK(source_stat.st_mode)) {
+		struct strbuf lpath = STRBUF_INIT;
+		if (!strbuf_readlink(&lpath, source, 0)) {
+			int r = symlink(lpath.buf, dest);
+			strbuf_release(&lpath);
+			if (r < 0)
+				return sys_error(_("can't create symlink '%s'"), dest);
+			if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
+				sys_error(_("can't preserve %s of '%s'"), "ownership", dest);
+		} else {
+			/* EINVAL => "file: Invalid argument" => puzzled user */
+			const char *errmsg = _("not a symlink");
+			int err = errno;
+
+			if (err != EINVAL)
+				errmsg = strerror(err);
+			error(_("%s: cannot read link: %s"), source, errmsg);
+			strbuf_release(&lpath);
+		}
+		/*
+		 * _Not_ jumping to preserve_mode_ugid_time: symlinks
+		 * don't have those
+		 */
+		return 0;
+	}
+	if (S_ISBLK(source_stat.st_mode) ||
+	    S_ISCHR(source_stat.st_mode) ||
+	    S_ISSOCK(source_stat.st_mode) ||
+	    S_ISFIFO(source_stat.st_mode)) {
+		if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0)
+			return sys_error(_("can't create '%s'"), dest);
+	} else
+		return sys_error(_("unrecognized file '%s' with mode %x"),
+				 source, source_stat.st_mode);
+
+preserve_mode_ugid_time:
+
+	if (1 /*FILEUTILS_PRESERVE_STATUS*/) {
+		struct timeval times[2];
+
+		times[1].tv_sec = times[0].tv_sec = source_stat.st_mtime;
+		times[1].tv_usec = times[0].tv_usec = 0;
+		/* BTW, utimes sets usec-precision time - just FYI */
+		if (utimes(dest, times) < 0)
+			sys_error(_("can't preserve %s of '%s'"), "times", dest);
+		if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {
+			source_stat.st_mode &= ~(S_ISUID | S_ISGID);
+			sys_error(_("can't preserve %s of '%s'"), "ownership", dest);
+		}
+		if (chmod(dest, source_stat.st_mode) < 0)
+			sys_error(_("can't preserve %s of '%s'"), "permissions", dest);
+	}
+
+	return retval;
+}
+
+/*
+ * Return:
+ * -1 error, copy not made
+ *  0 copy is made
+ *
+ * Failures to preserve mode/owner/times are not reported in exit
+ * code. No support for preserving SELinux security context. Symlinks
+ * and hardlinks are preserved.
+ */
+int copy_dir_recursively(const char *source, const char *dest)
+{
+	int ret;
+	struct hashmap inode_map;
+
+	hashmap_init(&inode_map, inode_cmp, 1024);
+	ret = copy_dir_1(&inode_map, source, dest);
+	hashmap_free(&inode_map, 1);
+	return ret;
+}
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 06/20] worktree.c: use is_dot_or_dotdot()
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (4 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 05/20] copy.c: add copy_dir_recursively() Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 07/20] worktree.c: store "id" instead of "git_dir" Nguyễn Thái Ngọc Duy
                   ` (14 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/worktree.c | 2 +-
 worktree.c         | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 475b958..7b217fb 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -94,7 +94,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))
diff --git a/worktree.c b/worktree.c
index 6181a66..ddb8cb7 100644
--- a/worktree.c
+++ b/worktree.c
@@ -2,6 +2,7 @@
 #include "refs.h"
 #include "strbuf.h"
 #include "worktree.h"
+#include "dir.h"
 
 void free_worktrees(struct worktree **worktrees)
 {
@@ -173,7 +174,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))) {
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 07/20] worktree.c: store "id" instead of "git_dir"
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (5 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 06/20] worktree.c: use is_dot_or_dotdot() Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 08/20] worktree.c: add clear_worktree() Nguyễn Thái Ngọc Duy
                   ` (13 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: 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.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 29 ++++++++++++++++-------------
 worktree.h |  7 ++++++-
 2 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/worktree.c b/worktree.c
index ddb8cb7..4c38414 100644
--- a/worktree.c
+++ b/worktree.c
@@ -10,7 +10,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]);
 	}
@@ -75,13 +75,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, "/.");
@@ -93,7 +91,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;
@@ -101,7 +99,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;
@@ -112,16 +109,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;
@@ -141,7 +135,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;
@@ -149,7 +143,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;
@@ -189,6 +182,14 @@ struct worktree **get_worktrees(void)
 	return list;
 }
 
+const char *get_worktree_git_dir(const struct worktree *wt)
+{
+	if (wt->id)
+		return git_common_path("worktrees/%s", wt->id);
+	else
+		return get_git_common_dir();
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
@@ -200,7 +201,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;
diff --git a/worktree.h b/worktree.h
index b4b3dda..e89d423 100644
--- a/worktree.h
+++ b/worktree.h
@@ -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,11 @@ struct worktree {
 extern struct worktree **get_worktrees(void);
 
 /*
+ * Return git dir of the worktree. Note that the path may be relative.
+ */
+extern const char *get_worktree_git_dir(const struct worktree *wt);
+
+/*
  * Free up the memory for worktree(s)
  */
 extern void free_worktrees(struct worktree **);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 08/20] worktree.c: add clear_worktree()
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (6 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 07/20] worktree.c: store "id" instead of "git_dir" Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 09/20] worktree.c: add find_worktree_by_path() Nguyễn Thái Ngọc Duy
                   ` (12 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 14 +++++++++++---
 worktree.h |  5 +++++
 2 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/worktree.c b/worktree.c
index 4c38414..b4e4b57 100644
--- a/worktree.c
+++ b/worktree.c
@@ -4,14 +4,22 @@
 #include "worktree.h"
 #include "dir.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);
diff --git a/worktree.h b/worktree.h
index e89d423..0ba07ab 100644
--- a/worktree.h
+++ b/worktree.h
@@ -28,6 +28,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)
  */
 extern void free_worktrees(struct worktree **);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 09/20] worktree.c: add find_worktree_by_path()
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (7 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 08/20] worktree.c: add clear_worktree() Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 10/20] worktree.c: add is_main_worktree() Nguyễn Thái Ngọc Duy
                   ` (11 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 16 ++++++++++++++++
 worktree.h |  6 ++++++
 2 files changed, 22 insertions(+)

diff --git a/worktree.c b/worktree.c
index b4e4b57..e444ad1 100644
--- a/worktree.c
+++ b/worktree.c
@@ -198,6 +198,22 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return get_git_common_dir();
 }
 
+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 (!strcmp_icase(path, real_path(wt->path)))
+			break;
+		wt = NULL;
+	}
+	free(path);
+	return wt;
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
diff --git a/worktree.h b/worktree.h
index 0ba07ab..c163b6b 100644
--- a/worktree.h
+++ b/worktree.h
@@ -28,6 +28,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
  */
 extern void clear_worktree(struct worktree *);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 10/20] worktree.c: add is_main_worktree()
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (8 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 09/20] worktree.c: add find_worktree_by_path() Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 11/20] worktree.c: recognize no main worktree Nguyễn Thái Ngọc Duy
                   ` (10 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 5 +++++
 worktree.h | 5 +++++
 2 files changed, 10 insertions(+)

diff --git a/worktree.c b/worktree.c
index e444ad1..e878f49 100644
--- a/worktree.c
+++ b/worktree.c
@@ -214,6 +214,11 @@ struct worktree *find_worktree_by_path(struct worktree **list,
 	return wt;
 }
 
+int is_main_worktree(const struct worktree *wt)
+{
+	return wt && !wt->id;
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
diff --git a/worktree.h b/worktree.h
index c163b6b..c7a4d20 100644
--- a/worktree.h
+++ b/worktree.h
@@ -34,6 +34,11 @@ 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
  */
 extern void clear_worktree(struct worktree *);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 11/20] worktree.c: recognize no main worktree
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (9 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 10/20] worktree.c: add is_main_worktree() Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 12/20] worktree.c: add update_worktree_location() Nguyễn Thái Ngọc Duy
                   ` (9 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Because HEAD is part of repository signature, even if it's per-worktree
file, we can't simply delete it. So main worktree is considered gone if
.git/HEAD contains all-zero SHA-1.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/worktree.c b/worktree.c
index e878f49..80c525b 100644
--- a/worktree.c
+++ b/worktree.c
@@ -49,6 +49,8 @@ static int parse_ref(char *path_to_ref, struct strbuf *ref, int *is_detached)
 		if (!starts_with(ref->buf, "ref:")) {
 			if (is_detached)
 				*is_detached = 1;
+			if (starts_with(ref->buf, sha1_to_hex(null_sha1)))
+				return -1;
 		} else {
 			strbuf_remove(ref, 0, strlen("ref:"));
 			strbuf_trim(ref);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 12/20] worktree.c: add update_worktree_location()
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (10 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 11/20] worktree.c: recognize no main worktree Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 13/20] worktree.c: add update_worktree_gitfile() Nguyễn Thái Ngọc Duy
                   ` (8 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 23 +++++++++++++++++++++++
 worktree.h |  6 ++++++
 2 files changed, 29 insertions(+)

diff --git a/worktree.c b/worktree.c
index 80c525b..a11c053 100644
--- a/worktree.c
+++ b/worktree.c
@@ -221,6 +221,29 @@ int is_main_worktree(const struct worktree *wt)
 	return wt && !wt->id;
 }
 
+int update_worktree_location(struct worktree *wt, const char *path_)
+{
+	struct strbuf path = STRBUF_INIT;
+	int ret;
+
+	if (is_main_worktree(wt))
+		return 0;
+
+	strbuf_add_absolute_path(&path, path_);
+	if (strcmp_icase(wt->path, path.buf)) {
+		if (!write_file_gently(git_common_path("worktrees/%s/gitdir", wt->id),
+				       "%s/.git", path.buf)) {
+			free(wt->path);
+			wt->path = strbuf_detach(&path, NULL);
+			ret = 0;
+		} else
+			ret = sys_error(_("failed to update '%s' for update"),
+					git_common_path("worktrees/%s/gitdir", wt->id));
+	}
+	strbuf_release(&path);
+	return ret;
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
diff --git a/worktree.h b/worktree.h
index c7a4d20..4c0395a 100644
--- a/worktree.h
+++ b/worktree.h
@@ -39,6 +39,12 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,
 extern int is_main_worktree(const struct worktree *wt);
 
 /*
+ * Update worktrees/xxx/gitdir with the new path.
+ */
+extern int update_worktree_location(struct worktree *wt,
+				    const char *path_);
+
+/*
  * Free up the memory for worktree
  */
 extern void clear_worktree(struct worktree *);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 13/20] worktree.c: add update_worktree_gitfile()
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (11 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 12/20] worktree.c: add update_worktree_location() Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 14/20] worktree.c: add collect_per_worktree_git_paths() Nguyễn Thái Ngọc Duy
                   ` (7 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 15 ++++++++++++++-
 worktree.h |  7 +++++++
 2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/worktree.c b/worktree.c
index a11c053..d4513b1 100644
--- a/worktree.c
+++ b/worktree.c
@@ -237,13 +237,26 @@ int update_worktree_location(struct worktree *wt, const char *path_)
 			wt->path = strbuf_detach(&path, NULL);
 			ret = 0;
 		} else
-			ret = sys_error(_("failed to update '%s' for update"),
+			ret = sys_error(_("failed to update '%s'"),
 					git_common_path("worktrees/%s/gitdir", wt->id));
 	}
 	strbuf_release(&path);
 	return ret;
 }
 
+int update_worktree_gitfile(const char *gitfile,
+			    const char *repo_path,
+			    const char *id)
+{
+	if (!id)
+		return error(_("cannot update .git of main worktree"));
+
+	if (write_file_gently(gitfile, "gitdir: %s/worktrees/%s", real_path(repo_path), id))
+		return sys_error(_("failed to update '%s'"), gitfile);
+
+	return 0;
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
diff --git a/worktree.h b/worktree.h
index 4c0395a..55f3449 100644
--- a/worktree.h
+++ b/worktree.h
@@ -45,6 +45,13 @@ extern int update_worktree_location(struct worktree *wt,
 				    const char *path_);
 
 /*
+ * Update .git file to point to the new repository's location.
+ */
+extern int update_worktree_gitfile(const char *gitfile,
+				   const char *repo_path,
+				   const char *id);
+
+/*
  * Free up the memory for worktree
  */
 extern void clear_worktree(struct worktree *);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 14/20] worktree.c: add collect_per_worktree_git_paths()
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (12 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 13/20] worktree.c: add update_worktree_gitfile() Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 15/20] worktree: avoid 0{40}, too many zeroes, hard to read Nguyễn Thái Ngọc Duy
                   ` (6 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 worktree.h |  6 ++++++
 2 files changed, 64 insertions(+)

diff --git a/worktree.c b/worktree.c
index d4513b1..047319b 100644
--- a/worktree.c
+++ b/worktree.c
@@ -257,6 +257,64 @@ int update_worktree_gitfile(const char *gitfile,
 	return 0;
 }
 
+static int collect_per_worktree_1(struct strbuf *base, int commonlen,
+				  struct string_list *paths)
+{
+	int base_len = base->len;
+	struct dirent *d;
+	DIR *dir;
+	int ret = 0;
+
+	dir = opendir(base->buf);
+	if (!dir)
+		return sys_error(_("failed to open '%s'"), base->buf);
+
+	while ((d = readdir(dir))) {
+		struct stat st;
+
+		if (is_dot_or_dotdot(d->d_name))
+			continue;
+
+		strbuf_setlen(base, base_len);
+		strbuf_addstr(base, d->d_name);
+
+		if (stat(base->buf, &st)) {
+			if (errno == ENOENT)
+				continue;
+			ret = sys_error(_("failed to stat '%s'"), base->buf);
+			break;
+		}
+		if (!S_ISDIR(st.st_mode)) {
+			const char *path = base->buf + commonlen;
+			if (!is_git_path_shared(path))
+				string_list_append(paths, path);
+			continue;
+		}
+
+		strbuf_addch(base, '/');
+		if ((ret = collect_per_worktree_1(base, commonlen, paths)))
+			break;
+	}
+	closedir(dir);
+	return ret;
+}
+
+int collect_per_worktree_git_paths(struct string_list *paths)
+{
+	struct strbuf base = STRBUF_INIT;
+	int ret;
+
+	/* force adjust_git_path() to always adjust paths */
+	git_common_dir_env = 1;
+
+	strbuf_addstr(&base, get_git_common_dir());
+	if (base.len && base.buf[base.len - 1] != '/')
+		strbuf_addch(&base, '/');
+	ret = collect_per_worktree_1(&base, base.len, paths);
+	strbuf_release(&base);
+	return ret;
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
diff --git a/worktree.h b/worktree.h
index 55f3449..8da1a13 100644
--- a/worktree.h
+++ b/worktree.h
@@ -52,6 +52,12 @@ extern int update_worktree_gitfile(const char *gitfile,
 				   const char *id);
 
 /*
+ * Given .git directory in "base", search all paths, collect the list
+ * of paths that are not shared in "paths".
+ */
+extern int collect_per_worktree_git_paths(struct string_list *paths);
+
+/*
  * Free up the memory for worktree
  */
 extern void clear_worktree(struct worktree *);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 15/20] worktree: avoid 0{40}, too many zeroes, hard to read
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (13 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 14/20] worktree.c: add collect_per_worktree_git_paths() Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 16/20] worktree: simplify prefixing paths Nguyễn Thái Ngọc Duy
                   ` (5 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/worktree.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 7b217fb..f490ca3 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -263,7 +263,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, "../..");
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 16/20] worktree: simplify prefixing paths
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (14 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 15/20] worktree: avoid 0{40}, too many zeroes, hard to read Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 17/20] worktree: add "move" commmand Nguyễn Thái Ngọc Duy
                   ` (4 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/worktree.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index f490ca3..36d942e 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -332,7 +332,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;
@@ -453,6 +453,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"))
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 17/20] worktree: add "move" commmand
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (15 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 16/20] worktree: simplify prefixing paths Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 18/20] worktree: refactor add_worktree() Nguyễn Thái Ngọc Duy
                   ` (3 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/worktree.c                | 51 +++++++++++++++++++++++++++++++++++++++
 t/t2028-worktree-move.sh (new +x) | 41 +++++++++++++++++++++++++++++++
 2 files changed, 92 insertions(+)
 create mode 100755 t/t2028-worktree-move.sh

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 36d942e..8d59199 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -445,6 +445,55 @@ static int list(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static int move(int ac, const char **av, const char *prefix)
+{
+	struct option options[] = {
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+	struct strbuf dst = STRBUF_INIT;
+	struct strbuf src = STRBUF_INIT;
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (ac != 2)
+		usage_with_options(worktree_usage, options);
+
+	strbuf_addstr(&dst, prefix_filename(prefix,
+					     strlen(prefix),
+					     av[1]));
+	if (file_exists(dst.buf))
+		die(_("target '%s' already exists"), av[1]);
+
+	worktrees = get_worktrees();
+	strbuf_addstr(&src, prefix_filename(prefix,
+					     strlen(prefix),
+					     av[0]));
+	wt = find_worktree_by_path(worktrees, src.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]);
+
+	/*
+	 * First try. Atomically move, and probably cheaper, if both
+	 * source and target are on the same file system.
+	 */
+	if (rename(src.buf, dst.buf) == -1) {
+		if (errno != EXDEV)
+			die_errno(_("failed to move '%s' to '%s'"),
+				  src.buf, dst.buf);
+
+		/* second try.. */
+		if (copy_dir_recursively(src.buf, dst.buf))
+			die(_("failed to copy '%s' to '%s'"),
+			    src.buf, dst.buf);
+		else
+			(void)remove_dir_recursively(&src, 0);
+	}
+
+	return update_worktree_location(wt, dst.buf);
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -461,5 +510,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], "move"))
+		return move(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
 }
diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh
new file mode 100755
index 0000000..e8f6f0c
--- /dev/null
+++ b/t/t2028-worktree-move.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+test_description='test git worktree add'
+
+. ./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 'move non-worktree' '
+	mkdir abc &&
+	test_must_fail git worktree move abc def
+'
+
+test_expect_success 'move worktree' '
+	git worktree move source destination &&
+	test_path_is_missing source &&
+	git worktree list --porcelain | grep "^worktree" >actual &&
+	cat <<-EOF >expected &&
+	worktree $TRASH_DIRECTORY
+	worktree $TRASH_DIRECTORY/destination
+	EOF
+	test_cmp expected actual &&
+	git -C destination log --format=%s >actual2 &&
+	echo init >expected2 &&
+	test_cmp expected2 actual2
+'
+
+test_expect_success 'move main worktree' '
+	test_must_fail git worktree move . def
+'
+
+test_done
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 18/20] worktree: refactor add_worktree()
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (16 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 17/20] worktree: add "move" commmand Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 19/20] worktree: move repo, simple case Nguyễn Thái Ngọc Duy
                   ` (2 subsequent siblings)
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/worktree.c | 130 +++++++++++++++++++++++++++++++++--------------------
 1 file changed, 81 insertions(+), 49 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 8d59199..3ebb9e9 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -184,34 +184,15 @@ static const char *worktree_basename(const char *path, int *olen)
 	return name;
 }
 
-static int add_worktree(const char *path, const char *refname,
-			const struct add_opts *opts)
+static void prepare_new_worktree(const char *path,
+				 struct worktree *wt,
+				 int create_gitdir_and_head)
 {
+	const char *name;
 	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
 	struct strbuf sb = STRBUF_INIT;
-	const char *name;
 	struct stat st;
-	struct child_process cp;
-	struct argv_array child_env = ARGV_ARRAY_INIT;
-	int counter = 0, len, ret;
-	struct strbuf symref = STRBUF_INIT;
-	struct commit *commit = NULL;
-
-	if (file_exists(path) && !is_empty_dir(path))
-		die(_("'%s' already exists"), path);
-
-	/* is 'refname' a branch or commit? */
-	if (opts->force_new_branch) /* definitely a branch */
-		;
-	else if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
-		 ref_exists(symref.buf)) { /* it's a branch */
-		if (!opts->force)
-			die_if_checked_out(symref.buf);
-	} else { /* must be a commit */
-		commit = lookup_commit_reference_by_name(refname);
-		if (!commit)
-			die(_("invalid reference: %s"), refname);
-	}
+	int counter = 0, len;
 
 	name = worktree_basename(path, &len);
 	strbuf_addstr(&sb_repo,
@@ -227,6 +208,10 @@ static int add_worktree(const char *path, const char *refname,
 	}
 	name = strrchr(sb_repo.buf, '/') + 1;
 
+	memset(wt, 0, sizeof(*wt));
+	wt->path = xstrdup(path);
+	wt->id = xstrdup(name);
+
 	junk_pid = getpid();
 	atexit(remove_junk);
 	sigchain_push_common(remove_junk_on_signal);
@@ -252,25 +237,81 @@ static int add_worktree(const char *path, const char *refname,
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
 	write_file(sb.buf, "%s", real_path(sb_git.buf));
-	write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
-		   real_path(get_git_common_dir()), name);
-	/*
-	 * This is to keep resolve_ref() happy. We need a valid HEAD
-	 * or is_git_directory() will reject the directory. Any value which
-	 * looks like an object ID will do since it will be immediately
-	 * replaced by the symbolic-ref or update-ref invocation in the new
-	 * worktree.
-	 */
-	strbuf_reset(&sb);
-	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
-	write_file(sb.buf, sha1_to_hex(null_sha1));
+	if (create_gitdir_and_head) {
+		write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
+			   real_path(get_git_common_dir()), name);
+
+		/*
+		 * This is to keep resolve_ref() happy. We need a valid HEAD
+		 * or is_git_directory() will reject the directory. Any value which
+		 * looks like an object ID will do since it will be immediately
+		 * replaced by the symbolic-ref or update-ref invocation in the new
+		 * worktree.
+		 */
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
+		write_file(sb.buf, sha1_to_hex(null_sha1));
+	}
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
 	write_file(sb.buf, "../..");
 
-	fprintf_ln(stderr, _("Preparing %s (identifier %s)"), path, name);
+	strbuf_release(&sb);
+	strbuf_release(&sb_repo);
+	strbuf_release(&sb_git);
+}
+
+static void new_worktree_complete(void)
+{
+	is_junk = 0;
+	free(junk_work_tree);
+	free(junk_git_dir);
+	junk_work_tree = NULL;
+	junk_git_dir = NULL;
+}
+
+static void cleanup_new_worktree(struct worktree *wt)
+{
+	struct strbuf sb = STRBUF_INIT;
+
+	strbuf_git_common_path(&sb, "worktrees/%s/locked", wt->id);
+	unlink_or_warn(sb.buf);
+	strbuf_release(&sb);
+	clear_worktree(wt);
+}
+
+static int add_worktree(const char *path, const char *refname,
+			const struct add_opts *opts)
+{
+	struct worktree wt;
+	struct child_process cp;
+	struct argv_array child_env = ARGV_ARRAY_INIT;
+	int ret;
+	struct strbuf symref = STRBUF_INIT;
+	struct commit *commit = NULL;
+
+	/* is 'refname' a branch or commit? */
+	if (opts->force_new_branch) /* definitely a branch */
+		;
+	else if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
+		 ref_exists(symref.buf)) { /* it's a branch */
+		if (!opts->force)
+			die_if_checked_out(symref.buf);
+	} else { /* must be a commit */
+		commit = lookup_commit_reference_by_name(refname);
+		if (!commit)
+			die(_("invalid reference: %s"), refname);
+	}
+
+	if (file_exists(path) && !is_empty_dir(path))
+		die(_("'%s' already exists"), path);
+
+	prepare_new_worktree(path, &wt, 1);
 
-	argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
+	fprintf_ln(stderr, _("Preparing %s (identifier %s)"), path, wt.id);
+
+	argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT,
+			 get_worktree_git_dir(&wt));
 	argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
 	memset(&cp, 0, sizeof(cp));
 	cp.git_cmd = 1;
@@ -292,21 +333,12 @@ static int add_worktree(const char *path, const char *refname,
 	cp.env = child_env.argv;
 	ret = run_command(&cp);
 	if (!ret) {
-		is_junk = 0;
-		free(junk_work_tree);
-		free(junk_git_dir);
-		junk_work_tree = NULL;
-		junk_git_dir = NULL;
+		new_worktree_complete();
 	}
 done:
-	strbuf_reset(&sb);
-	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
-	unlink_or_warn(sb.buf);
 	argv_array_clear(&child_env);
-	strbuf_release(&sb);
 	strbuf_release(&symref);
-	strbuf_release(&sb_repo);
-	strbuf_release(&sb_git);
+	cleanup_new_worktree(&wt);
 	return ret;
 }
 
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 19/20] worktree: move repo, simple case
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (17 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 18/20] worktree: refactor add_worktree() Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-03  9:35 ` [PATCH 20/20] worktree: move repo, convert main worktree Nguyễn Thái Ngọc Duy
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/worktree.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 3ebb9e9..80a8b80 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -477,9 +477,52 @@ static int list(int ac, const char **av, const char *prefix)
 	return 0;
 }
 
+static int move_gitdir(int ac, const char **av, const char *prefix)
+{
+	struct string_list paths = STRING_LIST_INIT_NODUP;
+	struct strbuf dst = STRBUF_INIT;
+	struct strbuf src = STRBUF_INIT;
+	struct worktree **worktrees, *mwt = NULL;
+	int i, ret = 0;
+
+	if (ac != 1)
+		die(_("--repository requires one argument"));
+
+	strbuf_addstr(&src, real_path(get_git_common_dir()));
+	strbuf_addstr(&dst, real_path(prefix_filename(prefix, strlen(prefix), av[0])));
+
+	worktrees = get_worktrees();
+	for (i = 0; worktrees[i]; i++) {
+		if (is_main_worktree(worktrees[i]))
+			mwt = worktrees[i];
+		string_list_append(&paths,
+				   get_worktree_git_dir(worktrees[i]));
+	}
+
+	if (mwt)
+		die(_("converting main worktree is not supported"));
+
+	ret = copy_dir_recursively(src.buf, dst.buf);
+	if (ret)
+		die(_("failed to copy '%s' to '%s'"), src.buf, dst.buf);
+
+	for (i = 0; paths.nr; i++)
+		if (update_worktree_gitfile(paths.items[i].string, dst.buf,
+					    worktrees[i]->id))
+			ret = -1;
+
+	if (!ret)
+		ret = remove_dir_recursively(&src, 0);
+
+	string_list_clear(&paths, 0);
+	return ret;
+}
+
 static int move(int ac, const char **av, const char *prefix)
 {
+	int move_repo = 0;
 	struct option options[] = {
+		OPT_BOOL(0, "repository", &move_repo, N_("move repository")),
 		OPT_END()
 	};
 	struct worktree **worktrees, *wt;
@@ -487,6 +530,8 @@ static int move(int ac, const char **av, const char *prefix)
 	struct strbuf src = STRBUF_INIT;
 
 	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (move_repo)
+		return move_gitdir(ac, av, prefix);
 	if (ac != 2)
 		usage_with_options(worktree_usage, options);
 
-- 
2.7.0.377.g4cd97dd

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

* [PATCH 20/20] worktree: move repo, convert main worktree
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (18 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 19/20] worktree: move repo, simple case Nguyễn Thái Ngọc Duy
@ 2016-02-03  9:35 ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
  20 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-03  9:35 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/worktree.c       | 87 ++++++++++++++++++++++++++++++++++++++++++------
 t/t2028-worktree-move.sh | 36 ++++++++++++++++++++
 2 files changed, 112 insertions(+), 11 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 80a8b80..3f69369 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -479,10 +479,11 @@ static int list(int ac, const char **av, const char *prefix)
 
 static int move_gitdir(int ac, const char **av, const char *prefix)
 {
-	struct string_list paths = STRING_LIST_INIT_NODUP;
+	struct string_list per_worktree = STRING_LIST_INIT_DUP;
 	struct strbuf dst = STRBUF_INIT;
 	struct strbuf src = STRBUF_INIT;
 	struct worktree **worktrees, *mwt = NULL;
+	struct worktree tmp;
 	int i, ret = 0;
 
 	if (ac != 1)
@@ -492,29 +493,93 @@ static int move_gitdir(int ac, const char **av, const char *prefix)
 	strbuf_addstr(&dst, real_path(prefix_filename(prefix, strlen(prefix), av[0])));
 
 	worktrees = get_worktrees();
-	for (i = 0; worktrees[i]; i++) {
-		if (is_main_worktree(worktrees[i]))
+	for (i = 0; worktrees[i]; i++)
+		if (is_main_worktree(worktrees[i])) {
 			mwt = worktrees[i];
-		string_list_append(&paths,
-				   get_worktree_git_dir(worktrees[i]));
-	}
+			break;
+		}
+
+	if (mwt) {
+		struct strbuf sb = STRBUF_INIT;
+
+		strbuf_addstr(&sb, real_path(get_git_common_dir()));
+		if (ends_with(sb.buf, "/.git"))
+			sb.len -= strlen("/.git");
+		else
+			die(_("unrecognized repository directory layout: %s"),
+			    sb.buf);
+		sb.buf[sb.len] = '\0';
+		prepare_new_worktree(sb.buf, &tmp, 0);
+		strbuf_release(&sb);
+
+		if (collect_per_worktree_git_paths(&per_worktree))
+			die(_("failed to collect per-worktree paths"));
+
+		for (i = 0; !ret && i < per_worktree.nr; i++) {
+			const char *path = per_worktree.items[i].string;
+
+			if (safe_create_leading_directories_const(
+				git_common_path("worktrees/%s/%s", tmp.id, path)) ||
+			    copy_file(git_common_path("worktrees/%s/%s", tmp.id, path),
+				      git_common_path("%s", path),
+				      0777))
+				die_errno(_("failed to copy '%s' to '%s'"),
+					  git_common_path("%s", path),
+					  git_common_path("worktrees/%s/%s", tmp.id, path));
+		}
 
-	if (mwt)
-		die(_("converting main worktree is not supported"));
+		mwt->id = xstrdup(tmp.id);
+		cleanup_new_worktree(&tmp);
+	}
 
 	ret = copy_dir_recursively(src.buf, dst.buf);
 	if (ret)
 		die(_("failed to copy '%s' to '%s'"), src.buf, dst.buf);
 
-	for (i = 0; paths.nr; i++)
-		if (update_worktree_gitfile(paths.items[i].string, dst.buf,
+	if (mwt) {
+		struct strbuf sb = STRBUF_INIT;
+		int len;
+
+		new_worktree_complete();
+
+		strbuf_addbuf(&sb, &dst);
+		if (sb.buf[sb.len - 1] != '/')
+			strbuf_addch(&sb, '/');
+		len = sb.len;
+		for (i = 0; !ret && i < per_worktree.nr; i++) {
+			const char *path = per_worktree.items[i].string;
+
+			strbuf_addstr(&sb, path);
+			unlink_or_warn(sb.buf);
+			strbuf_setlen(&sb, len);
+		}
+		string_list_clear(&per_worktree, 0);
+
+		strbuf_addstr(&sb, "HEAD");
+		write_file(sb.buf, sha1_to_hex(null_sha1));
+
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "%s.old", src.buf);
+		if (rename(src.buf, sb.buf))
+			die_errno(_("failed to move '%s' to '%s'"), src.buf, sb.buf);
+		/* XXX: rename back on die() */
+		strbuf_swap(&sb, &src);
+		strbuf_release(&sb);
+	}
+
+	for (i = 0; worktrees[i]; i++) {
+		struct strbuf sb = STRBUF_INIT;
+
+		strbuf_addf(&sb, "%s/.git", worktrees[i]->path);
+		if (update_worktree_gitfile(sb.buf, dst.buf,
 					    worktrees[i]->id))
 			ret = -1;
+		strbuf_release(&sb);
+	}
 
 	if (!ret)
 		ret = remove_dir_recursively(&src, 0);
 
-	string_list_clear(&paths, 0);
 	return ret;
 }
 
diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh
index e8f6f0c..5e9e1d4 100755
--- a/t/t2028-worktree-move.sh
+++ b/t/t2028-worktree-move.sh
@@ -38,4 +38,40 @@ test_expect_success 'move main worktree' '
 	test_must_fail git worktree move . def
 '
 
+test_expect_success 'move repository and convert main worktree' '
+	git worktree move --repository repo &&
+	test_path_is_file .git &&
+	test_path_is_dir repo &&
+	git fsck &&
+	git worktree list --porcelain | grep "^worktree" >actual &&
+	cat <<-EOF >expected &&
+	worktree $TRASH_DIRECTORY
+	worktree $TRASH_DIRECTORY/destination
+	EOF
+	test_cmp expected actual &&
+	git -C destination log --format=%s >actual2 &&
+	echo init >expected2 &&
+	test_cmp expected2 actual2 &&
+	git log --format=%s >actual3 &&
+	test_cmp expected2 actual3
+'
+
+test_expect_success 'move repository alone' '
+	git worktree move --repository repo2 &&
+	test_path_is_file .git &&
+	test_path_is_dir repo2 &&
+	git fsck &&
+	git worktree list --porcelain | grep "^worktree" >actual &&
+	cat <<-EOF >expected &&
+	worktree $TRASH_DIRECTORY
+	worktree $TRASH_DIRECTORY/destination
+	EOF
+	test_cmp expected actual &&
+	git -C destination log --format=%s >actual2 &&
+	echo init >expected2 &&
+	test_cmp expected2 actual2 &&
+	git log --format=%s >actual3 &&
+	test_cmp expected2 actual3
+'
+
 test_done
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 00/26] worktree lock, move, remove and unlock
  2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
                   ` (19 preceding siblings ...)
  2016-02-03  9:35 ` [PATCH 20/20] worktree: move repo, convert main worktree Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29 ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 01/26] usage.c: move format processing out of die_errno() Nguyễn Thái Ngọc Duy
                     ` (25 more replies)
  20 siblings, 26 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

I think this is good enough to be further polished and maybe released.
The series adds four new commands and bash completion for git-worktree.

Remaining work to do on git-worktree:

 - "git worktree move --repository", in v1 but left out in v2 because
   it's trickier.

 - .git/config issue and submodule support

A bit off topic, I've been using this script to add "cw" command, to
chdir to another worktree with tab-completion. It works wonderfully,
but I'm not sure if it can ever be merged to git-completion.bash.

-- 8< --
cw() {
    cd "$(git worktree list --porcelain | grep -B2 refs/heads/$1 | head -n1 | cut -f 2 -d ' ')"
}

_git_cw() {
	local cur words cword prev
	_get_comp_words_by_ref -n =: cur words cword prev

    	local list="$(git worktree list --porcelain | grep '^branch refs/heads/' | cut -c 19-)"
	__gitcomp "$list"
}

complete -o bashdefault -o default -o nospace -F _git_cw cw 2>/dev/null || \
    complete -o default -o nospace -F _git_cw cw
-- 8< --

Nguyễn Thái Ngọc Duy (26):
  usage.c: move format processing out of die_errno()
  usage.c: add sys_error() that prints strerror() automatically
  copy.c: import copy_file() from busybox
  copy.c: delete unused code in copy_file()
  copy.c: convert bb_(p)error_msg to (sys_)error
  copy.c: style fix
  copy.c: convert copy_file() to copy_dir_recursively()
  completion: support git-worktree
  git-worktree.txt: keep subcommand listing in alphabetical order
  wrapper.c: allow to create an empty file with write_file()
  path.c: add git_common_path() and strbuf_git_common_path()
  worktree.c: use is_dot_or_dotdot()
  worktree.c: store "id" instead of "git_dir"
  worktree.c: add clear_worktree()
  worktree.c: add find_worktree_by_path()
  worktree.c: add is_main_worktree()
  worktree.c: add validate_worktree()
  worktree.c: add update_worktree_location()
  worktree.c: add is_worktree_locked()
  worktree: avoid 0{40}, too many zeroes, hard to read
  worktree: simplify prefixing paths
  worktree: add "lock" command
  worktree: add "unlock" command
  worktree: add "move" commmand
  worktree move: accept destination as directory
  worktree: add "remove" command

 Documentation/git-worktree.txt         |  55 +++--
 builtin/worktree.c                     | 240 ++++++++++++++++++++-
 cache.h                                |   4 +
 contrib/completion/git-completion.bash |  29 +++
 copy.c                                 | 369 +++++++++++++++++++++++++++++++++
 git-compat-util.h                      |   1 +
 path.c                                 |  29 +++
 t/t2028-worktree-move.sh (new +x)      | 103 +++++++++
 usage.c                                |  35 +++-
 worktree.c                             | 171 +++++++++++++--
 worktree.h                             |  40 +++-
 wrapper.c                              |   2 +-
 12 files changed, 1029 insertions(+), 49 deletions(-)
 create mode 100755 t/t2028-worktree-move.sh

-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 01/26] usage.c: move format processing out of die_errno()
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 02/26] usage.c: add sys_error() that prints strerror() automatically Nguyễn Thái Ngọc Duy
                     ` (24 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 usage.c | 25 +++++++++++++++----------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/usage.c b/usage.c
index 82ff131..0dba0c5 100644
--- a/usage.c
+++ b/usage.c
@@ -109,19 +109,12 @@ void NORETURN die(const char *err, ...)
 	va_end(params);
 }
 
-void NORETURN die_errno(const char *fmt, ...)
+static const char *fmt_with_err(const char *fmt)
 {
-	va_list params;
-	char fmt_with_err[1024];
+	static char fmt_with_err[1024];
 	char str_error[256], *err;
 	int i, j;
 
-	if (die_is_recursing()) {
-		fputs("fatal: recursion detected in die_errno handler\n",
-			stderr);
-		exit(128);
-	}
-
 	err = strerror(errno);
 	for (i = j = 0; err[i] && j < sizeof(str_error) - 1; ) {
 		if ((str_error[j++] = err[i++]) != '%')
@@ -137,9 +130,21 @@ void NORETURN die_errno(const char *fmt, ...)
 	}
 	str_error[j] = 0;
 	snprintf(fmt_with_err, sizeof(fmt_with_err), "%s: %s", fmt, str_error);
+	return fmt_with_err;
+}
+
+void NORETURN die_errno(const char *fmt, ...)
+{
+	va_list params;
+
+	if (die_is_recursing()) {
+		fputs("fatal: recursion detected in die_errno handler\n",
+			stderr);
+		exit(128);
+	}
 
 	va_start(params, fmt);
-	die_routine(fmt_with_err, params);
+	die_routine(fmt_with_err(fmt), params);
 	va_end(params);
 }
 
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 02/26] usage.c: add sys_error() that prints strerror() automatically
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 01/26] usage.c: move format processing out of die_errno() Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 03/26] copy.c: import copy_file() from busybox Nguyễn Thái Ngọc Duy
                     ` (23 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 git-compat-util.h |  1 +
 usage.c           | 10 ++++++++++
 2 files changed, 11 insertions(+)

diff --git a/git-compat-util.h b/git-compat-util.h
index 693a336..fd1d374 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -408,6 +408,7 @@ extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf,
 extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern NORETURN void die_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
+extern int sys_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
 
 #ifndef NO_OPENSSL
diff --git a/usage.c b/usage.c
index 0dba0c5..e7c37f2 100644
--- a/usage.c
+++ b/usage.c
@@ -148,6 +148,16 @@ void NORETURN die_errno(const char *fmt, ...)
 	va_end(params);
 }
 
+int sys_error(const char *fmt, ...)
+{
+	va_list params;
+
+	va_start(params, fmt);
+	error_routine(fmt_with_err(fmt), params);
+	va_end(params);
+	return -1;
+}
+
 #undef error
 int error(const char *err, ...)
 {
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 03/26] copy.c: import copy_file() from busybox
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 01/26] usage.c: move format processing out of die_errno() Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 02/26] usage.c: add sys_error() that prints strerror() automatically Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 04/26] copy.c: delete unused code in copy_file() Nguyễn Thái Ngọc Duy
                     ` (22 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

This is busybox's unmodified copy_file() in libbb/copy_file.c from the
GPL2+ commit f2c043acfcf9dad9fd3d65821b81f89986bbe54e (busybox: fix
uninitialized memory when displaying IPv6 addresses - 2016-01-18)

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 copy.c | 331 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 331 insertions(+)

diff --git a/copy.c b/copy.c
index 574fa1f..29e9d5b 100644
--- a/copy.c
+++ b/copy.c
@@ -65,3 +65,334 @@ int copy_file_with_time(const char *dst, const char *src, int mode)
 		return copy_times(dst, src);
 	return status;
 }
+
+#if 0
+/* Return:
+ * -1 error, copy not made
+ *  0 copy is made or user answered "no" in interactive mode
+ *    (failures to preserve mode/owner/times are not reported in exit code)
+ */
+int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
+{
+	/* This is a recursive function, try to minimize stack usage */
+	/* NB: each struct stat is ~100 bytes */
+	struct stat source_stat;
+	struct stat dest_stat;
+	smallint retval = 0;
+	smallint dest_exists = 0;
+	smallint ovr;
+
+/* Inverse of cp -d ("cp without -d") */
+#define FLAGS_DEREF (flags & (FILEUTILS_DEREFERENCE + FILEUTILS_DEREFERENCE_L0))
+
+	if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) {
+		/* This may be a dangling symlink.
+		 * Making [sym]links to dangling symlinks works, so... */
+		if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK))
+			goto make_links;
+		bb_perror_msg("can't stat '%s'", source);
+		return -1;
+	}
+
+	if (lstat(dest, &dest_stat) < 0) {
+		if (errno != ENOENT) {
+			bb_perror_msg("can't stat '%s'", dest);
+			return -1;
+		}
+	} else {
+		if (source_stat.st_dev == dest_stat.st_dev
+		 && source_stat.st_ino == dest_stat.st_ino
+		) {
+			bb_error_msg("'%s' and '%s' are the same file", source, dest);
+			return -1;
+		}
+		dest_exists = 1;
+	}
+
+#if ENABLE_SELINUX
+	if ((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) && is_selinux_enabled() > 0) {
+		security_context_t con;
+		if (lgetfilecon(source, &con) >= 0) {
+			if (setfscreatecon(con) < 0) {
+				bb_perror_msg("can't set setfscreatecon %s", con);
+				freecon(con);
+				return -1;
+			}
+		} else if (errno == ENOTSUP || errno == ENODATA) {
+			setfscreatecon_or_die(NULL);
+		} else {
+			bb_perror_msg("can't lgetfilecon %s", source);
+			return -1;
+		}
+	}
+#endif
+
+	if (S_ISDIR(source_stat.st_mode)) {
+		DIR *dp;
+		const char *tp;
+		struct dirent *d;
+		mode_t saved_umask = 0;
+
+		if (!(flags & FILEUTILS_RECUR)) {
+			bb_error_msg("omitting directory '%s'", source);
+			return -1;
+		}
+
+		/* Did we ever create source ourself before? */
+		tp = is_in_ino_dev_hashtable(&source_stat);
+		if (tp) {
+			/* We did! it's a recursion! man the lifeboats... */
+			bb_error_msg("recursion detected, omitting directory '%s'",
+					source);
+			return -1;
+		}
+
+		if (dest_exists) {
+			if (!S_ISDIR(dest_stat.st_mode)) {
+				bb_error_msg("target '%s' is not a directory", dest);
+				return -1;
+			}
+			/* race here: user can substitute a symlink between
+			 * this check and actual creation of files inside dest */
+		} else {
+			/* Create DEST */
+			mode_t mode;
+			saved_umask = umask(0);
+
+			mode = source_stat.st_mode;
+			if (!(flags & FILEUTILS_PRESERVE_STATUS))
+				mode = source_stat.st_mode & ~saved_umask;
+			/* Allow owner to access new dir (at least for now) */
+			mode |= S_IRWXU;
+			if (mkdir(dest, mode) < 0) {
+				umask(saved_umask);
+				bb_perror_msg("can't create directory '%s'", dest);
+				return -1;
+			}
+			umask(saved_umask);
+			/* need stat info for add_to_ino_dev_hashtable */
+			if (lstat(dest, &dest_stat) < 0) {
+				bb_perror_msg("can't stat '%s'", dest);
+				return -1;
+			}
+		}
+		/* remember (dev,inode) of each created dir.
+		 * NULL: name is not remembered */
+		add_to_ino_dev_hashtable(&dest_stat, NULL);
+
+		/* Recursively copy files in SOURCE */
+		dp = opendir(source);
+		if (dp == NULL) {
+			retval = -1;
+			goto preserve_mode_ugid_time;
+		}
+
+		while ((d = readdir(dp)) != NULL) {
+			char *new_source, *new_dest;
+
+			new_source = concat_subpath_file(source, d->d_name);
+			if (new_source == NULL)
+				continue;
+			new_dest = concat_path_file(dest, d->d_name);
+			if (copy_file(new_source, new_dest, flags & ~FILEUTILS_DEREFERENCE_L0) < 0)
+				retval = -1;
+			free(new_source);
+			free(new_dest);
+		}
+		closedir(dp);
+
+		if (!dest_exists
+		 && chmod(dest, source_stat.st_mode & ~saved_umask) < 0
+		) {
+			bb_perror_msg("can't preserve %s of '%s'", "permissions", dest);
+			/* retval = -1; - WRONG! copy *WAS* made */
+		}
+		goto preserve_mode_ugid_time;
+	}
+
+	if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) {
+		int (*lf)(const char *oldpath, const char *newpath);
+ make_links:
+		/* Hmm... maybe
+		 * if (DEREF && MAKE_SOFTLINK) source = realpath(source) ?
+		 * (but realpath returns NULL on dangling symlinks...) */
+		lf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link;
+		if (lf(source, dest) < 0) {
+			ovr = ask_and_unlink(dest, flags);
+			if (ovr <= 0)
+				return ovr;
+			if (lf(source, dest) < 0) {
+				bb_perror_msg("can't create link '%s'", dest);
+				return -1;
+			}
+		}
+		/* _Not_ jumping to preserve_mode_ugid_time:
+		 * (sym)links don't have those */
+		return 0;
+	}
+
+	if (/* "cp thing1 thing2" without -R: just open and read() from thing1 */
+	    !(flags & FILEUTILS_RECUR)
+	    /* "cp [-opts] regular_file thing2" */
+	 || S_ISREG(source_stat.st_mode)
+	 /* DEREF uses stat, which never returns S_ISLNK() == true.
+	  * So the below is never true: */
+	 /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */
+	) {
+		int src_fd;
+		int dst_fd;
+		mode_t new_mode;
+
+		if (!FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) {
+			/* "cp -d symlink dst": create a link */
+			goto dont_cat;
+		}
+
+		if (ENABLE_FEATURE_PRESERVE_HARDLINKS && !FLAGS_DEREF) {
+			const char *link_target;
+			link_target = is_in_ino_dev_hashtable(&source_stat);
+			if (link_target) {
+				if (link(link_target, dest) < 0) {
+					ovr = ask_and_unlink(dest, flags);
+					if (ovr <= 0)
+						return ovr;
+					if (link(link_target, dest) < 0) {
+						bb_perror_msg("can't create link '%s'", dest);
+						return -1;
+					}
+				}
+				return 0;
+			}
+			add_to_ino_dev_hashtable(&source_stat, dest);
+		}
+
+		src_fd = open_or_warn(source, O_RDONLY);
+		if (src_fd < 0)
+			return -1;
+
+		/* Do not try to open with weird mode fields */
+		new_mode = source_stat.st_mode;
+		if (!S_ISREG(source_stat.st_mode))
+			new_mode = 0666;
+
+		// POSIX way is a security problem versus (sym)link attacks
+		if (!ENABLE_FEATURE_NON_POSIX_CP) {
+			dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode);
+		} else { /* safe way: */
+			dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
+		}
+		if (dst_fd == -1) {
+			ovr = ask_and_unlink(dest, flags);
+			if (ovr <= 0) {
+				close(src_fd);
+				return ovr;
+			}
+			/* It shouldn't exist. If it exists, do not open (symlink attack?) */
+			dst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
+			if (dst_fd < 0) {
+				close(src_fd);
+				return -1;
+			}
+		}
+
+#if ENABLE_SELINUX
+		if ((flags & (FILEUTILS_PRESERVE_SECURITY_CONTEXT|FILEUTILS_SET_SECURITY_CONTEXT))
+		 && is_selinux_enabled() > 0
+		) {
+			security_context_t con;
+			if (getfscreatecon(&con) == -1) {
+				bb_perror_msg("getfscreatecon");
+				return -1;
+			}
+			if (con) {
+				if (setfilecon(dest, con) == -1) {
+					bb_perror_msg("setfilecon:%s,%s", dest, con);
+					freecon(con);
+					return -1;
+				}
+				freecon(con);
+			}
+		}
+#endif
+		if (bb_copyfd_eof(src_fd, dst_fd) == -1)
+			retval = -1;
+		/* Careful with writing... */
+		if (close(dst_fd) < 0) {
+			bb_perror_msg("error writing to '%s'", dest);
+			retval = -1;
+		}
+		/* ...but read size is already checked by bb_copyfd_eof */
+		close(src_fd);
+		/* "cp /dev/something new_file" should not
+		 * copy mode of /dev/something */
+		if (!S_ISREG(source_stat.st_mode))
+			return retval;
+		goto preserve_mode_ugid_time;
+	}
+ dont_cat:
+
+	/* Source is a symlink or a special file */
+	/* We are lazy here, a bit lax with races... */
+	if (dest_exists) {
+		errno = EEXIST;
+		ovr = ask_and_unlink(dest, flags);
+		if (ovr <= 0)
+			return ovr;
+	}
+	if (S_ISLNK(source_stat.st_mode)) {
+		char *lpath = xmalloc_readlink_or_warn(source);
+		if (lpath) {
+			int r = symlink(lpath, dest);
+			free(lpath);
+			if (r < 0) {
+				bb_perror_msg("can't create symlink '%s'", dest);
+				return -1;
+			}
+			if (flags & FILEUTILS_PRESERVE_STATUS)
+				if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
+					bb_perror_msg("can't preserve %s of '%s'", "ownership", dest);
+		}
+		/* _Not_ jumping to preserve_mode_ugid_time:
+		 * symlinks don't have those */
+		return 0;
+	}
+	if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode)
+	 || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode)
+	) {
+		if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) {
+			bb_perror_msg("can't create '%s'", dest);
+			return -1;
+		}
+	} else {
+		bb_error_msg("unrecognized file '%s' with mode %x", source, source_stat.st_mode);
+		return -1;
+	}
+
+ preserve_mode_ugid_time:
+
+	if (flags & FILEUTILS_PRESERVE_STATUS
+	/* Cannot happen: */
+	/* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */
+	) {
+		struct timeval times[2];
+
+		times[1].tv_sec = times[0].tv_sec = source_stat.st_mtime;
+		times[1].tv_usec = times[0].tv_usec = 0;
+		/* BTW, utimes sets usec-precision time - just FYI */
+		if (utimes(dest, times) < 0)
+			bb_perror_msg("can't preserve %s of '%s'", "times", dest);
+		if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {
+			source_stat.st_mode &= ~(S_ISUID | S_ISGID);
+			bb_perror_msg("can't preserve %s of '%s'", "ownership", dest);
+		}
+		if (chmod(dest, source_stat.st_mode) < 0)
+			bb_perror_msg("can't preserve %s of '%s'", "permissions", dest);
+	}
+
+	if (flags & FILEUTILS_VERBOSE) {
+		printf("'%s' -> '%s'\n", source, dest);
+	}
+
+	return retval;
+}
+#endif
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 04/26] copy.c: delete unused code in copy_file()
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (2 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 03/26] copy.c: import copy_file() from busybox Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 05/26] copy.c: convert bb_(p)error_msg to (sys_)error Nguyễn Thái Ngọc Duy
                     ` (21 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

 - selinux preservation code
 - make-link code
 - delete link dereference code
 - non-recursive copy code
 - stat no preservation code
 - verbose printing code

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 copy.c | 101 +++++------------------------------------------------------------
 1 file changed, 7 insertions(+), 94 deletions(-)

diff --git a/copy.c b/copy.c
index 29e9d5b..d24a8ae 100644
--- a/copy.c
+++ b/copy.c
@@ -82,14 +82,7 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 	smallint dest_exists = 0;
 	smallint ovr;
 
-/* Inverse of cp -d ("cp without -d") */
-#define FLAGS_DEREF (flags & (FILEUTILS_DEREFERENCE + FILEUTILS_DEREFERENCE_L0))
-
-	if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) {
-		/* This may be a dangling symlink.
-		 * Making [sym]links to dangling symlinks works, so... */
-		if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK))
-			goto make_links;
+	if (lstat(source, &source_stat) < 0) {
 		bb_perror_msg("can't stat '%s'", source);
 		return -1;
 	}
@@ -109,35 +102,12 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 		dest_exists = 1;
 	}
 
-#if ENABLE_SELINUX
-	if ((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) && is_selinux_enabled() > 0) {
-		security_context_t con;
-		if (lgetfilecon(source, &con) >= 0) {
-			if (setfscreatecon(con) < 0) {
-				bb_perror_msg("can't set setfscreatecon %s", con);
-				freecon(con);
-				return -1;
-			}
-		} else if (errno == ENOTSUP || errno == ENODATA) {
-			setfscreatecon_or_die(NULL);
-		} else {
-			bb_perror_msg("can't lgetfilecon %s", source);
-			return -1;
-		}
-	}
-#endif
-
 	if (S_ISDIR(source_stat.st_mode)) {
 		DIR *dp;
 		const char *tp;
 		struct dirent *d;
 		mode_t saved_umask = 0;
 
-		if (!(flags & FILEUTILS_RECUR)) {
-			bb_error_msg("omitting directory '%s'", source);
-			return -1;
-		}
-
 		/* Did we ever create source ourself before? */
 		tp = is_in_ino_dev_hashtable(&source_stat);
 		if (tp) {
@@ -160,8 +130,6 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 			saved_umask = umask(0);
 
 			mode = source_stat.st_mode;
-			if (!(flags & FILEUTILS_PRESERVE_STATUS))
-				mode = source_stat.st_mode & ~saved_umask;
 			/* Allow owner to access new dir (at least for now) */
 			mode |= S_IRWXU;
 			if (mkdir(dest, mode) < 0) {
@@ -210,45 +178,17 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 		goto preserve_mode_ugid_time;
 	}
 
-	if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) {
-		int (*lf)(const char *oldpath, const char *newpath);
- make_links:
-		/* Hmm... maybe
-		 * if (DEREF && MAKE_SOFTLINK) source = realpath(source) ?
-		 * (but realpath returns NULL on dangling symlinks...) */
-		lf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link;
-		if (lf(source, dest) < 0) {
-			ovr = ask_and_unlink(dest, flags);
-			if (ovr <= 0)
-				return ovr;
-			if (lf(source, dest) < 0) {
-				bb_perror_msg("can't create link '%s'", dest);
-				return -1;
-			}
-		}
-		/* _Not_ jumping to preserve_mode_ugid_time:
-		 * (sym)links don't have those */
-		return 0;
-	}
-
-	if (/* "cp thing1 thing2" without -R: just open and read() from thing1 */
-	    !(flags & FILEUTILS_RECUR)
-	    /* "cp [-opts] regular_file thing2" */
-	 || S_ISREG(source_stat.st_mode)
-	 /* DEREF uses stat, which never returns S_ISLNK() == true.
-	  * So the below is never true: */
-	 /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */
-	) {
+	if (S_ISREG(source_stat.st_mode) ) { /* "cp [-opts] regular_file thing2" */
 		int src_fd;
 		int dst_fd;
 		mode_t new_mode;
 
-		if (!FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) {
+		if (S_ISLNK(source_stat.st_mode)) {
 			/* "cp -d symlink dst": create a link */
 			goto dont_cat;
 		}
 
-		if (ENABLE_FEATURE_PRESERVE_HARDLINKS && !FLAGS_DEREF) {
+		if (ENABLE_FEATURE_PRESERVE_HARDLINKS) {
 			const char *link_target;
 			link_target = is_in_ino_dev_hashtable(&source_stat);
 			if (link_target) {
@@ -295,25 +235,6 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 			}
 		}
 
-#if ENABLE_SELINUX
-		if ((flags & (FILEUTILS_PRESERVE_SECURITY_CONTEXT|FILEUTILS_SET_SECURITY_CONTEXT))
-		 && is_selinux_enabled() > 0
-		) {
-			security_context_t con;
-			if (getfscreatecon(&con) == -1) {
-				bb_perror_msg("getfscreatecon");
-				return -1;
-			}
-			if (con) {
-				if (setfilecon(dest, con) == -1) {
-					bb_perror_msg("setfilecon:%s,%s", dest, con);
-					freecon(con);
-					return -1;
-				}
-				freecon(con);
-			}
-		}
-#endif
 		if (bb_copyfd_eof(src_fd, dst_fd) == -1)
 			retval = -1;
 		/* Careful with writing... */
@@ -348,9 +269,8 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 				bb_perror_msg("can't create symlink '%s'", dest);
 				return -1;
 			}
-			if (flags & FILEUTILS_PRESERVE_STATUS)
-				if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
-					bb_perror_msg("can't preserve %s of '%s'", "ownership", dest);
+			if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
+				bb_perror_msg("can't preserve %s of '%s'", "ownership", dest);
 		}
 		/* _Not_ jumping to preserve_mode_ugid_time:
 		 * symlinks don't have those */
@@ -370,10 +290,7 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 
  preserve_mode_ugid_time:
 
-	if (flags & FILEUTILS_PRESERVE_STATUS
-	/* Cannot happen: */
-	/* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */
-	) {
+	if (1 /*FILEUTILS_PRESERVE_STATUS*/) {
 		struct timeval times[2];
 
 		times[1].tv_sec = times[0].tv_sec = source_stat.st_mtime;
@@ -389,10 +306,6 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 			bb_perror_msg("can't preserve %s of '%s'", "permissions", dest);
 	}
 
-	if (flags & FILEUTILS_VERBOSE) {
-		printf("'%s' -> '%s'\n", source, dest);
-	}
-
 	return retval;
 }
 #endif
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 05/26] copy.c: convert bb_(p)error_msg to (sys_)error
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (3 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 04/26] copy.c: delete unused code in copy_file() Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 06/26] copy.c: style fix Nguyễn Thái Ngọc Duy
                     ` (20 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 copy.c | 85 ++++++++++++++++++++++++------------------------------------------
 1 file changed, 31 insertions(+), 54 deletions(-)

diff --git a/copy.c b/copy.c
index d24a8ae..cdb38d5 100644
--- a/copy.c
+++ b/copy.c
@@ -82,23 +82,16 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 	smallint dest_exists = 0;
 	smallint ovr;
 
-	if (lstat(source, &source_stat) < 0) {
-		bb_perror_msg("can't stat '%s'", source);
-		return -1;
-	}
+	if (lstat(source, &source_stat) < 0)
+		return sys_error(_("can't stat '%s'"), source);
 
 	if (lstat(dest, &dest_stat) < 0) {
-		if (errno != ENOENT) {
-			bb_perror_msg("can't stat '%s'", dest);
-			return -1;
-		}
+		if (errno != ENOENT)
+			return sys_error(_("can't stat '%s'"), dest);
 	} else {
-		if (source_stat.st_dev == dest_stat.st_dev
-		 && source_stat.st_ino == dest_stat.st_ino
-		) {
-			bb_error_msg("'%s' and '%s' are the same file", source, dest);
-			return -1;
-		}
+		if (source_stat.st_dev == dest_stat.st_dev &&
+		    source_stat.st_ino == dest_stat.st_ino)
+			return error(_("'%s' and '%s' are the same file"), source, dest);
 		dest_exists = 1;
 	}
 
@@ -110,18 +103,14 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 
 		/* Did we ever create source ourself before? */
 		tp = is_in_ino_dev_hashtable(&source_stat);
-		if (tp) {
+		if (tp)
 			/* We did! it's a recursion! man the lifeboats... */
-			bb_error_msg("recursion detected, omitting directory '%s'",
-					source);
-			return -1;
-		}
+			return error(_("recursion detected, omitting directory '%s'"),
+				     source);
 
 		if (dest_exists) {
-			if (!S_ISDIR(dest_stat.st_mode)) {
-				bb_error_msg("target '%s' is not a directory", dest);
-				return -1;
-			}
+			if (!S_ISDIR(dest_stat.st_mode))
+				return error(_("target '%s' is not a directory"), dest);
 			/* race here: user can substitute a symlink between
 			 * this check and actual creation of files inside dest */
 		} else {
@@ -134,15 +123,12 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 			mode |= S_IRWXU;
 			if (mkdir(dest, mode) < 0) {
 				umask(saved_umask);
-				bb_perror_msg("can't create directory '%s'", dest);
-				return -1;
+				return sys_error(_("can't create directory '%s'"), dest);
 			}
 			umask(saved_umask);
 			/* need stat info for add_to_ino_dev_hashtable */
-			if (lstat(dest, &dest_stat) < 0) {
-				bb_perror_msg("can't stat '%s'", dest);
-				return -1;
-			}
+			if (lstat(dest, &dest_stat) < 0)
+				return sys_error(_("can't stat '%s'"), dest);
 		}
 		/* remember (dev,inode) of each created dir.
 		 * NULL: name is not remembered */
@@ -172,7 +158,7 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 		if (!dest_exists
 		 && chmod(dest, source_stat.st_mode & ~saved_umask) < 0
 		) {
-			bb_perror_msg("can't preserve %s of '%s'", "permissions", dest);
+			sys_error(_("can't preserve permissions of '%s'"), dest);
 			/* retval = -1; - WRONG! copy *WAS* made */
 		}
 		goto preserve_mode_ugid_time;
@@ -196,10 +182,8 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 					ovr = ask_and_unlink(dest, flags);
 					if (ovr <= 0)
 						return ovr;
-					if (link(link_target, dest) < 0) {
-						bb_perror_msg("can't create link '%s'", dest);
-						return -1;
-					}
+					if (link(link_target, dest) < 0)
+						return sys_error(_("can't create link '%s'"), dest);
 				}
 				return 0;
 			}
@@ -238,10 +222,8 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 		if (bb_copyfd_eof(src_fd, dst_fd) == -1)
 			retval = -1;
 		/* Careful with writing... */
-		if (close(dst_fd) < 0) {
-			bb_perror_msg("error writing to '%s'", dest);
-			retval = -1;
-		}
+		if (close(dst_fd) < 0)
+			retval = sys_error(_("error writing to '%s'"), dest);
 		/* ...but read size is already checked by bb_copyfd_eof */
 		close(src_fd);
 		/* "cp /dev/something new_file" should not
@@ -265,12 +247,10 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 		if (lpath) {
 			int r = symlink(lpath, dest);
 			free(lpath);
-			if (r < 0) {
-				bb_perror_msg("can't create symlink '%s'", dest);
-				return -1;
-			}
+			if (r < 0)
+				return sys_error(_("can't create symlink '%s'"), dest);
 			if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
-				bb_perror_msg("can't preserve %s of '%s'", "ownership", dest);
+				sys_error(_("can't preserve %s of '%s'"), "ownership", dest);
 		}
 		/* _Not_ jumping to preserve_mode_ugid_time:
 		 * symlinks don't have those */
@@ -279,14 +259,11 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 	if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode)
 	 || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode)
 	) {
-		if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) {
-			bb_perror_msg("can't create '%s'", dest);
-			return -1;
-		}
-	} else {
-		bb_error_msg("unrecognized file '%s' with mode %x", source, source_stat.st_mode);
-		return -1;
-	}
+		if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0)
+			return sys_error(_("can't create '%s'"), dest);
+	} else
+		return error(_("unrecognized file '%s' with mode %x"),
+			     source, source_stat.st_mode);
 
  preserve_mode_ugid_time:
 
@@ -297,13 +274,13 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 		times[1].tv_usec = times[0].tv_usec = 0;
 		/* BTW, utimes sets usec-precision time - just FYI */
 		if (utimes(dest, times) < 0)
-			bb_perror_msg("can't preserve %s of '%s'", "times", dest);
+			sys_error(_("can't preserve %s of '%s'"), "times", dest);
 		if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {
 			source_stat.st_mode &= ~(S_ISUID | S_ISGID);
-			bb_perror_msg("can't preserve %s of '%s'", "ownership", dest);
+			sys_error(_("can't preserve %s of '%s'"), "ownership", dest);
 		}
 		if (chmod(dest, source_stat.st_mode) < 0)
-			bb_perror_msg("can't preserve %s of '%s'", "permissions", dest);
+			sys_error(_("can't preserve %s of '%s'"), "permissions", dest);
 	}
 
 	return retval;
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 06/26] copy.c: style fix
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (4 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 05/26] copy.c: convert bb_(p)error_msg to (sys_)error Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 07/26] copy.c: convert copy_file() to copy_dir_recursively() Nguyễn Thái Ngọc Duy
                     ` (19 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 copy.c | 50 +++++++++++++++++++++++++++++---------------------
 1 file changed, 29 insertions(+), 21 deletions(-)

diff --git a/copy.c b/copy.c
index cdb38d5..00f8349 100644
--- a/copy.c
+++ b/copy.c
@@ -111,8 +111,10 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 		if (dest_exists) {
 			if (!S_ISDIR(dest_stat.st_mode))
 				return error(_("target '%s' is not a directory"), dest);
-			/* race here: user can substitute a symlink between
-			 * this check and actual creation of files inside dest */
+			/*
+			 * race here: user can substitute a symlink between
+			 * this check and actual creation of files inside dest
+			 */
 		} else {
 			/* Create DEST */
 			mode_t mode;
@@ -130,22 +132,24 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 			if (lstat(dest, &dest_stat) < 0)
 				return sys_error(_("can't stat '%s'"), dest);
 		}
-		/* remember (dev,inode) of each created dir.
-		 * NULL: name is not remembered */
+		/*
+		 * remember (dev,inode) of each created dir. name is
+		 * not remembered
+		 */
 		add_to_ino_dev_hashtable(&dest_stat, NULL);
 
 		/* Recursively copy files in SOURCE */
 		dp = opendir(source);
-		if (dp == NULL) {
+		if (!dp) {
 			retval = -1;
 			goto preserve_mode_ugid_time;
 		}
 
-		while ((d = readdir(dp)) != NULL) {
+		while ((d = readdir(dp))) {
 			char *new_source, *new_dest;
 
 			new_source = concat_subpath_file(source, d->d_name);
-			if (new_source == NULL)
+			if (!new_source)
 				continue;
 			new_dest = concat_path_file(dest, d->d_name);
 			if (copy_file(new_source, new_dest, flags & ~FILEUTILS_DEREFERENCE_L0) < 0)
@@ -155,16 +159,15 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 		}
 		closedir(dp);
 
-		if (!dest_exists
-		 && chmod(dest, source_stat.st_mode & ~saved_umask) < 0
-		) {
+		if (!dest_exists &&
+		    chmod(dest, source_stat.st_mode & ~saved_umask) < 0) {
 			sys_error(_("can't preserve permissions of '%s'"), dest);
 			/* retval = -1; - WRONG! copy *WAS* made */
 		}
 		goto preserve_mode_ugid_time;
 	}
 
-	if (S_ISREG(source_stat.st_mode) ) { /* "cp [-opts] regular_file thing2" */
+	if (S_ISREG(source_stat.st_mode)) { /* "cp [-opts] regular_file thing2" */
 		int src_fd;
 		int dst_fd;
 		mode_t new_mode;
@@ -199,7 +202,7 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 		if (!S_ISREG(source_stat.st_mode))
 			new_mode = 0666;
 
-		// POSIX way is a security problem versus (sym)link attacks
+		/* POSIX way is a security problem versus (sym)link attacks */
 		if (!ENABLE_FEATURE_NON_POSIX_CP) {
 			dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode);
 		} else { /* safe way: */
@@ -226,13 +229,15 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 			retval = sys_error(_("error writing to '%s'"), dest);
 		/* ...but read size is already checked by bb_copyfd_eof */
 		close(src_fd);
-		/* "cp /dev/something new_file" should not
-		 * copy mode of /dev/something */
+		/*
+		 * "cp /dev/something new_file" should not
+		 * copy mode of /dev/something
+		 */
 		if (!S_ISREG(source_stat.st_mode))
 			return retval;
 		goto preserve_mode_ugid_time;
 	}
- dont_cat:
+dont_cat:
 
 	/* Source is a symlink or a special file */
 	/* We are lazy here, a bit lax with races... */
@@ -252,20 +257,23 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 			if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
 				sys_error(_("can't preserve %s of '%s'"), "ownership", dest);
 		}
-		/* _Not_ jumping to preserve_mode_ugid_time:
-		 * symlinks don't have those */
+		/*
+		 * _Not_ jumping to preserve_mode_ugid_time: symlinks
+		 * don't have those
+		 */
 		return 0;
 	}
-	if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode)
-	 || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode)
-	) {
+	if (S_ISBLK(source_stat.st_mode) ||
+	    S_ISCHR(source_stat.st_mode) ||
+	    S_ISSOCK(source_stat.st_mode) ||
+	    S_ISFIFO(source_stat.st_mode)) {
 		if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0)
 			return sys_error(_("can't create '%s'"), dest);
 	} else
 		return error(_("unrecognized file '%s' with mode %x"),
 			     source, source_stat.st_mode);
 
- preserve_mode_ugid_time:
+preserve_mode_ugid_time:
 
 	if (1 /*FILEUTILS_PRESERVE_STATUS*/) {
 		struct timeval times[2];
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 07/26] copy.c: convert copy_file() to copy_dir_recursively()
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (5 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 06/26] copy.c: style fix Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 08/26] completion: support git-worktree Nguyễn Thái Ngọc Duy
                     ` (18 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

This finally enables busybox's copy_file() code under a new name
(because "copy_file" is already taken in Git code base). Because this
comes from busybox, POSIXy (or even Linuxy) behavior is expected. More
changes may be needed for Windows support.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 cache.h |   1 +
 copy.c  | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++++------------
 2 files changed, 179 insertions(+), 38 deletions(-)

diff --git a/cache.h b/cache.h
index 26640b4..30a2708 100644
--- a/cache.h
+++ b/cache.h
@@ -1659,6 +1659,7 @@ extern void fprintf_or_die(FILE *, const char *fmt, ...);
 extern int copy_fd(int ifd, int ofd);
 extern int copy_file(const char *dst, const char *src, int mode);
 extern int copy_file_with_time(const char *dst, const char *src, int mode);
+extern int copy_dir_recursively(const char *source, const char *dest);
 
 extern void write_or_die(int fd, const void *buf, size_t count);
 extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
diff --git a/copy.c b/copy.c
index 00f8349..f04ac87 100644
--- a/copy.c
+++ b/copy.c
@@ -1,4 +1,6 @@
 #include "cache.h"
+#include "dir.h"
+#include "hashmap.h"
 
 int copy_fd(int ifd, int ofd)
 {
@@ -66,21 +68,126 @@ int copy_file_with_time(const char *dst, const char *src, int mode)
 	return status;
 }
 
-#if 0
-/* Return:
- * -1 error, copy not made
- *  0 copy is made or user answered "no" in interactive mode
- *    (failures to preserve mode/owner/times are not reported in exit code)
+struct inode_key {
+	struct hashmap_entry entry;
+	ino_t ino;
+	dev_t dev;
+	/*
+	 * Reportedly, on cramfs a file and a dir can have same ino.
+	 * Need to also remember "file/dir" bit:
+	 */
+	char isdir; /* bool */
+};
+
+struct inode_value {
+	struct inode_key key;
+	char name[FLEX_ARRAY];
+};
+
+#define HASH_SIZE      311u   /* Should be prime */
+static inline unsigned hash_inode(ino_t i)
+{
+	return i % HASH_SIZE;
+}
+
+static int inode_cmp(const void *entry, const void *entry_or_key,
+		     const void *keydata)
+{
+	const struct inode_value *inode = entry;
+	const struct inode_key   *key   = entry_or_key;
+
+	return !(inode->key.ino   == key->ino &&
+		 inode->key.dev   == key->dev &&
+		 inode->key.isdir == key->isdir);
+}
+
+static const char *is_in_ino_dev_hashtable(const struct hashmap *map,
+					   const struct stat *st)
+{
+	struct inode_key key;
+	struct inode_value *value;
+
+	key.entry.hash = hash_inode(st->st_ino);
+	key.ino	       = st->st_ino;
+	key.dev	       = st->st_dev;
+	key.isdir      = !!S_ISDIR(st->st_mode);
+	value	       = hashmap_get(map, &key, NULL);
+	return value ? value->name : NULL;
+}
+
+static void add_to_ino_dev_hashtable(struct hashmap *map,
+				     const struct stat *st,
+				     const char *path)
+{
+	struct inode_value *v;
+	int len = strlen(path);
+
+	v = xmalloc(offsetof(struct inode_value, name) + len + 1);
+	v->key.entry.hash = hash_inode(st->st_ino);
+	v->key.ino	  = st->st_ino;
+	v->key.dev	  = st->st_dev;
+	v->key.isdir      = !!S_ISDIR(st->st_mode);
+	memcpy(v->name, path, len + 1);
+	hashmap_add(map, v);
+}
+
+/*
+ * Find out if the last character of a string matches the one given.
+ * Don't underrun the buffer if the string length is 0.
  */
-int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
+static inline char *last_char_is(const char *s, int c)
+{
+	if (s && *s) {
+		size_t sz = strlen(s) - 1;
+		s += sz;
+		if ( (unsigned char)*s == c)
+			return (char*)s;
+	}
+	return NULL;
+}
+
+static inline char *concat_path_file(const char *path, const char *filename)
+{
+	struct strbuf sb = STRBUF_INIT;
+	char *lc;
+
+	if (!path)
+		path = "";
+	lc = last_char_is(path, '/');
+	while (*filename == '/')
+		filename++;
+	strbuf_addf(&sb, "%s%s%s", path, (lc==NULL ? "/" : ""), filename);
+	return strbuf_detach(&sb, NULL);
+}
+
+static char *concat_subpath_file(const char *path, const char *f)
+{
+	if (f && is_dot_or_dotdot(f))
+		return NULL;
+	return concat_path_file(path, f);
+}
+
+static int do_unlink(const char *dest)
+{
+	int e = errno;
+
+	if (unlink(dest) < 0) {
+		errno = e; /* do not use errno from unlink */
+		return sys_error(_("can't create '%s'"), dest);
+	}
+	return 0;
+}
+
+static int copy_dir_1(struct hashmap *inode_map,
+		      const char *source,
+		      const char *dest)
 {
 	/* This is a recursive function, try to minimize stack usage */
-	/* NB: each struct stat is ~100 bytes */
 	struct stat source_stat;
 	struct stat dest_stat;
-	smallint retval = 0;
-	smallint dest_exists = 0;
-	smallint ovr;
+	int retval = 0;
+	int dest_exists = 0;
+	int ovr;
 
 	if (lstat(source, &source_stat) < 0)
 		return sys_error(_("can't stat '%s'"), source);
@@ -102,7 +209,7 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 		mode_t saved_umask = 0;
 
 		/* Did we ever create source ourself before? */
-		tp = is_in_ino_dev_hashtable(&source_stat);
+		tp = is_in_ino_dev_hashtable(inode_map, &source_stat);
 		if (tp)
 			/* We did! it's a recursion! man the lifeboats... */
 			return error(_("recursion detected, omitting directory '%s'"),
@@ -132,11 +239,12 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 			if (lstat(dest, &dest_stat) < 0)
 				return sys_error(_("can't stat '%s'"), dest);
 		}
+
 		/*
 		 * remember (dev,inode) of each created dir. name is
 		 * not remembered
 		 */
-		add_to_ino_dev_hashtable(&dest_stat, NULL);
+		add_to_ino_dev_hashtable(inode_map, &dest_stat, "");
 
 		/* Recursively copy files in SOURCE */
 		dp = opendir(source);
@@ -152,7 +260,7 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 			if (!new_source)
 				continue;
 			new_dest = concat_path_file(dest, d->d_name);
-			if (copy_file(new_source, new_dest, flags & ~FILEUTILS_DEREFERENCE_L0) < 0)
+			if (copy_dir_1(inode_map, new_source, new_dest) < 0)
 				retval = -1;
 			free(new_source);
 			free(new_dest);
@@ -177,53 +285,57 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
 			goto dont_cat;
 		}
 
-		if (ENABLE_FEATURE_PRESERVE_HARDLINKS) {
+		if (1 /*ENABLE_FEATURE_PRESERVE_HARDLINKS*/) {
 			const char *link_target;
-			link_target = is_in_ino_dev_hashtable(&source_stat);
+			link_target = is_in_ino_dev_hashtable(inode_map, &source_stat);
 			if (link_target) {
 				if (link(link_target, dest) < 0) {
-					ovr = ask_and_unlink(dest, flags);
-					if (ovr <= 0)
+					ovr = do_unlink(dest);
+					if (ovr < 0)
 						return ovr;
 					if (link(link_target, dest) < 0)
 						return sys_error(_("can't create link '%s'"), dest);
 				}
 				return 0;
 			}
-			add_to_ino_dev_hashtable(&source_stat, dest);
+			add_to_ino_dev_hashtable(inode_map, &source_stat, dest);
 		}
 
-		src_fd = open_or_warn(source, O_RDONLY);
+		src_fd = open(source, O_RDONLY);
 		if (src_fd < 0)
-			return -1;
+			return sys_error(_("can't open '%s'"), source);
 
 		/* Do not try to open with weird mode fields */
 		new_mode = source_stat.st_mode;
 		if (!S_ISREG(source_stat.st_mode))
 			new_mode = 0666;
 
-		/* POSIX way is a security problem versus (sym)link attacks */
-		if (!ENABLE_FEATURE_NON_POSIX_CP) {
-			dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode);
-		} else { /* safe way: */
-			dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
-		}
+		dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
 		if (dst_fd == -1) {
-			ovr = ask_and_unlink(dest, flags);
-			if (ovr <= 0) {
+			ovr = do_unlink(dest);
+			if (ovr < 0) {
 				close(src_fd);
 				return ovr;
 			}
 			/* It shouldn't exist. If it exists, do not open (symlink attack?) */
-			dst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
+			dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
 			if (dst_fd < 0) {
 				close(src_fd);
-				return -1;
+				return sys_error(_("can't open '%s'"), dest);
 			}
 		}
 
-		if (bb_copyfd_eof(src_fd, dst_fd) == -1)
+		switch (copy_fd(src_fd, dst_fd)) {
+		case COPY_READ_ERROR:
+			error(_("copy-fd: read returned %s"), strerror(errno));
 			retval = -1;
+			break;
+		case COPY_WRITE_ERROR:
+			error(_("copy-fd: write returned %s"), strerror(errno));
+			retval = -1;
+			break;
+		}
+
 		/* Careful with writing... */
 		if (close(dst_fd) < 0)
 			retval = sys_error(_("error writing to '%s'"), dest);
@@ -243,19 +355,28 @@ dont_cat:
 	/* We are lazy here, a bit lax with races... */
 	if (dest_exists) {
 		errno = EEXIST;
-		ovr = ask_and_unlink(dest, flags);
-		if (ovr <= 0)
+		ovr = do_unlink(dest);
+		if (ovr < 0)
 			return ovr;
 	}
 	if (S_ISLNK(source_stat.st_mode)) {
-		char *lpath = xmalloc_readlink_or_warn(source);
-		if (lpath) {
-			int r = symlink(lpath, dest);
-			free(lpath);
+		struct strbuf lpath = STRBUF_INIT;
+		if (!strbuf_readlink(&lpath, source, 0)) {
+			int r = symlink(lpath.buf, dest);
+			strbuf_release(&lpath);
 			if (r < 0)
 				return sys_error(_("can't create symlink '%s'"), dest);
 			if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
 				sys_error(_("can't preserve %s of '%s'"), "ownership", dest);
+		} else {
+			/* EINVAL => "file: Invalid argument" => puzzled user */
+			const char *errmsg = _("not a symlink");
+			int err = errno;
+
+			if (err != EINVAL)
+				errmsg = strerror(err);
+			error(_("%s: cannot read link: %s"), source, errmsg);
+			strbuf_release(&lpath);
 		}
 		/*
 		 * _Not_ jumping to preserve_mode_ugid_time: symlinks
@@ -293,4 +414,23 @@ preserve_mode_ugid_time:
 
 	return retval;
 }
-#endif
+
+/*
+ * Return:
+ * -1 error, copy not made
+ *  0 copy is made
+ *
+ * Failures to preserve mode/owner/times are not reported in exit
+ * code. No support for preserving SELinux security context. Symlinks
+ * and hardlinks are preserved.
+ */
+int copy_dir_recursively(const char *source, const char *dest)
+{
+	int ret;
+	struct hashmap inode_map;
+
+	hashmap_init(&inode_map, inode_cmp, 1024);
+	ret = copy_dir_1(&inode_map, source, dest);
+	hashmap_free(&inode_map, 1);
+	return ret;
+}
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 08/26] completion: support git-worktree
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (6 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 07/26] copy.c: convert copy_file() to copy_dir_recursively() Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 09/26] git-worktree.txt: keep subcommand listing in alphabetical order Nguyễn Thái Ngọc Duy
                     ` (17 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 contrib/completion/git-completion.bash | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 45ec47f..099d52e 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -2594,6 +2594,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.7.0.377.g4cd97dd

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

* [PATCH v2 09/26] git-worktree.txt: keep subcommand listing in alphabetical order
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (7 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 08/26] completion: support git-worktree Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 10/26] wrapper.c: allow to create an empty file with write_file() Nguyễn Thái Ngọc Duy
                     ` (16 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-worktree.txt | 10 +++++-----
 builtin/worktree.c             |  2 +-
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 62c76c1..1c9d7c1 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -10,8 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git worktree add' [-f] [--detach] [-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
 -------
 
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 475b958..68341e4 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -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
 };
 
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 10/26] wrapper.c: allow to create an empty file with write_file()
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (8 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 09/26] git-worktree.txt: keep subcommand listing in alphabetical order Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-17 22:29     ` Junio C Hamano
  2016-02-16 13:29   ` [PATCH v2 11/26] path.c: add git_common_path() and strbuf_git_common_path() Nguyễn Thái Ngọc Duy
                     ` (15 subsequent siblings)
  25 siblings, 1 reply; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

This is used later on to create empty .git/worktrees/xxx/locked when
"git worktree lock" is called with no reason given.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 wrapper.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/wrapper.c b/wrapper.c
index 29a45d2..1dc1eff 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -666,7 +666,7 @@ static int write_file_v(const char *path, int fatal,
 	}
 	strbuf_vaddf(&sb, fmt, params);
 	strbuf_complete_line(&sb);
-	if (write_in_full(fd, sb.buf, sb.len) != sb.len) {
+	if (sb.len && write_in_full(fd, sb.buf, sb.len) != sb.len) {
 		int err = errno;
 		close(fd);
 		strbuf_release(&sb);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 11/26] path.c: add git_common_path() and strbuf_git_common_path()
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (9 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 10/26] wrapper.c: allow to create an empty file with write_file() Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 12/26] worktree.c: use is_dot_or_dotdot() Nguyễn Thái Ngọc Duy
                     ` (14 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 cache.h |  3 +++
 path.c  | 29 +++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)

diff --git a/cache.h b/cache.h
index 30a2708..04818c2 100644
--- a/cache.h
+++ b/cache.h
@@ -765,11 +765,14 @@ extern int 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,
diff --git a/path.c b/path.c
index 8b7e168..0c294a1 100644
--- a/path.c
+++ b/path.c
@@ -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;
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 12/26] worktree.c: use is_dot_or_dotdot()
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (10 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 11/26] path.c: add git_common_path() and strbuf_git_common_path() Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 13/26] worktree.c: store "id" instead of "git_dir" Nguyễn Thái Ngọc Duy
                     ` (13 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/worktree.c | 2 +-
 worktree.c         | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 68341e4..cfc848d 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -94,7 +94,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))
diff --git a/worktree.c b/worktree.c
index 6181a66..ddb8cb7 100644
--- a/worktree.c
+++ b/worktree.c
@@ -2,6 +2,7 @@
 #include "refs.h"
 #include "strbuf.h"
 #include "worktree.h"
+#include "dir.h"
 
 void free_worktrees(struct worktree **worktrees)
 {
@@ -173,7 +174,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))) {
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 13/26] worktree.c: store "id" instead of "git_dir"
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (11 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 12/26] worktree.c: use is_dot_or_dotdot() Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 14/26] worktree.c: add clear_worktree() Nguyễn Thái Ngọc Duy
                     ` (12 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: 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.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 29 ++++++++++++++++-------------
 worktree.h |  7 ++++++-
 2 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/worktree.c b/worktree.c
index ddb8cb7..4c38414 100644
--- a/worktree.c
+++ b/worktree.c
@@ -10,7 +10,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]);
 	}
@@ -75,13 +75,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, "/.");
@@ -93,7 +91,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;
@@ -101,7 +99,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;
@@ -112,16 +109,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;
@@ -141,7 +135,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;
@@ -149,7 +143,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;
@@ -189,6 +182,14 @@ struct worktree **get_worktrees(void)
 	return list;
 }
 
+const char *get_worktree_git_dir(const struct worktree *wt)
+{
+	if (wt->id)
+		return git_common_path("worktrees/%s", wt->id);
+	else
+		return get_git_common_dir();
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
@@ -200,7 +201,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;
diff --git a/worktree.h b/worktree.h
index b4b3dda..e89d423 100644
--- a/worktree.h
+++ b/worktree.h
@@ -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,11 @@ struct worktree {
 extern struct worktree **get_worktrees(void);
 
 /*
+ * Return git dir of the worktree. Note that the path may be relative.
+ */
+extern const char *get_worktree_git_dir(const struct worktree *wt);
+
+/*
  * Free up the memory for worktree(s)
  */
 extern void free_worktrees(struct worktree **);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 14/26] worktree.c: add clear_worktree()
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (12 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 13/26] worktree.c: store "id" instead of "git_dir" Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 15/26] worktree.c: add find_worktree_by_path() Nguyễn Thái Ngọc Duy
                     ` (11 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 14 +++++++++++---
 worktree.h |  5 +++++
 2 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/worktree.c b/worktree.c
index 4c38414..b4e4b57 100644
--- a/worktree.c
+++ b/worktree.c
@@ -4,14 +4,22 @@
 #include "worktree.h"
 #include "dir.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);
diff --git a/worktree.h b/worktree.h
index e89d423..0ba07ab 100644
--- a/worktree.h
+++ b/worktree.h
@@ -28,6 +28,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)
  */
 extern void free_worktrees(struct worktree **);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 15/26] worktree.c: add find_worktree_by_path()
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (13 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 14/26] worktree.c: add clear_worktree() Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 16/26] worktree.c: add is_main_worktree() Nguyễn Thái Ngọc Duy
                     ` (10 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 16 ++++++++++++++++
 worktree.h |  6 ++++++
 2 files changed, 22 insertions(+)

diff --git a/worktree.c b/worktree.c
index b4e4b57..e444ad1 100644
--- a/worktree.c
+++ b/worktree.c
@@ -198,6 +198,22 @@ const char *get_worktree_git_dir(const struct worktree *wt)
 		return get_git_common_dir();
 }
 
+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 (!strcmp_icase(path, real_path(wt->path)))
+			break;
+		wt = NULL;
+	}
+	free(path);
+	return wt;
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
diff --git a/worktree.h b/worktree.h
index 0ba07ab..c163b6b 100644
--- a/worktree.h
+++ b/worktree.h
@@ -28,6 +28,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
  */
 extern void clear_worktree(struct worktree *);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 16/26] worktree.c: add is_main_worktree()
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (14 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 15/26] worktree.c: add find_worktree_by_path() Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 17/26] worktree.c: add validate_worktree() Nguyễn Thái Ngọc Duy
                     ` (9 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 5 +++++
 worktree.h | 5 +++++
 2 files changed, 10 insertions(+)

diff --git a/worktree.c b/worktree.c
index e444ad1..e878f49 100644
--- a/worktree.c
+++ b/worktree.c
@@ -214,6 +214,11 @@ struct worktree *find_worktree_by_path(struct worktree **list,
 	return wt;
 }
 
+int is_main_worktree(const struct worktree *wt)
+{
+	return wt && !wt->id;
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
diff --git a/worktree.h b/worktree.h
index c163b6b..c7a4d20 100644
--- a/worktree.h
+++ b/worktree.h
@@ -34,6 +34,11 @@ 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
  */
 extern void clear_worktree(struct worktree *);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 17/26] worktree.c: add validate_worktree()
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (15 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 16/26] worktree.c: add is_main_worktree() Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 18/26] worktree.c: add update_worktree_location() Nguyễn Thái Ngọc Duy
                     ` (8 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

This function is later used by "worktree move" and "worktree remove"
to ensure that we have a good connection between the repository and
the worktree.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 worktree.h |  5 +++++
 2 files changed, 68 insertions(+)

diff --git a/worktree.c b/worktree.c
index e878f49..28195b1 100644
--- a/worktree.c
+++ b/worktree.c
@@ -219,6 +219,69 @@ int is_main_worktree(const struct worktree *wt)
 	return wt && !wt->id;
 }
 
+static int report(int quiet, const char *fmt, ...)
+{
+	va_list params;
+
+	if (quiet)
+		return -1;
+
+	va_start(params, fmt);
+	vfprintf(stderr, fmt, params);
+	fputc('\n', stderr);
+	va_end(params);
+	return -1;
+}
+
+int validate_worktree(const struct worktree *wt, int quiet)
+{
+	struct strbuf sb = STRBUF_INIT;
+	const char *path;
+	int err;
+
+	if (is_main_worktree(wt)) {
+		/*
+		 * Main worktree using .git file to point to the
+		 * repository would make it impossible to know where
+		 * the actual worktree is if this function is executed
+		 * from another worktree. No .git file support for now.
+		 */
+		strbuf_addf(&sb, "%s/.git", wt->path);
+		if (!is_directory(sb.buf)) {
+			strbuf_release(&sb);
+			return report(quiet, _("'%s/.git' at main worktree is not the repository directory"),
+				      wt->path);
+		}
+		return 0;
+	}
+
+	/*
+	 * Make sure "gitdir" file points to a real .git file and that
+	 * file points back here.
+	 */
+	if (!is_absolute_path(wt->path))
+		return report(quiet, _("'%s' file does not contain absolute path to the worktree location"),
+			      git_common_path("worktrees/%s/gitdir", wt->id));
+
+	strbuf_addf(&sb, "%s/.git", wt->path);
+	if (!file_exists(sb.buf)) {
+		strbuf_release(&sb);
+		return report(quiet, _("'%s/.git' does not exist"), wt->path);
+	}
+
+	path = read_gitfile_gently(sb.buf, &err);
+	strbuf_release(&sb);
+	if (!path)
+		return report(quiet, _("'%s/.git' is not a .git file, error code %d"),
+			      wt->path, err);
+
+	if (strcmp_icase(path, real_path(git_common_path("worktrees/%s", wt->id))))
+		return report(quiet, _("'%s' does not point back to"),
+			      wt->path, git_common_path("worktrees/%s", wt->id));
+
+	return 0;
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
diff --git a/worktree.h b/worktree.h
index c7a4d20..0d6be18 100644
--- a/worktree.h
+++ b/worktree.h
@@ -39,6 +39,11 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,
 extern int is_main_worktree(const struct worktree *wt);
 
 /*
+ * Return zero if the worktree is in good condition.
+ */
+extern int validate_worktree(const struct worktree *wt, int quiet);
+
+/*
  * Free up the memory for worktree
  */
 extern void clear_worktree(struct worktree *);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 18/26] worktree.c: add update_worktree_location()
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (16 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 17/26] worktree.c: add validate_worktree() Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 19/26] worktree.c: add is_worktree_locked() Nguyễn Thái Ngọc Duy
                     ` (7 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 25 +++++++++++++++++++++++++
 worktree.h |  6 ++++++
 2 files changed, 31 insertions(+)

diff --git a/worktree.c b/worktree.c
index 28195b1..04aac73 100644
--- a/worktree.c
+++ b/worktree.c
@@ -282,6 +282,31 @@ int validate_worktree(const struct worktree *wt, int quiet)
 	return 0;
 }
 
+int update_worktree_location(struct worktree *wt, const char *path_)
+{
+	struct strbuf path = STRBUF_INIT;
+	int ret;
+
+	if (is_main_worktree(wt))
+		return 0;
+
+	strbuf_add_absolute_path(&path, path_);
+	if (strcmp_icase(wt->path, path.buf)) {
+		if (!write_file_gently(git_common_path("worktrees/%s/gitdir",
+						       wt->id),
+				       "%s/.git", real_path(path.buf))) {
+			free(wt->path);
+			wt->path = strbuf_detach(&path, NULL);
+			ret = 0;
+		} else
+			ret = sys_error(_("failed to update '%s'"),
+					git_common_path("worktrees/%s/gitdir",
+							wt->id));
+	}
+	strbuf_release(&path);
+	return ret;
+}
+
 char *find_shared_symref(const char *symref, const char *target)
 {
 	char *existing = NULL;
diff --git a/worktree.h b/worktree.h
index 0d6be18..bbe40ef 100644
--- a/worktree.h
+++ b/worktree.h
@@ -44,6 +44,12 @@ extern int is_main_worktree(const struct worktree *wt);
 extern int validate_worktree(const struct worktree *wt, int quiet);
 
 /*
+ * Update worktrees/xxx/gitdir with the new path.
+ */
+extern int update_worktree_location(struct worktree *wt,
+				    const char *path_);
+
+/*
  * Free up the memory for worktree
  */
 extern void clear_worktree(struct worktree *);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 19/26] worktree.c: add is_worktree_locked()
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (17 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 18/26] worktree.c: add update_worktree_location() Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 20/26] worktree: avoid 0{40}, too many zeroes, hard to read Nguyễn Thái Ngọc Duy
                     ` (6 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 worktree.c | 18 ++++++++++++++++++
 worktree.h |  6 ++++++
 2 files changed, 24 insertions(+)

diff --git a/worktree.c b/worktree.c
index 04aac73..facb907 100644
--- a/worktree.c
+++ b/worktree.c
@@ -219,6 +219,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;
+}
+
 static int report(int quiet, const char *fmt, ...)
 {
 	va_list params;
diff --git a/worktree.h b/worktree.h
index bbe40ef..cbd5389 100644
--- a/worktree.h
+++ b/worktree.h
@@ -39,6 +39,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);
+
+/*
  * Return zero if the worktree is in good condition.
  */
 extern int validate_worktree(const struct worktree *wt, int quiet);
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 20/26] worktree: avoid 0{40}, too many zeroes, hard to read
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (18 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 19/26] worktree.c: add is_worktree_locked() Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 21/26] worktree: simplify prefixing paths Nguyễn Thái Ngọc Duy
                     ` (5 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/worktree.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index cfc848d..97666cc 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -263,7 +263,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, "../..");
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 21/26] worktree: simplify prefixing paths
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (19 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 20/26] worktree: avoid 0{40}, too many zeroes, hard to read Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 22/26] worktree: add "lock" command Nguyễn Thái Ngọc Duy
                     ` (4 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/worktree.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index 97666cc..b261e11 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -332,7 +332,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;
@@ -453,6 +453,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"))
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 22/26] worktree: add "lock" command
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (20 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 21/26] worktree: simplify prefixing paths Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 23/26] worktree: add "unlock" command Nguyễn Thái Ngọc Duy
                     ` (3 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 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

diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 1c9d7c1..9f0c9f0 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -11,6 +11,7 @@ SYNOPSIS
 [verse]
 'git worktree add' [-f] [--detach] [-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.
@@ -104,6 +111,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
@@ -220,8 +230,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
 ---
diff --git a/builtin/worktree.c b/builtin/worktree.c
index b261e11..f45a425 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -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
 };
@@ -445,6 +446,44 @@ 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;
+	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]);
+
+	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;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -461,5 +500,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);
 }
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 099d52e..c9a8d04 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -2596,7 +2596,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"
@@ -2608,6 +2608,9 @@ _git_worktree ()
 		list,--*)
 			__gitcomp "--porcelain"
 			;;
+		lock,--*)
+			__gitcomp "--reason"
+			;;
 		prune,--*)
 			__gitcomp "--dry-run --expire --verbose"
 			;;
diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh
new file mode 100755
index 0000000..97434be
--- /dev/null
+++ b/t/t2028-worktree-move.sh
@@ -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
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 23/26] worktree: add "unlock" command
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (21 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 22/26] worktree: add "lock" command Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 24/26] worktree: add "move" commmand Nguyễn Thái Ngọc Duy
                     ` (2 subsequent siblings)
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 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(-)

diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 9f0c9f0..8315e5b 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -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
 -------
 
diff --git a/builtin/worktree.c b/builtin/worktree.c
index f45a425..c5b26e7 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -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
 };
 
@@ -484,6 +485,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[] = {
@@ -502,5 +531,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);
 }
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index c9a8d04..8d7867c 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -2596,7 +2596,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"
diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh
index 97434be..f4b2816 100755
--- a/t/t2028-worktree-move.sh
+++ b/t/t2028-worktree-move.sh
@@ -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
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 24/26] worktree: add "move" commmand
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (22 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 23/26] worktree: add "unlock" command Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 25/26] worktree move: accept destination as directory Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 26/26] worktree: add "remove" command Nguyễn Thái Ngọc Duy
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-worktree.txt         |  6 +++-
 builtin/worktree.c                     | 60 ++++++++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  2 +-
 t/t2028-worktree-move.sh               | 29 ++++++++++++++++
 4 files changed, 95 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 8315e5b..a302f0a 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -12,6 +12,7 @@ SYNOPSIS
 'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]
 'git worktree list' [--porcelain]
 'git worktree lock' [--reason <string>] <path>
+'git worktree move' <path> <new-path>
 'git worktree prune' [-n] [-v] [--expire <expire>]
 'git worktree unlock' <path>
 
@@ -69,6 +70,10 @@ 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.
 
+move::
+
+Move a worktree to a new location. Note that the main worktree cannot be moved.
+
 prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
@@ -234,7 +239,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
 
 GIT
 ---
diff --git a/builtin/worktree.c b/builtin/worktree.c
index c5b26e7..acd49da 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -15,6 +15,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 move <path> <new-path>"),
 	N_("git worktree prune [<options>]"),
 	N_("git worktree unlock <path>"),
 	NULL
@@ -513,6 +514,63 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)
 	return unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
 }
 
+static int move_worktree(int ac, const char **av, const char *prefix)
+{
+	struct option options[] = {
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+	struct strbuf dst = STRBUF_INIT;
+	struct strbuf src = STRBUF_INIT;
+	const char *reason;
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (ac != 2)
+		usage_with_options(worktree_usage, options);
+
+	strbuf_addstr(&dst, prefix_filename(prefix,
+					    strlen(prefix),
+					    av[1]));
+	if (file_exists(dst.buf))
+		die(_("target '%s' already exists"), av[1]);
+
+	worktrees = get_worktrees();
+	strbuf_addstr(&src, prefix_filename(prefix,
+					     strlen(prefix),
+					     av[0]));
+	wt = find_worktree_by_path(worktrees, src.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 ((reason = is_worktree_locked(wt))) {
+		if (*reason)
+			die(_("already locked, reason: %s"), reason);
+		die(_("already locked, no reason"));
+	}
+	if (validate_worktree(wt, 0))
+		return -1;
+
+	/*
+	 * First try. Atomically move, and probably cheaper, if both
+	 * source and target are on the same file system.
+	 */
+	if (rename(src.buf, dst.buf) == -1) {
+		if (errno != EXDEV)
+			die_errno(_("failed to move '%s' to '%s'"),
+				  src.buf, dst.buf);
+
+		/* second try.. */
+		if (copy_dir_recursively(src.buf, dst.buf))
+			die(_("failed to copy '%s' to '%s'"),
+			    src.buf, dst.buf);
+		else
+			(void)remove_dir_recursively(&src, 0);
+	}
+
+	return update_worktree_location(wt, dst.buf);
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -533,5 +591,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 		return lock_worktree(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "unlock"))
 		return unlock_worktree(ac - 1, av + 1, prefix);
+	if (!strcmp(av[1], "move"))
+		return move_worktree(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
 }
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 8d7867c..4e01841 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -2596,7 +2596,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list lock prune unlock"
+	local subcommands="add list lock move prune unlock"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh
index f4b2816..83fbab5 100755
--- a/t/t2028-worktree-move.sh
+++ b/t/t2028-worktree-move.sh
@@ -45,4 +45,33 @@ test_expect_success 'unlock worktree twice' '
 	test_path_is_missing .git/worktrees/source/locked
 '
 
+test_expect_success 'move non-worktree' '
+	mkdir abc &&
+	test_must_fail git worktree move abc def
+'
+
+test_expect_success 'move locked worktree' '
+	git worktree lock source &&
+	test_must_fail git worktree move source destination &&
+	git worktree unlock source
+'
+
+test_expect_success 'move worktree' '
+	git worktree move source destination &&
+	test_path_is_missing source &&
+	git worktree list --porcelain | grep "^worktree" >actual &&
+	cat <<-EOF >expected &&
+	worktree $TRASH_DIRECTORY
+	worktree $TRASH_DIRECTORY/destination
+	EOF
+	test_cmp expected actual &&
+	git -C destination log --format=%s >actual2 &&
+	echo init >expected2 &&
+	test_cmp expected2 actual2
+'
+
+test_expect_success 'move main worktree' '
+	test_must_fail git worktree move . def
+'
+
 test_done
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 25/26] worktree move: accept destination as directory
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (23 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 24/26] worktree: add "move" commmand Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  2016-02-16 13:29   ` [PATCH v2 26/26] worktree: add "remove" command Nguyễn Thái Ngọc Duy
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Similar to "mv a b/", which is actually "mv a b/a", we extract basename
of source worktree and create a directory of the same name at
destination if dst path is a directory.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/worktree.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index acd49da..d4ce987 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -531,7 +531,13 @@ static int move_worktree(int ac, const char **av, const char *prefix)
 	strbuf_addstr(&dst, prefix_filename(prefix,
 					    strlen(prefix),
 					    av[1]));
-	if (file_exists(dst.buf))
+	if (is_directory(dst.buf))
+		/*
+		 * keep going, dst will be appended after we get the
+		 * source's absolute path
+		 */
+		;
+	else if (file_exists(dst.buf))
 		die(_("target '%s' already exists"), av[1]);
 
 	worktrees = get_worktrees();
@@ -551,6 +557,17 @@ static int move_worktree(int ac, const char **av, const char *prefix)
 	if (validate_worktree(wt, 0))
 		return -1;
 
+	if (is_directory(dst.buf)) {
+		const char *sep = strrchr(wt->path, '/');
+
+		if (!sep)
+			die(_("could not figure out destination name from '%s'"),
+			    wt->path);
+		strbuf_addstr(&dst, sep);
+		if (file_exists(dst.buf))
+			die(_("target '%s' already exists"), dst.buf);
+	}
+
 	/*
 	 * First try. Atomically move, and probably cheaper, if both
 	 * source and target are on the same file system.
-- 
2.7.0.377.g4cd97dd

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

* [PATCH v2 26/26] worktree: add "remove" command
  2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
                     ` (24 preceding siblings ...)
  2016-02-16 13:29   ` [PATCH v2 25/26] worktree move: accept destination as directory Nguyễn Thái Ngọc Duy
@ 2016-02-16 13:29   ` Nguyễn Thái Ngọc Duy
  25 siblings, 0 replies; 50+ messages in thread
From: Nguyễn Thái Ngọc Duy @ 2016-02-16 13:29 UTC (permalink / raw)
  To: git; +Cc: Nguyễn Thái Ngọc Duy

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-worktree.txt         | 22 +++++----
 builtin/worktree.c                     | 81 ++++++++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  5 ++-
 t/t2028-worktree-move.sh               | 26 +++++++++++
 4 files changed, 124 insertions(+), 10 deletions(-)

diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index a302f0a..60ad465 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -14,6 +14,7 @@ SYNOPSIS
 'git worktree lock' [--reason <string>] <path>
 'git worktree move' <path> <new-path>
 'git worktree prune' [-n] [-v] [--expire <expire>]
+'git worktree remove' [--force] <path>
 'git worktree unlock' <path>
 
 DESCRIPTION
@@ -78,6 +79,14 @@ prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
 
+remove::
+
+Remove a worktree. Only clean worktrees, no untracked files and no
+modification in tracked files, can be removed. Unclean worktrees can
+be removed with `--force`. Main worktree cannot be removed. It needs
+to be converted to a linked worktree first by moving the repository
+away.
+
 unlock::
 
 Unlock a worktree, allowing it to be pruned, moved or deleted.
@@ -87,9 +96,10 @@ OPTIONS
 
 -f::
 --force::
-	By default, `add` refuses to create a new working tree when `<branch>`
-	is already checked out by another working tree. This option overrides
-	that safeguard.
+	By default, `add` refuses to create a new working tree when
+	`<branch>` is already checked out by another working tree and
+	`remove` refuses to remove an unclean worktree. This option
+	overrides that safeguard.
 
 -b <new-branch>::
 -B <new-branch>::
@@ -234,12 +244,6 @@ Multiple checkout in general is still experimental, and the support
 for submodules is incomplete. It is NOT recommended to make multiple
 checkouts of a superproject.
 
-git-worktree could provide more automation for tasks currently
-performed manually, such as:
-
-- `remove` to remove a linked working tree and its administrative files (and
-  warn if the working tree is dirty)
-
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/builtin/worktree.c b/builtin/worktree.c
index d4ce987..8cff943 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -17,6 +17,7 @@ static const char * const worktree_usage[] = {
 	N_("git worktree lock [<options>] <path>"),
 	N_("git worktree move <path> <new-path>"),
 	N_("git worktree prune [<options>]"),
+	N_("git worktree remove [<options>] <path>"),
 	N_("git worktree unlock <path>"),
 	NULL
 };
@@ -588,6 +589,84 @@ static int move_worktree(int ac, const char **av, const char *prefix)
 	return update_worktree_location(wt, dst.buf);
 }
 
+static int remove_worktree(int ac, const char **av, const char *prefix)
+{
+	int force = 0;
+	struct option options[] = {
+		OPT_BOOL(0, "force", &force,
+			 N_("force removing even if the worktree is dirty")),
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+	struct strbuf dst = STRBUF_INIT;
+	const char *reason;
+	int ret = 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);
+	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 ((reason = is_worktree_locked(wt))) {
+		if (*reason)
+			die(_("already locked, reason: %s"), reason);
+		die(_("already locked, no reason"));
+	}
+	if (validate_worktree(wt, 0))
+		return -1;
+
+	if (!force) {
+		struct argv_array child_env = ARGV_ARRAY_INIT;
+		struct child_process cp;
+		char buf[1];
+
+		argv_array_pushf(&child_env, "%s=%s/.git",
+				 GIT_DIR_ENVIRONMENT, wt->path);
+		argv_array_pushf(&child_env, "%s=%s",
+				 GIT_WORK_TREE_ENVIRONMENT, wt->path);
+		memset(&cp, 0, sizeof(cp));
+		argv_array_pushl(&cp.args, "status", "--porcelain", NULL);
+		cp.env = child_env.argv;
+		cp.git_cmd = 1;
+		cp.dir = wt->path;
+		cp.out = -1;
+		ret = start_command(&cp);
+		if (ret)
+			die_errno(_("failed to run git-status on '%s', code %d"),
+				  av[0], ret);
+		ret = xread(cp.out, buf, sizeof(buf));
+		if (ret)
+			die(_("'%s' is dirty, use --force to delete it"), av[0]);
+		close(cp.out);
+		ret = finish_command(&cp);
+		if (ret)
+			die_errno(_("failed to run git-status on '%s', code %d"),
+				  av[0], ret);
+	}
+	if (remove_dir_recursively(&dst, 0)) {
+		sys_error(_("failed to delete '%s'"), wt->path);
+		ret = -1;
+	}
+	strbuf_reset(&dst);
+	strbuf_addstr(&dst, git_common_path("worktrees/%s", wt->id));
+	if (remove_dir_recursively(&dst, 0)) {
+		sys_error(_("failed to delete '%s'"), dst.buf);
+		ret = -1;
+	}
+	strbuf_release(&dst);
+	free_worktrees(worktrees);
+	return ret;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -610,5 +689,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 		return unlock_worktree(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "move"))
 		return move_worktree(ac - 1, av + 1, prefix);
+	if (!strcmp(av[1], "remove"))
+		return remove_worktree(ac - 1, av + 1, prefix);
 	usage_with_options(worktree_usage, options);
 }
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 4e01841..6eda7ed 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -2596,7 +2596,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list lock move prune unlock"
+	local subcommands="add list lock move prune remove unlock"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
@@ -2614,6 +2614,9 @@ _git_worktree ()
 		prune,--*)
 			__gitcomp "--dry-run --expire --verbose"
 			;;
+		remove,--*)
+			__gitcomp "--force"
+			;;
 		*)
 			;;
 		esac
diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh
index 83fbab5..668ee05 100755
--- a/t/t2028-worktree-move.sh
+++ b/t/t2028-worktree-move.sh
@@ -74,4 +74,30 @@ test_expect_success 'move main worktree' '
 	test_must_fail git worktree move . def
 '
 
+test_expect_success 'remove main worktree' '
+	test_must_fail git worktree remove .
+'
+
+test_expect_success 'remove locked worktree' '
+	git worktree lock destination &&
+	test_must_fail git worktree remove destination &&
+	git worktree unlock destination
+'
+
+test_expect_success 'remove worktree with dirty tracked file' '
+	echo dirty >>destination/init.t &&
+	test_must_fail git worktree remove destination
+'
+
+test_expect_success 'remove worktree with untracked file' '
+	git -C destination checkout init.t &&
+	: >destination/untracked &&
+	test_must_fail git worktree remove destination
+'
+
+test_expect_success 'force remove worktree with untracked file' '
+	git worktree remove --force destination &&
+	test_path_is_missing destination
+'
+
 test_done
-- 
2.7.0.377.g4cd97dd

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

* Re: [PATCH v2 10/26] wrapper.c: allow to create an empty file with write_file()
  2016-02-16 13:29   ` [PATCH v2 10/26] wrapper.c: allow to create an empty file with write_file() Nguyễn Thái Ngọc Duy
@ 2016-02-17 22:29     ` Junio C Hamano
  2016-02-18  0:49       ` Duy Nguyen
  0 siblings, 1 reply; 50+ messages in thread
From: Junio C Hamano @ 2016-02-17 22:29 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:

> This is used later on to create empty .git/worktrees/xxx/locked when
> "git worktree lock" is called with no reason given.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  wrapper.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/wrapper.c b/wrapper.c
> index 29a45d2..1dc1eff 100644
> --- a/wrapper.c
> +++ b/wrapper.c
> @@ -666,7 +666,7 @@ static int write_file_v(const char *path, int fatal,
>  	}
>  	strbuf_vaddf(&sb, fmt, params);
>  	strbuf_complete_line(&sb);
> -	if (write_in_full(fd, sb.buf, sb.len) != sb.len) {
> +	if (sb.len && write_in_full(fd, sb.buf, sb.len) != sb.len) {

I do not quite see how this change relates to what the log message
claims this commit does.  write_in_full() returns 0 when called with
count==0 just fine, no?

>  		int err = errno;
>  		close(fd);
>  		strbuf_release(&sb);

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

* Re: [PATCH v2 10/26] wrapper.c: allow to create an empty file with write_file()
  2016-02-17 22:29     ` Junio C Hamano
@ 2016-02-18  0:49       ` Duy Nguyen
  0 siblings, 0 replies; 50+ messages in thread
From: Duy Nguyen @ 2016-02-18  0:49 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

On Thu, Feb 18, 2016 at 5:29 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
>
>> This is used later on to create empty .git/worktrees/xxx/locked when
>> "git worktree lock" is called with no reason given.
>>
>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
>> ---
>>  wrapper.c | 2 +-
>>  1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/wrapper.c b/wrapper.c
>> index 29a45d2..1dc1eff 100644
>> --- a/wrapper.c
>> +++ b/wrapper.c
>> @@ -666,7 +666,7 @@ static int write_file_v(const char *path, int fatal,
>>       }
>>       strbuf_vaddf(&sb, fmt, params);
>>       strbuf_complete_line(&sb);
>> -     if (write_in_full(fd, sb.buf, sb.len) != sb.len) {
>> +     if (sb.len && write_in_full(fd, sb.buf, sb.len) != sb.len) {
>
> I do not quite see how this change relates to what the log message
> claims this commit does.  write_in_full() returns 0 when called with
> count==0 just fine, no?

You're right. Somehow I got the impression that xwrite() was called,
but it's not in this case.
-- 
Duy

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

end of thread, other threads:[~2016-02-18  0:49 UTC | newest]

Thread overview: 50+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-02-03  9:35 [PATCH 00/20] "git worktree move" preview Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 01/20] usage.c: move format processing out of die_errno() Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 02/20] usage.c: add sys_error() that prints strerror() automatically Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 03/20] path.c: add git_common_path() and strbuf_git_common_path() Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 04/20] path.c: add is_git_path_shared() Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 05/20] copy.c: add copy_dir_recursively() Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 06/20] worktree.c: use is_dot_or_dotdot() Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 07/20] worktree.c: store "id" instead of "git_dir" Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 08/20] worktree.c: add clear_worktree() Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 09/20] worktree.c: add find_worktree_by_path() Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 10/20] worktree.c: add is_main_worktree() Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 11/20] worktree.c: recognize no main worktree Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 12/20] worktree.c: add update_worktree_location() Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 13/20] worktree.c: add update_worktree_gitfile() Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 14/20] worktree.c: add collect_per_worktree_git_paths() Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 15/20] worktree: avoid 0{40}, too many zeroes, hard to read Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 16/20] worktree: simplify prefixing paths Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 17/20] worktree: add "move" commmand Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 18/20] worktree: refactor add_worktree() Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 19/20] worktree: move repo, simple case Nguyễn Thái Ngọc Duy
2016-02-03  9:35 ` [PATCH 20/20] worktree: move repo, convert main worktree Nguyễn Thái Ngọc Duy
2016-02-16 13:29 ` [PATCH v2 00/26] worktree lock, move, remove and unlock Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 01/26] usage.c: move format processing out of die_errno() Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 02/26] usage.c: add sys_error() that prints strerror() automatically Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 03/26] copy.c: import copy_file() from busybox Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 04/26] copy.c: delete unused code in copy_file() Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 05/26] copy.c: convert bb_(p)error_msg to (sys_)error Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 06/26] copy.c: style fix Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 07/26] copy.c: convert copy_file() to copy_dir_recursively() Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 08/26] completion: support git-worktree Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 09/26] git-worktree.txt: keep subcommand listing in alphabetical order Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 10/26] wrapper.c: allow to create an empty file with write_file() Nguyễn Thái Ngọc Duy
2016-02-17 22:29     ` Junio C Hamano
2016-02-18  0:49       ` Duy Nguyen
2016-02-16 13:29   ` [PATCH v2 11/26] path.c: add git_common_path() and strbuf_git_common_path() Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 12/26] worktree.c: use is_dot_or_dotdot() Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 13/26] worktree.c: store "id" instead of "git_dir" Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 14/26] worktree.c: add clear_worktree() Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 15/26] worktree.c: add find_worktree_by_path() Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 16/26] worktree.c: add is_main_worktree() Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 17/26] worktree.c: add validate_worktree() Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 18/26] worktree.c: add update_worktree_location() Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 19/26] worktree.c: add is_worktree_locked() Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 20/26] worktree: avoid 0{40}, too many zeroes, hard to read Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 21/26] worktree: simplify prefixing paths Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 22/26] worktree: add "lock" command Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 23/26] worktree: add "unlock" command Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 24/26] worktree: add "move" commmand Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 25/26] worktree move: accept destination as directory Nguyễn Thái Ngọc Duy
2016-02-16 13:29   ` [PATCH v2 26/26] worktree: add "remove" command Nguyễn Thái Ngọc Duy

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.