{"thread":{"id":"41321","subject":"[PATCH 00/20] \"git worktree move\" preview","startedAt":"2016-02-03T09:35:30Z","lastAt":"2016-02-18T00:49:17Z","messageCount":50,"participants":["Nguyễn Thái Ngọc Duy","Junio C Hamano","Duy Nguyen"],"isPatch":true,"patchVersion":1,"patchTotal":20},"messages":[{"id":"277313","messageId":"1454492150-10628-1-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":null,"subject":"[PATCH 00/20] \"git worktree move\" preview","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:30Z","receivedAt":"2016-02-03T09:35:30Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is the rough first cut of \"git worktree move\" for early\nfeedback. The series adds \"git worktree move\" which can\n\n - move a linked worktree to another place\n - move .git repository away\n\nThe first use case is obvious. The second less so.\n\nMain worktree has always been an odd ball because it does not always\nbehave like a linked worktree. You can't move it (because you'll be\nmoving the repo along), you don't know where it is from 'worktree\nlist' because it's not tracked the same way. The move of .git dir will\nautomatically convert the main worktree to linked one, making it equal\nto all other worktrees.\n\nAt the code level, it should be noted that I import copy_file() from\nbusybox for whole directory copy that preserves file permission and\nstuff. Either that or I need to rely on POSIX command \"cp\" or \"mv\".\nBut that would make Windows adaptation harder later on.\n\nA little off topic. The first two patches adds sys_error() that, like\ndie_errno(), automatically do strerror(). There's a 38 patches series\nto convert error(\"...: %s\", ..., strerror(errno)) to use it. I think\nit's a good idea. So unless somebody objects, I'll post it some time\nlater.\n\nNguyễn Thái Ngọc Duy (20):\n  usage.c: move format processing out of die_errno()\n  usage.c: add sys_error() that prints strerror() automatically\n  path.c: add git_common_path() and strbuf_git_common_path()\n  path.c: add is_git_path_shared()\n  copy.c: add copy_dir_recursively()\n  worktree.c: use is_dot_or_dotdot()\n  worktree.c: store \"id\" instead of \"git_dir\"\n  worktree.c: add clear_worktree()\n  worktree.c: add find_worktree_by_path()\n  worktree.c: add is_main_worktree()\n  worktree.c: recognize no main worktree\n  worktree.c: add update_worktree_location()\n  worktree.c: add update_worktree_gitfile()\n  worktree.c: add collect_per_worktree_git_paths()\n  worktree: avoid 0{40}, too many zeroes, hard to read\n  worktree: simplify prefixing paths\n  worktree: add \"move\" commmand\n  worktree: refactor add_worktree()\n  worktree: move repo, simple case\n  worktree: move repo, convert main worktree\n\n builtin/worktree.c                | 297 ++++++++++++++++++++++++------\n cache.h                           |   6 +\n copy.c                            | 371 ++++++++++++++++++++++++++++++++++++++\n git-compat-util.h                 |   1 +\n path.c                            |  56 +++++-\n t/t2028-worktree-move.sh (new +x) |  77 ++++++++\n usage.c                           |  35 +++-\n worktree.c                        | 161 +++++++++++++++--\n worktree.h                        |  42 ++++-\n 9 files changed, 963 insertions(+), 83 deletions(-)\n create mode 100755 t/t2028-worktree-move.sh\n\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277315","messageId":"1454492150-10628-2-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 01/20] usage.c: move format processing out of die_errno()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:31Z","receivedAt":"2016-02-03T09:35:31Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n usage.c | 25 +++++++++++++++----------\n 1 file changed, 15 insertions(+), 10 deletions(-)\n\ndiff --git a/usage.c b/usage.c\nindex 82ff131..0dba0c5 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -109,19 +109,12 @@ void NORETURN die(const char *err, ...)\n \tva_end(params);\n }\n \n-void NORETURN die_errno(const char *fmt, ...)\n+static const char *fmt_with_err(const char *fmt)\n {\n-\tva_list params;\n-\tchar fmt_with_err[1024];\n+\tstatic char fmt_with_err[1024];\n \tchar str_error[256], *err;\n \tint i, j;\n \n-\tif (die_is_recursing()) {\n-\t\tfputs(\"fatal: recursion detected in die_errno handler\\n\",\n-\t\t\tstderr);\n-\t\texit(128);\n-\t}\n-\n \terr = strerror(errno);\n \tfor (i = j = 0; err[i] && j < sizeof(str_error) - 1; ) {\n \t\tif ((str_error[j++] = err[i++]) != '%')\n@@ -137,9 +130,21 @@ void NORETURN die_errno(const char *fmt, ...)\n \t}\n \tstr_error[j] = 0;\n \tsnprintf(fmt_with_err, sizeof(fmt_with_err), \"%s: %s\", fmt, str_error);\n+\treturn fmt_with_err;\n+}\n+\n+void NORETURN die_errno(const char *fmt, ...)\n+{\n+\tva_list params;\n+\n+\tif (die_is_recursing()) {\n+\t\tfputs(\"fatal: recursion detected in die_errno handler\\n\",\n+\t\t\tstderr);\n+\t\texit(128);\n+\t}\n \n \tva_start(params, fmt);\n-\tdie_routine(fmt_with_err, params);\n+\tdie_routine(fmt_with_err(fmt), params);\n \tva_end(params);\n }\n \n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277314","messageId":"1454492150-10628-3-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 02/20] usage.c: add sys_error() that prints strerror() automatically","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:32Z","receivedAt":"2016-02-03T09:35:32Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n git-compat-util.h |  1 +\n usage.c           | 10 ++++++++++\n 2 files changed, 11 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex e8f2867..f2bd775 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -398,6 +398,7 @@ extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf,\n extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2)));\n extern NORETURN void die_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));\n extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n+extern int sys_error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));\n \n #ifndef NO_OPENSSL\ndiff --git a/usage.c b/usage.c\nindex 0dba0c5..e7c37f2 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -148,6 +148,16 @@ void NORETURN die_errno(const char *fmt, ...)\n \tva_end(params);\n }\n \n+int sys_error(const char *fmt, ...)\n+{\n+\tva_list params;\n+\n+\tva_start(params, fmt);\n+\terror_routine(fmt_with_err(fmt), params);\n+\tva_end(params);\n+\treturn -1;\n+}\n+\n #undef error\n int error(const char *err, ...)\n {\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277316","messageId":"1454492150-10628-4-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 03/20] path.c: add git_common_path() and strbuf_git_common_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:33Z","receivedAt":"2016-02-03T09:35:33Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h |  3 +++\n path.c  | 29 +++++++++++++++++++++++++++++\n 2 files changed, 32 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex dfc459c..ec71181 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -747,11 +747,14 @@ extern int check_repository_format(void);\n  */\n extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n+extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n \n extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n \t__attribute__((format (printf, 3, 4)));\n extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n+extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)\n+\t__attribute__((format (printf, 2, 3)));\n extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,\ndiff --git a/path.c b/path.c\nindex 3cd155e..08ff346 100644\n--- a/path.c\n+++ b/path.c\n@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,\n \tva_end(args);\n }\n \n+static void do_git_common_path(struct strbuf *buf,\n+\t\t\t       const char *fmt,\n+\t\t\t       va_list args)\n+{\n+\tstrbuf_addstr(buf, get_git_common_dir());\n+\tif (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_vaddf(buf, fmt, args);\n+\tstrbuf_cleanup_path(buf);\n+}\n+\n+const char *git_common_path(const char *fmt, ...)\n+{\n+\tstruct strbuf *pathname = get_pathname();\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_common_path(pathname, fmt, args);\n+\tva_end(args);\n+\treturn pathname->buf;\n+}\n+\n+void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)\n+{\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_common_path(sb, fmt, args);\n+\tva_end(args);\n+}\n+\n int validate_headref(const char *path)\n {\n \tstruct stat st;\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277317","messageId":"1454492150-10628-5-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 04/20] path.c: add is_git_path_shared()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:34Z","receivedAt":"2016-02-03T09:35:34Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h |  2 ++\n path.c  | 27 ++++++++++++++++++++++-----\n 2 files changed, 24 insertions(+), 5 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex ec71181..c75d13f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -748,6 +748,8 @@ extern int check_repository_format(void);\n extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n+extern int is_git_path_shared(const char *path);\n+\n \n extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n \t__attribute__((format (printf, 3, 4)));\ndiff --git a/path.c b/path.c\nindex 08ff346..9e350e7 100644\n--- a/path.c\n+++ b/path.c\n@@ -335,15 +335,18 @@ static int check_common(const char *unmatched, void *value, void *baton)\n \treturn 0;\n }\n \n-static void update_common_dir(struct strbuf *buf, int git_dir_len,\n-\t\t\t      const char *common_dir)\n+static int update_common_dir(struct strbuf *buf, int git_dir_len,\n+\t\t\t     const char *common_dir)\n {\n \tchar *base = buf->buf + git_dir_len;\n \tinit_common_trie();\n \tif (!common_dir)\n \t\tcommon_dir = get_git_common_dir();\n-\tif (trie_find(&common_trie, base, check_common, NULL) > 0)\n+\tif (trie_find(&common_trie, base, check_common, NULL) > 0) {\n \t\treplace_dir(buf, git_dir_len, common_dir);\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n }\n \n void report_linked_checkout_garbage(void)\n@@ -368,7 +371,7 @@ void report_linked_checkout_garbage(void)\n \tstrbuf_release(&sb);\n }\n \n-static void adjust_git_path(struct strbuf *buf, int git_dir_len)\n+static int adjust_git_path(struct strbuf *buf, int git_dir_len)\n {\n \tconst char *base = buf->buf + git_dir_len;\n \tif (git_graft_env && is_dir_file(base, \"info\", \"grafts\"))\n@@ -380,7 +383,10 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)\n \telse if (git_db_env && dir_prefix(base, \"objects\"))\n \t\treplace_dir(buf, git_dir_len + 7, get_object_directory());\n \telse if (git_common_dir_env)\n-\t\tupdate_common_dir(buf, git_dir_len, NULL);\n+\t\treturn update_common_dir(buf, git_dir_len, NULL);\n+\telse\n+\t\treturn 0;\n+\treturn 1;\n }\n \n static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)\n@@ -532,6 +538,17 @@ void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)\n \tva_end(args);\n }\n \n+int is_git_path_shared(const char *path)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tint ret;\n+\n+\tstrbuf_addf(&buf, \".git/%s\", path);\n+\tret = adjust_git_path(&buf, 5);\n+\tstrbuf_release(&buf);\n+\treturn ret;\n+}\n+\n int validate_headref(const char *path)\n {\n \tstruct stat st;\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277318","messageId":"1454492150-10628-6-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 05/20] copy.c: add copy_dir_recursively()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:35Z","receivedAt":"2016-02-03T09:35:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is busybox's copy_file() [1] modified to fit in Git. Because this\nis busybox, the code is likely POSIX-y (or even Linux-y). Windows\nsupport may not be there yet.\n\n[1] in libbb/copy_file.c from the GPL2+ commit\n    f2c043acfcf9dad9fd3d65821b81f89986bbe54e (busybox: fix\n    uninitialized memory when displaying IPv6 addresses - 2016-01-18)\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h |   1 +\n copy.c  | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 372 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex c75d13f..3fbb38d 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1638,6 +1638,7 @@ extern void fprintf_or_die(FILE *, const char *fmt, ...);\n extern int copy_fd(int ifd, int ofd);\n extern int copy_file(const char *dst, const char *src, int mode);\n extern int copy_file_with_time(const char *dst, const char *src, int mode);\n+extern int copy_dir_recursively(const char *source, const char *dest);\n \n extern void write_or_die(int fd, const void *buf, size_t count);\n extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);\ndiff --git a/copy.c b/copy.c\nindex 574fa1f..c99d6e5 100644\n--- a/copy.c\n+++ b/copy.c\n@@ -1,4 +1,6 @@\n #include \"cache.h\"\n+#include \"dir.h\"\n+#include \"hashmap.h\"\n \n int copy_fd(int ifd, int ofd)\n {\n@@ -65,3 +67,372 @@ int copy_file_with_time(const char *dst, const char *src, int mode)\n \t\treturn copy_times(dst, src);\n \treturn status;\n }\n+\n+struct inode_key {\n+\tstruct hashmap_entry entry;\n+\tino_t ino;\n+\tdev_t dev;\n+\t/*\n+\t * Reportedly, on cramfs a file and a dir can have same ino.\n+\t * Need to also remember \"file/dir\" bit:\n+\t */\n+\tchar isdir; /* bool */\n+};\n+\n+struct inode_value {\n+\tstruct inode_key key;\n+\tchar name[FLEX_ARRAY];\n+};\n+\n+#define HASH_SIZE      311u   /* Should be prime */\n+static inline unsigned hash_inode(ino_t i)\n+{\n+\treturn i % HASH_SIZE;\n+}\n+\n+static int inode_cmp(const void *entry, const void *entry_or_key,\n+\t\t     const void *keydata)\n+{\n+\tconst struct inode_value *inode = entry;\n+\tconst struct inode_key   *key   = entry_or_key;\n+\n+\treturn !(inode->key.ino   == key->ino &&\n+\t\t inode->key.dev   == key->dev &&\n+\t\t inode->key.isdir == key->isdir);\n+}\n+\n+static const char *is_in_ino_dev_hashtable(const struct hashmap *map,\n+\t\t\t\t\t   const struct stat *st)\n+{\n+\tstruct inode_key key;\n+\tstruct inode_value *value;\n+\n+\tkey.entry.hash = hash_inode(st->st_ino);\n+\tkey.ino\t       = st->st_ino;\n+\tkey.dev\t       = st->st_dev;\n+\tkey.isdir      = !!S_ISDIR(st->st_mode);\n+\tvalue\t       = hashmap_get(map, &key, NULL);\n+\treturn value ? value->name : NULL;\n+}\n+\n+static void add_to_ino_dev_hashtable(struct hashmap *map,\n+\t\t\t\t     const struct stat *st,\n+\t\t\t\t     const char *path)\n+{\n+\tstruct inode_value *v;\n+\tint len = strlen(path);\n+\n+\tv = xmalloc(offsetof(struct inode_value, name) + len + 1);\n+\tv->key.entry.hash = hash_inode(st->st_ino);\n+\tv->key.ino\t  = st->st_ino;\n+\tv->key.dev\t  = st->st_dev;\n+\tv->key.isdir      = !!S_ISDIR(st->st_mode);\n+\tmemcpy(v->name, path, len + 1);\n+\thashmap_add(map, v);\n+}\n+\n+/*\n+ * Find out if the last character of a string matches the one given.\n+ * Don't underrun the buffer if the string length is 0.\n+ */\n+static inline char *last_char_is(const char *s, int c)\n+{\n+\tif (s && *s) {\n+\t\tsize_t sz = strlen(s) - 1;\n+\t\ts += sz;\n+\t\tif ( (unsigned char)*s == c)\n+\t\t\treturn (char*)s;\n+\t}\n+\treturn NULL;\n+}\n+\n+static inline char *concat_path_file(const char *path, const char *filename)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tchar *lc;\n+\n+\tif (!path)\n+\t\tpath = \"\";\n+\tlc = last_char_is(path, '/');\n+\twhile (*filename == '/')\n+\t\tfilename++;\n+\tstrbuf_addf(&sb, \"%s%s%s\", path, (lc==NULL ? \"/\" : \"\"), filename);\n+\treturn strbuf_detach(&sb, NULL);\n+}\n+\n+static char *concat_subpath_file(const char *path, const char *f)\n+{\n+\tif (f && is_dot_or_dotdot(f))\n+\t\treturn NULL;\n+\treturn concat_path_file(path, f);\n+}\n+\n+static int do_unlink(const char *dest)\n+{\n+\tint e = errno;\n+\n+\tif (unlink(dest) < 0) {\n+\t\terrno = e; /* do not use errno from unlink */\n+\t\treturn sys_error(_(\"can't create '%s'\"), dest);\n+\t}\n+\treturn 0;\n+}\n+\n+/* See busybox.git, libbb/copy_file.c for the original implementation */\n+static int copy_dir_1(struct hashmap *inode_map,\n+\t\t      const char *source,\n+\t\t      const char *dest)\n+{\n+\t/* This is a recursive function, try to minimize stack usage */\n+\tstruct stat source_stat;\n+\tstruct stat dest_stat;\n+\tint retval = 0;\n+\tint dest_exists = 0;\n+\tint ovr;\n+\n+\tif (stat(source, &source_stat) < 0)\n+\t\treturn sys_error(_(\"can't stat '%s'\"), source);\n+\n+\tif (lstat(dest, &dest_stat) < 0) {\n+\t\tif (errno != ENOENT)\n+\t\t\treturn sys_error(_(\"can't stat '%s'\"), dest);\n+\t} else {\n+\t\tif (source_stat.st_dev == dest_stat.st_dev &&\n+\t\t    source_stat.st_ino == dest_stat.st_ino)\n+\t\t\treturn sys_error(_(\"'%s' and '%s' are the same file\"), source, dest);\n+\t\tdest_exists = 1;\n+\t}\n+\n+\tif (S_ISDIR(source_stat.st_mode)) {\n+\t\tDIR *dp;\n+\t\tconst char *tp;\n+\t\tstruct dirent *d;\n+\t\tmode_t saved_umask = 0;\n+\n+\t\t/* Did we ever create source ourself before? */\n+\t\ttp = is_in_ino_dev_hashtable(inode_map, &source_stat);\n+\t\tif (tp)\n+\t\t\t/* We did! it's a recursion! man the lifeboats... */\n+\t\t\treturn error(_(\"recursion detected, omitting directory '%s'\"),\n+\t\t\t\t     source);\n+\n+\t\tif (dest_exists) {\n+\t\t\tif (!S_ISDIR(dest_stat.st_mode))\n+\t\t\t\treturn sys_error(_(\"target '%s' is not a directory\"), dest);\n+\t\t\t/*\n+\t\t\t * race here: user can substitute a symlink between\n+\t\t\t * this check and actual creation of files inside dest\n+\t\t\t */\n+\t\t} else {\n+\t\t\t/* Create DEST */\n+\t\t\tmode_t mode;\n+\t\t\tsaved_umask = umask(0);\n+\n+\t\t\tmode = source_stat.st_mode;\n+\t\t\t/* Allow owner to access new dir (at least for now) */\n+\t\t\tmode |= S_IRWXU;\n+\t\t\tif (mkdir(dest, mode) < 0) {\n+\t\t\t\tumask(saved_umask);\n+\t\t\t\treturn sys_error(_(\"can't create directory '%s'\"), dest);\n+\t\t\t}\n+\t\t\tumask(saved_umask);\n+\t\t\t/* need stat info for add_to_ino_dev_hashtable */\n+\t\t\tif (lstat(dest, &dest_stat) < 0)\n+\t\t\t\treturn sys_error(_(\"can't stat '%s'\"), dest);\n+\t\t}\n+\n+\t\t/*\n+\t\t * remember (dev,inode) of each created dir. name is\n+\t\t * not remembered\n+\t\t */\n+\t\tadd_to_ino_dev_hashtable(inode_map, &dest_stat, \"\");\n+\n+\t\t/* Recursively copy files in SOURCE */\n+\t\tdp = opendir(source);\n+\t\tif (!dp) {\n+\t\t\tretval = -1;\n+\t\t\tgoto preserve_mode_ugid_time;\n+\t\t}\n+\n+\t\twhile ((d = readdir(dp))) {\n+\t\t\tchar *new_source, *new_dest;\n+\n+\t\t\tnew_source = concat_subpath_file(source, d->d_name);\n+\t\t\tif (!new_source)\n+\t\t\t\tcontinue;\n+\t\t\tnew_dest = concat_path_file(dest, d->d_name);\n+\t\t\tif (copy_dir_1(inode_map, new_source, new_dest) < 0)\n+\t\t\t\tretval = -1;\n+\t\t\tfree(new_source);\n+\t\t\tfree(new_dest);\n+\t\t}\n+\t\tclosedir(dp);\n+\n+\t\tif (!dest_exists &&\n+\t\t    chmod(dest, source_stat.st_mode & ~saved_umask) < 0) {\n+\t\t\tsys_error(_(\"can't preserve permissions of '%s'\"), dest);\n+\t\t\t/* retval = -1; - WRONG! copy *WAS* made */\n+\t\t}\n+\t\tgoto preserve_mode_ugid_time;\n+\t}\n+\n+\t/* \"cp [-opts] regular_file thing2\" */\n+\tif (S_ISREG(source_stat.st_mode)) {\n+\t\tconst char *link_target;\n+\t\tint src_fd;\n+\t\tint dst_fd;\n+\t\tmode_t new_mode;\n+\n+\t\tif (S_ISLNK(source_stat.st_mode)) {\n+\t\t\t/* \"cp -d symlink dst\": create a link */\n+\t\t\tgoto dont_cat;\n+\t\t}\n+\n+\t\tlink_target = is_in_ino_dev_hashtable(inode_map, &source_stat);\n+\t\tif (link_target) {\n+\t\t\tif (link(link_target, dest) < 0) {\n+\t\t\t\tovr = do_unlink(dest);\n+\t\t\t\tif (ovr < 0)\n+\t\t\t\t\treturn ovr;\n+\t\t\t\tif (link(link_target, dest) < 0) {\n+\t\t\t\t\tsys_error(_(\"can't create link '%s'\"), dest);\n+\t\t\t\t\treturn -1;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\treturn 0;\n+\t\t}\n+\t\tadd_to_ino_dev_hashtable(inode_map, &source_stat, dest);\n+\n+\t\tsrc_fd = open(source, O_RDONLY);\n+\t\tif (src_fd < 0)\n+\t\t\treturn sys_error(_(\"can't open '%s'\"), source);\n+\n+\t\t/* Do not try to open with weird mode fields */\n+\t\tnew_mode = source_stat.st_mode;\n+\t\tif (!S_ISREG(source_stat.st_mode))\n+\t\t\tnew_mode = 0666;\n+\n+\t\tdst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);\n+\t\tif (dst_fd == -1) {\n+\t\t\tovr = do_unlink(dest);\n+\t\t\tif (ovr < 0) {\n+\t\t\t\tclose(src_fd);\n+\t\t\t\treturn ovr;\n+\t\t\t}\n+\t\t\t/* It shouldn't exist. If it exists, do not open (symlink attack?) */\n+\t\t\tdst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);\n+\t\t\tif (dst_fd < 0) {\n+\t\t\t\tclose(src_fd);\n+\t\t\t\treturn sys_error(_(\"can't open '%s'\"), dest);\n+\t\t\t}\n+\t\t}\n+\n+\t\tswitch (copy_fd(src_fd, dst_fd)) {\n+\t\tcase COPY_READ_ERROR:\n+\t\t\terror(_(\"copy-fd: read returned %s\"), strerror(errno));\n+\t\t\tretval = -1;\n+\t\t\tbreak;\n+\t\tcase COPY_WRITE_ERROR:\n+\t\t\terror(_(\"copy-fd: write returned %s\"), strerror(errno));\n+\t\t\tretval = -1;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* Careful with writing... */\n+\t\tif (close(dst_fd) < 0)\n+\t\t\tretval = sys_error(_(\"error writing to '%s'\"), dest);\n+\t\t/* ...but read size is already checked by bb_copyfd_eof */\n+\t\tclose(src_fd);\n+\t\t/*\n+\t\t * \"cp /dev/something new_file\" should not\n+\t\t * copy mode of /dev/something\n+\t\t */\n+\t\tif (!S_ISREG(source_stat.st_mode))\n+\t\t\treturn retval;\n+\t\tgoto preserve_mode_ugid_time;\n+\t}\n+dont_cat:\n+\n+\t/* Source is a symlink or a special file */\n+\t/* We are lazy here, a bit lax with races... */\n+\tif (dest_exists) {\n+\t\terrno = EEXIST;\n+\t\tovr = do_unlink(dest);\n+\t\tif (ovr < 0)\n+\t\t\treturn ovr;\n+\t}\n+\tif (S_ISLNK(source_stat.st_mode)) {\n+\t\tstruct strbuf lpath = STRBUF_INIT;\n+\t\tif (!strbuf_readlink(&lpath, source, 0)) {\n+\t\t\tint r = symlink(lpath.buf, dest);\n+\t\t\tstrbuf_release(&lpath);\n+\t\t\tif (r < 0)\n+\t\t\t\treturn sys_error(_(\"can't create symlink '%s'\"), dest);\n+\t\t\tif (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)\n+\t\t\t\tsys_error(_(\"can't preserve %s of '%s'\"), \"ownership\", dest);\n+\t\t} else {\n+\t\t\t/* EINVAL => \"file: Invalid argument\" => puzzled user */\n+\t\t\tconst char *errmsg = _(\"not a symlink\");\n+\t\t\tint err = errno;\n+\n+\t\t\tif (err != EINVAL)\n+\t\t\t\terrmsg = strerror(err);\n+\t\t\terror(_(\"%s: cannot read link: %s\"), source, errmsg);\n+\t\t\tstrbuf_release(&lpath);\n+\t\t}\n+\t\t/*\n+\t\t * _Not_ jumping to preserve_mode_ugid_time: symlinks\n+\t\t * don't have those\n+\t\t */\n+\t\treturn 0;\n+\t}\n+\tif (S_ISBLK(source_stat.st_mode) ||\n+\t    S_ISCHR(source_stat.st_mode) ||\n+\t    S_ISSOCK(source_stat.st_mode) ||\n+\t    S_ISFIFO(source_stat.st_mode)) {\n+\t\tif (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0)\n+\t\t\treturn sys_error(_(\"can't create '%s'\"), dest);\n+\t} else\n+\t\treturn sys_error(_(\"unrecognized file '%s' with mode %x\"),\n+\t\t\t\t source, source_stat.st_mode);\n+\n+preserve_mode_ugid_time:\n+\n+\tif (1 /*FILEUTILS_PRESERVE_STATUS*/) {\n+\t\tstruct timeval times[2];\n+\n+\t\ttimes[1].tv_sec = times[0].tv_sec = source_stat.st_mtime;\n+\t\ttimes[1].tv_usec = times[0].tv_usec = 0;\n+\t\t/* BTW, utimes sets usec-precision time - just FYI */\n+\t\tif (utimes(dest, times) < 0)\n+\t\t\tsys_error(_(\"can't preserve %s of '%s'\"), \"times\", dest);\n+\t\tif (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {\n+\t\t\tsource_stat.st_mode &= ~(S_ISUID | S_ISGID);\n+\t\t\tsys_error(_(\"can't preserve %s of '%s'\"), \"ownership\", dest);\n+\t\t}\n+\t\tif (chmod(dest, source_stat.st_mode) < 0)\n+\t\t\tsys_error(_(\"can't preserve %s of '%s'\"), \"permissions\", dest);\n+\t}\n+\n+\treturn retval;\n+}\n+\n+/*\n+ * Return:\n+ * -1 error, copy not made\n+ *  0 copy is made\n+ *\n+ * Failures to preserve mode/owner/times are not reported in exit\n+ * code. No support for preserving SELinux security context. Symlinks\n+ * and hardlinks are preserved.\n+ */\n+int copy_dir_recursively(const char *source, const char *dest)\n+{\n+\tint ret;\n+\tstruct hashmap inode_map;\n+\n+\thashmap_init(&inode_map, inode_cmp, 1024);\n+\tret = copy_dir_1(&inode_map, source, dest);\n+\thashmap_free(&inode_map, 1);\n+\treturn ret;\n+}\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277321","messageId":"1454492150-10628-7-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 06/20] worktree.c: use is_dot_or_dotdot()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:36Z","receivedAt":"2016-02-03T09:35:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 2 +-\n worktree.c         | 3 ++-\n 2 files changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 475b958..7b217fb 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -94,7 +94,7 @@ static void prune_worktrees(void)\n \tif (!dir)\n \t\treturn;\n \twhile ((d = readdir(dir)) != NULL) {\n-\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\tif (is_dot_or_dotdot(d->d_name))\n \t\t\tcontinue;\n \t\tstrbuf_reset(&reason);\n \t\tif (!prune_worktree(d->d_name, &reason))\ndiff --git a/worktree.c b/worktree.c\nindex 6181a66..ddb8cb7 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -2,6 +2,7 @@\n #include \"refs.h\"\n #include \"strbuf.h\"\n #include \"worktree.h\"\n+#include \"dir.h\"\n \n void free_worktrees(struct worktree **worktrees)\n {\n@@ -173,7 +174,7 @@ struct worktree **get_worktrees(void)\n \tif (dir) {\n \t\twhile ((d = readdir(dir)) != NULL) {\n \t\t\tstruct worktree *linked = NULL;\n-\t\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\t\tif (is_dot_or_dotdot(d->d_name))\n \t\t\t\tcontinue;\n \n \t\t\tif ((linked = get_linked_worktree(d->d_name))) {\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277319","messageId":"1454492150-10628-8-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 07/20] worktree.c: store \"id\" instead of \"git_dir\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:37Z","receivedAt":"2016-02-03T09:35:37Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We can reconstruct git_dir from id quite easily. It's a bit hackier to\ndo the reverse.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 29 ++++++++++++++++-------------\n worktree.h |  7 ++++++-\n 2 files changed, 22 insertions(+), 14 deletions(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex ddb8cb7..4c38414 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -10,7 +10,7 @@ void free_worktrees(struct worktree **worktrees)\n \n \tfor (i = 0; worktrees[i]; i++) {\n \t\tfree(worktrees[i]->path);\n-\t\tfree(worktrees[i]->git_dir);\n+\t\tfree(worktrees[i]->id);\n \t\tfree(worktrees[i]->head_ref);\n \t\tfree(worktrees[i]);\n \t}\n@@ -75,13 +75,11 @@ static struct worktree *get_main_worktree(void)\n \tstruct worktree *worktree = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf worktree_path = STRBUF_INIT;\n-\tstruct strbuf gitdir = STRBUF_INIT;\n \tstruct strbuf head_ref = STRBUF_INIT;\n \tint is_bare = 0;\n \tint is_detached = 0;\n \n-\tstrbuf_addf(&gitdir, \"%s\", absolute_path(get_git_common_dir()));\n-\tstrbuf_addbuf(&worktree_path, &gitdir);\n+\tstrbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));\n \tis_bare = !strbuf_strip_suffix(&worktree_path, \"/.git\");\n \tif (is_bare)\n \t\tstrbuf_strip_suffix(&worktree_path, \"/.\");\n@@ -93,7 +91,7 @@ static struct worktree *get_main_worktree(void)\n \n \tworktree = xmalloc(sizeof(struct worktree));\n \tworktree->path = strbuf_detach(&worktree_path, NULL);\n-\tworktree->git_dir = strbuf_detach(&gitdir, NULL);\n+\tworktree->id = NULL;\n \tworktree->is_bare = is_bare;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n@@ -101,7 +99,6 @@ static struct worktree *get_main_worktree(void)\n \n done:\n \tstrbuf_release(&path);\n-\tstrbuf_release(&gitdir);\n \tstrbuf_release(&worktree_path);\n \tstrbuf_release(&head_ref);\n \treturn worktree;\n@@ -112,16 +109,13 @@ static struct worktree *get_linked_worktree(const char *id)\n \tstruct worktree *worktree = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf worktree_path = STRBUF_INIT;\n-\tstruct strbuf gitdir = STRBUF_INIT;\n \tstruct strbuf head_ref = STRBUF_INIT;\n \tint is_detached = 0;\n \n \tif (!id)\n \t\tdie(\"Missing linked worktree name\");\n \n-\tstrbuf_addf(&gitdir, \"%s/worktrees/%s\",\n-\t\t\tabsolute_path(get_git_common_dir()), id);\n-\tstrbuf_addf(&path, \"%s/gitdir\", gitdir.buf);\n+\tstrbuf_git_common_path(&path, \"worktrees/%s/gitdir\", id);\n \tif (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)\n \t\t/* invalid gitdir file */\n \t\tgoto done;\n@@ -141,7 +135,7 @@ static struct worktree *get_linked_worktree(const char *id)\n \n \tworktree = xmalloc(sizeof(struct worktree));\n \tworktree->path = strbuf_detach(&worktree_path, NULL);\n-\tworktree->git_dir = strbuf_detach(&gitdir, NULL);\n+\tworktree->id = xstrdup(id);\n \tworktree->is_bare = 0;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n@@ -149,7 +143,6 @@ static struct worktree *get_linked_worktree(const char *id)\n \n done:\n \tstrbuf_release(&path);\n-\tstrbuf_release(&gitdir);\n \tstrbuf_release(&worktree_path);\n \tstrbuf_release(&head_ref);\n \treturn worktree;\n@@ -189,6 +182,14 @@ struct worktree **get_worktrees(void)\n \treturn list;\n }\n \n+const char *get_worktree_git_dir(const struct worktree *wt)\n+{\n+\tif (wt->id)\n+\t\treturn git_common_path(\"worktrees/%s\", wt->id);\n+\telse\n+\t\treturn get_git_common_dir();\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\n@@ -200,7 +201,9 @@ char *find_shared_symref(const char *symref, const char *target)\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n-\t\tstrbuf_addf(&path, \"%s/%s\", worktrees[i]->git_dir, symref);\n+\t\tstrbuf_addf(&path, \"%s/%s\",\n+\t\t\t    get_worktree_git_dir(worktrees[i]),\n+\t\t\t    symref);\n \n \t\tif (parse_ref(path.buf, &sb, NULL)) {\n \t\t\tcontinue;\ndiff --git a/worktree.h b/worktree.h\nindex b4b3dda..e89d423 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -3,7 +3,7 @@\n \n struct worktree {\n \tchar *path;\n-\tchar *git_dir;\n+\tchar *id;\n \tchar *head_ref;\n \tunsigned char head_sha1[20];\n \tint is_detached;\n@@ -23,6 +23,11 @@ struct worktree {\n extern struct worktree **get_worktrees(void);\n \n /*\n+ * Return git dir of the worktree. Note that the path may be relative.\n+ */\n+extern const char *get_worktree_git_dir(const struct worktree *wt);\n+\n+/*\n  * Free up the memory for worktree(s)\n  */\n extern void free_worktrees(struct worktree **);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277320","messageId":"1454492150-10628-9-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 08/20] worktree.c: add clear_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:38Z","receivedAt":"2016-02-03T09:35:38Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 14 +++++++++++---\n worktree.h |  5 +++++\n 2 files changed, 16 insertions(+), 3 deletions(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex 4c38414..b4e4b57 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -4,14 +4,22 @@\n #include \"worktree.h\"\n #include \"dir.h\"\n \n+void clear_worktree(struct worktree *wt)\n+{\n+\tif (!wt)\n+\t\treturn;\n+\tfree(wt->path);\n+\tfree(wt->id);\n+\tfree(wt->head_ref);\n+\tmemset(wt, 0, sizeof(*wt));\n+}\n+\n void free_worktrees(struct worktree **worktrees)\n {\n \tint i = 0;\n \n \tfor (i = 0; worktrees[i]; i++) {\n-\t\tfree(worktrees[i]->path);\n-\t\tfree(worktrees[i]->id);\n-\t\tfree(worktrees[i]->head_ref);\n+\t\tclear_worktree(worktrees[i]);\n \t\tfree(worktrees[i]);\n \t}\n \tfree (worktrees);\ndiff --git a/worktree.h b/worktree.h\nindex e89d423..0ba07ab 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -28,6 +28,11 @@ extern struct worktree **get_worktrees(void);\n extern const char *get_worktree_git_dir(const struct worktree *wt);\n \n /*\n+ * Free up the memory for worktree\n+ */\n+extern void clear_worktree(struct worktree *);\n+\n+/*\n  * Free up the memory for worktree(s)\n  */\n extern void free_worktrees(struct worktree **);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277322","messageId":"1454492150-10628-10-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 09/20] worktree.c: add find_worktree_by_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:39Z","receivedAt":"2016-02-03T09:35:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 16 ++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 22 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex b4e4b57..e444ad1 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -198,6 +198,22 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn get_git_common_dir();\n }\n \n+struct worktree *find_worktree_by_path(struct worktree **list,\n+\t\t\t\t       const char *path_)\n+{\n+\tchar *path = xstrdup(real_path(path_));\n+\tstruct worktree *wt = NULL;\n+\n+\twhile (*list) {\n+\t\twt = *list++;\n+\t\tif (!strcmp_icase(path, real_path(wt->path)))\n+\t\t\tbreak;\n+\t\twt = NULL;\n+\t}\n+\tfree(path);\n+\treturn wt;\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\ndiff --git a/worktree.h b/worktree.h\nindex 0ba07ab..c163b6b 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -28,6 +28,12 @@ extern struct worktree **get_worktrees(void);\n extern const char *get_worktree_git_dir(const struct worktree *wt);\n \n /*\n+ * Search a worktree by its path. Paths are normalized internally.\n+ */\n+extern struct worktree *find_worktree_by_path(struct worktree **list,\n+\t\t\t\t\t      const char *path_);\n+\n+/*\n  * Free up the memory for worktree\n  */\n extern void clear_worktree(struct worktree *);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277323","messageId":"1454492150-10628-11-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 10/20] worktree.c: add is_main_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:40Z","receivedAt":"2016-02-03T09:35:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 5 +++++\n worktree.h | 5 +++++\n 2 files changed, 10 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex e444ad1..e878f49 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -214,6 +214,11 @@ struct worktree *find_worktree_by_path(struct worktree **list,\n \treturn wt;\n }\n \n+int is_main_worktree(const struct worktree *wt)\n+{\n+\treturn wt && !wt->id;\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\ndiff --git a/worktree.h b/worktree.h\nindex c163b6b..c7a4d20 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -34,6 +34,11 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,\n \t\t\t\t\t      const char *path_);\n \n /*\n+ * Return true if the given worktree is the main one.\n+ */\n+extern int is_main_worktree(const struct worktree *wt);\n+\n+/*\n  * Free up the memory for worktree\n  */\n extern void clear_worktree(struct worktree *);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277325","messageId":"1454492150-10628-12-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 11/20] worktree.c: recognize no main worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:41Z","receivedAt":"2016-02-03T09:35:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Because HEAD is part of repository signature, even if it's per-worktree\nfile, we can't simply delete it. So main worktree is considered gone if\n.git/HEAD contains all-zero SHA-1.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex e878f49..80c525b 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -49,6 +49,8 @@ static int parse_ref(char *path_to_ref, struct strbuf *ref, int *is_detached)\n \t\tif (!starts_with(ref->buf, \"ref:\")) {\n \t\t\tif (is_detached)\n \t\t\t\t*is_detached = 1;\n+\t\t\tif (starts_with(ref->buf, sha1_to_hex(null_sha1)))\n+\t\t\t\treturn -1;\n \t\t} else {\n \t\t\tstrbuf_remove(ref, 0, strlen(\"ref:\"));\n \t\t\tstrbuf_trim(ref);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277324","messageId":"1454492150-10628-13-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 12/20] worktree.c: add update_worktree_location()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:42Z","receivedAt":"2016-02-03T09:35:42Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 23 +++++++++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 29 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 80c525b..a11c053 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -221,6 +221,29 @@ int is_main_worktree(const struct worktree *wt)\n \treturn wt && !wt->id;\n }\n \n+int update_worktree_location(struct worktree *wt, const char *path_)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tint ret;\n+\n+\tif (is_main_worktree(wt))\n+\t\treturn 0;\n+\n+\tstrbuf_add_absolute_path(&path, path_);\n+\tif (strcmp_icase(wt->path, path.buf)) {\n+\t\tif (!write_file_gently(git_common_path(\"worktrees/%s/gitdir\", wt->id),\n+\t\t\t\t       \"%s/.git\", path.buf)) {\n+\t\t\tfree(wt->path);\n+\t\t\twt->path = strbuf_detach(&path, NULL);\n+\t\t\tret = 0;\n+\t\t} else\n+\t\t\tret = sys_error(_(\"failed to update '%s' for update\"),\n+\t\t\t\t\tgit_common_path(\"worktrees/%s/gitdir\", wt->id));\n+\t}\n+\tstrbuf_release(&path);\n+\treturn ret;\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\ndiff --git a/worktree.h b/worktree.h\nindex c7a4d20..4c0395a 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -39,6 +39,12 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,\n extern int is_main_worktree(const struct worktree *wt);\n \n /*\n+ * Update worktrees/xxx/gitdir with the new path.\n+ */\n+extern int update_worktree_location(struct worktree *wt,\n+\t\t\t\t    const char *path_);\n+\n+/*\n  * Free up the memory for worktree\n  */\n extern void clear_worktree(struct worktree *);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277326","messageId":"1454492150-10628-14-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 13/20] worktree.c: add update_worktree_gitfile()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:43Z","receivedAt":"2016-02-03T09:35:43Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 15 ++++++++++++++-\n worktree.h |  7 +++++++\n 2 files changed, 21 insertions(+), 1 deletion(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex a11c053..d4513b1 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -237,13 +237,26 @@ int update_worktree_location(struct worktree *wt, const char *path_)\n \t\t\twt->path = strbuf_detach(&path, NULL);\n \t\t\tret = 0;\n \t\t} else\n-\t\t\tret = sys_error(_(\"failed to update '%s' for update\"),\n+\t\t\tret = sys_error(_(\"failed to update '%s'\"),\n \t\t\t\t\tgit_common_path(\"worktrees/%s/gitdir\", wt->id));\n \t}\n \tstrbuf_release(&path);\n \treturn ret;\n }\n \n+int update_worktree_gitfile(const char *gitfile,\n+\t\t\t    const char *repo_path,\n+\t\t\t    const char *id)\n+{\n+\tif (!id)\n+\t\treturn error(_(\"cannot update .git of main worktree\"));\n+\n+\tif (write_file_gently(gitfile, \"gitdir: %s/worktrees/%s\", real_path(repo_path), id))\n+\t\treturn sys_error(_(\"failed to update '%s'\"), gitfile);\n+\n+\treturn 0;\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\ndiff --git a/worktree.h b/worktree.h\nindex 4c0395a..55f3449 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -45,6 +45,13 @@ extern int update_worktree_location(struct worktree *wt,\n \t\t\t\t    const char *path_);\n \n /*\n+ * Update .git file to point to the new repository's location.\n+ */\n+extern int update_worktree_gitfile(const char *gitfile,\n+\t\t\t\t   const char *repo_path,\n+\t\t\t\t   const char *id);\n+\n+/*\n  * Free up the memory for worktree\n  */\n extern void clear_worktree(struct worktree *);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277328","messageId":"1454492150-10628-15-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 14/20] worktree.c: add collect_per_worktree_git_paths()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:44Z","receivedAt":"2016-02-03T09:35:44Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 64 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex d4513b1..047319b 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -257,6 +257,64 @@ int update_worktree_gitfile(const char *gitfile,\n \treturn 0;\n }\n \n+static int collect_per_worktree_1(struct strbuf *base, int commonlen,\n+\t\t\t\t  struct string_list *paths)\n+{\n+\tint base_len = base->len;\n+\tstruct dirent *d;\n+\tDIR *dir;\n+\tint ret = 0;\n+\n+\tdir = opendir(base->buf);\n+\tif (!dir)\n+\t\treturn sys_error(_(\"failed to open '%s'\"), base->buf);\n+\n+\twhile ((d = readdir(dir))) {\n+\t\tstruct stat st;\n+\n+\t\tif (is_dot_or_dotdot(d->d_name))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_setlen(base, base_len);\n+\t\tstrbuf_addstr(base, d->d_name);\n+\n+\t\tif (stat(base->buf, &st)) {\n+\t\t\tif (errno == ENOENT)\n+\t\t\t\tcontinue;\n+\t\t\tret = sys_error(_(\"failed to stat '%s'\"), base->buf);\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (!S_ISDIR(st.st_mode)) {\n+\t\t\tconst char *path = base->buf + commonlen;\n+\t\t\tif (!is_git_path_shared(path))\n+\t\t\t\tstring_list_append(paths, path);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tstrbuf_addch(base, '/');\n+\t\tif ((ret = collect_per_worktree_1(base, commonlen, paths)))\n+\t\t\tbreak;\n+\t}\n+\tclosedir(dir);\n+\treturn ret;\n+}\n+\n+int collect_per_worktree_git_paths(struct string_list *paths)\n+{\n+\tstruct strbuf base = STRBUF_INIT;\n+\tint ret;\n+\n+\t/* force adjust_git_path() to always adjust paths */\n+\tgit_common_dir_env = 1;\n+\n+\tstrbuf_addstr(&base, get_git_common_dir());\n+\tif (base.len && base.buf[base.len - 1] != '/')\n+\t\tstrbuf_addch(&base, '/');\n+\tret = collect_per_worktree_1(&base, base.len, paths);\n+\tstrbuf_release(&base);\n+\treturn ret;\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\ndiff --git a/worktree.h b/worktree.h\nindex 55f3449..8da1a13 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -52,6 +52,12 @@ extern int update_worktree_gitfile(const char *gitfile,\n \t\t\t\t   const char *id);\n \n /*\n+ * Given .git directory in \"base\", search all paths, collect the list\n+ * of paths that are not shared in \"paths\".\n+ */\n+extern int collect_per_worktree_git_paths(struct string_list *paths);\n+\n+/*\n  * Free up the memory for worktree\n  */\n extern void clear_worktree(struct worktree *);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277327","messageId":"1454492150-10628-16-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 15/20] worktree: avoid 0{40}, too many zeroes, hard to read","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:45Z","receivedAt":"2016-02-03T09:35:45Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 7b217fb..f490ca3 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -263,7 +263,7 @@ static int add_worktree(const char *path, const char *refname,\n \t */\n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/HEAD\", sb_repo.buf);\n-\twrite_file(sb.buf, \"0000000000000000000000000000000000000000\");\n+\twrite_file(sb.buf, sha1_to_hex(null_sha1));\n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/commondir\", sb_repo.buf);\n \twrite_file(sb.buf, \"../..\");\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277329","messageId":"1454492150-10628-17-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 16/20] worktree: simplify prefixing paths","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:46Z","receivedAt":"2016-02-03T09:35:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex f490ca3..36d942e 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -332,7 +332,7 @@ static int add(int ac, const char **av, const char *prefix)\n \tif (ac < 1 || ac > 2)\n \t\tusage_with_options(worktree_usage, options);\n \n-\tpath = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];\n+\tpath = prefix_filename(prefix, strlen(prefix), av[0]);\n \tbranch = ac < 2 ? \"HEAD\" : av[1];\n \n \topts.force_new_branch = !!new_branch_force;\n@@ -453,6 +453,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \n \tif (ac < 2)\n \t\tusage_with_options(worktree_usage, options);\n+\tif (!prefix)\n+\t\tprefix = \"\";\n \tif (!strcmp(av[1], \"add\"))\n \t\treturn add(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"prune\"))\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277331","messageId":"1454492150-10628-18-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 17/20] worktree: add \"move\" commmand","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:47Z","receivedAt":"2016-02-03T09:35:47Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c                | 51 +++++++++++++++++++++++++++++++++++++++\n t/t2028-worktree-move.sh (new +x) | 41 +++++++++++++++++++++++++++++++\n 2 files changed, 92 insertions(+)\n create mode 100755 t/t2028-worktree-move.sh\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 36d942e..8d59199 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -445,6 +445,55 @@ static int list(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int move(int ac, const char **av, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\tstruct strbuf dst = STRBUF_INIT;\n+\tstruct strbuf src = STRBUF_INIT;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 2)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tstrbuf_addstr(&dst, prefix_filename(prefix,\n+\t\t\t\t\t     strlen(prefix),\n+\t\t\t\t\t     av[1]));\n+\tif (file_exists(dst.buf))\n+\t\tdie(_(\"target '%s' already exists\"), av[1]);\n+\n+\tworktrees = get_worktrees();\n+\tstrbuf_addstr(&src, prefix_filename(prefix,\n+\t\t\t\t\t     strlen(prefix),\n+\t\t\t\t\t     av[0]));\n+\twt = find_worktree_by_path(worktrees, src.buf);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\n+\t/*\n+\t * First try. Atomically move, and probably cheaper, if both\n+\t * source and target are on the same file system.\n+\t */\n+\tif (rename(src.buf, dst.buf) == -1) {\n+\t\tif (errno != EXDEV)\n+\t\t\tdie_errno(_(\"failed to move '%s' to '%s'\"),\n+\t\t\t\t  src.buf, dst.buf);\n+\n+\t\t/* second try.. */\n+\t\tif (copy_dir_recursively(src.buf, dst.buf))\n+\t\t\tdie(_(\"failed to copy '%s' to '%s'\"),\n+\t\t\t    src.buf, dst.buf);\n+\t\telse\n+\t\t\t(void)remove_dir_recursively(&src, 0);\n+\t}\n+\n+\treturn update_worktree_location(wt, dst.buf);\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -461,5 +510,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn prune(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"list\"))\n \t\treturn list(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"move\"))\n+\t\treturn move(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nnew file mode 100755\nindex 0000000..e8f6f0c\n--- /dev/null\n+++ b/t/t2028-worktree-move.sh\n@@ -0,0 +1,41 @@\n+#!/bin/sh\n+\n+test_description='test git worktree add'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit init &&\n+\tgit worktree add source &&\n+\tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n+\tcat <<-EOF >expected &&\n+\tworktree $TRASH_DIRECTORY\n+\tworktree $TRASH_DIRECTORY/source\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'move non-worktree' '\n+\tmkdir abc &&\n+\ttest_must_fail git worktree move abc def\n+'\n+\n+test_expect_success 'move worktree' '\n+\tgit worktree move source destination &&\n+\ttest_path_is_missing source &&\n+\tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n+\tcat <<-EOF >expected &&\n+\tworktree $TRASH_DIRECTORY\n+\tworktree $TRASH_DIRECTORY/destination\n+\tEOF\n+\ttest_cmp expected actual &&\n+\tgit -C destination log --format=%s >actual2 &&\n+\techo init >expected2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'move main worktree' '\n+\ttest_must_fail git worktree move . def\n+'\n+\n+test_done\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277330","messageId":"1454492150-10628-19-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 18/20] worktree: refactor add_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:48Z","receivedAt":"2016-02-03T09:35:48Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 130 +++++++++++++++++++++++++++++++++--------------------\n 1 file changed, 81 insertions(+), 49 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 8d59199..3ebb9e9 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -184,34 +184,15 @@ static const char *worktree_basename(const char *path, int *olen)\n \treturn name;\n }\n \n-static int add_worktree(const char *path, const char *refname,\n-\t\t\tconst struct add_opts *opts)\n+static void prepare_new_worktree(const char *path,\n+\t\t\t\t struct worktree *wt,\n+\t\t\t\t int create_gitdir_and_head)\n {\n+\tconst char *name;\n \tstruct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;\n \tstruct strbuf sb = STRBUF_INIT;\n-\tconst char *name;\n \tstruct stat st;\n-\tstruct child_process cp;\n-\tstruct argv_array child_env = ARGV_ARRAY_INIT;\n-\tint counter = 0, len, ret;\n-\tstruct strbuf symref = STRBUF_INIT;\n-\tstruct commit *commit = NULL;\n-\n-\tif (file_exists(path) && !is_empty_dir(path))\n-\t\tdie(_(\"'%s' already exists\"), path);\n-\n-\t/* is 'refname' a branch or commit? */\n-\tif (opts->force_new_branch) /* definitely a branch */\n-\t\t;\n-\telse if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&\n-\t\t ref_exists(symref.buf)) { /* it's a branch */\n-\t\tif (!opts->force)\n-\t\t\tdie_if_checked_out(symref.buf);\n-\t} else { /* must be a commit */\n-\t\tcommit = lookup_commit_reference_by_name(refname);\n-\t\tif (!commit)\n-\t\t\tdie(_(\"invalid reference: %s\"), refname);\n-\t}\n+\tint counter = 0, len;\n \n \tname = worktree_basename(path, &len);\n \tstrbuf_addstr(&sb_repo,\n@@ -227,6 +208,10 @@ static int add_worktree(const char *path, const char *refname,\n \t}\n \tname = strrchr(sb_repo.buf, '/') + 1;\n \n+\tmemset(wt, 0, sizeof(*wt));\n+\twt->path = xstrdup(path);\n+\twt->id = xstrdup(name);\n+\n \tjunk_pid = getpid();\n \tatexit(remove_junk);\n \tsigchain_push_common(remove_junk_on_signal);\n@@ -252,25 +237,81 @@ static int add_worktree(const char *path, const char *refname,\n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/gitdir\", sb_repo.buf);\n \twrite_file(sb.buf, \"%s\", real_path(sb_git.buf));\n-\twrite_file(sb_git.buf, \"gitdir: %s/worktrees/%s\",\n-\t\t   real_path(get_git_common_dir()), name);\n-\t/*\n-\t * This is to keep resolve_ref() happy. We need a valid HEAD\n-\t * or is_git_directory() will reject the directory. Any value which\n-\t * looks like an object ID will do since it will be immediately\n-\t * replaced by the symbolic-ref or update-ref invocation in the new\n-\t * worktree.\n-\t */\n-\tstrbuf_reset(&sb);\n-\tstrbuf_addf(&sb, \"%s/HEAD\", sb_repo.buf);\n-\twrite_file(sb.buf, sha1_to_hex(null_sha1));\n+\tif (create_gitdir_and_head) {\n+\t\twrite_file(sb_git.buf, \"gitdir: %s/worktrees/%s\",\n+\t\t\t   real_path(get_git_common_dir()), name);\n+\n+\t\t/*\n+\t\t * This is to keep resolve_ref() happy. We need a valid HEAD\n+\t\t * or is_git_directory() will reject the directory. Any value which\n+\t\t * looks like an object ID will do since it will be immediately\n+\t\t * replaced by the symbolic-ref or update-ref invocation in the new\n+\t\t * worktree.\n+\t\t */\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s/HEAD\", sb_repo.buf);\n+\t\twrite_file(sb.buf, sha1_to_hex(null_sha1));\n+\t}\n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/commondir\", sb_repo.buf);\n \twrite_file(sb.buf, \"../..\");\n \n-\tfprintf_ln(stderr, _(\"Preparing %s (identifier %s)\"), path, name);\n+\tstrbuf_release(&sb);\n+\tstrbuf_release(&sb_repo);\n+\tstrbuf_release(&sb_git);\n+}\n+\n+static void new_worktree_complete(void)\n+{\n+\tis_junk = 0;\n+\tfree(junk_work_tree);\n+\tfree(junk_git_dir);\n+\tjunk_work_tree = NULL;\n+\tjunk_git_dir = NULL;\n+}\n+\n+static void cleanup_new_worktree(struct worktree *wt)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tstrbuf_git_common_path(&sb, \"worktrees/%s/locked\", wt->id);\n+\tunlink_or_warn(sb.buf);\n+\tstrbuf_release(&sb);\n+\tclear_worktree(wt);\n+}\n+\n+static int add_worktree(const char *path, const char *refname,\n+\t\t\tconst struct add_opts *opts)\n+{\n+\tstruct worktree wt;\n+\tstruct child_process cp;\n+\tstruct argv_array child_env = ARGV_ARRAY_INIT;\n+\tint ret;\n+\tstruct strbuf symref = STRBUF_INIT;\n+\tstruct commit *commit = NULL;\n+\n+\t/* is 'refname' a branch or commit? */\n+\tif (opts->force_new_branch) /* definitely a branch */\n+\t\t;\n+\telse if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&\n+\t\t ref_exists(symref.buf)) { /* it's a branch */\n+\t\tif (!opts->force)\n+\t\t\tdie_if_checked_out(symref.buf);\n+\t} else { /* must be a commit */\n+\t\tcommit = lookup_commit_reference_by_name(refname);\n+\t\tif (!commit)\n+\t\t\tdie(_(\"invalid reference: %s\"), refname);\n+\t}\n+\n+\tif (file_exists(path) && !is_empty_dir(path))\n+\t\tdie(_(\"'%s' already exists\"), path);\n+\n+\tprepare_new_worktree(path, &wt, 1);\n \n-\targv_array_pushf(&child_env, \"%s=%s\", GIT_DIR_ENVIRONMENT, sb_git.buf);\n+\tfprintf_ln(stderr, _(\"Preparing %s (identifier %s)\"), path, wt.id);\n+\n+\targv_array_pushf(&child_env, \"%s=%s\", GIT_DIR_ENVIRONMENT,\n+\t\t\t get_worktree_git_dir(&wt));\n \targv_array_pushf(&child_env, \"%s=%s\", GIT_WORK_TREE_ENVIRONMENT, path);\n \tmemset(&cp, 0, sizeof(cp));\n \tcp.git_cmd = 1;\n@@ -292,21 +333,12 @@ static int add_worktree(const char *path, const char *refname,\n \tcp.env = child_env.argv;\n \tret = run_command(&cp);\n \tif (!ret) {\n-\t\tis_junk = 0;\n-\t\tfree(junk_work_tree);\n-\t\tfree(junk_git_dir);\n-\t\tjunk_work_tree = NULL;\n-\t\tjunk_git_dir = NULL;\n+\t\tnew_worktree_complete();\n \t}\n done:\n-\tstrbuf_reset(&sb);\n-\tstrbuf_addf(&sb, \"%s/locked\", sb_repo.buf);\n-\tunlink_or_warn(sb.buf);\n \targv_array_clear(&child_env);\n-\tstrbuf_release(&sb);\n \tstrbuf_release(&symref);\n-\tstrbuf_release(&sb_repo);\n-\tstrbuf_release(&sb_git);\n+\tcleanup_new_worktree(&wt);\n \treturn ret;\n }\n \n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277332","messageId":"1454492150-10628-20-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 19/20] worktree: move repo, simple case","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:49Z","receivedAt":"2016-02-03T09:35:49Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 45 +++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 45 insertions(+)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 3ebb9e9..80a8b80 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -477,9 +477,52 @@ static int list(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int move_gitdir(int ac, const char **av, const char *prefix)\n+{\n+\tstruct string_list paths = STRING_LIST_INIT_NODUP;\n+\tstruct strbuf dst = STRBUF_INIT;\n+\tstruct strbuf src = STRBUF_INIT;\n+\tstruct worktree **worktrees, *mwt = NULL;\n+\tint i, ret = 0;\n+\n+\tif (ac != 1)\n+\t\tdie(_(\"--repository requires one argument\"));\n+\n+\tstrbuf_addstr(&src, real_path(get_git_common_dir()));\n+\tstrbuf_addstr(&dst, real_path(prefix_filename(prefix, strlen(prefix), av[0])));\n+\n+\tworktrees = get_worktrees();\n+\tfor (i = 0; worktrees[i]; i++) {\n+\t\tif (is_main_worktree(worktrees[i]))\n+\t\t\tmwt = worktrees[i];\n+\t\tstring_list_append(&paths,\n+\t\t\t\t   get_worktree_git_dir(worktrees[i]));\n+\t}\n+\n+\tif (mwt)\n+\t\tdie(_(\"converting main worktree is not supported\"));\n+\n+\tret = copy_dir_recursively(src.buf, dst.buf);\n+\tif (ret)\n+\t\tdie(_(\"failed to copy '%s' to '%s'\"), src.buf, dst.buf);\n+\n+\tfor (i = 0; paths.nr; i++)\n+\t\tif (update_worktree_gitfile(paths.items[i].string, dst.buf,\n+\t\t\t\t\t    worktrees[i]->id))\n+\t\t\tret = -1;\n+\n+\tif (!ret)\n+\t\tret = remove_dir_recursively(&src, 0);\n+\n+\tstring_list_clear(&paths, 0);\n+\treturn ret;\n+}\n+\n static int move(int ac, const char **av, const char *prefix)\n {\n+\tint move_repo = 0;\n \tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"repository\", &move_repo, N_(\"move repository\")),\n \t\tOPT_END()\n \t};\n \tstruct worktree **worktrees, *wt;\n@@ -487,6 +530,8 @@ static int move(int ac, const char **av, const char *prefix)\n \tstruct strbuf src = STRBUF_INIT;\n \n \tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (move_repo)\n+\t\treturn move_gitdir(ac, av, prefix);\n \tif (ac != 2)\n \t\tusage_with_options(worktree_usage, options);\n \n-- \n2.7.0.377.g4cd97dd\n"},{"id":"277333","messageId":"1454492150-10628-21-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 20/20] worktree: move repo, convert main worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-03T09:35:50Z","receivedAt":"2016-02-03T09:35:50Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c       | 87 ++++++++++++++++++++++++++++++++++++++++++------\n t/t2028-worktree-move.sh | 36 ++++++++++++++++++++\n 2 files changed, 112 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 80a8b80..3f69369 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -479,10 +479,11 @@ static int list(int ac, const char **av, const char *prefix)\n \n static int move_gitdir(int ac, const char **av, const char *prefix)\n {\n-\tstruct string_list paths = STRING_LIST_INIT_NODUP;\n+\tstruct string_list per_worktree = STRING_LIST_INIT_DUP;\n \tstruct strbuf dst = STRBUF_INIT;\n \tstruct strbuf src = STRBUF_INIT;\n \tstruct worktree **worktrees, *mwt = NULL;\n+\tstruct worktree tmp;\n \tint i, ret = 0;\n \n \tif (ac != 1)\n@@ -492,29 +493,93 @@ static int move_gitdir(int ac, const char **av, const char *prefix)\n \tstrbuf_addstr(&dst, real_path(prefix_filename(prefix, strlen(prefix), av[0])));\n \n \tworktrees = get_worktrees();\n-\tfor (i = 0; worktrees[i]; i++) {\n-\t\tif (is_main_worktree(worktrees[i]))\n+\tfor (i = 0; worktrees[i]; i++)\n+\t\tif (is_main_worktree(worktrees[i])) {\n \t\t\tmwt = worktrees[i];\n-\t\tstring_list_append(&paths,\n-\t\t\t\t   get_worktree_git_dir(worktrees[i]));\n-\t}\n+\t\t\tbreak;\n+\t\t}\n+\n+\tif (mwt) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\tstrbuf_addstr(&sb, real_path(get_git_common_dir()));\n+\t\tif (ends_with(sb.buf, \"/.git\"))\n+\t\t\tsb.len -= strlen(\"/.git\");\n+\t\telse\n+\t\t\tdie(_(\"unrecognized repository directory layout: %s\"),\n+\t\t\t    sb.buf);\n+\t\tsb.buf[sb.len] = '\\0';\n+\t\tprepare_new_worktree(sb.buf, &tmp, 0);\n+\t\tstrbuf_release(&sb);\n+\n+\t\tif (collect_per_worktree_git_paths(&per_worktree))\n+\t\t\tdie(_(\"failed to collect per-worktree paths\"));\n+\n+\t\tfor (i = 0; !ret && i < per_worktree.nr; i++) {\n+\t\t\tconst char *path = per_worktree.items[i].string;\n+\n+\t\t\tif (safe_create_leading_directories_const(\n+\t\t\t\tgit_common_path(\"worktrees/%s/%s\", tmp.id, path)) ||\n+\t\t\t    copy_file(git_common_path(\"worktrees/%s/%s\", tmp.id, path),\n+\t\t\t\t      git_common_path(\"%s\", path),\n+\t\t\t\t      0777))\n+\t\t\t\tdie_errno(_(\"failed to copy '%s' to '%s'\"),\n+\t\t\t\t\t  git_common_path(\"%s\", path),\n+\t\t\t\t\t  git_common_path(\"worktrees/%s/%s\", tmp.id, path));\n+\t\t}\n \n-\tif (mwt)\n-\t\tdie(_(\"converting main worktree is not supported\"));\n+\t\tmwt->id = xstrdup(tmp.id);\n+\t\tcleanup_new_worktree(&tmp);\n+\t}\n \n \tret = copy_dir_recursively(src.buf, dst.buf);\n \tif (ret)\n \t\tdie(_(\"failed to copy '%s' to '%s'\"), src.buf, dst.buf);\n \n-\tfor (i = 0; paths.nr; i++)\n-\t\tif (update_worktree_gitfile(paths.items[i].string, dst.buf,\n+\tif (mwt) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tint len;\n+\n+\t\tnew_worktree_complete();\n+\n+\t\tstrbuf_addbuf(&sb, &dst);\n+\t\tif (sb.buf[sb.len - 1] != '/')\n+\t\t\tstrbuf_addch(&sb, '/');\n+\t\tlen = sb.len;\n+\t\tfor (i = 0; !ret && i < per_worktree.nr; i++) {\n+\t\t\tconst char *path = per_worktree.items[i].string;\n+\n+\t\t\tstrbuf_addstr(&sb, path);\n+\t\t\tunlink_or_warn(sb.buf);\n+\t\t\tstrbuf_setlen(&sb, len);\n+\t\t}\n+\t\tstring_list_clear(&per_worktree, 0);\n+\n+\t\tstrbuf_addstr(&sb, \"HEAD\");\n+\t\twrite_file(sb.buf, sha1_to_hex(null_sha1));\n+\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s.old\", src.buf);\n+\t\tif (rename(src.buf, sb.buf))\n+\t\t\tdie_errno(_(\"failed to move '%s' to '%s'\"), src.buf, sb.buf);\n+\t\t/* XXX: rename back on die() */\n+\t\tstrbuf_swap(&sb, &src);\n+\t\tstrbuf_release(&sb);\n+\t}\n+\n+\tfor (i = 0; worktrees[i]; i++) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\tstrbuf_addf(&sb, \"%s/.git\", worktrees[i]->path);\n+\t\tif (update_worktree_gitfile(sb.buf, dst.buf,\n \t\t\t\t\t    worktrees[i]->id))\n \t\t\tret = -1;\n+\t\tstrbuf_release(&sb);\n+\t}\n \n \tif (!ret)\n \t\tret = remove_dir_recursively(&src, 0);\n \n-\tstring_list_clear(&paths, 0);\n \treturn ret;\n }\n \ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nindex e8f6f0c..5e9e1d4 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -38,4 +38,40 @@ test_expect_success 'move main worktree' '\n \ttest_must_fail git worktree move . def\n '\n \n+test_expect_success 'move repository and convert main worktree' '\n+\tgit worktree move --repository repo &&\n+\ttest_path_is_file .git &&\n+\ttest_path_is_dir repo &&\n+\tgit fsck &&\n+\tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n+\tcat <<-EOF >expected &&\n+\tworktree $TRASH_DIRECTORY\n+\tworktree $TRASH_DIRECTORY/destination\n+\tEOF\n+\ttest_cmp expected actual &&\n+\tgit -C destination log --format=%s >actual2 &&\n+\techo init >expected2 &&\n+\ttest_cmp expected2 actual2 &&\n+\tgit log --format=%s >actual3 &&\n+\ttest_cmp expected2 actual3\n+'\n+\n+test_expect_success 'move repository alone' '\n+\tgit worktree move --repository repo2 &&\n+\ttest_path_is_file .git &&\n+\ttest_path_is_dir repo2 &&\n+\tgit fsck &&\n+\tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n+\tcat <<-EOF >expected &&\n+\tworktree $TRASH_DIRECTORY\n+\tworktree $TRASH_DIRECTORY/destination\n+\tEOF\n+\ttest_cmp expected actual &&\n+\tgit -C destination log --format=%s >actual2 &&\n+\techo init >expected2 &&\n+\ttest_cmp expected2 actual2 &&\n+\tgit log --format=%s >actual3 &&\n+\ttest_cmp expected2 actual3\n+'\n+\n test_done\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278316","messageId":"1455629367-26193-1-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1454492150-10628-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 00/26] worktree lock, move, remove and unlock","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:01Z","receivedAt":"2016-02-16T13:29:01Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"I think this is good enough to be further polished and maybe released.\nThe series adds four new commands and bash completion for git-worktree.\n\nRemaining work to do on git-worktree:\n\n - \"git worktree move --repository\", in v1 but left out in v2 because\n   it's trickier.\n\n - .git/config issue and submodule support\n\nA bit off topic, I've been using this script to add \"cw\" command, to\nchdir to another worktree with tab-completion. It works wonderfully,\nbut I'm not sure if it can ever be merged to git-completion.bash.\n\n-- 8< --\ncw() {\n    cd \"$(git worktree list --porcelain | grep -B2 refs/heads/$1 | head -n1 | cut -f 2 -d ' ')\"\n}\n\n_git_cw() {\n\tlocal cur words cword prev\n\t_get_comp_words_by_ref -n =: cur words cword prev\n\n    \tlocal list=\"$(git worktree list --porcelain | grep '^branch refs/heads/' | cut -c 19-)\"\n\t__gitcomp \"$list\"\n}\n\ncomplete -o bashdefault -o default -o nospace -F _git_cw cw 2>/dev/null || \\\n    complete -o default -o nospace -F _git_cw cw\n-- 8< --\n\nNguyễn Thái Ngọc Duy (26):\n  usage.c: move format processing out of die_errno()\n  usage.c: add sys_error() that prints strerror() automatically\n  copy.c: import copy_file() from busybox\n  copy.c: delete unused code in copy_file()\n  copy.c: convert bb_(p)error_msg to (sys_)error\n  copy.c: style fix\n  copy.c: convert copy_file() to copy_dir_recursively()\n  completion: support git-worktree\n  git-worktree.txt: keep subcommand listing in alphabetical order\n  wrapper.c: allow to create an empty file with write_file()\n  path.c: add git_common_path() and strbuf_git_common_path()\n  worktree.c: use is_dot_or_dotdot()\n  worktree.c: store \"id\" instead of \"git_dir\"\n  worktree.c: add clear_worktree()\n  worktree.c: add find_worktree_by_path()\n  worktree.c: add is_main_worktree()\n  worktree.c: add validate_worktree()\n  worktree.c: add update_worktree_location()\n  worktree.c: add is_worktree_locked()\n  worktree: avoid 0{40}, too many zeroes, hard to read\n  worktree: simplify prefixing paths\n  worktree: add \"lock\" command\n  worktree: add \"unlock\" command\n  worktree: add \"move\" commmand\n  worktree move: accept destination as directory\n  worktree: add \"remove\" command\n\n Documentation/git-worktree.txt         |  55 +++--\n builtin/worktree.c                     | 240 ++++++++++++++++++++-\n cache.h                                |   4 +\n contrib/completion/git-completion.bash |  29 +++\n copy.c                                 | 369 +++++++++++++++++++++++++++++++++\n git-compat-util.h                      |   1 +\n path.c                                 |  29 +++\n t/t2028-worktree-move.sh (new +x)      | 103 +++++++++\n usage.c                                |  35 +++-\n worktree.c                             | 171 +++++++++++++--\n worktree.h                             |  40 +++-\n wrapper.c                              |   2 +-\n 12 files changed, 1029 insertions(+), 49 deletions(-)\n create mode 100755 t/t2028-worktree-move.sh\n\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278317","messageId":"1455629367-26193-2-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 01/26] usage.c: move format processing out of die_errno()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:02Z","receivedAt":"2016-02-16T13:29:02Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n usage.c | 25 +++++++++++++++----------\n 1 file changed, 15 insertions(+), 10 deletions(-)\n\ndiff --git a/usage.c b/usage.c\nindex 82ff131..0dba0c5 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -109,19 +109,12 @@ void NORETURN die(const char *err, ...)\n \tva_end(params);\n }\n \n-void NORETURN die_errno(const char *fmt, ...)\n+static const char *fmt_with_err(const char *fmt)\n {\n-\tva_list params;\n-\tchar fmt_with_err[1024];\n+\tstatic char fmt_with_err[1024];\n \tchar str_error[256], *err;\n \tint i, j;\n \n-\tif (die_is_recursing()) {\n-\t\tfputs(\"fatal: recursion detected in die_errno handler\\n\",\n-\t\t\tstderr);\n-\t\texit(128);\n-\t}\n-\n \terr = strerror(errno);\n \tfor (i = j = 0; err[i] && j < sizeof(str_error) - 1; ) {\n \t\tif ((str_error[j++] = err[i++]) != '%')\n@@ -137,9 +130,21 @@ void NORETURN die_errno(const char *fmt, ...)\n \t}\n \tstr_error[j] = 0;\n \tsnprintf(fmt_with_err, sizeof(fmt_with_err), \"%s: %s\", fmt, str_error);\n+\treturn fmt_with_err;\n+}\n+\n+void NORETURN die_errno(const char *fmt, ...)\n+{\n+\tva_list params;\n+\n+\tif (die_is_recursing()) {\n+\t\tfputs(\"fatal: recursion detected in die_errno handler\\n\",\n+\t\t\tstderr);\n+\t\texit(128);\n+\t}\n \n \tva_start(params, fmt);\n-\tdie_routine(fmt_with_err, params);\n+\tdie_routine(fmt_with_err(fmt), params);\n \tva_end(params);\n }\n \n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278319","messageId":"1455629367-26193-3-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 02/26] usage.c: add sys_error() that prints strerror() automatically","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:03Z","receivedAt":"2016-02-16T13:29:03Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n git-compat-util.h |  1 +\n usage.c           | 10 ++++++++++\n 2 files changed, 11 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 693a336..fd1d374 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -408,6 +408,7 @@ extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf,\n extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2)));\n extern NORETURN void die_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));\n extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n+extern int sys_error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));\n \n #ifndef NO_OPENSSL\ndiff --git a/usage.c b/usage.c\nindex 0dba0c5..e7c37f2 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -148,6 +148,16 @@ void NORETURN die_errno(const char *fmt, ...)\n \tva_end(params);\n }\n \n+int sys_error(const char *fmt, ...)\n+{\n+\tva_list params;\n+\n+\tva_start(params, fmt);\n+\terror_routine(fmt_with_err(fmt), params);\n+\tva_end(params);\n+\treturn -1;\n+}\n+\n #undef error\n int error(const char *err, ...)\n {\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278318","messageId":"1455629367-26193-4-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 03/26] copy.c: import copy_file() from busybox","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:04Z","receivedAt":"2016-02-16T13:29:04Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is busybox's unmodified copy_file() in libbb/copy_file.c from the\nGPL2+ commit f2c043acfcf9dad9fd3d65821b81f89986bbe54e (busybox: fix\nuninitialized memory when displaying IPv6 addresses - 2016-01-18)\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n copy.c | 331 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 331 insertions(+)\n\ndiff --git a/copy.c b/copy.c\nindex 574fa1f..29e9d5b 100644\n--- a/copy.c\n+++ b/copy.c\n@@ -65,3 +65,334 @@ int copy_file_with_time(const char *dst, const char *src, int mode)\n \t\treturn copy_times(dst, src);\n \treturn status;\n }\n+\n+#if 0\n+/* Return:\n+ * -1 error, copy not made\n+ *  0 copy is made or user answered \"no\" in interactive mode\n+ *    (failures to preserve mode/owner/times are not reported in exit code)\n+ */\n+int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n+{\n+\t/* This is a recursive function, try to minimize stack usage */\n+\t/* NB: each struct stat is ~100 bytes */\n+\tstruct stat source_stat;\n+\tstruct stat dest_stat;\n+\tsmallint retval = 0;\n+\tsmallint dest_exists = 0;\n+\tsmallint ovr;\n+\n+/* Inverse of cp -d (\"cp without -d\") */\n+#define FLAGS_DEREF (flags & (FILEUTILS_DEREFERENCE + FILEUTILS_DEREFERENCE_L0))\n+\n+\tif ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) {\n+\t\t/* This may be a dangling symlink.\n+\t\t * Making [sym]links to dangling symlinks works, so... */\n+\t\tif (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK))\n+\t\t\tgoto make_links;\n+\t\tbb_perror_msg(\"can't stat '%s'\", source);\n+\t\treturn -1;\n+\t}\n+\n+\tif (lstat(dest, &dest_stat) < 0) {\n+\t\tif (errno != ENOENT) {\n+\t\t\tbb_perror_msg(\"can't stat '%s'\", dest);\n+\t\t\treturn -1;\n+\t\t}\n+\t} else {\n+\t\tif (source_stat.st_dev == dest_stat.st_dev\n+\t\t && source_stat.st_ino == dest_stat.st_ino\n+\t\t) {\n+\t\t\tbb_error_msg(\"'%s' and '%s' are the same file\", source, dest);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tdest_exists = 1;\n+\t}\n+\n+#if ENABLE_SELINUX\n+\tif ((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) && is_selinux_enabled() > 0) {\n+\t\tsecurity_context_t con;\n+\t\tif (lgetfilecon(source, &con) >= 0) {\n+\t\t\tif (setfscreatecon(con) < 0) {\n+\t\t\t\tbb_perror_msg(\"can't set setfscreatecon %s\", con);\n+\t\t\t\tfreecon(con);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t} else if (errno == ENOTSUP || errno == ENODATA) {\n+\t\t\tsetfscreatecon_or_die(NULL);\n+\t\t} else {\n+\t\t\tbb_perror_msg(\"can't lgetfilecon %s\", source);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+#endif\n+\n+\tif (S_ISDIR(source_stat.st_mode)) {\n+\t\tDIR *dp;\n+\t\tconst char *tp;\n+\t\tstruct dirent *d;\n+\t\tmode_t saved_umask = 0;\n+\n+\t\tif (!(flags & FILEUTILS_RECUR)) {\n+\t\t\tbb_error_msg(\"omitting directory '%s'\", source);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* Did we ever create source ourself before? */\n+\t\ttp = is_in_ino_dev_hashtable(&source_stat);\n+\t\tif (tp) {\n+\t\t\t/* We did! it's a recursion! man the lifeboats... */\n+\t\t\tbb_error_msg(\"recursion detected, omitting directory '%s'\",\n+\t\t\t\t\tsource);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (dest_exists) {\n+\t\t\tif (!S_ISDIR(dest_stat.st_mode)) {\n+\t\t\t\tbb_error_msg(\"target '%s' is not a directory\", dest);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\t/* race here: user can substitute a symlink between\n+\t\t\t * this check and actual creation of files inside dest */\n+\t\t} else {\n+\t\t\t/* Create DEST */\n+\t\t\tmode_t mode;\n+\t\t\tsaved_umask = umask(0);\n+\n+\t\t\tmode = source_stat.st_mode;\n+\t\t\tif (!(flags & FILEUTILS_PRESERVE_STATUS))\n+\t\t\t\tmode = source_stat.st_mode & ~saved_umask;\n+\t\t\t/* Allow owner to access new dir (at least for now) */\n+\t\t\tmode |= S_IRWXU;\n+\t\t\tif (mkdir(dest, mode) < 0) {\n+\t\t\t\tumask(saved_umask);\n+\t\t\t\tbb_perror_msg(\"can't create directory '%s'\", dest);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tumask(saved_umask);\n+\t\t\t/* need stat info for add_to_ino_dev_hashtable */\n+\t\t\tif (lstat(dest, &dest_stat) < 0) {\n+\t\t\t\tbb_perror_msg(\"can't stat '%s'\", dest);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t}\n+\t\t/* remember (dev,inode) of each created dir.\n+\t\t * NULL: name is not remembered */\n+\t\tadd_to_ino_dev_hashtable(&dest_stat, NULL);\n+\n+\t\t/* Recursively copy files in SOURCE */\n+\t\tdp = opendir(source);\n+\t\tif (dp == NULL) {\n+\t\t\tretval = -1;\n+\t\t\tgoto preserve_mode_ugid_time;\n+\t\t}\n+\n+\t\twhile ((d = readdir(dp)) != NULL) {\n+\t\t\tchar *new_source, *new_dest;\n+\n+\t\t\tnew_source = concat_subpath_file(source, d->d_name);\n+\t\t\tif (new_source == NULL)\n+\t\t\t\tcontinue;\n+\t\t\tnew_dest = concat_path_file(dest, d->d_name);\n+\t\t\tif (copy_file(new_source, new_dest, flags & ~FILEUTILS_DEREFERENCE_L0) < 0)\n+\t\t\t\tretval = -1;\n+\t\t\tfree(new_source);\n+\t\t\tfree(new_dest);\n+\t\t}\n+\t\tclosedir(dp);\n+\n+\t\tif (!dest_exists\n+\t\t && chmod(dest, source_stat.st_mode & ~saved_umask) < 0\n+\t\t) {\n+\t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"permissions\", dest);\n+\t\t\t/* retval = -1; - WRONG! copy *WAS* made */\n+\t\t}\n+\t\tgoto preserve_mode_ugid_time;\n+\t}\n+\n+\tif (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) {\n+\t\tint (*lf)(const char *oldpath, const char *newpath);\n+ make_links:\n+\t\t/* Hmm... maybe\n+\t\t * if (DEREF && MAKE_SOFTLINK) source = realpath(source) ?\n+\t\t * (but realpath returns NULL on dangling symlinks...) */\n+\t\tlf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link;\n+\t\tif (lf(source, dest) < 0) {\n+\t\t\tovr = ask_and_unlink(dest, flags);\n+\t\t\tif (ovr <= 0)\n+\t\t\t\treturn ovr;\n+\t\t\tif (lf(source, dest) < 0) {\n+\t\t\t\tbb_perror_msg(\"can't create link '%s'\", dest);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t}\n+\t\t/* _Not_ jumping to preserve_mode_ugid_time:\n+\t\t * (sym)links don't have those */\n+\t\treturn 0;\n+\t}\n+\n+\tif (/* \"cp thing1 thing2\" without -R: just open and read() from thing1 */\n+\t    !(flags & FILEUTILS_RECUR)\n+\t    /* \"cp [-opts] regular_file thing2\" */\n+\t || S_ISREG(source_stat.st_mode)\n+\t /* DEREF uses stat, which never returns S_ISLNK() == true.\n+\t  * So the below is never true: */\n+\t /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */\n+\t) {\n+\t\tint src_fd;\n+\t\tint dst_fd;\n+\t\tmode_t new_mode;\n+\n+\t\tif (!FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) {\n+\t\t\t/* \"cp -d symlink dst\": create a link */\n+\t\t\tgoto dont_cat;\n+\t\t}\n+\n+\t\tif (ENABLE_FEATURE_PRESERVE_HARDLINKS && !FLAGS_DEREF) {\n+\t\t\tconst char *link_target;\n+\t\t\tlink_target = is_in_ino_dev_hashtable(&source_stat);\n+\t\t\tif (link_target) {\n+\t\t\t\tif (link(link_target, dest) < 0) {\n+\t\t\t\t\tovr = ask_and_unlink(dest, flags);\n+\t\t\t\t\tif (ovr <= 0)\n+\t\t\t\t\t\treturn ovr;\n+\t\t\t\t\tif (link(link_target, dest) < 0) {\n+\t\t\t\t\t\tbb_perror_msg(\"can't create link '%s'\", dest);\n+\t\t\t\t\t\treturn -1;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t\tadd_to_ino_dev_hashtable(&source_stat, dest);\n+\t\t}\n+\n+\t\tsrc_fd = open_or_warn(source, O_RDONLY);\n+\t\tif (src_fd < 0)\n+\t\t\treturn -1;\n+\n+\t\t/* Do not try to open with weird mode fields */\n+\t\tnew_mode = source_stat.st_mode;\n+\t\tif (!S_ISREG(source_stat.st_mode))\n+\t\t\tnew_mode = 0666;\n+\n+\t\t// POSIX way is a security problem versus (sym)link attacks\n+\t\tif (!ENABLE_FEATURE_NON_POSIX_CP) {\n+\t\t\tdst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode);\n+\t\t} else { /* safe way: */\n+\t\t\tdst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);\n+\t\t}\n+\t\tif (dst_fd == -1) {\n+\t\t\tovr = ask_and_unlink(dest, flags);\n+\t\t\tif (ovr <= 0) {\n+\t\t\t\tclose(src_fd);\n+\t\t\t\treturn ovr;\n+\t\t\t}\n+\t\t\t/* It shouldn't exist. If it exists, do not open (symlink attack?) */\n+\t\t\tdst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);\n+\t\t\tif (dst_fd < 0) {\n+\t\t\t\tclose(src_fd);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t}\n+\n+#if ENABLE_SELINUX\n+\t\tif ((flags & (FILEUTILS_PRESERVE_SECURITY_CONTEXT|FILEUTILS_SET_SECURITY_CONTEXT))\n+\t\t && is_selinux_enabled() > 0\n+\t\t) {\n+\t\t\tsecurity_context_t con;\n+\t\t\tif (getfscreatecon(&con) == -1) {\n+\t\t\t\tbb_perror_msg(\"getfscreatecon\");\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tif (con) {\n+\t\t\t\tif (setfilecon(dest, con) == -1) {\n+\t\t\t\t\tbb_perror_msg(\"setfilecon:%s,%s\", dest, con);\n+\t\t\t\t\tfreecon(con);\n+\t\t\t\t\treturn -1;\n+\t\t\t\t}\n+\t\t\t\tfreecon(con);\n+\t\t\t}\n+\t\t}\n+#endif\n+\t\tif (bb_copyfd_eof(src_fd, dst_fd) == -1)\n+\t\t\tretval = -1;\n+\t\t/* Careful with writing... */\n+\t\tif (close(dst_fd) < 0) {\n+\t\t\tbb_perror_msg(\"error writing to '%s'\", dest);\n+\t\t\tretval = -1;\n+\t\t}\n+\t\t/* ...but read size is already checked by bb_copyfd_eof */\n+\t\tclose(src_fd);\n+\t\t/* \"cp /dev/something new_file\" should not\n+\t\t * copy mode of /dev/something */\n+\t\tif (!S_ISREG(source_stat.st_mode))\n+\t\t\treturn retval;\n+\t\tgoto preserve_mode_ugid_time;\n+\t}\n+ dont_cat:\n+\n+\t/* Source is a symlink or a special file */\n+\t/* We are lazy here, a bit lax with races... */\n+\tif (dest_exists) {\n+\t\terrno = EEXIST;\n+\t\tovr = ask_and_unlink(dest, flags);\n+\t\tif (ovr <= 0)\n+\t\t\treturn ovr;\n+\t}\n+\tif (S_ISLNK(source_stat.st_mode)) {\n+\t\tchar *lpath = xmalloc_readlink_or_warn(source);\n+\t\tif (lpath) {\n+\t\t\tint r = symlink(lpath, dest);\n+\t\t\tfree(lpath);\n+\t\t\tif (r < 0) {\n+\t\t\t\tbb_perror_msg(\"can't create symlink '%s'\", dest);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tif (flags & FILEUTILS_PRESERVE_STATUS)\n+\t\t\t\tif (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)\n+\t\t\t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"ownership\", dest);\n+\t\t}\n+\t\t/* _Not_ jumping to preserve_mode_ugid_time:\n+\t\t * symlinks don't have those */\n+\t\treturn 0;\n+\t}\n+\tif (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode)\n+\t || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode)\n+\t) {\n+\t\tif (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) {\n+\t\t\tbb_perror_msg(\"can't create '%s'\", dest);\n+\t\t\treturn -1;\n+\t\t}\n+\t} else {\n+\t\tbb_error_msg(\"unrecognized file '%s' with mode %x\", source, source_stat.st_mode);\n+\t\treturn -1;\n+\t}\n+\n+ preserve_mode_ugid_time:\n+\n+\tif (flags & FILEUTILS_PRESERVE_STATUS\n+\t/* Cannot happen: */\n+\t/* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */\n+\t) {\n+\t\tstruct timeval times[2];\n+\n+\t\ttimes[1].tv_sec = times[0].tv_sec = source_stat.st_mtime;\n+\t\ttimes[1].tv_usec = times[0].tv_usec = 0;\n+\t\t/* BTW, utimes sets usec-precision time - just FYI */\n+\t\tif (utimes(dest, times) < 0)\n+\t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"times\", dest);\n+\t\tif (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {\n+\t\t\tsource_stat.st_mode &= ~(S_ISUID | S_ISGID);\n+\t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"ownership\", dest);\n+\t\t}\n+\t\tif (chmod(dest, source_stat.st_mode) < 0)\n+\t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"permissions\", dest);\n+\t}\n+\n+\tif (flags & FILEUTILS_VERBOSE) {\n+\t\tprintf(\"'%s' -> '%s'\\n\", source, dest);\n+\t}\n+\n+\treturn retval;\n+}\n+#endif\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278320","messageId":"1455629367-26193-5-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 04/26] copy.c: delete unused code in copy_file()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:05Z","receivedAt":"2016-02-16T13:29:05Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":" - selinux preservation code\n - make-link code\n - delete link dereference code\n - non-recursive copy code\n - stat no preservation code\n - verbose printing code\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n copy.c | 101 +++++------------------------------------------------------------\n 1 file changed, 7 insertions(+), 94 deletions(-)\n\ndiff --git a/copy.c b/copy.c\nindex 29e9d5b..d24a8ae 100644\n--- a/copy.c\n+++ b/copy.c\n@@ -82,14 +82,7 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \tsmallint dest_exists = 0;\n \tsmallint ovr;\n \n-/* Inverse of cp -d (\"cp without -d\") */\n-#define FLAGS_DEREF (flags & (FILEUTILS_DEREFERENCE + FILEUTILS_DEREFERENCE_L0))\n-\n-\tif ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) {\n-\t\t/* This may be a dangling symlink.\n-\t\t * Making [sym]links to dangling symlinks works, so... */\n-\t\tif (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK))\n-\t\t\tgoto make_links;\n+\tif (lstat(source, &source_stat) < 0) {\n \t\tbb_perror_msg(\"can't stat '%s'\", source);\n \t\treturn -1;\n \t}\n@@ -109,35 +102,12 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\tdest_exists = 1;\n \t}\n \n-#if ENABLE_SELINUX\n-\tif ((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) && is_selinux_enabled() > 0) {\n-\t\tsecurity_context_t con;\n-\t\tif (lgetfilecon(source, &con) >= 0) {\n-\t\t\tif (setfscreatecon(con) < 0) {\n-\t\t\t\tbb_perror_msg(\"can't set setfscreatecon %s\", con);\n-\t\t\t\tfreecon(con);\n-\t\t\t\treturn -1;\n-\t\t\t}\n-\t\t} else if (errno == ENOTSUP || errno == ENODATA) {\n-\t\t\tsetfscreatecon_or_die(NULL);\n-\t\t} else {\n-\t\t\tbb_perror_msg(\"can't lgetfilecon %s\", source);\n-\t\t\treturn -1;\n-\t\t}\n-\t}\n-#endif\n-\n \tif (S_ISDIR(source_stat.st_mode)) {\n \t\tDIR *dp;\n \t\tconst char *tp;\n \t\tstruct dirent *d;\n \t\tmode_t saved_umask = 0;\n \n-\t\tif (!(flags & FILEUTILS_RECUR)) {\n-\t\t\tbb_error_msg(\"omitting directory '%s'\", source);\n-\t\t\treturn -1;\n-\t\t}\n-\n \t\t/* Did we ever create source ourself before? */\n \t\ttp = is_in_ino_dev_hashtable(&source_stat);\n \t\tif (tp) {\n@@ -160,8 +130,6 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t\tsaved_umask = umask(0);\n \n \t\t\tmode = source_stat.st_mode;\n-\t\t\tif (!(flags & FILEUTILS_PRESERVE_STATUS))\n-\t\t\t\tmode = source_stat.st_mode & ~saved_umask;\n \t\t\t/* Allow owner to access new dir (at least for now) */\n \t\t\tmode |= S_IRWXU;\n \t\t\tif (mkdir(dest, mode) < 0) {\n@@ -210,45 +178,17 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\tgoto preserve_mode_ugid_time;\n \t}\n \n-\tif (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) {\n-\t\tint (*lf)(const char *oldpath, const char *newpath);\n- make_links:\n-\t\t/* Hmm... maybe\n-\t\t * if (DEREF && MAKE_SOFTLINK) source = realpath(source) ?\n-\t\t * (but realpath returns NULL on dangling symlinks...) */\n-\t\tlf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link;\n-\t\tif (lf(source, dest) < 0) {\n-\t\t\tovr = ask_and_unlink(dest, flags);\n-\t\t\tif (ovr <= 0)\n-\t\t\t\treturn ovr;\n-\t\t\tif (lf(source, dest) < 0) {\n-\t\t\t\tbb_perror_msg(\"can't create link '%s'\", dest);\n-\t\t\t\treturn -1;\n-\t\t\t}\n-\t\t}\n-\t\t/* _Not_ jumping to preserve_mode_ugid_time:\n-\t\t * (sym)links don't have those */\n-\t\treturn 0;\n-\t}\n-\n-\tif (/* \"cp thing1 thing2\" without -R: just open and read() from thing1 */\n-\t    !(flags & FILEUTILS_RECUR)\n-\t    /* \"cp [-opts] regular_file thing2\" */\n-\t || S_ISREG(source_stat.st_mode)\n-\t /* DEREF uses stat, which never returns S_ISLNK() == true.\n-\t  * So the below is never true: */\n-\t /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */\n-\t) {\n+\tif (S_ISREG(source_stat.st_mode) ) { /* \"cp [-opts] regular_file thing2\" */\n \t\tint src_fd;\n \t\tint dst_fd;\n \t\tmode_t new_mode;\n \n-\t\tif (!FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) {\n+\t\tif (S_ISLNK(source_stat.st_mode)) {\n \t\t\t/* \"cp -d symlink dst\": create a link */\n \t\t\tgoto dont_cat;\n \t\t}\n \n-\t\tif (ENABLE_FEATURE_PRESERVE_HARDLINKS && !FLAGS_DEREF) {\n+\t\tif (ENABLE_FEATURE_PRESERVE_HARDLINKS) {\n \t\t\tconst char *link_target;\n \t\t\tlink_target = is_in_ino_dev_hashtable(&source_stat);\n \t\t\tif (link_target) {\n@@ -295,25 +235,6 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t\t}\n \t\t}\n \n-#if ENABLE_SELINUX\n-\t\tif ((flags & (FILEUTILS_PRESERVE_SECURITY_CONTEXT|FILEUTILS_SET_SECURITY_CONTEXT))\n-\t\t && is_selinux_enabled() > 0\n-\t\t) {\n-\t\t\tsecurity_context_t con;\n-\t\t\tif (getfscreatecon(&con) == -1) {\n-\t\t\t\tbb_perror_msg(\"getfscreatecon\");\n-\t\t\t\treturn -1;\n-\t\t\t}\n-\t\t\tif (con) {\n-\t\t\t\tif (setfilecon(dest, con) == -1) {\n-\t\t\t\t\tbb_perror_msg(\"setfilecon:%s,%s\", dest, con);\n-\t\t\t\t\tfreecon(con);\n-\t\t\t\t\treturn -1;\n-\t\t\t\t}\n-\t\t\t\tfreecon(con);\n-\t\t\t}\n-\t\t}\n-#endif\n \t\tif (bb_copyfd_eof(src_fd, dst_fd) == -1)\n \t\t\tretval = -1;\n \t\t/* Careful with writing... */\n@@ -348,9 +269,8 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t\t\tbb_perror_msg(\"can't create symlink '%s'\", dest);\n \t\t\t\treturn -1;\n \t\t\t}\n-\t\t\tif (flags & FILEUTILS_PRESERVE_STATUS)\n-\t\t\t\tif (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)\n-\t\t\t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"ownership\", dest);\n+\t\t\tif (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)\n+\t\t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"ownership\", dest);\n \t\t}\n \t\t/* _Not_ jumping to preserve_mode_ugid_time:\n \t\t * symlinks don't have those */\n@@ -370,10 +290,7 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \n  preserve_mode_ugid_time:\n \n-\tif (flags & FILEUTILS_PRESERVE_STATUS\n-\t/* Cannot happen: */\n-\t/* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */\n-\t) {\n+\tif (1 /*FILEUTILS_PRESERVE_STATUS*/) {\n \t\tstruct timeval times[2];\n \n \t\ttimes[1].tv_sec = times[0].tv_sec = source_stat.st_mtime;\n@@ -389,10 +306,6 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"permissions\", dest);\n \t}\n \n-\tif (flags & FILEUTILS_VERBOSE) {\n-\t\tprintf(\"'%s' -> '%s'\\n\", source, dest);\n-\t}\n-\n \treturn retval;\n }\n #endif\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278321","messageId":"1455629367-26193-6-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 05/26] copy.c: convert bb_(p)error_msg to (sys_)error","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:06Z","receivedAt":"2016-02-16T13:29:06Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n copy.c | 85 ++++++++++++++++++++++++------------------------------------------\n 1 file changed, 31 insertions(+), 54 deletions(-)\n\ndiff --git a/copy.c b/copy.c\nindex d24a8ae..cdb38d5 100644\n--- a/copy.c\n+++ b/copy.c\n@@ -82,23 +82,16 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \tsmallint dest_exists = 0;\n \tsmallint ovr;\n \n-\tif (lstat(source, &source_stat) < 0) {\n-\t\tbb_perror_msg(\"can't stat '%s'\", source);\n-\t\treturn -1;\n-\t}\n+\tif (lstat(source, &source_stat) < 0)\n+\t\treturn sys_error(_(\"can't stat '%s'\"), source);\n \n \tif (lstat(dest, &dest_stat) < 0) {\n-\t\tif (errno != ENOENT) {\n-\t\t\tbb_perror_msg(\"can't stat '%s'\", dest);\n-\t\t\treturn -1;\n-\t\t}\n+\t\tif (errno != ENOENT)\n+\t\t\treturn sys_error(_(\"can't stat '%s'\"), dest);\n \t} else {\n-\t\tif (source_stat.st_dev == dest_stat.st_dev\n-\t\t && source_stat.st_ino == dest_stat.st_ino\n-\t\t) {\n-\t\t\tbb_error_msg(\"'%s' and '%s' are the same file\", source, dest);\n-\t\t\treturn -1;\n-\t\t}\n+\t\tif (source_stat.st_dev == dest_stat.st_dev &&\n+\t\t    source_stat.st_ino == dest_stat.st_ino)\n+\t\t\treturn error(_(\"'%s' and '%s' are the same file\"), source, dest);\n \t\tdest_exists = 1;\n \t}\n \n@@ -110,18 +103,14 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \n \t\t/* Did we ever create source ourself before? */\n \t\ttp = is_in_ino_dev_hashtable(&source_stat);\n-\t\tif (tp) {\n+\t\tif (tp)\n \t\t\t/* We did! it's a recursion! man the lifeboats... */\n-\t\t\tbb_error_msg(\"recursion detected, omitting directory '%s'\",\n-\t\t\t\t\tsource);\n-\t\t\treturn -1;\n-\t\t}\n+\t\t\treturn error(_(\"recursion detected, omitting directory '%s'\"),\n+\t\t\t\t     source);\n \n \t\tif (dest_exists) {\n-\t\t\tif (!S_ISDIR(dest_stat.st_mode)) {\n-\t\t\t\tbb_error_msg(\"target '%s' is not a directory\", dest);\n-\t\t\t\treturn -1;\n-\t\t\t}\n+\t\t\tif (!S_ISDIR(dest_stat.st_mode))\n+\t\t\t\treturn error(_(\"target '%s' is not a directory\"), dest);\n \t\t\t/* race here: user can substitute a symlink between\n \t\t\t * this check and actual creation of files inside dest */\n \t\t} else {\n@@ -134,15 +123,12 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t\tmode |= S_IRWXU;\n \t\t\tif (mkdir(dest, mode) < 0) {\n \t\t\t\tumask(saved_umask);\n-\t\t\t\tbb_perror_msg(\"can't create directory '%s'\", dest);\n-\t\t\t\treturn -1;\n+\t\t\t\treturn sys_error(_(\"can't create directory '%s'\"), dest);\n \t\t\t}\n \t\t\tumask(saved_umask);\n \t\t\t/* need stat info for add_to_ino_dev_hashtable */\n-\t\t\tif (lstat(dest, &dest_stat) < 0) {\n-\t\t\t\tbb_perror_msg(\"can't stat '%s'\", dest);\n-\t\t\t\treturn -1;\n-\t\t\t}\n+\t\t\tif (lstat(dest, &dest_stat) < 0)\n+\t\t\t\treturn sys_error(_(\"can't stat '%s'\"), dest);\n \t\t}\n \t\t/* remember (dev,inode) of each created dir.\n \t\t * NULL: name is not remembered */\n@@ -172,7 +158,7 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\tif (!dest_exists\n \t\t && chmod(dest, source_stat.st_mode & ~saved_umask) < 0\n \t\t) {\n-\t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"permissions\", dest);\n+\t\t\tsys_error(_(\"can't preserve permissions of '%s'\"), dest);\n \t\t\t/* retval = -1; - WRONG! copy *WAS* made */\n \t\t}\n \t\tgoto preserve_mode_ugid_time;\n@@ -196,10 +182,8 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t\t\t\tovr = ask_and_unlink(dest, flags);\n \t\t\t\t\tif (ovr <= 0)\n \t\t\t\t\t\treturn ovr;\n-\t\t\t\t\tif (link(link_target, dest) < 0) {\n-\t\t\t\t\t\tbb_perror_msg(\"can't create link '%s'\", dest);\n-\t\t\t\t\t\treturn -1;\n-\t\t\t\t\t}\n+\t\t\t\t\tif (link(link_target, dest) < 0)\n+\t\t\t\t\t\treturn sys_error(_(\"can't create link '%s'\"), dest);\n \t\t\t\t}\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -238,10 +222,8 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\tif (bb_copyfd_eof(src_fd, dst_fd) == -1)\n \t\t\tretval = -1;\n \t\t/* Careful with writing... */\n-\t\tif (close(dst_fd) < 0) {\n-\t\t\tbb_perror_msg(\"error writing to '%s'\", dest);\n-\t\t\tretval = -1;\n-\t\t}\n+\t\tif (close(dst_fd) < 0)\n+\t\t\tretval = sys_error(_(\"error writing to '%s'\"), dest);\n \t\t/* ...but read size is already checked by bb_copyfd_eof */\n \t\tclose(src_fd);\n \t\t/* \"cp /dev/something new_file\" should not\n@@ -265,12 +247,10 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\tif (lpath) {\n \t\t\tint r = symlink(lpath, dest);\n \t\t\tfree(lpath);\n-\t\t\tif (r < 0) {\n-\t\t\t\tbb_perror_msg(\"can't create symlink '%s'\", dest);\n-\t\t\t\treturn -1;\n-\t\t\t}\n+\t\t\tif (r < 0)\n+\t\t\t\treturn sys_error(_(\"can't create symlink '%s'\"), dest);\n \t\t\tif (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)\n-\t\t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"ownership\", dest);\n+\t\t\t\tsys_error(_(\"can't preserve %s of '%s'\"), \"ownership\", dest);\n \t\t}\n \t\t/* _Not_ jumping to preserve_mode_ugid_time:\n \t\t * symlinks don't have those */\n@@ -279,14 +259,11 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \tif (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode)\n \t || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode)\n \t) {\n-\t\tif (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) {\n-\t\t\tbb_perror_msg(\"can't create '%s'\", dest);\n-\t\t\treturn -1;\n-\t\t}\n-\t} else {\n-\t\tbb_error_msg(\"unrecognized file '%s' with mode %x\", source, source_stat.st_mode);\n-\t\treturn -1;\n-\t}\n+\t\tif (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0)\n+\t\t\treturn sys_error(_(\"can't create '%s'\"), dest);\n+\t} else\n+\t\treturn error(_(\"unrecognized file '%s' with mode %x\"),\n+\t\t\t     source, source_stat.st_mode);\n \n  preserve_mode_ugid_time:\n \n@@ -297,13 +274,13 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\ttimes[1].tv_usec = times[0].tv_usec = 0;\n \t\t/* BTW, utimes sets usec-precision time - just FYI */\n \t\tif (utimes(dest, times) < 0)\n-\t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"times\", dest);\n+\t\t\tsys_error(_(\"can't preserve %s of '%s'\"), \"times\", dest);\n \t\tif (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {\n \t\t\tsource_stat.st_mode &= ~(S_ISUID | S_ISGID);\n-\t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"ownership\", dest);\n+\t\t\tsys_error(_(\"can't preserve %s of '%s'\"), \"ownership\", dest);\n \t\t}\n \t\tif (chmod(dest, source_stat.st_mode) < 0)\n-\t\t\tbb_perror_msg(\"can't preserve %s of '%s'\", \"permissions\", dest);\n+\t\t\tsys_error(_(\"can't preserve %s of '%s'\"), \"permissions\", dest);\n \t}\n \n \treturn retval;\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278322","messageId":"1455629367-26193-7-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 06/26] copy.c: style fix","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:07Z","receivedAt":"2016-02-16T13:29:07Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n copy.c | 50 +++++++++++++++++++++++++++++---------------------\n 1 file changed, 29 insertions(+), 21 deletions(-)\n\ndiff --git a/copy.c b/copy.c\nindex cdb38d5..00f8349 100644\n--- a/copy.c\n+++ b/copy.c\n@@ -111,8 +111,10 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\tif (dest_exists) {\n \t\t\tif (!S_ISDIR(dest_stat.st_mode))\n \t\t\t\treturn error(_(\"target '%s' is not a directory\"), dest);\n-\t\t\t/* race here: user can substitute a symlink between\n-\t\t\t * this check and actual creation of files inside dest */\n+\t\t\t/*\n+\t\t\t * race here: user can substitute a symlink between\n+\t\t\t * this check and actual creation of files inside dest\n+\t\t\t */\n \t\t} else {\n \t\t\t/* Create DEST */\n \t\t\tmode_t mode;\n@@ -130,22 +132,24 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t\tif (lstat(dest, &dest_stat) < 0)\n \t\t\t\treturn sys_error(_(\"can't stat '%s'\"), dest);\n \t\t}\n-\t\t/* remember (dev,inode) of each created dir.\n-\t\t * NULL: name is not remembered */\n+\t\t/*\n+\t\t * remember (dev,inode) of each created dir. name is\n+\t\t * not remembered\n+\t\t */\n \t\tadd_to_ino_dev_hashtable(&dest_stat, NULL);\n \n \t\t/* Recursively copy files in SOURCE */\n \t\tdp = opendir(source);\n-\t\tif (dp == NULL) {\n+\t\tif (!dp) {\n \t\t\tretval = -1;\n \t\t\tgoto preserve_mode_ugid_time;\n \t\t}\n \n-\t\twhile ((d = readdir(dp)) != NULL) {\n+\t\twhile ((d = readdir(dp))) {\n \t\t\tchar *new_source, *new_dest;\n \n \t\t\tnew_source = concat_subpath_file(source, d->d_name);\n-\t\t\tif (new_source == NULL)\n+\t\t\tif (!new_source)\n \t\t\t\tcontinue;\n \t\t\tnew_dest = concat_path_file(dest, d->d_name);\n \t\t\tif (copy_file(new_source, new_dest, flags & ~FILEUTILS_DEREFERENCE_L0) < 0)\n@@ -155,16 +159,15 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t}\n \t\tclosedir(dp);\n \n-\t\tif (!dest_exists\n-\t\t && chmod(dest, source_stat.st_mode & ~saved_umask) < 0\n-\t\t) {\n+\t\tif (!dest_exists &&\n+\t\t    chmod(dest, source_stat.st_mode & ~saved_umask) < 0) {\n \t\t\tsys_error(_(\"can't preserve permissions of '%s'\"), dest);\n \t\t\t/* retval = -1; - WRONG! copy *WAS* made */\n \t\t}\n \t\tgoto preserve_mode_ugid_time;\n \t}\n \n-\tif (S_ISREG(source_stat.st_mode) ) { /* \"cp [-opts] regular_file thing2\" */\n+\tif (S_ISREG(source_stat.st_mode)) { /* \"cp [-opts] regular_file thing2\" */\n \t\tint src_fd;\n \t\tint dst_fd;\n \t\tmode_t new_mode;\n@@ -199,7 +202,7 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\tif (!S_ISREG(source_stat.st_mode))\n \t\t\tnew_mode = 0666;\n \n-\t\t// POSIX way is a security problem versus (sym)link attacks\n+\t\t/* POSIX way is a security problem versus (sym)link attacks */\n \t\tif (!ENABLE_FEATURE_NON_POSIX_CP) {\n \t\t\tdst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode);\n \t\t} else { /* safe way: */\n@@ -226,13 +229,15 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t\tretval = sys_error(_(\"error writing to '%s'\"), dest);\n \t\t/* ...but read size is already checked by bb_copyfd_eof */\n \t\tclose(src_fd);\n-\t\t/* \"cp /dev/something new_file\" should not\n-\t\t * copy mode of /dev/something */\n+\t\t/*\n+\t\t * \"cp /dev/something new_file\" should not\n+\t\t * copy mode of /dev/something\n+\t\t */\n \t\tif (!S_ISREG(source_stat.st_mode))\n \t\t\treturn retval;\n \t\tgoto preserve_mode_ugid_time;\n \t}\n- dont_cat:\n+dont_cat:\n \n \t/* Source is a symlink or a special file */\n \t/* We are lazy here, a bit lax with races... */\n@@ -252,20 +257,23 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t\tif (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)\n \t\t\t\tsys_error(_(\"can't preserve %s of '%s'\"), \"ownership\", dest);\n \t\t}\n-\t\t/* _Not_ jumping to preserve_mode_ugid_time:\n-\t\t * symlinks don't have those */\n+\t\t/*\n+\t\t * _Not_ jumping to preserve_mode_ugid_time: symlinks\n+\t\t * don't have those\n+\t\t */\n \t\treturn 0;\n \t}\n-\tif (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode)\n-\t || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode)\n-\t) {\n+\tif (S_ISBLK(source_stat.st_mode) ||\n+\t    S_ISCHR(source_stat.st_mode) ||\n+\t    S_ISSOCK(source_stat.st_mode) ||\n+\t    S_ISFIFO(source_stat.st_mode)) {\n \t\tif (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0)\n \t\t\treturn sys_error(_(\"can't create '%s'\"), dest);\n \t} else\n \t\treturn error(_(\"unrecognized file '%s' with mode %x\"),\n \t\t\t     source, source_stat.st_mode);\n \n- preserve_mode_ugid_time:\n+preserve_mode_ugid_time:\n \n \tif (1 /*FILEUTILS_PRESERVE_STATUS*/) {\n \t\tstruct timeval times[2];\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278325","messageId":"1455629367-26193-8-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 07/26] copy.c: convert copy_file() to copy_dir_recursively()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:08Z","receivedAt":"2016-02-16T13:29:08Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This finally enables busybox's copy_file() code under a new name\n(because \"copy_file\" is already taken in Git code base). Because this\ncomes from busybox, POSIXy (or even Linuxy) behavior is expected. More\nchanges may be needed for Windows support.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h |   1 +\n copy.c  | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++++------------\n 2 files changed, 179 insertions(+), 38 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 26640b4..30a2708 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1659,6 +1659,7 @@ extern void fprintf_or_die(FILE *, const char *fmt, ...);\n extern int copy_fd(int ifd, int ofd);\n extern int copy_file(const char *dst, const char *src, int mode);\n extern int copy_file_with_time(const char *dst, const char *src, int mode);\n+extern int copy_dir_recursively(const char *source, const char *dest);\n \n extern void write_or_die(int fd, const void *buf, size_t count);\n extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);\ndiff --git a/copy.c b/copy.c\nindex 00f8349..f04ac87 100644\n--- a/copy.c\n+++ b/copy.c\n@@ -1,4 +1,6 @@\n #include \"cache.h\"\n+#include \"dir.h\"\n+#include \"hashmap.h\"\n \n int copy_fd(int ifd, int ofd)\n {\n@@ -66,21 +68,126 @@ int copy_file_with_time(const char *dst, const char *src, int mode)\n \treturn status;\n }\n \n-#if 0\n-/* Return:\n- * -1 error, copy not made\n- *  0 copy is made or user answered \"no\" in interactive mode\n- *    (failures to preserve mode/owner/times are not reported in exit code)\n+struct inode_key {\n+\tstruct hashmap_entry entry;\n+\tino_t ino;\n+\tdev_t dev;\n+\t/*\n+\t * Reportedly, on cramfs a file and a dir can have same ino.\n+\t * Need to also remember \"file/dir\" bit:\n+\t */\n+\tchar isdir; /* bool */\n+};\n+\n+struct inode_value {\n+\tstruct inode_key key;\n+\tchar name[FLEX_ARRAY];\n+};\n+\n+#define HASH_SIZE      311u   /* Should be prime */\n+static inline unsigned hash_inode(ino_t i)\n+{\n+\treturn i % HASH_SIZE;\n+}\n+\n+static int inode_cmp(const void *entry, const void *entry_or_key,\n+\t\t     const void *keydata)\n+{\n+\tconst struct inode_value *inode = entry;\n+\tconst struct inode_key   *key   = entry_or_key;\n+\n+\treturn !(inode->key.ino   == key->ino &&\n+\t\t inode->key.dev   == key->dev &&\n+\t\t inode->key.isdir == key->isdir);\n+}\n+\n+static const char *is_in_ino_dev_hashtable(const struct hashmap *map,\n+\t\t\t\t\t   const struct stat *st)\n+{\n+\tstruct inode_key key;\n+\tstruct inode_value *value;\n+\n+\tkey.entry.hash = hash_inode(st->st_ino);\n+\tkey.ino\t       = st->st_ino;\n+\tkey.dev\t       = st->st_dev;\n+\tkey.isdir      = !!S_ISDIR(st->st_mode);\n+\tvalue\t       = hashmap_get(map, &key, NULL);\n+\treturn value ? value->name : NULL;\n+}\n+\n+static void add_to_ino_dev_hashtable(struct hashmap *map,\n+\t\t\t\t     const struct stat *st,\n+\t\t\t\t     const char *path)\n+{\n+\tstruct inode_value *v;\n+\tint len = strlen(path);\n+\n+\tv = xmalloc(offsetof(struct inode_value, name) + len + 1);\n+\tv->key.entry.hash = hash_inode(st->st_ino);\n+\tv->key.ino\t  = st->st_ino;\n+\tv->key.dev\t  = st->st_dev;\n+\tv->key.isdir      = !!S_ISDIR(st->st_mode);\n+\tmemcpy(v->name, path, len + 1);\n+\thashmap_add(map, v);\n+}\n+\n+/*\n+ * Find out if the last character of a string matches the one given.\n+ * Don't underrun the buffer if the string length is 0.\n  */\n-int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n+static inline char *last_char_is(const char *s, int c)\n+{\n+\tif (s && *s) {\n+\t\tsize_t sz = strlen(s) - 1;\n+\t\ts += sz;\n+\t\tif ( (unsigned char)*s == c)\n+\t\t\treturn (char*)s;\n+\t}\n+\treturn NULL;\n+}\n+\n+static inline char *concat_path_file(const char *path, const char *filename)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tchar *lc;\n+\n+\tif (!path)\n+\t\tpath = \"\";\n+\tlc = last_char_is(path, '/');\n+\twhile (*filename == '/')\n+\t\tfilename++;\n+\tstrbuf_addf(&sb, \"%s%s%s\", path, (lc==NULL ? \"/\" : \"\"), filename);\n+\treturn strbuf_detach(&sb, NULL);\n+}\n+\n+static char *concat_subpath_file(const char *path, const char *f)\n+{\n+\tif (f && is_dot_or_dotdot(f))\n+\t\treturn NULL;\n+\treturn concat_path_file(path, f);\n+}\n+\n+static int do_unlink(const char *dest)\n+{\n+\tint e = errno;\n+\n+\tif (unlink(dest) < 0) {\n+\t\terrno = e; /* do not use errno from unlink */\n+\t\treturn sys_error(_(\"can't create '%s'\"), dest);\n+\t}\n+\treturn 0;\n+}\n+\n+static int copy_dir_1(struct hashmap *inode_map,\n+\t\t      const char *source,\n+\t\t      const char *dest)\n {\n \t/* This is a recursive function, try to minimize stack usage */\n-\t/* NB: each struct stat is ~100 bytes */\n \tstruct stat source_stat;\n \tstruct stat dest_stat;\n-\tsmallint retval = 0;\n-\tsmallint dest_exists = 0;\n-\tsmallint ovr;\n+\tint retval = 0;\n+\tint dest_exists = 0;\n+\tint ovr;\n \n \tif (lstat(source, &source_stat) < 0)\n \t\treturn sys_error(_(\"can't stat '%s'\"), source);\n@@ -102,7 +209,7 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\tmode_t saved_umask = 0;\n \n \t\t/* Did we ever create source ourself before? */\n-\t\ttp = is_in_ino_dev_hashtable(&source_stat);\n+\t\ttp = is_in_ino_dev_hashtable(inode_map, &source_stat);\n \t\tif (tp)\n \t\t\t/* We did! it's a recursion! man the lifeboats... */\n \t\t\treturn error(_(\"recursion detected, omitting directory '%s'\"),\n@@ -132,11 +239,12 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t\tif (lstat(dest, &dest_stat) < 0)\n \t\t\t\treturn sys_error(_(\"can't stat '%s'\"), dest);\n \t\t}\n+\n \t\t/*\n \t\t * remember (dev,inode) of each created dir. name is\n \t\t * not remembered\n \t\t */\n-\t\tadd_to_ino_dev_hashtable(&dest_stat, NULL);\n+\t\tadd_to_ino_dev_hashtable(inode_map, &dest_stat, \"\");\n \n \t\t/* Recursively copy files in SOURCE */\n \t\tdp = opendir(source);\n@@ -152,7 +260,7 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t\tif (!new_source)\n \t\t\t\tcontinue;\n \t\t\tnew_dest = concat_path_file(dest, d->d_name);\n-\t\t\tif (copy_file(new_source, new_dest, flags & ~FILEUTILS_DEREFERENCE_L0) < 0)\n+\t\t\tif (copy_dir_1(inode_map, new_source, new_dest) < 0)\n \t\t\t\tretval = -1;\n \t\t\tfree(new_source);\n \t\t\tfree(new_dest);\n@@ -177,53 +285,57 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\n \t\t\tgoto dont_cat;\n \t\t}\n \n-\t\tif (ENABLE_FEATURE_PRESERVE_HARDLINKS) {\n+\t\tif (1 /*ENABLE_FEATURE_PRESERVE_HARDLINKS*/) {\n \t\t\tconst char *link_target;\n-\t\t\tlink_target = is_in_ino_dev_hashtable(&source_stat);\n+\t\t\tlink_target = is_in_ino_dev_hashtable(inode_map, &source_stat);\n \t\t\tif (link_target) {\n \t\t\t\tif (link(link_target, dest) < 0) {\n-\t\t\t\t\tovr = ask_and_unlink(dest, flags);\n-\t\t\t\t\tif (ovr <= 0)\n+\t\t\t\t\tovr = do_unlink(dest);\n+\t\t\t\t\tif (ovr < 0)\n \t\t\t\t\t\treturn ovr;\n \t\t\t\t\tif (link(link_target, dest) < 0)\n \t\t\t\t\t\treturn sys_error(_(\"can't create link '%s'\"), dest);\n \t\t\t\t}\n \t\t\t\treturn 0;\n \t\t\t}\n-\t\t\tadd_to_ino_dev_hashtable(&source_stat, dest);\n+\t\t\tadd_to_ino_dev_hashtable(inode_map, &source_stat, dest);\n \t\t}\n \n-\t\tsrc_fd = open_or_warn(source, O_RDONLY);\n+\t\tsrc_fd = open(source, O_RDONLY);\n \t\tif (src_fd < 0)\n-\t\t\treturn -1;\n+\t\t\treturn sys_error(_(\"can't open '%s'\"), source);\n \n \t\t/* Do not try to open with weird mode fields */\n \t\tnew_mode = source_stat.st_mode;\n \t\tif (!S_ISREG(source_stat.st_mode))\n \t\t\tnew_mode = 0666;\n \n-\t\t/* POSIX way is a security problem versus (sym)link attacks */\n-\t\tif (!ENABLE_FEATURE_NON_POSIX_CP) {\n-\t\t\tdst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode);\n-\t\t} else { /* safe way: */\n-\t\t\tdst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);\n-\t\t}\n+\t\tdst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);\n \t\tif (dst_fd == -1) {\n-\t\t\tovr = ask_and_unlink(dest, flags);\n-\t\t\tif (ovr <= 0) {\n+\t\t\tovr = do_unlink(dest);\n+\t\t\tif (ovr < 0) {\n \t\t\t\tclose(src_fd);\n \t\t\t\treturn ovr;\n \t\t\t}\n \t\t\t/* It shouldn't exist. If it exists, do not open (symlink attack?) */\n-\t\t\tdst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);\n+\t\t\tdst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);\n \t\t\tif (dst_fd < 0) {\n \t\t\t\tclose(src_fd);\n-\t\t\t\treturn -1;\n+\t\t\t\treturn sys_error(_(\"can't open '%s'\"), dest);\n \t\t\t}\n \t\t}\n \n-\t\tif (bb_copyfd_eof(src_fd, dst_fd) == -1)\n+\t\tswitch (copy_fd(src_fd, dst_fd)) {\n+\t\tcase COPY_READ_ERROR:\n+\t\t\terror(_(\"copy-fd: read returned %s\"), strerror(errno));\n \t\t\tretval = -1;\n+\t\t\tbreak;\n+\t\tcase COPY_WRITE_ERROR:\n+\t\t\terror(_(\"copy-fd: write returned %s\"), strerror(errno));\n+\t\t\tretval = -1;\n+\t\t\tbreak;\n+\t\t}\n+\n \t\t/* Careful with writing... */\n \t\tif (close(dst_fd) < 0)\n \t\t\tretval = sys_error(_(\"error writing to '%s'\"), dest);\n@@ -243,19 +355,28 @@ dont_cat:\n \t/* We are lazy here, a bit lax with races... */\n \tif (dest_exists) {\n \t\terrno = EEXIST;\n-\t\tovr = ask_and_unlink(dest, flags);\n-\t\tif (ovr <= 0)\n+\t\tovr = do_unlink(dest);\n+\t\tif (ovr < 0)\n \t\t\treturn ovr;\n \t}\n \tif (S_ISLNK(source_stat.st_mode)) {\n-\t\tchar *lpath = xmalloc_readlink_or_warn(source);\n-\t\tif (lpath) {\n-\t\t\tint r = symlink(lpath, dest);\n-\t\t\tfree(lpath);\n+\t\tstruct strbuf lpath = STRBUF_INIT;\n+\t\tif (!strbuf_readlink(&lpath, source, 0)) {\n+\t\t\tint r = symlink(lpath.buf, dest);\n+\t\t\tstrbuf_release(&lpath);\n \t\t\tif (r < 0)\n \t\t\t\treturn sys_error(_(\"can't create symlink '%s'\"), dest);\n \t\t\tif (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)\n \t\t\t\tsys_error(_(\"can't preserve %s of '%s'\"), \"ownership\", dest);\n+\t\t} else {\n+\t\t\t/* EINVAL => \"file: Invalid argument\" => puzzled user */\n+\t\t\tconst char *errmsg = _(\"not a symlink\");\n+\t\t\tint err = errno;\n+\n+\t\t\tif (err != EINVAL)\n+\t\t\t\terrmsg = strerror(err);\n+\t\t\terror(_(\"%s: cannot read link: %s\"), source, errmsg);\n+\t\t\tstrbuf_release(&lpath);\n \t\t}\n \t\t/*\n \t\t * _Not_ jumping to preserve_mode_ugid_time: symlinks\n@@ -293,4 +414,23 @@ preserve_mode_ugid_time:\n \n \treturn retval;\n }\n-#endif\n+\n+/*\n+ * Return:\n+ * -1 error, copy not made\n+ *  0 copy is made\n+ *\n+ * Failures to preserve mode/owner/times are not reported in exit\n+ * code. No support for preserving SELinux security context. Symlinks\n+ * and hardlinks are preserved.\n+ */\n+int copy_dir_recursively(const char *source, const char *dest)\n+{\n+\tint ret;\n+\tstruct hashmap inode_map;\n+\n+\thashmap_init(&inode_map, inode_cmp, 1024);\n+\tret = copy_dir_1(&inode_map, source, dest);\n+\thashmap_free(&inode_map, 1);\n+\treturn ret;\n+}\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278359","messageId":"1455629367-26193-9-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 08/26] completion: support git-worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:09Z","receivedAt":"2016-02-16T13:29:09Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n contrib/completion/git-completion.bash | 23 +++++++++++++++++++++++\n 1 file changed, 23 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 45ec47f..099d52e 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2594,6 +2594,29 @@ _git_whatchanged ()\n \t_git_log\n }\n \n+_git_worktree ()\n+{\n+\tlocal subcommands=\"add list prune\"\n+\tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n+\tif [ -z \"$subcommand\" ]; then\n+\t\t__gitcomp \"$subcommands\"\n+\telse\n+\t\tcase \"$subcommand,$cur\" in\n+\t\tadd,--*)\n+\t\t\t__gitcomp \"--detach --force\"\n+\t\t\t;;\n+\t\tlist,--*)\n+\t\t\t__gitcomp \"--porcelain\"\n+\t\t\t;;\n+\t\tprune,--*)\n+\t\t\t__gitcomp \"--dry-run --expire --verbose\"\n+\t\t\t;;\n+\t\t*)\n+\t\t\t;;\n+\t\tesac\n+\tfi\n+}\n+\n __git_main ()\n {\n \tlocal i c=1 command __git_dir\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278331","messageId":"1455629367-26193-10-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 09/26] git-worktree.txt: keep subcommand listing in alphabetical order","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:10Z","receivedAt":"2016-02-16T13:29:10Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt | 10 +++++-----\n builtin/worktree.c             |  2 +-\n 2 files changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 62c76c1..1c9d7c1 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -10,8 +10,8 @@ SYNOPSIS\n --------\n [verse]\n 'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]\n-'git worktree prune' [-n] [-v] [--expire <expire>]\n 'git worktree list' [--porcelain]\n+'git worktree prune' [-n] [-v] [--expire <expire>]\n \n DESCRIPTION\n -----------\n@@ -54,10 +54,6 @@ If `<branch>` is omitted and neither `-b` nor `-B` nor `--detached` used,\n then, as a convenience, a new branch based at HEAD is created automatically,\n as if `-b $(basename <path>)` was specified.\n \n-prune::\n-\n-Prune working tree information in $GIT_DIR/worktrees.\n-\n list::\n \n List details of each worktree.  The main worktree is listed first, followed by\n@@ -65,6 +61,10 @@ each of the linked worktrees.  The output details include if the worktree is\n bare, the revision currently checked out, and the branch currently checked out\n (or 'detached HEAD' if none).\n \n+prune::\n+\n+Prune working tree information in $GIT_DIR/worktrees.\n+\n OPTIONS\n -------\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 475b958..68341e4 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -13,8 +13,8 @@\n \n static const char * const worktree_usage[] = {\n \tN_(\"git worktree add [<options>] <path> [<branch>]\"),\n-\tN_(\"git worktree prune [<options>]\"),\n \tN_(\"git worktree list [<options>]\"),\n+\tN_(\"git worktree prune [<options>]\"),\n \tNULL\n };\n \n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278328","messageId":"1455629367-26193-11-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 10/26] wrapper.c: allow to create an empty file with write_file()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:11Z","receivedAt":"2016-02-16T13:29:11Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is used later on to create empty .git/worktrees/xxx/locked when\n\"git worktree lock\" is called with no reason given.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n wrapper.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/wrapper.c b/wrapper.c\nindex 29a45d2..1dc1eff 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -666,7 +666,7 @@ static int write_file_v(const char *path, int fatal,\n \t}\n \tstrbuf_vaddf(&sb, fmt, params);\n \tstrbuf_complete_line(&sb);\n-\tif (write_in_full(fd, sb.buf, sb.len) != sb.len) {\n+\tif (sb.len && write_in_full(fd, sb.buf, sb.len) != sb.len) {\n \t\tint err = errno;\n \t\tclose(fd);\n \t\tstrbuf_release(&sb);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278332","messageId":"1455629367-26193-12-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 11/26] path.c: add git_common_path() and strbuf_git_common_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:12Z","receivedAt":"2016-02-16T13:29:12Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n cache.h |  3 +++\n path.c  | 29 +++++++++++++++++++++++++++++\n 2 files changed, 32 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 30a2708..04818c2 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -765,11 +765,14 @@ extern int check_repository_format(void);\n  */\n extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n+extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n \n extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)\n \t__attribute__((format (printf, 3, 4)));\n extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n+extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)\n+\t__attribute__((format (printf, 2, 3)));\n extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)\n \t__attribute__((format (printf, 2, 3)));\n extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,\ndiff --git a/path.c b/path.c\nindex 8b7e168..0c294a1 100644\n--- a/path.c\n+++ b/path.c\n@@ -503,6 +503,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,\n \tva_end(args);\n }\n \n+static void do_git_common_path(struct strbuf *buf,\n+\t\t\t       const char *fmt,\n+\t\t\t       va_list args)\n+{\n+\tstrbuf_addstr(buf, get_git_common_dir());\n+\tif (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))\n+\t\tstrbuf_addch(buf, '/');\n+\tstrbuf_vaddf(buf, fmt, args);\n+\tstrbuf_cleanup_path(buf);\n+}\n+\n+const char *git_common_path(const char *fmt, ...)\n+{\n+\tstruct strbuf *pathname = get_pathname();\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_common_path(pathname, fmt, args);\n+\tva_end(args);\n+\treturn pathname->buf;\n+}\n+\n+void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)\n+{\n+\tva_list args;\n+\tva_start(args, fmt);\n+\tdo_git_common_path(sb, fmt, args);\n+\tva_end(args);\n+}\n+\n int validate_headref(const char *path)\n {\n \tstruct stat st;\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278356","messageId":"1455629367-26193-13-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 12/26] worktree.c: use is_dot_or_dotdot()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:13Z","receivedAt":"2016-02-16T13:29:13Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 2 +-\n worktree.c         | 3 ++-\n 2 files changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 68341e4..cfc848d 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -94,7 +94,7 @@ static void prune_worktrees(void)\n \tif (!dir)\n \t\treturn;\n \twhile ((d = readdir(dir)) != NULL) {\n-\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\tif (is_dot_or_dotdot(d->d_name))\n \t\t\tcontinue;\n \t\tstrbuf_reset(&reason);\n \t\tif (!prune_worktree(d->d_name, &reason))\ndiff --git a/worktree.c b/worktree.c\nindex 6181a66..ddb8cb7 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -2,6 +2,7 @@\n #include \"refs.h\"\n #include \"strbuf.h\"\n #include \"worktree.h\"\n+#include \"dir.h\"\n \n void free_worktrees(struct worktree **worktrees)\n {\n@@ -173,7 +174,7 @@ struct worktree **get_worktrees(void)\n \tif (dir) {\n \t\twhile ((d = readdir(dir)) != NULL) {\n \t\t\tstruct worktree *linked = NULL;\n-\t\t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n+\t\t\tif (is_dot_or_dotdot(d->d_name))\n \t\t\t\tcontinue;\n \n \t\t\tif ((linked = get_linked_worktree(d->d_name))) {\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278353","messageId":"1455629367-26193-14-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 13/26] worktree.c: store \"id\" instead of \"git_dir\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:14Z","receivedAt":"2016-02-16T13:29:14Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"We can reconstruct git_dir from id quite easily. It's a bit hackier to\ndo the reverse.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 29 ++++++++++++++++-------------\n worktree.h |  7 ++++++-\n 2 files changed, 22 insertions(+), 14 deletions(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex ddb8cb7..4c38414 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -10,7 +10,7 @@ void free_worktrees(struct worktree **worktrees)\n \n \tfor (i = 0; worktrees[i]; i++) {\n \t\tfree(worktrees[i]->path);\n-\t\tfree(worktrees[i]->git_dir);\n+\t\tfree(worktrees[i]->id);\n \t\tfree(worktrees[i]->head_ref);\n \t\tfree(worktrees[i]);\n \t}\n@@ -75,13 +75,11 @@ static struct worktree *get_main_worktree(void)\n \tstruct worktree *worktree = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf worktree_path = STRBUF_INIT;\n-\tstruct strbuf gitdir = STRBUF_INIT;\n \tstruct strbuf head_ref = STRBUF_INIT;\n \tint is_bare = 0;\n \tint is_detached = 0;\n \n-\tstrbuf_addf(&gitdir, \"%s\", absolute_path(get_git_common_dir()));\n-\tstrbuf_addbuf(&worktree_path, &gitdir);\n+\tstrbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));\n \tis_bare = !strbuf_strip_suffix(&worktree_path, \"/.git\");\n \tif (is_bare)\n \t\tstrbuf_strip_suffix(&worktree_path, \"/.\");\n@@ -93,7 +91,7 @@ static struct worktree *get_main_worktree(void)\n \n \tworktree = xmalloc(sizeof(struct worktree));\n \tworktree->path = strbuf_detach(&worktree_path, NULL);\n-\tworktree->git_dir = strbuf_detach(&gitdir, NULL);\n+\tworktree->id = NULL;\n \tworktree->is_bare = is_bare;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n@@ -101,7 +99,6 @@ static struct worktree *get_main_worktree(void)\n \n done:\n \tstrbuf_release(&path);\n-\tstrbuf_release(&gitdir);\n \tstrbuf_release(&worktree_path);\n \tstrbuf_release(&head_ref);\n \treturn worktree;\n@@ -112,16 +109,13 @@ static struct worktree *get_linked_worktree(const char *id)\n \tstruct worktree *worktree = NULL;\n \tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf worktree_path = STRBUF_INIT;\n-\tstruct strbuf gitdir = STRBUF_INIT;\n \tstruct strbuf head_ref = STRBUF_INIT;\n \tint is_detached = 0;\n \n \tif (!id)\n \t\tdie(\"Missing linked worktree name\");\n \n-\tstrbuf_addf(&gitdir, \"%s/worktrees/%s\",\n-\t\t\tabsolute_path(get_git_common_dir()), id);\n-\tstrbuf_addf(&path, \"%s/gitdir\", gitdir.buf);\n+\tstrbuf_git_common_path(&path, \"worktrees/%s/gitdir\", id);\n \tif (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)\n \t\t/* invalid gitdir file */\n \t\tgoto done;\n@@ -141,7 +135,7 @@ static struct worktree *get_linked_worktree(const char *id)\n \n \tworktree = xmalloc(sizeof(struct worktree));\n \tworktree->path = strbuf_detach(&worktree_path, NULL);\n-\tworktree->git_dir = strbuf_detach(&gitdir, NULL);\n+\tworktree->id = xstrdup(id);\n \tworktree->is_bare = 0;\n \tworktree->head_ref = NULL;\n \tworktree->is_detached = is_detached;\n@@ -149,7 +143,6 @@ static struct worktree *get_linked_worktree(const char *id)\n \n done:\n \tstrbuf_release(&path);\n-\tstrbuf_release(&gitdir);\n \tstrbuf_release(&worktree_path);\n \tstrbuf_release(&head_ref);\n \treturn worktree;\n@@ -189,6 +182,14 @@ struct worktree **get_worktrees(void)\n \treturn list;\n }\n \n+const char *get_worktree_git_dir(const struct worktree *wt)\n+{\n+\tif (wt->id)\n+\t\treturn git_common_path(\"worktrees/%s\", wt->id);\n+\telse\n+\t\treturn get_git_common_dir();\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\n@@ -200,7 +201,9 @@ char *find_shared_symref(const char *symref, const char *target)\n \tfor (i = 0; worktrees[i]; i++) {\n \t\tstrbuf_reset(&path);\n \t\tstrbuf_reset(&sb);\n-\t\tstrbuf_addf(&path, \"%s/%s\", worktrees[i]->git_dir, symref);\n+\t\tstrbuf_addf(&path, \"%s/%s\",\n+\t\t\t    get_worktree_git_dir(worktrees[i]),\n+\t\t\t    symref);\n \n \t\tif (parse_ref(path.buf, &sb, NULL)) {\n \t\t\tcontinue;\ndiff --git a/worktree.h b/worktree.h\nindex b4b3dda..e89d423 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -3,7 +3,7 @@\n \n struct worktree {\n \tchar *path;\n-\tchar *git_dir;\n+\tchar *id;\n \tchar *head_ref;\n \tunsigned char head_sha1[20];\n \tint is_detached;\n@@ -23,6 +23,11 @@ struct worktree {\n extern struct worktree **get_worktrees(void);\n \n /*\n+ * Return git dir of the worktree. Note that the path may be relative.\n+ */\n+extern const char *get_worktree_git_dir(const struct worktree *wt);\n+\n+/*\n  * Free up the memory for worktree(s)\n  */\n extern void free_worktrees(struct worktree **);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278351","messageId":"1455629367-26193-15-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 14/26] worktree.c: add clear_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:15Z","receivedAt":"2016-02-16T13:29:15Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 14 +++++++++++---\n worktree.h |  5 +++++\n 2 files changed, 16 insertions(+), 3 deletions(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex 4c38414..b4e4b57 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -4,14 +4,22 @@\n #include \"worktree.h\"\n #include \"dir.h\"\n \n+void clear_worktree(struct worktree *wt)\n+{\n+\tif (!wt)\n+\t\treturn;\n+\tfree(wt->path);\n+\tfree(wt->id);\n+\tfree(wt->head_ref);\n+\tmemset(wt, 0, sizeof(*wt));\n+}\n+\n void free_worktrees(struct worktree **worktrees)\n {\n \tint i = 0;\n \n \tfor (i = 0; worktrees[i]; i++) {\n-\t\tfree(worktrees[i]->path);\n-\t\tfree(worktrees[i]->id);\n-\t\tfree(worktrees[i]->head_ref);\n+\t\tclear_worktree(worktrees[i]);\n \t\tfree(worktrees[i]);\n \t}\n \tfree (worktrees);\ndiff --git a/worktree.h b/worktree.h\nindex e89d423..0ba07ab 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -28,6 +28,11 @@ extern struct worktree **get_worktrees(void);\n extern const char *get_worktree_git_dir(const struct worktree *wt);\n \n /*\n+ * Free up the memory for worktree\n+ */\n+extern void clear_worktree(struct worktree *);\n+\n+/*\n  * Free up the memory for worktree(s)\n  */\n extern void free_worktrees(struct worktree **);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278337","messageId":"1455629367-26193-16-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 15/26] worktree.c: add find_worktree_by_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:16Z","receivedAt":"2016-02-16T13:29:16Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 16 ++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 22 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex b4e4b57..e444ad1 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -198,6 +198,22 @@ const char *get_worktree_git_dir(const struct worktree *wt)\n \t\treturn get_git_common_dir();\n }\n \n+struct worktree *find_worktree_by_path(struct worktree **list,\n+\t\t\t\t       const char *path_)\n+{\n+\tchar *path = xstrdup(real_path(path_));\n+\tstruct worktree *wt = NULL;\n+\n+\twhile (*list) {\n+\t\twt = *list++;\n+\t\tif (!strcmp_icase(path, real_path(wt->path)))\n+\t\t\tbreak;\n+\t\twt = NULL;\n+\t}\n+\tfree(path);\n+\treturn wt;\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\ndiff --git a/worktree.h b/worktree.h\nindex 0ba07ab..c163b6b 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -28,6 +28,12 @@ extern struct worktree **get_worktrees(void);\n extern const char *get_worktree_git_dir(const struct worktree *wt);\n \n /*\n+ * Search a worktree by its path. Paths are normalized internally.\n+ */\n+extern struct worktree *find_worktree_by_path(struct worktree **list,\n+\t\t\t\t\t      const char *path_);\n+\n+/*\n  * Free up the memory for worktree\n  */\n extern void clear_worktree(struct worktree *);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278339","messageId":"1455629367-26193-17-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 16/26] worktree.c: add is_main_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:17Z","receivedAt":"2016-02-16T13:29:17Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 5 +++++\n worktree.h | 5 +++++\n 2 files changed, 10 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex e444ad1..e878f49 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -214,6 +214,11 @@ struct worktree *find_worktree_by_path(struct worktree **list,\n \treturn wt;\n }\n \n+int is_main_worktree(const struct worktree *wt)\n+{\n+\treturn wt && !wt->id;\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\ndiff --git a/worktree.h b/worktree.h\nindex c163b6b..c7a4d20 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -34,6 +34,11 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,\n \t\t\t\t\t      const char *path_);\n \n /*\n+ * Return true if the given worktree is the main one.\n+ */\n+extern int is_main_worktree(const struct worktree *wt);\n+\n+/*\n  * Free up the memory for worktree\n  */\n extern void clear_worktree(struct worktree *);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278340","messageId":"1455629367-26193-18-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 17/26] worktree.c: add validate_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:18Z","receivedAt":"2016-02-16T13:29:18Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This function is later used by \"worktree move\" and \"worktree remove\"\nto ensure that we have a good connection between the repository and\nthe worktree.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n worktree.h |  5 +++++\n 2 files changed, 68 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex e878f49..28195b1 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -219,6 +219,69 @@ int is_main_worktree(const struct worktree *wt)\n \treturn wt && !wt->id;\n }\n \n+static int report(int quiet, const char *fmt, ...)\n+{\n+\tva_list params;\n+\n+\tif (quiet)\n+\t\treturn -1;\n+\n+\tva_start(params, fmt);\n+\tvfprintf(stderr, fmt, params);\n+\tfputc('\\n', stderr);\n+\tva_end(params);\n+\treturn -1;\n+}\n+\n+int validate_worktree(const struct worktree *wt, int quiet)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tconst char *path;\n+\tint err;\n+\n+\tif (is_main_worktree(wt)) {\n+\t\t/*\n+\t\t * Main worktree using .git file to point to the\n+\t\t * repository would make it impossible to know where\n+\t\t * the actual worktree is if this function is executed\n+\t\t * from another worktree. No .git file support for now.\n+\t\t */\n+\t\tstrbuf_addf(&sb, \"%s/.git\", wt->path);\n+\t\tif (!is_directory(sb.buf)) {\n+\t\t\tstrbuf_release(&sb);\n+\t\t\treturn report(quiet, _(\"'%s/.git' at main worktree is not the repository directory\"),\n+\t\t\t\t      wt->path);\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * Make sure \"gitdir\" file points to a real .git file and that\n+\t * file points back here.\n+\t */\n+\tif (!is_absolute_path(wt->path))\n+\t\treturn report(quiet, _(\"'%s' file does not contain absolute path to the worktree location\"),\n+\t\t\t      git_common_path(\"worktrees/%s/gitdir\", wt->id));\n+\n+\tstrbuf_addf(&sb, \"%s/.git\", wt->path);\n+\tif (!file_exists(sb.buf)) {\n+\t\tstrbuf_release(&sb);\n+\t\treturn report(quiet, _(\"'%s/.git' does not exist\"), wt->path);\n+\t}\n+\n+\tpath = read_gitfile_gently(sb.buf, &err);\n+\tstrbuf_release(&sb);\n+\tif (!path)\n+\t\treturn report(quiet, _(\"'%s/.git' is not a .git file, error code %d\"),\n+\t\t\t      wt->path, err);\n+\n+\tif (strcmp_icase(path, real_path(git_common_path(\"worktrees/%s\", wt->id))))\n+\t\treturn report(quiet, _(\"'%s' does not point back to\"),\n+\t\t\t      wt->path, git_common_path(\"worktrees/%s\", wt->id));\n+\n+\treturn 0;\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\ndiff --git a/worktree.h b/worktree.h\nindex c7a4d20..0d6be18 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -39,6 +39,11 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,\n extern int is_main_worktree(const struct worktree *wt);\n \n /*\n+ * Return zero if the worktree is in good condition.\n+ */\n+extern int validate_worktree(const struct worktree *wt, int quiet);\n+\n+/*\n  * Free up the memory for worktree\n  */\n extern void clear_worktree(struct worktree *);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278349","messageId":"1455629367-26193-19-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 18/26] worktree.c: add update_worktree_location()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:19Z","receivedAt":"2016-02-16T13:29:19Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 25 +++++++++++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 31 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 28195b1..04aac73 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -282,6 +282,31 @@ int validate_worktree(const struct worktree *wt, int quiet)\n \treturn 0;\n }\n \n+int update_worktree_location(struct worktree *wt, const char *path_)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tint ret;\n+\n+\tif (is_main_worktree(wt))\n+\t\treturn 0;\n+\n+\tstrbuf_add_absolute_path(&path, path_);\n+\tif (strcmp_icase(wt->path, path.buf)) {\n+\t\tif (!write_file_gently(git_common_path(\"worktrees/%s/gitdir\",\n+\t\t\t\t\t\t       wt->id),\n+\t\t\t\t       \"%s/.git\", real_path(path.buf))) {\n+\t\t\tfree(wt->path);\n+\t\t\twt->path = strbuf_detach(&path, NULL);\n+\t\t\tret = 0;\n+\t\t} else\n+\t\t\tret = sys_error(_(\"failed to update '%s'\"),\n+\t\t\t\t\tgit_common_path(\"worktrees/%s/gitdir\",\n+\t\t\t\t\t\t\twt->id));\n+\t}\n+\tstrbuf_release(&path);\n+\treturn ret;\n+}\n+\n char *find_shared_symref(const char *symref, const char *target)\n {\n \tchar *existing = NULL;\ndiff --git a/worktree.h b/worktree.h\nindex 0d6be18..bbe40ef 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -44,6 +44,12 @@ extern int is_main_worktree(const struct worktree *wt);\n extern int validate_worktree(const struct worktree *wt, int quiet);\n \n /*\n+ * Update worktrees/xxx/gitdir with the new path.\n+ */\n+extern int update_worktree_location(struct worktree *wt,\n+\t\t\t\t    const char *path_);\n+\n+/*\n  * Free up the memory for worktree\n  */\n extern void clear_worktree(struct worktree *);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278342","messageId":"1455629367-26193-20-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 19/26] worktree.c: add is_worktree_locked()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:20Z","receivedAt":"2016-02-16T13:29:20Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 18 ++++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 24 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 04aac73..facb907 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -219,6 +219,24 @@ int is_main_worktree(const struct worktree *wt)\n \treturn wt && !wt->id;\n }\n \n+const char *is_worktree_locked(const struct worktree *wt)\n+{\n+\tstatic struct strbuf sb = STRBUF_INIT;\n+\n+\tif (!file_exists(git_common_path(\"worktrees/%s/locked\", wt->id)))\n+\t\treturn NULL;\n+\n+\tstrbuf_reset(&sb);\n+\tif (strbuf_read_file(&sb,\n+\t\t\t     git_common_path(\"worktrees/%s/locked\", wt->id),\n+\t\t\t     0) < 0)\n+\t\tdie_errno(_(\"failed to read '%s'\"),\n+\t\t\t  git_common_path(\"worktrees/%s/locked\", wt->id));\n+\n+\tstrbuf_rtrim(&sb);\n+\treturn sb.buf;\n+}\n+\n static int report(int quiet, const char *fmt, ...)\n {\n \tva_list params;\ndiff --git a/worktree.h b/worktree.h\nindex bbe40ef..cbd5389 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -39,6 +39,12 @@ extern struct worktree *find_worktree_by_path(struct worktree **list,\n extern int is_main_worktree(const struct worktree *wt);\n \n /*\n+ * Return the reason string if the given worktree is locked. Return\n+ * NULL otherwise.\n+ */\n+extern const char *is_worktree_locked(const struct worktree *wt);\n+\n+/*\n  * Return zero if the worktree is in good condition.\n  */\n extern int validate_worktree(const struct worktree *wt, int quiet);\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278343","messageId":"1455629367-26193-21-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 20/26] worktree: avoid 0{40}, too many zeroes, hard to read","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:21Z","receivedAt":"2016-02-16T13:29:21Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex cfc848d..97666cc 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -263,7 +263,7 @@ static int add_worktree(const char *path, const char *refname,\n \t */\n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/HEAD\", sb_repo.buf);\n-\twrite_file(sb.buf, \"0000000000000000000000000000000000000000\");\n+\twrite_file(sb.buf, sha1_to_hex(null_sha1));\n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/commondir\", sb_repo.buf);\n \twrite_file(sb.buf, \"../..\");\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278344","messageId":"1455629367-26193-22-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 21/26] worktree: simplify prefixing paths","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:22Z","receivedAt":"2016-02-16T13:29:22Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 97666cc..b261e11 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -332,7 +332,7 @@ static int add(int ac, const char **av, const char *prefix)\n \tif (ac < 1 || ac > 2)\n \t\tusage_with_options(worktree_usage, options);\n \n-\tpath = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];\n+\tpath = prefix_filename(prefix, strlen(prefix), av[0]);\n \tbranch = ac < 2 ? \"HEAD\" : av[1];\n \n \topts.force_new_branch = !!new_branch_force;\n@@ -453,6 +453,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \n \tif (ac < 2)\n \t\tusage_with_options(worktree_usage, options);\n+\tif (!prefix)\n+\t\tprefix = \"\";\n \tif (!strcmp(av[1], \"add\"))\n \t\treturn add(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"prune\"))\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278346","messageId":"1455629367-26193-23-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 22/26] worktree: add \"lock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:23Z","receivedAt":"2016-02-16T13:29:23Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         | 12 ++++++++--\n builtin/worktree.c                     | 41 ++++++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 ++++-\n t/t2028-worktree-move.sh (new +x)      | 34 ++++++++++++++++++++++++++++\n 4 files changed, 89 insertions(+), 3 deletions(-)\n create mode 100755 t/t2028-worktree-move.sh\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 1c9d7c1..9f0c9f0 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n [verse]\n 'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]\n 'git worktree list' [--porcelain]\n+'git worktree lock' [--reason <string>] <path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n \n DESCRIPTION\n@@ -61,6 +62,12 @@ each of the linked worktrees.  The output details include if the worktree is\n bare, the revision currently checked out, and the branch currently checked out\n (or 'detached HEAD' if none).\n \n+lock::\n+\n+When a worktree is locked, it cannot be pruned, moved or deleted. For\n+example, if the worktree is on portable device that is not available\n+when \"git worktree <command>\" is executed.\n+\n prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n@@ -104,6 +111,9 @@ OPTIONS\n --expire <time>::\n \tWith `prune`, only expire unused working trees older than <time>.\n \n+--reason <string>:\n+\tAn explanation why the worktree is locked.\n+\n DETAILS\n -------\n Each linked working tree has a private sub-directory in the repository's\n@@ -220,8 +230,6 @@ performed manually, such as:\n - `remove` to remove a linked working tree and its administrative files (and\n   warn if the working tree is dirty)\n - `mv` to move or rename a working tree and update its administrative files\n-- `lock` to prevent automatic pruning of administrative files (for instance,\n-  for a working tree on a portable device)\n \n GIT\n ---\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex b261e11..f45a425 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -14,6 +14,7 @@\n static const char * const worktree_usage[] = {\n \tN_(\"git worktree add [<options>] <path> [<branch>]\"),\n \tN_(\"git worktree list [<options>]\"),\n+\tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n \tNULL\n };\n@@ -445,6 +446,44 @@ static int list(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int lock_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tconst char *reason = \"\", *old_reason;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"reason\", &reason, N_(\"string\"),\n+\t\t\t   N_(\"reason for locking\")),\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\tstruct strbuf dst = STRBUF_INIT;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tstrbuf_addstr(&dst, prefix_filename(prefix,\n+\t\t\t\t\t    strlen(prefix),\n+\t\t\t\t\t    av[0]));\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree_by_path(worktrees, dst.buf);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\n+\told_reason = is_worktree_locked(wt);\n+\tif (old_reason) {\n+\t\tif (*old_reason)\n+\t\t\tdie(_(\"already locked, reason: %s\"), old_reason);\n+\t\tdie(_(\"already locked, no reason\"));\n+\t}\n+\n+\twrite_file(git_common_path(\"worktrees/%s/locked\", wt->id),\n+\t\t   \"%s\", reason);\n+\treturn 0;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -461,5 +500,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn prune(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"list\"))\n \t\treturn list(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"lock\"))\n+\t\treturn lock_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 099d52e..c9a8d04 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2596,7 +2596,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list prune\"\n+\tlocal subcommands=\"add list lock prune\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\n@@ -2608,6 +2608,9 @@ _git_worktree ()\n \t\tlist,--*)\n \t\t\t__gitcomp \"--porcelain\"\n \t\t\t;;\n+\t\tlock,--*)\n+\t\t\t__gitcomp \"--reason\"\n+\t\t\t;;\n \t\tprune,--*)\n \t\t\t__gitcomp \"--dry-run --expire --verbose\"\n \t\t\t;;\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nnew file mode 100755\nindex 0000000..97434be\n--- /dev/null\n+++ b/t/t2028-worktree-move.sh\n@@ -0,0 +1,34 @@\n+#!/bin/sh\n+\n+test_description='test git worktree move, remove, lock and unlock'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit init &&\n+\tgit worktree add source &&\n+\tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n+\tcat <<-EOF >expected &&\n+\tworktree $TRASH_DIRECTORY\n+\tworktree $TRASH_DIRECTORY/source\n+\tEOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'lock main worktree' '\n+\ttest_must_fail git worktree lock .\n+'\n+\n+test_expect_success 'lock linked worktree' '\n+\tgit worktree lock --reason hahaha source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'lock worktree twice' '\n+\ttest_must_fail git worktree lock source &&\n+\techo hahaha >expected &&\n+\ttest_cmp expected .git/worktrees/source/locked\n+'\n+\n+test_done\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278345","messageId":"1455629367-26193-24-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 23/26] worktree: add \"unlock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:24Z","receivedAt":"2016-02-16T13:29:24Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         |  5 +++++\n builtin/worktree.c                     | 31 +++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  2 +-\n t/t2028-worktree-move.sh               | 14 ++++++++++++++\n 4 files changed, 51 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 9f0c9f0..8315e5b 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n 'git worktree list' [--porcelain]\n 'git worktree lock' [--reason <string>] <path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n+'git worktree unlock' <path>\n \n DESCRIPTION\n -----------\n@@ -72,6 +73,10 @@ prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n \n+unlock::\n+\n+Unlock a worktree, allowing it to be pruned, moved or deleted.\n+\n OPTIONS\n -------\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex f45a425..c5b26e7 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -16,6 +16,7 @@ static const char * const worktree_usage[] = {\n \tN_(\"git worktree list [<options>]\"),\n \tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n+\tN_(\"git worktree unlock <path>\"),\n \tNULL\n };\n \n@@ -484,6 +485,34 @@ static int lock_worktree(int ac, const char **av, const char *prefix)\n \treturn 0;\n }\n \n+static int unlock_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\tstruct strbuf dst = STRBUF_INIT;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tstrbuf_addstr(&dst, prefix_filename(prefix,\n+\t\t\t\t\t    strlen(prefix),\n+\t\t\t\t\t    av[0]));\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree_by_path(worktrees, dst.buf);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\tif (!is_worktree_locked(wt))\n+\t\tdie(_(\"not locked\"));\n+\n+\treturn unlink_or_warn(git_common_path(\"worktrees/%s/locked\", wt->id));\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -502,5 +531,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn list(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"lock\"))\n \t\treturn lock_worktree(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"unlock\"))\n+\t\treturn unlock_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex c9a8d04..8d7867c 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2596,7 +2596,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list lock prune\"\n+\tlocal subcommands=\"add list lock prune unlock\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nindex 97434be..f4b2816 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -31,4 +31,18 @@ test_expect_success 'lock worktree twice' '\n \ttest_cmp expected .git/worktrees/source/locked\n '\n \n+test_expect_success 'unlock main worktree' '\n+\ttest_must_fail git worktree unlock .\n+'\n+\n+test_expect_success 'unlock linked worktree' '\n+\tgit worktree unlock source &&\n+\ttest_path_is_missing .git/worktrees/source/locked\n+'\n+\n+test_expect_success 'unlock worktree twice' '\n+\ttest_must_fail git worktree unlock source &&\n+\ttest_path_is_missing .git/worktrees/source/locked\n+'\n+\n test_done\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278350","messageId":"1455629367-26193-25-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 24/26] worktree: add \"move\" commmand","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:25Z","receivedAt":"2016-02-16T13:29:25Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         |  6 +++-\n builtin/worktree.c                     | 60 ++++++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  2 +-\n t/t2028-worktree-move.sh               | 29 ++++++++++++++++\n 4 files changed, 95 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 8315e5b..a302f0a 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -12,6 +12,7 @@ SYNOPSIS\n 'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]\n 'git worktree list' [--porcelain]\n 'git worktree lock' [--reason <string>] <path>\n+'git worktree move' <path> <new-path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n 'git worktree unlock' <path>\n \n@@ -69,6 +70,10 @@ When a worktree is locked, it cannot be pruned, moved or deleted. For\n example, if the worktree is on portable device that is not available\n when \"git worktree <command>\" is executed.\n \n+move::\n+\n+Move a worktree to a new location. Note that the main worktree cannot be moved.\n+\n prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n@@ -234,7 +239,6 @@ performed manually, such as:\n \n - `remove` to remove a linked working tree and its administrative files (and\n   warn if the working tree is dirty)\n-- `mv` to move or rename a working tree and update its administrative files\n \n GIT\n ---\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex c5b26e7..acd49da 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -15,6 +15,7 @@ static const char * const worktree_usage[] = {\n \tN_(\"git worktree add [<options>] <path> [<branch>]\"),\n \tN_(\"git worktree list [<options>]\"),\n \tN_(\"git worktree lock [<options>] <path>\"),\n+\tN_(\"git worktree move <path> <new-path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n \tN_(\"git worktree unlock <path>\"),\n \tNULL\n@@ -513,6 +514,63 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)\n \treturn unlink_or_warn(git_common_path(\"worktrees/%s/locked\", wt->id));\n }\n \n+static int move_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\tstruct strbuf dst = STRBUF_INIT;\n+\tstruct strbuf src = STRBUF_INIT;\n+\tconst char *reason;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 2)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tstrbuf_addstr(&dst, prefix_filename(prefix,\n+\t\t\t\t\t    strlen(prefix),\n+\t\t\t\t\t    av[1]));\n+\tif (file_exists(dst.buf))\n+\t\tdie(_(\"target '%s' already exists\"), av[1]);\n+\n+\tworktrees = get_worktrees();\n+\tstrbuf_addstr(&src, prefix_filename(prefix,\n+\t\t\t\t\t     strlen(prefix),\n+\t\t\t\t\t     av[0]));\n+\twt = find_worktree_by_path(worktrees, src.buf);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\tif ((reason = is_worktree_locked(wt))) {\n+\t\tif (*reason)\n+\t\t\tdie(_(\"already locked, reason: %s\"), reason);\n+\t\tdie(_(\"already locked, no reason\"));\n+\t}\n+\tif (validate_worktree(wt, 0))\n+\t\treturn -1;\n+\n+\t/*\n+\t * First try. Atomically move, and probably cheaper, if both\n+\t * source and target are on the same file system.\n+\t */\n+\tif (rename(src.buf, dst.buf) == -1) {\n+\t\tif (errno != EXDEV)\n+\t\t\tdie_errno(_(\"failed to move '%s' to '%s'\"),\n+\t\t\t\t  src.buf, dst.buf);\n+\n+\t\t/* second try.. */\n+\t\tif (copy_dir_recursively(src.buf, dst.buf))\n+\t\t\tdie(_(\"failed to copy '%s' to '%s'\"),\n+\t\t\t    src.buf, dst.buf);\n+\t\telse\n+\t\t\t(void)remove_dir_recursively(&src, 0);\n+\t}\n+\n+\treturn update_worktree_location(wt, dst.buf);\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -533,5 +591,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn lock_worktree(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"unlock\"))\n \t\treturn unlock_worktree(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"move\"))\n+\t\treturn move_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 8d7867c..4e01841 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2596,7 +2596,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list lock prune unlock\"\n+\tlocal subcommands=\"add list lock move prune unlock\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nindex f4b2816..83fbab5 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -45,4 +45,33 @@ test_expect_success 'unlock worktree twice' '\n \ttest_path_is_missing .git/worktrees/source/locked\n '\n \n+test_expect_success 'move non-worktree' '\n+\tmkdir abc &&\n+\ttest_must_fail git worktree move abc def\n+'\n+\n+test_expect_success 'move locked worktree' '\n+\tgit worktree lock source &&\n+\ttest_must_fail git worktree move source destination &&\n+\tgit worktree unlock source\n+'\n+\n+test_expect_success 'move worktree' '\n+\tgit worktree move source destination &&\n+\ttest_path_is_missing source &&\n+\tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n+\tcat <<-EOF >expected &&\n+\tworktree $TRASH_DIRECTORY\n+\tworktree $TRASH_DIRECTORY/destination\n+\tEOF\n+\ttest_cmp expected actual &&\n+\tgit -C destination log --format=%s >actual2 &&\n+\techo init >expected2 &&\n+\ttest_cmp expected2 actual2\n+'\n+\n+test_expect_success 'move main worktree' '\n+\ttest_must_fail git worktree move . def\n+'\n+\n test_done\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278348","messageId":"1455629367-26193-26-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 25/26] worktree move: accept destination as directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:26Z","receivedAt":"2016-02-16T13:29:26Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Similar to \"mv a b/\", which is actually \"mv a b/a\", we extract basename\nof source worktree and create a directory of the same name at\ndestination if dst path is a directory.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 19 ++++++++++++++++++-\n 1 file changed, 18 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex acd49da..d4ce987 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -531,7 +531,13 @@ static int move_worktree(int ac, const char **av, const char *prefix)\n \tstrbuf_addstr(&dst, prefix_filename(prefix,\n \t\t\t\t\t    strlen(prefix),\n \t\t\t\t\t    av[1]));\n-\tif (file_exists(dst.buf))\n+\tif (is_directory(dst.buf))\n+\t\t/*\n+\t\t * keep going, dst will be appended after we get the\n+\t\t * source's absolute path\n+\t\t */\n+\t\t;\n+\telse if (file_exists(dst.buf))\n \t\tdie(_(\"target '%s' already exists\"), av[1]);\n \n \tworktrees = get_worktrees();\n@@ -551,6 +557,17 @@ static int move_worktree(int ac, const char **av, const char *prefix)\n \tif (validate_worktree(wt, 0))\n \t\treturn -1;\n \n+\tif (is_directory(dst.buf)) {\n+\t\tconst char *sep = strrchr(wt->path, '/');\n+\n+\t\tif (!sep)\n+\t\t\tdie(_(\"could not figure out destination name from '%s'\"),\n+\t\t\t    wt->path);\n+\t\tstrbuf_addstr(&dst, sep);\n+\t\tif (file_exists(dst.buf))\n+\t\t\tdie(_(\"target '%s' already exists\"), dst.buf);\n+\t}\n+\n \t/*\n \t * First try. Atomically move, and probably cheaper, if both\n \t * source and target are on the same file system.\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278352","messageId":"1455629367-26193-27-git-send-email-pclouds@gmail.com","threadId":"41321","inReplyTo":"1455629367-26193-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2 26/26] worktree: add \"remove\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-16T13:29:27Z","receivedAt":"2016-02-16T13:29:27Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         | 22 +++++----\n builtin/worktree.c                     | 81 ++++++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 ++-\n t/t2028-worktree-move.sh               | 26 +++++++++++\n 4 files changed, 124 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex a302f0a..60ad465 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -14,6 +14,7 @@ SYNOPSIS\n 'git worktree lock' [--reason <string>] <path>\n 'git worktree move' <path> <new-path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n+'git worktree remove' [--force] <path>\n 'git worktree unlock' <path>\n \n DESCRIPTION\n@@ -78,6 +79,14 @@ prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n \n+remove::\n+\n+Remove a worktree. Only clean worktrees, no untracked files and no\n+modification in tracked files, can be removed. Unclean worktrees can\n+be removed with `--force`. Main worktree cannot be removed. It needs\n+to be converted to a linked worktree first by moving the repository\n+away.\n+\n unlock::\n \n Unlock a worktree, allowing it to be pruned, moved or deleted.\n@@ -87,9 +96,10 @@ OPTIONS\n \n -f::\n --force::\n-\tBy default, `add` refuses to create a new working tree when `<branch>`\n-\tis already checked out by another working tree. This option overrides\n-\tthat safeguard.\n+\tBy default, `add` refuses to create a new working tree when\n+\t`<branch>` is already checked out by another working tree and\n+\t`remove` refuses to remove an unclean worktree. This option\n+\toverrides that safeguard.\n \n -b <new-branch>::\n -B <new-branch>::\n@@ -234,12 +244,6 @@ Multiple checkout in general is still experimental, and the support\n for submodules is incomplete. It is NOT recommended to make multiple\n checkouts of a superproject.\n \n-git-worktree could provide more automation for tasks currently\n-performed manually, such as:\n-\n-- `remove` to remove a linked working tree and its administrative files (and\n-  warn if the working tree is dirty)\n-\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex d4ce987..8cff943 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -17,6 +17,7 @@ static const char * const worktree_usage[] = {\n \tN_(\"git worktree lock [<options>] <path>\"),\n \tN_(\"git worktree move <path> <new-path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n+\tN_(\"git worktree remove [<options>] <path>\"),\n \tN_(\"git worktree unlock <path>\"),\n \tNULL\n };\n@@ -588,6 +589,84 @@ static int move_worktree(int ac, const char **av, const char *prefix)\n \treturn update_worktree_location(wt, dst.buf);\n }\n \n+static int remove_worktree(int ac, const char **av, const char *prefix)\n+{\n+\tint force = 0;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"force\", &force,\n+\t\t\t N_(\"force removing even if the worktree is dirty\")),\n+\t\tOPT_END()\n+\t};\n+\tstruct worktree **worktrees, *wt;\n+\tstruct strbuf dst = STRBUF_INIT;\n+\tconst char *reason;\n+\tint ret = 0;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac != 1)\n+\t\tusage_with_options(worktree_usage, options);\n+\n+\tstrbuf_addstr(&dst, prefix_filename(prefix,\n+\t\t\t\t\t    strlen(prefix),\n+\t\t\t\t\t    av[0]));\n+\n+\tworktrees = get_worktrees();\n+\twt = find_worktree_by_path(worktrees, dst.buf);\n+\tif (!wt)\n+\t\tdie(_(\"'%s' is not a working directory\"), av[0]);\n+\tif (is_main_worktree(wt))\n+\t\tdie(_(\"'%s' is a main working directory\"), av[0]);\n+\tif ((reason = is_worktree_locked(wt))) {\n+\t\tif (*reason)\n+\t\t\tdie(_(\"already locked, reason: %s\"), reason);\n+\t\tdie(_(\"already locked, no reason\"));\n+\t}\n+\tif (validate_worktree(wt, 0))\n+\t\treturn -1;\n+\n+\tif (!force) {\n+\t\tstruct argv_array child_env = ARGV_ARRAY_INIT;\n+\t\tstruct child_process cp;\n+\t\tchar buf[1];\n+\n+\t\targv_array_pushf(&child_env, \"%s=%s/.git\",\n+\t\t\t\t GIT_DIR_ENVIRONMENT, wt->path);\n+\t\targv_array_pushf(&child_env, \"%s=%s\",\n+\t\t\t\t GIT_WORK_TREE_ENVIRONMENT, wt->path);\n+\t\tmemset(&cp, 0, sizeof(cp));\n+\t\targv_array_pushl(&cp.args, \"status\", \"--porcelain\", NULL);\n+\t\tcp.env = child_env.argv;\n+\t\tcp.git_cmd = 1;\n+\t\tcp.dir = wt->path;\n+\t\tcp.out = -1;\n+\t\tret = start_command(&cp);\n+\t\tif (ret)\n+\t\t\tdie_errno(_(\"failed to run git-status on '%s', code %d\"),\n+\t\t\t\t  av[0], ret);\n+\t\tret = xread(cp.out, buf, sizeof(buf));\n+\t\tif (ret)\n+\t\t\tdie(_(\"'%s' is dirty, use --force to delete it\"), av[0]);\n+\t\tclose(cp.out);\n+\t\tret = finish_command(&cp);\n+\t\tif (ret)\n+\t\t\tdie_errno(_(\"failed to run git-status on '%s', code %d\"),\n+\t\t\t\t  av[0], ret);\n+\t}\n+\tif (remove_dir_recursively(&dst, 0)) {\n+\t\tsys_error(_(\"failed to delete '%s'\"), wt->path);\n+\t\tret = -1;\n+\t}\n+\tstrbuf_reset(&dst);\n+\tstrbuf_addstr(&dst, git_common_path(\"worktrees/%s\", wt->id));\n+\tif (remove_dir_recursively(&dst, 0)) {\n+\t\tsys_error(_(\"failed to delete '%s'\"), dst.buf);\n+\t\tret = -1;\n+\t}\n+\tstrbuf_release(&dst);\n+\tfree_worktrees(worktrees);\n+\treturn ret;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -610,5 +689,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn unlock_worktree(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"move\"))\n \t\treturn move_worktree(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"remove\"))\n+\t\treturn remove_worktree(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 4e01841..6eda7ed 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2596,7 +2596,7 @@ _git_whatchanged ()\n \n _git_worktree ()\n {\n-\tlocal subcommands=\"add list lock move prune unlock\"\n+\tlocal subcommands=\"add list lock move prune remove unlock\"\n \tlocal subcommand=\"$(__git_find_on_cmdline \"$subcommands\")\"\n \tif [ -z \"$subcommand\" ]; then\n \t\t__gitcomp \"$subcommands\"\n@@ -2614,6 +2614,9 @@ _git_worktree ()\n \t\tprune,--*)\n \t\t\t__gitcomp \"--dry-run --expire --verbose\"\n \t\t\t;;\n+\t\tremove,--*)\n+\t\t\t__gitcomp \"--force\"\n+\t\t\t;;\n \t\t*)\n \t\t\t;;\n \t\tesac\ndiff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh\nindex 83fbab5..668ee05 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -74,4 +74,30 @@ test_expect_success 'move main worktree' '\n \ttest_must_fail git worktree move . def\n '\n \n+test_expect_success 'remove main worktree' '\n+\ttest_must_fail git worktree remove .\n+'\n+\n+test_expect_success 'remove locked worktree' '\n+\tgit worktree lock destination &&\n+\ttest_must_fail git worktree remove destination &&\n+\tgit worktree unlock destination\n+'\n+\n+test_expect_success 'remove worktree with dirty tracked file' '\n+\techo dirty >>destination/init.t &&\n+\ttest_must_fail git worktree remove destination\n+'\n+\n+test_expect_success 'remove worktree with untracked file' '\n+\tgit -C destination checkout init.t &&\n+\t: >destination/untracked &&\n+\ttest_must_fail git worktree remove destination\n+'\n+\n+test_expect_success 'force remove worktree with untracked file' '\n+\tgit worktree remove --force destination &&\n+\ttest_path_is_missing destination\n+'\n+\n test_done\n-- \n2.7.0.377.g4cd97dd\n"},{"id":"278517","messageId":"xmqqpovvgeca.fsf@gitster.mtv.corp.google.com","threadId":"41321","inReplyTo":"1455629367-26193-11-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2 10/26] wrapper.c: allow to create an empty file with write_file()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-02-17T22:29:57Z","receivedAt":"2016-02-17T22:29:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> This is used later on to create empty .git/worktrees/xxx/locked when\n> \"git worktree lock\" is called with no reason given.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  wrapper.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n>\n> diff --git a/wrapper.c b/wrapper.c\n> index 29a45d2..1dc1eff 100644\n> --- a/wrapper.c\n> +++ b/wrapper.c\n> @@ -666,7 +666,7 @@ static int write_file_v(const char *path, int fatal,\n>  \t}\n>  \tstrbuf_vaddf(&sb, fmt, params);\n>  \tstrbuf_complete_line(&sb);\n> -\tif (write_in_full(fd, sb.buf, sb.len) != sb.len) {\n> +\tif (sb.len && write_in_full(fd, sb.buf, sb.len) != sb.len) {\n\nI do not quite see how this change relates to what the log message\nclaims this commit does.  write_in_full() returns 0 when called with\ncount==0 just fine, no?\n\n>  \t\tint err = errno;\n>  \t\tclose(fd);\n>  \t\tstrbuf_release(&sb);\n"},{"id":"278524","messageId":"CACsJy8AQKfJoq6qcOoiay79zGKn1F3s=fnsdbBxbHyJRir-LfQ@mail.gmail.com","threadId":"41321","inReplyTo":"xmqqpovvgeca.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v2 10/26] wrapper.c: allow to create an empty file with write_file()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-02-18T00:49:17Z","receivedAt":"2016-02-18T00:49:17Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Feb 18, 2016 at 5:29 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> This is used later on to create empty .git/worktrees/xxx/locked when\n>> \"git worktree lock\" is called with no reason given.\n>>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>>  wrapper.c | 2 +-\n>>  1 file changed, 1 insertion(+), 1 deletion(-)\n>>\n>> diff --git a/wrapper.c b/wrapper.c\n>> index 29a45d2..1dc1eff 100644\n>> --- a/wrapper.c\n>> +++ b/wrapper.c\n>> @@ -666,7 +666,7 @@ static int write_file_v(const char *path, int fatal,\n>>       }\n>>       strbuf_vaddf(&sb, fmt, params);\n>>       strbuf_complete_line(&sb);\n>> -     if (write_in_full(fd, sb.buf, sb.len) != sb.len) {\n>> +     if (sb.len && write_in_full(fd, sb.buf, sb.len) != sb.len) {\n>\n> I do not quite see how this change relates to what the log message\n> claims this commit does.  write_in_full() returns 0 when called with\n> count==0 just fine, no?\n\nYou're right. Somehow I got the impression that xwrite() was called,\nbut it's not in this case.\n-- \nDuy\n"}]}