{"thread":{"id":"42017","subject":"[PATCH 00/25] worktree lock, move, remove and unlock","startedAt":"2016-04-13T13:15:21Z","lastAt":"2016-05-12T05:58:51Z","messageCount":35,"participants":["Nguyễn Thái Ngọc Duy","Junio C Hamano","Duy Nguyen","Eric Sunshine","Johannes Sixt"],"isPatch":true,"patchVersion":1,"patchTotal":25},"messages":[{"id":"283344","messageId":"1460553346-12985-1-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":null,"subject":"[PATCH 00/25] worktree lock, move, remove and unlock","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:21Z","receivedAt":"2016-04-13T13:15:21Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is basically a resend from last time, which happened during rc\ntime. It adds 4 more commands, basically cleaning up the \"TODO\" list\nin git-worktree.txt.\n\nSo far I've only actually used move and remove (and maybe unlock once\nbecause worktree-add failed on me and I had to unlock it manually).\nAnd I don't get to move worktrees a lot either so not really extensive\ntesting.\n\n  [01/25] usage.c: move format processing out of die_errno()\n  [02/25] usage.c: add sys_error() that prints strerror() automatically\n  [03/25] copy.c: import copy_file() from busybox\n  [04/25] copy.c: delete unused code in copy_file()\n  [05/25] copy.c: convert bb_(p)error_msg to (sys_)error\n  [06/25] copy.c: style fix\n  [07/25] copy.c: convert copy_file() to copy_dir_recursively()\n  [08/25] completion: support git-worktree\n  [09/25] git-worktree.txt: keep subcommand listing in alphabetical order\n  [10/25] path.c: add git_common_path() and strbuf_git_common_path()\n  [11/25] worktree.c: use is_dot_or_dotdot()\n  [12/25] worktree.c: store \"id\" instead of \"git_dir\"\n  [13/25] worktree.c: add clear_worktree()\n  [14/25] worktree.c: add find_worktree_by_path()\n  [15/25] worktree.c: add is_main_worktree()\n  [16/25] worktree.c: add validate_worktree()\n  [17/25] worktree.c: add update_worktree_location()\n  [18/25] worktree.c: add is_worktree_locked()\n  [19/25] worktree: avoid 0{40}, too many zeroes, hard to read\n  [20/25] worktree: simplify prefixing paths\n  [21/25] worktree: add \"lock\" command\n  [22/25] worktree: add \"unlock\" command\n  [23/25] worktree: add \"move\" commmand\n  [24/25] worktree move: accept destination as directory\n  [25/25] worktree: add \"remove\" command\n\nTotal 11 files changed, 1028 insertions(+), 48 deletions(-)\n"},{"id":"283345","messageId":"1460553346-12985-2-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 01/25] usage.c: move format processing out of die_errno()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:22Z","receivedAt":"2016-04-13T13:15: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 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.8.0.rc0.210.gd302cd2\n"},{"id":"283346","messageId":"1460553346-12985-3-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 02/25] usage.c: add sys_error() that prints strerror() automatically","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:23Z","receivedAt":"2016-04-13T13:15: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 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 4743954..e8e7765 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -412,6 +412,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.8.0.rc0.210.gd302cd2\n"},{"id":"283347","messageId":"1460553346-12985-4-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 03/25] copy.c: import copy_file() from busybox","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:24Z","receivedAt":"2016-04-13T13:15:24Z","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.8.0.rc0.210.gd302cd2\n"},{"id":"283348","messageId":"1460553346-12985-5-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 04/25] copy.c: delete unused code in copy_file()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:25Z","receivedAt":"2016-04-13T13:15:25Z","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.8.0.rc0.210.gd302cd2\n"},{"id":"283349","messageId":"1460553346-12985-6-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 05/25] copy.c: convert bb_(p)error_msg to (sys_)error","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:26Z","receivedAt":"2016-04-13T13:15:26Z","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.8.0.rc0.210.gd302cd2\n"},{"id":"283352","messageId":"1460553346-12985-7-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 06/25] copy.c: style fix","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:27Z","receivedAt":"2016-04-13T13:15: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 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.8.0.rc0.210.gd302cd2\n"},{"id":"283350","messageId":"1460553346-12985-8-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 07/25] copy.c: convert copy_file() to copy_dir_recursively()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:28Z","receivedAt":"2016-04-13T13:15:28Z","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 9f09540..213a8d3 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1677,6 +1677,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.8.0.rc0.210.gd302cd2\n"},{"id":"283351","messageId":"1460553346-12985-9-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 08/25] completion: support git-worktree","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:29Z","receivedAt":"2016-04-13T13:15:29Z","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 e3918c8..f66db2d 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.8.0.rc0.210.gd302cd2\n"},{"id":"283355","messageId":"1460553346-12985-10-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 09/25] git-worktree.txt: keep subcommand listing in alphabetical order","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:30Z","receivedAt":"2016-04-13T13:15:30Z","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 38b5609..575f899 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.8.0.rc0.210.gd302cd2\n"},{"id":"283353","messageId":"1460553346-12985-11-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 10/25] 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-04-13T13:15:31Z","receivedAt":"2016-04-13T13:15: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 cache.h |  3 +++\n path.c  | 29 +++++++++++++++++++++++++++++\n 2 files changed, 32 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 213a8d3..c81f654 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -767,11 +767,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 969b494..e315dd6 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.8.0.rc0.210.gd302cd2\n"},{"id":"283354","messageId":"1460553346-12985-12-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 11/25] worktree.c: use is_dot_or_dotdot()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:32Z","receivedAt":"2016-04-13T13:15: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 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 575f899..7ff66fa 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.8.0.rc0.210.gd302cd2\n"},{"id":"283356","messageId":"1460553346-12985-13-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 12/25] worktree.c: store \"id\" instead of \"git_dir\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:33Z","receivedAt":"2016-04-13T13:15:33Z","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.8.0.rc0.210.gd302cd2\n"},{"id":"283358","messageId":"1460553346-12985-14-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 13/25] worktree.c: add clear_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:34Z","receivedAt":"2016-04-13T13:15: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 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.8.0.rc0.210.gd302cd2\n"},{"id":"283357","messageId":"1460553346-12985-15-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 14/25] worktree.c: add find_worktree_by_path()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:35Z","receivedAt":"2016-04-13T13:15:35Z","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.8.0.rc0.210.gd302cd2\n"},{"id":"283359","messageId":"1460553346-12985-16-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 15/25] worktree.c: add is_main_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:36Z","receivedAt":"2016-04-13T13:15: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 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.8.0.rc0.210.gd302cd2\n"},{"id":"283361","messageId":"1460553346-12985-17-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 16/25] worktree.c: add validate_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:37Z","receivedAt":"2016-04-13T13:15:37Z","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.8.0.rc0.210.gd302cd2\n"},{"id":"283362","messageId":"1460553346-12985-18-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 17/25] worktree.c: add update_worktree_location()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:38Z","receivedAt":"2016-04-13T13:15: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 | 25 +++++++++++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 31 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 28195b1..e526e25 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 = 0;\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.8.0.rc0.210.gd302cd2\n"},{"id":"283360","messageId":"1460553346-12985-19-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 18/25] worktree.c: add is_worktree_locked()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:39Z","receivedAt":"2016-04-13T13:15: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 | 18 ++++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 24 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex e526e25..37eec09 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.8.0.rc0.210.gd302cd2\n"},{"id":"283363","messageId":"1460553346-12985-20-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 19/25] worktree: avoid 0{40}, too many zeroes, hard to read","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:40Z","receivedAt":"2016-04-13T13:15: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 builtin/worktree.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 7ff66fa..e041d7f 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -261,7 +261,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.8.0.rc0.210.gd302cd2\n"},{"id":"283364","messageId":"1460553346-12985-21-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 20/25] worktree: simplify prefixing paths","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:41Z","receivedAt":"2016-04-13T13:15:41Z","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 e041d7f..a9e91ab 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -330,7 +330,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@@ -460,6 +460,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.8.0.rc0.210.gd302cd2\n"},{"id":"283365","messageId":"1460553346-12985-22-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 21/25] worktree: add \"lock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:42Z","receivedAt":"2016-04-13T13:15: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 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 a9e91ab..736caff 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@@ -452,6 +453,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@@ -468,5 +507,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 f66db2d..cdae408 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.8.0.rc0.210.gd302cd2\n"},{"id":"283367","messageId":"1460553346-12985-23-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 22/25] worktree: add \"unlock\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:43Z","receivedAt":"2016-04-13T13:15: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 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 736caff..13f4083 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@@ -491,6 +492,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@@ -509,5 +538,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 cdae408..d7d92ac 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.8.0.rc0.210.gd302cd2\n"},{"id":"283366","messageId":"1460553346-12985-24-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 23/25] worktree: add \"move\" commmand","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:44Z","receivedAt":"2016-04-13T13:15: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 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 13f4083..a988913 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@@ -520,6 +521,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@@ -540,5 +598,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 d7d92ac..022d990 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.8.0.rc0.210.gd302cd2\n"},{"id":"283368","messageId":"1460553346-12985-25-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 24/25] worktree move: accept destination as directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:45Z","receivedAt":"2016-04-13T13:15:45Z","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 a988913..5402a4e 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -538,7 +538,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@@ -558,6 +564,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.8.0.rc0.210.gd302cd2\n"},{"id":"283369","messageId":"1460553346-12985-26-git-send-email-pclouds@gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 25/25] worktree: add \"remove\" command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-13T13:15:46Z","receivedAt":"2016-04-13T13:15: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 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 5402a4e..084f8fd 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@@ -595,6 +596,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@@ -617,5 +696,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 022d990..6fb45ea 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.8.0.rc0.210.gd302cd2\n"},{"id":"283452","messageId":"xmqqfuuoi35o.fsf@gitster.mtv.corp.google.com","threadId":"42017","inReplyTo":"1460553346-12985-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 00/25] worktree lock, move, remove and unlock","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-14T16:08:35Z","receivedAt":"2016-04-14T16:08:35Z","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 basically a resend from last time, which happened during rc\n> time.\n\nIt would have made them a much more pleasant read if you re-read\nthem during that time and added the missing \"why\" to many of the\ncommit log message.\n\n> It adds 4 more commands, basically cleaning up the \"TODO\" list\n> in git-worktree.txt.\n>\n> So far I've only actually used move and remove (and maybe unlock once\n> because worktree-add failed on me and I had to unlock it manually).\n> And I don't get to move worktrees a lot either so not really extensive\n> testing.\n>\n>   [01/25] usage.c: move format processing out of die_errno()\n>   [02/25] usage.c: add sys_error() that prints strerror() automatically\n\nThis looks parallel to die_errno(); isn't error_errno() a better name?\n\n>   [03/25] copy.c: import copy_file() from busybox\n>   [04/25] copy.c: delete unused code in copy_file()\n>   [05/25] copy.c: convert bb_(p)error_msg to (sys_)error\n>   [06/25] copy.c: style fix\n>   [07/25] copy.c: convert copy_file() to copy_dir_recursively()\n\nSomewhere among these, there needs to be a single overview of why we\nwant \"cp\" implementation of busybox, e.g. what part of \"cp\" we want?\nthe whole thing?  or \"because this is to be used from this and that\ncodepaths to make copy of these things, we only need these parts and\ncan remove other features like this and that?\"\n\n>   [08/25] completion: support git-worktree\n>   [09/25] git-worktree.txt: keep subcommand listing in alphabetical order\n\nI'd defer doing this immediately before 21 if I were doing this\nseries.\n\nOffhand, I think it makes it easier to look things up in an\nalphabetical list in the description section, but it probably is\neasier to get an overview if the synopsis part groups things along\nconcepts and/or lists things along the order in typical workflows\n(e.g. \"create, list, rename, remove\" would be a list along\nlifecycle), not alphabetical.\n\nBut such judgement is better done when we know what are the final\nelements that are to be listed, i.e. closer to where new things are\nintroduced.  This is especially true, as the log messages of patches\nleading to 21 are all sketchy and do not give the readers a good\nbirds-view picture.\n\n>   [10/25] path.c: add git_common_path() and strbuf_git_common_path()\n\nWrite \"what for\" when adding a new API function.  \"Wanting to learn\nX is very common and there are many existing code or new code that\nrepeats sequence A, B and C to compute it.  Give a helper function\nto do so to refactor the existing codepaths\" or something like that?\n\nMove some part of [12/25] that is not about \"store 'id'\" but is\nabout this refactoring to this commit.\n\n>   [11/25] worktree.c: use is_dot_or_dotdot()\n>   [12/25] worktree.c: store \"id\" instead of \"git_dir\"\n\nIt is better to have these (and other conceptually \"small and\nobvious\" ones) as preliminary clean-up to make the main body of the\nseries that may need to go through iterations smaller.\n\n>   [13/25] worktree.c: add clear_worktree()\n>   [14/25] worktree.c: add find_worktree_by_path()\n>   [15/25] worktree.c: add is_main_worktree()\n>   [16/25] worktree.c: add validate_worktree()\n>   [17/25] worktree.c: add update_worktree_location()\n>   [18/25] worktree.c: add is_worktree_locked()\n>   [19/25] worktree: avoid 0{40}, too many zeroes, hard to read\n>   [20/25] worktree: simplify prefixing paths\n>   [21/25] worktree: add \"lock\" command\n>   [22/25] worktree: add \"unlock\" command\n>   [23/25] worktree: add \"move\" commmand\n>   [24/25] worktree move: accept destination as directory\n>   [25/25] worktree: add \"remove\" command\n>\n> Total 11 files changed, 1028 insertions(+), 48 deletions(-)\n"},{"id":"283508","messageId":"CACsJy8DK863+rgseeYrQJ1db+xSeFfm8WsNvGBmJwD_pr1yMJQ@mail.gmail.com","threadId":"42017","inReplyTo":"xmqqfuuoi35o.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 00/25] worktree lock, move, remove and unlock","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-04-15T00:40:30Z","receivedAt":"2016-04-15T00:40:30Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Apr 14, 2016 at 11:08 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> This is basically a resend from last time, which happened during rc\n>> time.\n>\n> It would have made them a much more pleasant read if you re-read\n> them during that time and added the missing \"why\" to many of the\n> commit log message.\n\nHmm... I thought I didn't receive any comments last time.\n\n>> It adds 4 more commands, basically cleaning up the \"TODO\" list\n>> in git-worktree.txt.\n>>\n>> So far I've only actually used move and remove (and maybe unlock once\n>> because worktree-add failed on me and I had to unlock it manually).\n>> And I don't get to move worktrees a lot either so not really extensive\n>> testing.\n>>\n>>   [01/25] usage.c: move format processing out of die_errno()\n>>   [02/25] usage.c: add sys_error() that prints strerror() automatically\n>\n> This looks parallel to die_errno(); isn't error_errno() a better name?\n\nTo me, no. Duplicating the \"err\" looks weird. error_no() does not look\ngood either. Though there's a couple of warning(..., strerror()),\nwhich could become warning_errno(). Then maybe error_errno() makes\nmore sense because all three follow the same naming convention.\n\n>>   [03/25] copy.c: import copy_file() from busybox\n>>   [04/25] copy.c: delete unused code in copy_file()\n>>   [05/25] copy.c: convert bb_(p)error_msg to (sys_)error\n>>   [06/25] copy.c: style fix\n>>   [07/25] copy.c: convert copy_file() to copy_dir_recursively()\n>\n> Somewhere among these, there needs to be a single overview of why we\n> want \"cp\" implementation of busybox, e.g. what part of \"cp\" we want?\n> the whole thing?  or \"because this is to be used from this and that\n> codepaths to make copy of these things, we only need these parts and\n> can remove other features like this and that?\"\n\nWe need directory move functionality. In the worst case when\nrename(<dir>, <dir>) wouldn't do the job, we have to fall back to\ncopying the whole directory over, preserving metadata as much as\npossible, then delete the old directory. I don't want to write new\ncode for this because I think it shows in busybox code that there are\npitfalls in dealing with filesystems. And I don't want to fall back to\n/bin/cp either. Windows won't have one (long term I hope we won't need\nmsys) and *nix is not famous for consistent command line behavior.\nPlus, if we want to clean up a failed cp operation, it's easier to do\nit in core by keeping track of what files have been copied.\n\n>>   [08/25] completion: support git-worktree\n>>   [09/25] git-worktree.txt: keep subcommand listing in alphabetical order\n>\n> I'd defer doing this immediately before 21 if I were doing this\n> series.\n\nWill do.\n\n> Offhand, I think it makes it easier to look things up in an\n> alphabetical list in the description section, but it probably is\n> easier to get an overview if the synopsis part groups things along\n> concepts and/or lists things along the order in typical workflows\n> (e.g. \"create, list, rename, remove\" would be a list along\n> lifecycle), not alphabetical.\n>\n> But such judgement is better done when we know what are the final\n> elements that are to be listed, i.e. closer to where new things are\n> introduced.  This is especially true, as the log messages of patches\n> leading to 21 are all sketchy and do not give the readers a good\n> birds-view picture.\n\nWell. I think all the commands are there now at the end of this\nseries. So we have add, list, prune, move, remove, lock and unlock. I\nguess we can group list/add/move/remove together and the rest as\nsupport commands. I might add \"git worktree migrate\" for converting\nbetween worktree v0 and v1. But it's not clear yet.\n\n>>   [10/25] path.c: add git_common_path() and strbuf_git_common_path()\n>\n> Write \"what for\" when adding a new API function.  \"Wanting to learn\n> X is very common and there are many existing code or new code that\n> repeats sequence A, B and C to compute it.  Give a helper function\n> to do so to refactor the existing codepaths\" or something like that?\n\nPretty much for convenience. Will add some more in commit message.\n\n> Move some part of [12/25] that is not about \"store 'id'\" but is\n> about this refactoring to this commit.\n>\n>>   [11/25] worktree.c: use is_dot_or_dotdot()\n>>   [12/25] worktree.c: store \"id\" instead of \"git_dir\"\n>\n> It is better to have these (and other conceptually \"small and\n> obvious\" ones) as preliminary clean-up to make the main body of the\n> series that may need to go through iterations smaller.\n\nSure.\n-- \nDuy\n"},{"id":"283510","messageId":"CAPc5daWHxyb5viwabR1-Tmx+7FNH4yxQJL7Oqd1FU9XXs4bNog@mail.gmail.com","threadId":"42017","inReplyTo":"CACsJy8DK863+rgseeYrQJ1db+xSeFfm8WsNvGBmJwD_pr1yMJQ@mail.gmail.com","subject":"Re: [PATCH 00/25] worktree lock, move, remove and unlock","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-15T01:21:57Z","receivedAt":"2016-04-15T01:21:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"On Thu, Apr 14, 2016 at 5:40 PM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Thu, Apr 14, 2016 at 11:08 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>>\n>>> This is basically a resend from last time, which happened during rc\n>>> time.\n>>\n>> It would have made them a much more pleasant read if you re-read\n>> them during that time and added the missing \"why\" to many of the\n>> commit log message.\n>\n> Hmm... I thought I didn't receive any comments last time.\n\nI think you've been here long enough to know that absense of comments\ndoes not mean anything more than that: lack of interest at that moment in time.\n\n>> This looks parallel to die_errno(); isn't error_errno() a better name?\n>\n> To me, no. Duplicating the \"err\" looks weird. error_no() does not look\n> good either. Though there's a couple of warning(..., strerror()),\n> which could become warning_errno(). Then maybe error_errno() makes\n> more sense because all three follow the same naming convention.\n\nSo in the end error_errno() would be a better name to you after all ;-)\nI agree the stuttering sound coming from repeating error twice feels\nsomewhat odd, but warning_errno() would be so natural and obvious\nfuture direction, so...\n\n>>>   [03/25] copy.c: import copy_file() from busybox\n>>>   [04/25] copy.c: delete unused code in copy_file()\n>>>   [05/25] copy.c: convert bb_(p)error_msg to (sys_)error\n>>>   [06/25] copy.c: style fix\n>>>   [07/25] copy.c: convert copy_file() to copy_dir_recursively()\n>>\n>> Somewhere among these, there needs to be a single overview of why we\n>> want \"cp\" implementation of busybox, e.g. what part of \"cp\" we want?\n>> the whole thing?  or \"because this is to be used from this and that\n>> codepaths to make copy of these things, we only need these parts and\n>> can remove other features like this and that?\"\n>\n> We need directory move functionality.\n\nYeah, I know all that but you do not want to explain that to me only when\nI asked in a mailing list response. You want to get into the habit of having\nthat in the log message to help reviewers, not just me, before they ask such\na question.\n\n>> But such judgement is better done when we know what are the final\n>> elements that are to be listed, i.e. closer to where new things are\n>> introduced.  This is especially true, as the log messages of patches\n>> leading to 21 are all sketchy and do not give the readers a good\n>> birds-view picture.\n>\n> Well. I think all the commands are there now at the end of this\n> series. So we have add, list, prune, move, remove, lock and unlock. I\n> guess we can group list/add/move/remove together and the rest as\n> support commands. I might add \"git worktree migrate\" for converting\n> between worktree v0 and v1. But it's not clear yet.\n\nJust so that there is no confusion, I am not opposed to reordering.\nI was just saying that the decision on what the right ordering is can\nbe easier to make when the readers more or less know what the final\nset of things in the set are, and because that becomes only clear when\nthey start reading 21, and because the log messages of patches\nleading to 21 do not show the direction clearly enough, it is hard to\nmake that judgment at this point so early in the series.\n"},{"id":"286148","messageId":"CAPig+cRtajkynLFj5Fknd72QQ=Eqevh1T9i9MvApwXfNmtMAFQ@mail.gmail.com","threadId":"42017","inReplyTo":"1460553346-12985-25-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 24/25] worktree move: accept destination as directory","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-11T04:43:16Z","receivedAt":"2016-05-11T04:43:16Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Apr 13, 2016 at 9:15 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> Similar to \"mv a b/\", which is actually \"mv a b/a\", we extract basename\n> of source worktree and create a directory of the same name at\n> destination if dst path is a directory.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> @@ -538,7 +538,13 @@ static int move_worktree(int ac, const char **av, const char *prefix)\n> -       if (file_exists(dst.buf))\n> +       if (is_directory(dst.buf))\n> +               /*\n> +                * keep going, dst will be appended after we get the\n> +                * source's absolute path\n> +                */\n> +               ;\n> +       else if (file_exists(dst.buf))\n>                 die(_(\"target '%s' already exists\"), av[1]);\n> @@ -558,6 +564,17 @@ static int move_worktree(int ac, const char **av, const char *prefix)\n> +       if (is_directory(dst.buf)) {\n> +               const char *sep = strrchr(wt->path, '/');\n\nDoes this need to take Windows into account? Perhaps git_find_last_dir_sep()?\n\n> +\n> +               if (!sep)\n> +                       die(_(\"could not figure out destination name from '%s'\"),\n> +                           wt->path);\n> +               strbuf_addstr(&dst, sep);\n> +               if (file_exists(dst.buf))\n> +                       die(_(\"target '%s' already exists\"), dst.buf);\n> +       }\n"},{"id":"286264","messageId":"CACsJy8CCAan9ALxULPFeGSU7wsfwbrywRWFr4Hsjx3=PGwosLA@mail.gmail.com","threadId":"42017","inReplyTo":"CAPig+cRtajkynLFj5Fknd72QQ=Eqevh1T9i9MvApwXfNmtMAFQ@mail.gmail.com","subject":"Re: [PATCH 24/25] worktree move: accept destination as directory","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-05-11T13:34:10Z","receivedAt":"2016-05-11T13:34:10Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, May 11, 2016 at 11:43 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> On Wed, Apr 13, 2016 at 9:15 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>> Similar to \"mv a b/\", which is actually \"mv a b/a\", we extract basename\n>> of source worktree and create a directory of the same name at\n>> destination if dst path is a directory.\n>>\n>> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n>> ---\n>> diff --git a/builtin/worktree.c b/builtin/worktree.c\n>> @@ -538,7 +538,13 @@ static int move_worktree(int ac, const char **av, const char *prefix)\n>> -       if (file_exists(dst.buf))\n>> +       if (is_directory(dst.buf))\n>> +               /*\n>> +                * keep going, dst will be appended after we get the\n>> +                * source's absolute path\n>> +                */\n>> +               ;\n>> +       else if (file_exists(dst.buf))\n>>                 die(_(\"target '%s' already exists\"), av[1]);\n>> @@ -558,6 +564,17 @@ static int move_worktree(int ac, const char **av, const char *prefix)\n>> +       if (is_directory(dst.buf)) {\n>> +               const char *sep = strrchr(wt->path, '/');\n>\n> Does this need to take Windows into account?\n\nwt->path comes from $GIT_DIR/worktrees/xxx/gitdir, which normally uses\nforward slashes, so we should be safe. We already rely on forward\nslashes in get_linked_worktree()\n\n> Perhaps git_find_last_dir_sep()?\n\nBut this is probably a good thing to do anyway, to be more robust in\nfuture. But it could confuse the reader later on why it's necessary\nwhen backward slashes can't exist in wt->path. I don't know. Maybe\njust have a comment that backward slashes can't never appear here?\n\nThere is also a potential problem with find_worktree_by_path(). I was\ncounting on real_path() to normalize paths and could simply do\nstrcmp_icase (or its new name, fspathcmp). But real_path() does not\nseem to convert unify slashes. I will need to have a closer look at\nthis. Hopefully prefix_filename() already makes sure everything uses\nforward slashes. Or maybe we could improve fspathcmp to see '/' and\n'\\' the same thing on Windows.\n-- \nDuy\n"},{"id":"286278","messageId":"CAPig+cTB8tdPo=wd5UdB84owKJ6c5hj6H9d4_YGDRecBdUE0vA@mail.gmail.com","threadId":"42017","inReplyTo":"CACsJy8CCAan9ALxULPFeGSU7wsfwbrywRWFr4Hsjx3=PGwosLA@mail.gmail.com","subject":"Re: [PATCH 24/25] worktree move: accept destination as directory","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2016-05-11T17:32:23Z","receivedAt":"2016-05-11T17:32:23Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, May 11, 2016 at 9:34 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n> On Wed, May 11, 2016 at 11:43 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>> On Wed, Apr 13, 2016 at 9:15 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>>> +       if (is_directory(dst.buf)) {\n>>> +               const char *sep = strrchr(wt->path, '/');\n>>\n>> Does this need to take Windows into account?\n>\n> wt->path comes from $GIT_DIR/worktrees/xxx/gitdir, which normally uses\n> forward slashes, so we should be safe. We already rely on forward\n> slashes in get_linked_worktree()\n>\n>> Perhaps git_find_last_dir_sep()?\n>\n> But this is probably a good thing to do anyway, to be more robust in\n> future. But it could confuse the reader later on why it's necessary\n> when backward slashes can't exist in wt->path. I don't know. Maybe\n> just have a comment that backward slashes can't never appear here?\n\nAs this path is read from a file git itself creates, and if we know\nthat it will always contain forward slashes, then I agree that it\ncould be potentially confusing to later readers to see\ngit_find_last_dir_sep(). So, keeping it as-is seems correct.\n\nNot sure if it needs a comment. I reviewed this rather quickly since\n(I think) you plan on re-rolling it and I'm far behind on my reviews.\nConsequently, I didn't check the existing code, and reviewed only\nwithin the context of the patch itself. If the end result is that it's\nclear from reading the code that it will always contain forward\nslashes, then a comment would be redundant. You could perhaps mention\nin the commit message that the slash will always be forward, which\nshould satisfy future reviewers and readers of the code once its in\nthe tree.\n\n> There is also a potential problem with find_worktree_by_path(). I was\n> counting on real_path() to normalize paths and could simply do\n> strcmp_icase (or its new name, fspathcmp). But real_path() does not\n> seem to convert unify slashes. I will need to have a closer look at\n> this. Hopefully prefix_filename() already makes sure everything uses\n> forward slashes. Or maybe we could improve fspathcmp to see '/' and\n> '\\' the same thing on Windows.\n\nIf we look at fspathcmp() as a function which performs whatever magic\nis needed to make comparisons work on a platform/filesystem, then it\nmight indeed be reasonable to enhance it to recognize '/' and '\\' as\nequivalent (with possible caveats for Windows corner cases).\n"},{"id":"286281","messageId":"57337ACF.3020402@kdbg.org","threadId":"42017","inReplyTo":"CAPig+cTB8tdPo=wd5UdB84owKJ6c5hj6H9d4_YGDRecBdUE0vA@mail.gmail.com","subject":"Re: [PATCH 24/25] worktree move: accept destination as directory","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2016-05-11T18:32:47Z","receivedAt":"2016-05-11T18:32:47Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 11.05.2016 um 19:32 schrieb Eric Sunshine:\n> On Wed, May 11, 2016 at 9:34 AM, Duy Nguyen <pclouds@gmail.com> wrote:\n>> On Wed, May 11, 2016 at 11:43 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>>> On Wed, Apr 13, 2016 at 9:15 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n>>>> +       if (is_directory(dst.buf)) {\n>>>> +               const char *sep = strrchr(wt->path, '/');\n>>>\n>>> Does this need to take Windows into account?\n>>\n>> wt->path comes from $GIT_DIR/worktrees/xxx/gitdir, which normally uses\n>> forward slashes, so we should be safe. We already rely on forward\n>> slashes in get_linked_worktree()\n>>\n>>> Perhaps git_find_last_dir_sep()?\n>>\n>> But this is probably a good thing to do anyway, to be more robust in\n>> future. But it could confuse the reader later on why it's necessary\n>> when backward slashes can't exist in wt->path. I don't know. Maybe\n>> just have a comment that backward slashes can't never appear here?\n>\n> As this path is read from a file git itself creates, and if we know\n> that it will always contain forward slashes, then I agree that it\n> could be potentially confusing to later readers to see\n> git_find_last_dir_sep(). So, keeping it as-is seems correct.\n\nPlease allow me to disagree. There should not be any assumption that a \npath uses forward slashes as directory separator, except when the path is\n\n- a pathspec\n- a ref\n- a path found in the object database including the index\n\nIn particular, everything concerning paths in the file system (including \npaths pointing to Git's own files) must not assume forward slashes.\n\nWe do convert backslashes to forward slashes in a number of places, but \nthis is only for cosmetic reasons, not to maintain an invariant.\n\n> If we look at fspathcmp() as a function which performs whatever magic\n> is needed to make comparisons work on a platform/filesystem, then it\n> might indeed be reasonable to enhance it to recognize '/' and '\\' as\n> equivalent (with possible caveats for Windows corner cases).\n\nThat sounds reasonable.\n\n-- Hannes\n"},{"id":"286290","messageId":"xmqqbn4cjn2g.fsf@gitster.mtv.corp.google.com","threadId":"42017","inReplyTo":"57337ACF.3020402@kdbg.org","subject":"Re: [PATCH 24/25] worktree move: accept destination as directory","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-05-11T21:34:47Z","receivedAt":"2016-05-11T21:34:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <j6t@kdbg.org> writes:\n\n>> As this path is read from a file git itself creates, and if we know\n>> that it will always contain forward slashes, then I agree that it\n>> could be potentially confusing to later readers to see\n>> git_find_last_dir_sep(). So, keeping it as-is seems correct.\n>\n> Please allow me to disagree. There should not be any assumption that a\n> path uses forward slashes as directory separator, except when the path\n> is\n>\n> - a pathspec\n> - a ref\n> - a path found in the object database including the index\n\nI think standardising on one way for what we write out would give\nless hassle to users.  The human end users should not be opening\nthese files in their editors and futzing with their contents, but\nthere are third-party tools and reimplementations of Git.  Forcing\nthem to be prepared for input with slashes and backslashes does not\nmake much sense to me.\n\nIs there an upside for us to accept both slashes in this file?\n"},{"id":"286314","messageId":"57341B9B.9010600@kdbg.org","threadId":"42017","inReplyTo":"xmqqbn4cjn2g.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 24/25] worktree move: accept destination as directory","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2016-05-12T05:58:51Z","receivedAt":"2016-05-12T05:58:51Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 11.05.2016 um 23:34 schrieb Junio C Hamano:\n> Johannes Sixt <j6t@kdbg.org> writes:\n>\n>>> As this path is read from a file git itself creates, and if we know\n>>> that it will always contain forward slashes, then I agree that it\n>>> could be potentially confusing to later readers to see\n>>> git_find_last_dir_sep(). So, keeping it as-is seems correct.\n>>\n>> Please allow me to disagree. There should not be any assumption that a\n>> path uses forward slashes as directory separator, except when the path\n>> is\n>>\n>> - a pathspec\n>> - a ref\n>> - a path found in the object database including the index\n>\n> I think standardising on one way for what we write out would give\n> less hassle to users.  The human end users should not be opening\n> these files in their editors and futzing with their contents, but\n> there are third-party tools and reimplementations of Git.  Forcing\n> them to be prepared for input with slashes and backslashes does not\n> make much sense to me.\n\nIt is the opposite: We would force other tools to write slashes even \nthough on Windows both types of slashes are allowed as directory separators.\n\n> Is there an upside for us to accept both slashes in this file?\n\nObviously, yes: We can accept what third-party tools write.\n\nBTW, we also have to accept absolute paths in the file, no? Then we \ncannot assume that the path begins with a slash on Windows; absolute \npaths would come in drive letter style on Windows.\n\n-- Hannes\n"}]}