{"thread":{"id":"44470","subject":"[PATCH 00/11] git worktree (re)move","startedAt":"2016-11-12T02:24:41Z","lastAt":"2016-12-19T20:58:27Z","messageCount":39,"participants":["Nguyễn Thái Ngọc Duy","Duy Nguyen","Junio C Hamano","Johannes Sixt","Stefan Beller"],"isPatch":true,"patchVersion":1,"patchTotal":11},"messages":[{"id":"305839","messageId":"20161112022337.13317-1-pclouds@gmail.com","threadId":"44470","inReplyTo":null,"subject":"[PATCH 00/11] git worktree (re)move","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-12T02:23:26Z","receivedAt":"2016-11-12T02:24:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This is mostly a resend from last time [1]. The main difference is\npatch 10/11 to prevent moving a worktree that contains submodules\nbecause these .git files may need rewritten to point to the right\nplace. I'm leaving the rewriting .git files for future (preferably\ndone by \"submodules guys\").\n\n[1] https://public-inbox.org/git/20160625075433.4608-1-pclouds@gmail.com/\n\nNguyễn Thái Ngọc Duy (11):\n  copy.c: import copy_file() from busybox\n  copy.c: delete unused code in copy_file()\n  copy.c: convert bb_(p)error_msg to error(_errno)\n  copy.c: style fix\n  copy.c: convert copy_file() to copy_dir_recursively()\n  worktree.c: add validate_worktree()\n  worktree.c: add update_worktree_location()\n  worktree move: new command\n  worktree move: accept destination as directory\n  worktree move: refuse to move worktrees with submodules\n  worktree remove: new command\n\n Documentation/git-worktree.txt         |  28 ++-\n builtin/worktree.c                     | 179 ++++++++++++++++\n cache.h                                |   1 +\n contrib/completion/git-completion.bash |   5 +-\n copy.c                                 | 369 +++++++++++++++++++++++++++++++++\n t/t2028-worktree-move.sh               |  56 +++++\n worktree.c                             |  84 ++++++++\n worktree.h                             |  11 +\n 8 files changed, 722 insertions(+), 11 deletions(-)\n\n-- \n2.8.2.524.g6ff3d78\n\n"},{"id":"305840","messageId":"20161112022337.13317-7-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161112022337.13317-1-pclouds@gmail.com","subject":"[PATCH 06/11] worktree.c: add validate_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-12T02:23:32Z","receivedAt":"2016-11-12T02:24:46Z","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. For example, if a worktree is moved manually, the\nworktree location recorded in $GIT_DIR/worktrees/.../gitdir is\nincorrect and we should not move that one.\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 f7869f8..7e15ec7 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -291,6 +291,69 @@ const char *is_worktree_locked(struct worktree *wt)\n \treturn wt->lock_reason;\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 (fspathcmp(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 int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 90e1311..e782ae5 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -50,6 +50,11 @@ extern int is_main_worktree(const struct worktree *wt);\n  */\n extern const char *is_worktree_locked(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(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n\n"},{"id":"305841","messageId":"20161112022337.13317-3-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161112022337.13317-1-pclouds@gmail.com","subject":"[PATCH 02/11] copy.c: delete unused code in copy_file()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-12T02:23:28Z","receivedAt":"2016-11-12T02:24:49Z","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\nSome of these are \"cp\" features that we don't need (for \"git worktree\nmove\"). Some do not make sense in source-control context (SELinux).\nSome probably need a reimplementation to better fit in (verbose\nprinting 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 79623ac..b7a87f1 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.2.524.g6ff3d78\n\n"},{"id":"305842","messageId":"20161112022337.13317-4-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161112022337.13317-1-pclouds@gmail.com","subject":"[PATCH 03/11] copy.c: convert bb_(p)error_msg to error(_errno)","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-12T02:23:29Z","receivedAt":"2016-11-12T02:24:56Z","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 b7a87f1..074b609 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 error_errno(_(\"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 error_errno(_(\"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 error_errno(_(\"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 error_errno(_(\"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\terror_errno(_(\"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 error_errno(_(\"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 = error_errno(_(\"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 error_errno(_(\"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\terror_errno(_(\"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 error_errno(_(\"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\terror_errno(_(\"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\terror_errno(_(\"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\terror_errno(_(\"can't preserve %s of '%s'\"), \"permissions\", dest);\n \t}\n \n \treturn retval;\n-- \n2.8.2.524.g6ff3d78\n\n"},{"id":"305843","messageId":"20161112022337.13317-8-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161112022337.13317-1-pclouds@gmail.com","subject":"[PATCH 07/11] worktree.c: add update_worktree_location()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-12T02:23:33Z","receivedAt":"2016-11-12T02:24:59Z","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 | 21 +++++++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 27 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 7e15ec7..db63758 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -354,6 +354,27 @@ 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 (fspathcmp(wt->path, path.buf)) {\n+\t\twrite_file(git_common_path(\"worktrees/%s/gitdir\",\n+\t\t\t\t\t   wt->id),\n+\t\t\t   \"%s/.git\", real_path(path.buf));\n+\t\tfree(wt->path);\n+\t\twt->path = strbuf_detach(&path, NULL);\n+\t\tret = 0;\n+\t}\n+\tstrbuf_release(&path);\n+\treturn ret;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex e782ae5..0477a3d 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -55,6 +55,12 @@ extern const char *is_worktree_locked(struct worktree *wt);\n  */\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(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n\n"},{"id":"305844","messageId":"20161112022337.13317-2-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161112022337.13317-1-pclouds@gmail.com","subject":"[PATCH 01/11] copy.c: import copy_file() from busybox","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-12T02:23:27Z","receivedAt":"2016-11-12T02:25:01Z","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 -\n2016-01-18). This is a no-op commit. More changes are needed before\nthis new code can compile.\n\nThis will be needed for implementing \"git worktree move\" where we have\nto move a directory recursively. We can implement it from scratch, but\nthen we will have to deal with corner cases (failure to move, circular\nsymlinks...). And delegating the task to \"/bin/mv\" takes a way the\nability to clean things up properly when things fail and we may have\nto deal with \"mv\" differences between platforms.\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 4de6a11..79623ac 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.2.524.g6ff3d78\n\n"},{"id":"305845","messageId":"20161112022337.13317-6-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161112022337.13317-1-pclouds@gmail.com","subject":"[PATCH 05/11] copy.c: convert copy_file() to copy_dir_recursively()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-12T02:23:31Z","receivedAt":"2016-11-12T02:25:02Z","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 a50a61a..a9a72f8 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1857,6 +1857,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 void fsync_or_die(int fd, const char *);\ndiff --git a/copy.c b/copy.c\nindex 60c7d8a..8471f7f 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 error_errno(_(\"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 error_errno(_(\"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 error_errno(_(\"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 error_errno(_(\"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 error_errno(_(\"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 error_errno(_(\"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 = error_errno(_(\"error writing to '%s'\"), dest);\n@@ -243,19 +355,28 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\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 error_errno(_(\"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\terror_errno(_(\"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 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\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.2.524.g6ff3d78\n\n"},{"id":"305846","messageId":"20161112022337.13317-5-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161112022337.13317-1-pclouds@gmail.com","subject":"[PATCH 04/11] copy.c: style fix","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-12T02:23:30Z","receivedAt":"2016-11-12T02:25:04Z","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 074b609..60c7d8a 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 error_errno(_(\"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\terror_errno(_(\"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 = error_errno(_(\"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\terror_errno(_(\"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 error_errno(_(\"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.2.524.g6ff3d78\n\n"},{"id":"305847","messageId":"20161112022337.13317-11-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161112022337.13317-1-pclouds@gmail.com","subject":"[PATCH 10/11] worktree move: refuse to move worktrees with submodules","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-12T02:23:36Z","receivedAt":"2016-11-12T02:25:07Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Submodules contains .git files with relative paths. After a worktree\nmove, these files need to be updated or they may point to nowhere.\n\nThis is a bandage patch to make sure \"worktree move\" don't break\npeople's worktrees by accident. When .git file update code is in\nplace, this validate_no_submodules() could be removed.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 23 +++++++++++++++++++++++\n 1 file changed, 23 insertions(+)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 307019c..11be345 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -523,6 +523,27 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)\n \treturn ret;\n }\n \n+static void validate_no_submodules(const struct worktree *wt)\n+{\n+\tstruct index_state istate = {0};\n+\tint i, found_submodules = 0;\n+\n+\tif (read_index_from(&istate, worktree_git_path(wt, \"index\")) > 0) {\n+\t\tfor (i = 0; i < istate.cache_nr; i++) {\n+\t\t\tstruct cache_entry *ce = istate.cache[i];\n+\n+\t\t\tif (S_ISGITLINK(ce->ce_mode)) {\n+\t\t\t\tfound_submodules = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\tdiscard_index(&istate);\n+\n+\tif (found_submodules)\n+\t\tdie(_(\"This working tree contains submodules and cannot be moved yet\"));\n+}\n+\n static int move_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -562,6 +583,8 @@ static int move_worktree(int ac, const char **av, const char *prefix)\n \tif (validate_worktree(wt, 0))\n \t\treturn -1;\n \n+\tvalidate_no_submodules(wt);\n+\n \tif (is_directory(dst.buf)) {\n \t\tconst char *sep = find_last_dir_sep(wt->path);\n \n-- \n2.8.2.524.g6ff3d78\n\n"},{"id":"305848","messageId":"20161112022337.13317-10-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161112022337.13317-1-pclouds@gmail.com","subject":"[PATCH 09/11] worktree move: accept destination as directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-12T02:23:35Z","receivedAt":"2016-11-12T02:25:09Z","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 c0d4a73..307019c 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -539,7 +539,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@@ -556,6 +562,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 = find_last_dir_sep(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.2.524.g6ff3d78\n\n"},{"id":"305849","messageId":"20161112022337.13317-9-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161112022337.13317-1-pclouds@gmail.com","subject":"[PATCH 08/11] worktree move: new command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-12T02:23:34Z","receivedAt":"2016-11-12T02:25:11Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"There are two options to move the main worktree, but both have\ncomplications, so it's not implemented yet. Anyway the options are:\n\n - convert the main worktree to a linked one and move it away, leave the\n   git repository where it is. The repo essentially becomes bare after\n   this move.\n\n - move the repository with the main worktree. The tricky part is make\n   sure all file descriptors to the repository are closed, or it may\n   fail on Windows.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         |  7 +++-\n builtin/worktree.c                     | 61 ++++++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  2 +-\n t/t2028-worktree-move.sh               | 30 +++++++++++++++++\n 4 files changed, 98 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 0aeb020..81f4fee 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] [--checkout] [-b <new-branch>] <path> [<branch>]\n 'git worktree list' [--porcelain]\n 'git worktree lock' [--reason <string>] <worktree>\n+'git worktree move' <worktree> <new-path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n 'git worktree unlock' <worktree>\n \n@@ -71,6 +72,11 @@ files from being pruned automatically. This also prevents it from\n being moved or deleted. Optionally, specify a reason for the lock\n with `--reason`.\n \n+move::\n+\n+Move a working tree to a new location. Note that the main working tree\n+cannot be moved yet.\n+\n prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n@@ -252,7 +258,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 5c4854d..c0d4a73 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 <worktree> <new-path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n \tN_(\"git worktree unlock <path>\"),\n \tNULL\n@@ -522,6 +523,64 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)\n \treturn ret;\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+\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+\twt = find_worktree(worktrees, prefix, av[0]);\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(wt->path, dst.buf) == -1) {\n+\t\tif (errno != EXDEV)\n+\t\t\tdie_errno(_(\"failed to move '%s' to '%s'\"),\n+\t\t\t\t  wt->path, dst.buf);\n+\n+\t\t/* second try.. */\n+\t\tif (copy_dir_recursively(wt->path, dst.buf))\n+\t\t\tdie(_(\"failed to copy '%s' to '%s'\"),\n+\t\t\t    wt->path, dst.buf);\n+\t\telse {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\t\tstrbuf_addstr(&sb, wt->path);\n+\t\t\t(void)remove_dir_recursively(&sb, 0);\n+\t\t\tstrbuf_release(&sb);\n+\t\t}\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@@ -544,5 +603,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 21016bf..613e03b 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2715,7 +2715,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 8298aaf..74070bd 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -59,4 +59,34 @@ 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+\tworktree $TRASH_DIRECTORY/elsewhere\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.2.524.g6ff3d78\n\n"},{"id":"305850","messageId":"20161112022337.13317-12-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161112022337.13317-1-pclouds@gmail.com","subject":"[PATCH 11/11] worktree remove: new command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-12T02:23:37Z","receivedAt":"2016-11-12T02:25:14Z","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         | 21 +++++----\n builtin/worktree.c                     | 78 ++++++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 ++-\n t/t2028-worktree-move.sh               | 26 ++++++++++++\n 4 files changed, 120 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 81f4fee..df0d551 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>] <worktree>\n 'git worktree move' <worktree> <new-path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n+'git worktree remove' [--force] <worktree>\n 'git worktree unlock' <worktree>\n \n DESCRIPTION\n@@ -81,6 +82,13 @@ prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n \n+remove::\n+\n+Remove a working tree. Only clean working trees (no untracked files\n+and no modification in tracked files) can be removed. Unclean working\n+trees can be removed with `--force`. The main working tree cannot be\n+removed.\n+\n unlock::\n \n Unlock a working tree, allowing it to be pruned, moved or deleted.\n@@ -90,9 +98,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 working tree. This option\n+\toverrides that safeguard.\n \n -b <new-branch>::\n -B <new-branch>::\n@@ -253,12 +262,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 11be345..60a6199 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 <worktree> <new-path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n+\tN_(\"git worktree remove [<options>] <worktree>\"),\n \tN_(\"git worktree unlock <path>\"),\n \tNULL\n };\n@@ -621,6 +622,81 @@ 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 sb = 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+\tworktrees = get_worktrees();\n+\twt = find_worktree(worktrees, prefix, av[0]);\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+\tstrbuf_addstr(&sb, wt->path);\n+\tif (remove_dir_recursively(&sb, 0)) {\n+\t\terror_errno(_(\"failed to delete '%s'\"), sb.buf);\n+\t\tret = -1;\n+\t}\n+\tstrbuf_reset(&sb);\n+\tstrbuf_addstr(&sb, git_common_path(\"worktrees/%s\", wt->id));\n+\tif (remove_dir_recursively(&sb, 0)) {\n+\t\terror_errno(_(\"failed to delete '%s'\"), sb.buf);\n+\t\tret = -1;\n+\t}\n+\tstrbuf_release(&sb);\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@@ -645,5 +721,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 613e03b..f6855af 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2715,7 +2715,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@@ -2733,6 +2733,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 74070bd..084acc6 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -89,4 +89,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.2.524.g6ff3d78\n\n"},{"id":"306041","messageId":"20161116130533.GA5393@ash","threadId":"44470","inReplyTo":"xmqqa8d4yts7.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 00/11] git worktree (re)move","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-16T13:05:34Z","receivedAt":"2016-11-16T13:05:45Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Sat, Nov 12, 2016 at 06:53:44PM -0800, Junio C Hamano wrote:\n> not ok 12 - move worktree\n> #\t\n> #\t\tgit worktree move source destination &&\n> #\t\ttest_path_is_missing source &&\n> #\t\tgit worktree list --porcelain | grep \"^worktree\" >actual &&\n> #\t\tcat <<-EOF >expected &&\n> #\t\tworktree $TRASH_DIRECTORY\n> #\t\tworktree $TRASH_DIRECTORY/destination\n> #\t\tworktree $TRASH_DIRECTORY/elsewhere\n> #\t\tEOF\n> #\t\ttest_cmp expected actual &&\n> #\t\tgit -C destination log --format=%s >actual2 &&\n> #\t\techo init >expected2 &&\n> #\t\ttest_cmp expected2 actual2\n\nI think I've seen this (i.e. 'expected' and 'actual' differ only in\nthe order of items) once after a rebase and ignored it, assuming\nsomething was changed during the rebase that caused this.\n\nThe following patch should fix it if that's the same thing you saw. I\ncould pile it on worktree-move series, or you can make it a separate\none-patch series. What's your preference?\n\n-- 8< --\nSubject: [PATCH] worktree list: keep the list sorted\n\nIt makes it easier to write tests for. But it should also be good for\nthe user since locating a worktree by eye would be easier once they\nnotice this.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 12 ++++++++++++\n 1 file changed, 12 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex f7869f8..fe92d6f 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -173,6 +173,13 @@ static void mark_current_worktree(struct worktree **worktrees)\n \tfree(git_dir);\n }\n \n+static int compare_worktree(const void *a_, const void *b_)\n+{\n+\tconst struct worktree *const *a = a_;\n+\tconst struct worktree *const *b = b_;\n+\treturn fspathcmp((*a)->path, (*b)->path);\n+}\n+\n struct worktree **get_worktrees(void)\n {\n \tstruct worktree **list = NULL;\n@@ -205,6 +212,11 @@ struct worktree **get_worktrees(void)\n \tALLOC_GROW(list, counter + 1, alloc);\n \tlist[counter] = NULL;\n \n+\t/*\n+\t * don't sort the first item (main worktree), which will\n+\t * always be the first\n+\t */\n+\tqsort(list + 1, counter - 1, sizeof(*list), compare_worktree);\n \tmark_current_worktree(list);\n \treturn list;\n }\n-- \n2.8.2.524.g6ff3d78\n\n-- 8< --\n"},{"id":"306042","messageId":"CACsJy8DOT_4N_48UaoYK61G_8JUaXbEs7N=n24CH2q1GN=++5g@mail.gmail.com","threadId":"44470","inReplyTo":"20161116130533.GA5393@ash","subject":"Re: [PATCH 00/11] git worktree (re)move","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-16T13:11:04Z","receivedAt":"2016-11-16T13:11:51Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Nov 16, 2016 at 8:05 PM, Duy Nguyen <pclouds@gmail.com> wrote:\n> diff --git a/worktree.c b/worktree.c\n> index f7869f8..fe92d6f 100644\n> --- a/worktree.c\n> +++ b/worktree.c\n> @@ -173,6 +173,13 @@ static void mark_current_worktree(struct worktree **worktrees)\n>         free(git_dir);\n>  }\n>\n> +static int compare_worktree(const void *a_, const void *b_)\n> +{\n> +       const struct worktree *const *a = a_;\n> +       const struct worktree *const *b = b_;\n> +       return fspathcmp((*a)->path, (*b)->path);\n> +}\n> +\n>  struct worktree **get_worktrees(void)\n>  {\n>         struct worktree **list = NULL;\n> @@ -205,6 +212,11 @@ struct worktree **get_worktrees(void)\n>         ALLOC_GROW(list, counter + 1, alloc);\n>         list[counter] = NULL;\n>\n> +       /*\n> +        * don't sort the first item (main worktree), which will\n> +        * always be the first\n> +        */\n\nUrgh.. I should review my patches more carefully before sending out :(\nThe main worktree could be missing (failing to parse HEAD) so I need a\nbetter trick than simply assuming the first item is the main worktree\nhere. Tests did not catch this, naturally..\n\n> +       qsort(list + 1, counter - 1, sizeof(*list), compare_worktree);\n>         mark_current_worktree(list);\n>         return list;\n>  }\n-- \nDuy\n"},{"id":"306065","messageId":"xmqqy40jqoqm.fsf@gitster.mtv.corp.google.com","threadId":"44470","inReplyTo":"20161116130533.GA5393@ash","subject":"Re: [PATCH 00/11] git worktree (re)move","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-16T18:11:13Z","receivedAt":"2016-11-16T18:13:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> The following patch should fix it if that's the same thing you saw. I\n> could pile it on worktree-move series, or you can make it a separate\n> one-patch series. What's your preference?\n\nGiving a stable output to the users is probably a good preparatory\nfix to what is already in the released versions, so it would make\nthe most sense to make it a separate patch to be applied to maint\nthen build the remainder on top.\n\nI do not think \"always show the primary first\" is necessarily a good\nidea (I would have expected an output more like \"git branch --list\").\n"},{"id":"306071","messageId":"xmqq8tsjqner.fsf@gitster.mtv.corp.google.com","threadId":"44470","inReplyTo":"xmqqy40jqoqm.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 00/11] git worktree (re)move","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-16T18:39:56Z","receivedAt":"2016-11-16T18:40:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Duy Nguyen <pclouds@gmail.com> writes:\n>\n>> The following patch should fix it if that's the same thing you saw. I\n>> could pile it on worktree-move series, or you can make it a separate\n>> one-patch series. What's your preference?\n>\n> Giving a stable output to the users is probably a good preparatory\n> fix to what is already in the released versions, so it would make\n> the most sense to make it a separate patch to be applied to maint\n> then build the remainder on top.\n>\n> I do not think \"always show the primary first\" is necessarily a good\n> idea (I would have expected an output more like \"git branch --list\").\n\nYikes, \"worktree list\" is documented like so:\n\n    List details of each worktree. The main worktree is listed\n    first, followed by each of the linked worktrees.\n\nIf the primary workree is somehow missing, it still should be listed\nfirst as missing---otherwise the readers of --porcelain readers will\nhave hard time telling what is going on.\n\n            \n"},{"id":"306480","messageId":"20161128094319.16176-1-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161112022337.13317-1-pclouds@gmail.com","subject":"[PATCH v2 00/11] git worktree (re)move","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-28T09:43:08Z","receivedAt":"2016-11-28T09:43:51Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"v2 contains some style fix and adapts to the new get_worktrees() api\nfrom nd/worktree-list-fixup (which means it can't be built without\nthat series).\n\nNguyễn Thái Ngọc Duy (11):\n  copy.c: import copy_file() from busybox\n  copy.c: delete unused code in copy_file()\n  copy.c: convert bb_(p)error_msg to error(_errno)\n  copy.c: style fix\n  copy.c: convert copy_file() to copy_dir_recursively()\n  worktree.c: add validate_worktree()\n  worktree.c: add update_worktree_location()\n  worktree move: new command\n  worktree move: accept destination as directory\n  worktree move: refuse to move worktrees with submodules\n  worktree remove: new command\n\n Documentation/git-worktree.txt         |  28 ++-\n builtin/worktree.c                     | 181 ++++++++++++++++\n cache.h                                |   1 +\n contrib/completion/git-completion.bash |   5 +-\n copy.c                                 | 369 +++++++++++++++++++++++++++++++++\n t/t2028-worktree-move.sh               |  56 +++++\n worktree.c                             |  84 ++++++++\n worktree.h                             |  11 +\n 8 files changed, 724 insertions(+), 11 deletions(-)\n\n-- \n2.8.2.524.g6ff3d78\n\n"},{"id":"306481","messageId":"20161128094319.16176-2-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161128094319.16176-1-pclouds@gmail.com","subject":"[PATCH v2 01/11] copy.c: import copy_file() from busybox","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-28T09:43:09Z","receivedAt":"2016-11-28T09:43:57Z","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 -\n2016-01-18). This is a no-op commit. More changes are needed before\nthis new code can compile.\n\nThis will be needed for implementing \"git worktree move\" where we have\nto move a directory recursively. We can implement it from scratch, but\nthen we will have to deal with corner cases (failure to move, circular\nsymlinks...). And delegating the task to \"/bin/mv\" takes a way the\nability to clean things up properly when things fail and we may have\nto deal with \"mv\" differences between platforms.\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 4de6a11..79623ac 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.2.524.g6ff3d78\n\n"},{"id":"306482","messageId":"20161128094319.16176-3-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161128094319.16176-1-pclouds@gmail.com","subject":"[PATCH v2 02/11] copy.c: delete unused code in copy_file()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-28T09:43:10Z","receivedAt":"2016-11-28T09:44:03Z","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\nSome of these are \"cp\" features that we don't need (for \"git worktree\nmove\"). Some do not make sense in source-control context (SELinux).\nSome probably need a reimplementation to better fit in (verbose\nprinting 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 79623ac..b7a87f1 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.2.524.g6ff3d78\n\n"},{"id":"306483","messageId":"20161128094319.16176-4-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161128094319.16176-1-pclouds@gmail.com","subject":"[PATCH v2 03/11] copy.c: convert bb_(p)error_msg to error(_errno)","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-28T09:43:11Z","receivedAt":"2016-11-28T09:44:09Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n copy.c | 85 ++++++++++++++++++++++++------------------------------------------\n 1 file changed, 31 insertions(+), 54 deletions(-)\n\ndiff --git a/copy.c b/copy.c\nindex b7a87f1..074b609 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 error_errno(_(\"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 error_errno(_(\"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 error_errno(_(\"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 error_errno(_(\"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\terror_errno(_(\"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 error_errno(_(\"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 = error_errno(_(\"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 error_errno(_(\"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\terror_errno(_(\"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 error_errno(_(\"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\terror_errno(_(\"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\terror_errno(_(\"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\terror_errno(_(\"can't preserve %s of '%s'\"), \"permissions\", dest);\n \t}\n \n \treturn retval;\n-- \n2.8.2.524.g6ff3d78\n\n"},{"id":"306484","messageId":"20161128094319.16176-5-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161128094319.16176-1-pclouds@gmail.com","subject":"[PATCH v2 04/11] copy.c: style fix","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-28T09:43:12Z","receivedAt":"2016-11-28T09:44:11Z","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 074b609..60c7d8a 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 error_errno(_(\"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\terror_errno(_(\"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 = error_errno(_(\"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\terror_errno(_(\"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 error_errno(_(\"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.2.524.g6ff3d78\n\n"},{"id":"306485","messageId":"20161128094319.16176-6-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161128094319.16176-1-pclouds@gmail.com","subject":"[PATCH v2 05/11] copy.c: convert copy_file() to copy_dir_recursively()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-28T09:43:13Z","receivedAt":"2016-11-28T09:44:15Z","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 a50a61a..a9a72f8 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1857,6 +1857,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 void fsync_or_die(int fd, const char *);\ndiff --git a/copy.c b/copy.c\nindex 60c7d8a..8ef4b23 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 ? \"/\" : \"\", 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 error_errno(_(\"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 error_errno(_(\"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 error_errno(_(\"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 error_errno(_(\"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 error_errno(_(\"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 error_errno(_(\"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 = error_errno(_(\"error writing to '%s'\"), dest);\n@@ -243,19 +355,28 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\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 error_errno(_(\"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\terror_errno(_(\"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 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags)\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.2.524.g6ff3d78\n\n"},{"id":"306486","messageId":"20161128094319.16176-7-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161128094319.16176-1-pclouds@gmail.com","subject":"[PATCH v2 06/11] worktree.c: add validate_worktree()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-28T09:43:14Z","receivedAt":"2016-11-28T09:44:21Z","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. For example, if a worktree is moved manually, the\nworktree location recorded in $GIT_DIR/worktrees/.../gitdir is\nincorrect and we should not move that one.\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 eb61212..929072a 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -291,6 +291,69 @@ const char *is_worktree_locked(struct worktree *wt)\n \treturn wt->lock_reason;\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 (fspathcmp(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 int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex d59ce1f..4433db2 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -52,6 +52,11 @@ extern int is_main_worktree(const struct worktree *wt);\n  */\n extern const char *is_worktree_locked(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(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n\n"},{"id":"306487","messageId":"20161128094319.16176-8-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161128094319.16176-1-pclouds@gmail.com","subject":"[PATCH v2 07/11] worktree.c: add update_worktree_location()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-28T09:43:15Z","receivedAt":"2016-11-28T09:44:25Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n worktree.c | 21 +++++++++++++++++++++\n worktree.h |  6 ++++++\n 2 files changed, 27 insertions(+)\n\ndiff --git a/worktree.c b/worktree.c\nindex 929072a..7684951 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -354,6 +354,27 @@ 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 (fspathcmp(wt->path, path.buf)) {\n+\t\twrite_file(git_common_path(\"worktrees/%s/gitdir\",\n+\t\t\t\t\t   wt->id),\n+\t\t\t   \"%s/.git\", real_path(path.buf));\n+\t\tfree(wt->path);\n+\t\twt->path = strbuf_detach(&path, NULL);\n+\t\tret = 0;\n+\t}\n+\tstrbuf_release(&path);\n+\treturn ret;\n+}\n+\n int is_worktree_being_rebased(const struct worktree *wt,\n \t\t\t      const char *target)\n {\ndiff --git a/worktree.h b/worktree.h\nindex 4433db2..1ee03f4 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -57,6 +57,12 @@ extern const char *is_worktree_locked(struct worktree *wt);\n  */\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(s)\n  */\n-- \n2.8.2.524.g6ff3d78\n\n"},{"id":"306488","messageId":"20161128094319.16176-9-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161128094319.16176-1-pclouds@gmail.com","subject":"[PATCH v2 08/11] worktree move: new command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-28T09:43:16Z","receivedAt":"2016-11-28T09:44:34Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"There are two options to move the main worktree, but both have\ncomplications, so it's not implemented yet. Anyway the options are:\n\n - convert the main worktree to a linked one and move it away, leave the\n   git repository where it is. The repo essentially becomes bare after\n   this move.\n\n - move the repository with the main worktree. The tricky part is make\n   sure all file descriptors to the repository are closed, or it may\n   fail on Windows.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-worktree.txt         |  7 +++-\n builtin/worktree.c                     | 62 ++++++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  2 +-\n t/t2028-worktree-move.sh               | 30 ++++++++++++++++\n 4 files changed, 99 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex e257c19..1310513 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] [--checkout] [-b <new-branch>] <path> [<branch>]\n 'git worktree list' [--porcelain]\n 'git worktree lock' [--reason <string>] <worktree>\n+'git worktree move' <worktree> <new-path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n 'git worktree unlock' <worktree>\n \n@@ -71,6 +72,11 @@ files from being pruned automatically. This also prevents it from\n being moved or deleted. Optionally, specify a reason for the lock\n with `--reason`.\n \n+move::\n+\n+Move a working tree to a new location. Note that the main working tree\n+cannot be moved yet.\n+\n prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n@@ -252,7 +258,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 9a97e37..f114965 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 <worktree> <new-path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n \tN_(\"git worktree unlock <path>\"),\n \tNULL\n@@ -524,6 +525,65 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)\n \treturn ret;\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+\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(0);\n+\twt = find_worktree(worktrees, prefix, av[0]);\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+\treason = is_worktree_locked(wt);\n+\tif (reason) {\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(wt->path, dst.buf) == -1) {\n+\t\tif (errno != EXDEV)\n+\t\t\tdie_errno(_(\"failed to move '%s' to '%s'\"),\n+\t\t\t\t  wt->path, dst.buf);\n+\n+\t\t/* second try.. */\n+\t\tif (copy_dir_recursively(wt->path, dst.buf))\n+\t\t\tdie(_(\"failed to copy '%s' to '%s'\"),\n+\t\t\t    wt->path, dst.buf);\n+\t\telse {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\t\tstrbuf_addstr(&sb, wt->path);\n+\t\t\t(void)remove_dir_recursively(&sb, 0);\n+\t\t\tstrbuf_release(&sb);\n+\t\t}\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@@ -546,5 +606,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 21016bf..613e03b 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2715,7 +2715,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 8298aaf..74070bd 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -59,4 +59,34 @@ 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+\tworktree $TRASH_DIRECTORY/elsewhere\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.2.524.g6ff3d78\n\n"},{"id":"306489","messageId":"20161128094319.16176-10-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161128094319.16176-1-pclouds@gmail.com","subject":"[PATCH v2 09/11] worktree move: accept destination as directory","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-28T09:43:17Z","receivedAt":"2016-11-28T09:44:37Z","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 f114965..f732a74 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -541,7 +541,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(0);\n@@ -559,6 +565,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 = find_last_dir_sep(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.2.524.g6ff3d78\n\n"},{"id":"306490","messageId":"20161128094319.16176-11-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161128094319.16176-1-pclouds@gmail.com","subject":"[PATCH v2 10/11] worktree move: refuse to move worktrees with submodules","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-28T09:43:18Z","receivedAt":"2016-11-28T09:44:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Submodules contains .git files with relative paths. After a worktree\nmove, these files need to be updated or they may point to nowhere.\n\nThis is a bandage patch to make sure \"worktree move\" don't break\npeople's worktrees by accident. When .git file update code is in\nplace, this validate_no_submodules() could be removed.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/worktree.c | 23 +++++++++++++++++++++++\n 1 file changed, 23 insertions(+)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex f732a74..e36e4dc 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -525,6 +525,27 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)\n \treturn ret;\n }\n \n+static void validate_no_submodules(const struct worktree *wt)\n+{\n+\tstruct index_state istate = {0};\n+\tint i, found_submodules = 0;\n+\n+\tif (read_index_from(&istate, worktree_git_path(wt, \"index\")) > 0) {\n+\t\tfor (i = 0; i < istate.cache_nr; i++) {\n+\t\t\tstruct cache_entry *ce = istate.cache[i];\n+\n+\t\t\tif (S_ISGITLINK(ce->ce_mode)) {\n+\t\t\t\tfound_submodules = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\tdiscard_index(&istate);\n+\n+\tif (found_submodules)\n+\t\tdie(_(\"This working tree contains submodules and cannot be moved yet\"));\n+}\n+\n static int move_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -565,6 +586,8 @@ static int move_worktree(int ac, const char **av, const char *prefix)\n \tif (validate_worktree(wt, 0))\n \t\treturn -1;\n \n+\tvalidate_no_submodules(wt);\n+\n \tif (is_directory(dst.buf)) {\n \t\tconst char *sep = find_last_dir_sep(wt->path);\n \n-- \n2.8.2.524.g6ff3d78\n\n"},{"id":"306491","messageId":"20161128094319.16176-12-pclouds@gmail.com","threadId":"44470","inReplyTo":"20161128094319.16176-1-pclouds@gmail.com","subject":"[PATCH v2 11/11] worktree remove: new command","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-28T09:43:19Z","receivedAt":"2016-11-28T09:44: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         | 21 +++++----\n builtin/worktree.c                     | 79 ++++++++++++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  5 ++-\n t/t2028-worktree-move.sh               | 26 +++++++++++\n 4 files changed, 121 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 1310513..bbde6b8 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>] <worktree>\n 'git worktree move' <worktree> <new-path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n+'git worktree remove' [--force] <worktree>\n 'git worktree unlock' <worktree>\n \n DESCRIPTION\n@@ -81,6 +82,13 @@ prune::\n \n Prune working tree information in $GIT_DIR/worktrees.\n \n+remove::\n+\n+Remove a working tree. Only clean working trees (no untracked files\n+and no modification in tracked files) can be removed. Unclean working\n+trees can be removed with `--force`. The main working tree cannot be\n+removed.\n+\n unlock::\n \n Unlock a working tree, allowing it to be pruned, moved or deleted.\n@@ -90,9 +98,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 working tree. This option\n+\toverrides that safeguard.\n \n -b <new-branch>::\n -B <new-branch>::\n@@ -253,12 +262,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 e36e4dc..3167409 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 <worktree> <new-path>\"),\n \tN_(\"git worktree prune [<options>]\"),\n+\tN_(\"git worktree remove [<options>] <worktree>\"),\n \tN_(\"git worktree unlock <path>\"),\n \tNULL\n };\n@@ -624,6 +625,82 @@ 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 sb = 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+\tworktrees = get_worktrees(0);\n+\twt = find_worktree(worktrees, prefix, av[0]);\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+\treason = is_worktree_locked(wt);\n+\tif (reason) {\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+\tstrbuf_addstr(&sb, wt->path);\n+\tif (remove_dir_recursively(&sb, 0)) {\n+\t\terror_errno(_(\"failed to delete '%s'\"), sb.buf);\n+\t\tret = -1;\n+\t}\n+\tstrbuf_reset(&sb);\n+\tstrbuf_addstr(&sb, git_common_path(\"worktrees/%s\", wt->id));\n+\tif (remove_dir_recursively(&sb, 0)) {\n+\t\terror_errno(_(\"failed to delete '%s'\"), sb.buf);\n+\t\tret = -1;\n+\t}\n+\tstrbuf_release(&sb);\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@@ -648,5 +725,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 613e03b..f6855af 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2715,7 +2715,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@@ -2733,6 +2733,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 74070bd..084acc6 100755\n--- a/t/t2028-worktree-move.sh\n+++ b/t/t2028-worktree-move.sh\n@@ -89,4 +89,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.2.524.g6ff3d78\n\n"},{"id":"306515","messageId":"xmqqwpfnidxm.fsf@gitster.mtv.corp.google.com","threadId":"44470","inReplyTo":"20161128094319.16176-1-pclouds@gmail.com","subject":"Re: [PATCH v2 00/11] git worktree (re)move","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-28T19:48:05Z","receivedAt":"2016-11-28T19:48:12Z","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> v2 contains some style fix and adapts to the new get_worktrees() api\n> from nd/worktree-list-fixup (which means it can't be built without\n> that series).\n>\n> Nguyễn Thái Ngọc Duy (11):\n>   copy.c: import copy_file() from busybox\n>   copy.c: delete unused code in copy_file()\n>   copy.c: convert bb_(p)error_msg to error(_errno)\n>   copy.c: style fix\n>   copy.c: convert copy_file() to copy_dir_recursively()\n>   worktree.c: add validate_worktree()\n>   worktree.c: add update_worktree_location()\n>   worktree move: new command\n>   worktree move: accept destination as directory\n>   worktree move: refuse to move worktrees with submodules\n>   worktree remove: new command\n>\n>  Documentation/git-worktree.txt         |  28 ++-\n>  builtin/worktree.c                     | 181 ++++++++++++++++\n>  cache.h                                |   1 +\n>  contrib/completion/git-completion.bash |   5 +-\n>  copy.c                                 | 369 +++++++++++++++++++++++++++++++++\n>  t/t2028-worktree-move.sh               |  56 +++++\n>  worktree.c                             |  84 ++++++++\n>  worktree.h                             |  11 +\n>  8 files changed, 724 insertions(+), 11 deletions(-)\n\nDoes this round address the issue raised in \n\n  http://public-inbox.org/git/alpine.DEB.2.20.1611161041040.3746@virtualbox\n\nby Dscho?\n\nEven if you are not tracking a fifo, for example, your working tree\nmay have one created in t/trash* directory during testing, and\nletting platform \"cp -r\" taking care of it (if that is possible---I\ndidn't look at the code that calls busybox copy to see if you are\ndoing something exotic or just blindly copying everything in the\ndirectory) may turn out to be a more portable way to do this, and I\nsuspect that the cost of copying one whole-tree would dominate the\nrun_command() overhead.\n\n"},{"id":"306516","messageId":"xmqqshqbicga.fsf@gitster.mtv.corp.google.com","threadId":"44470","inReplyTo":"xmqqwpfnidxm.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v2 00/11] git worktree (re)move","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-28T20:20:05Z","receivedAt":"2016-11-28T20:20:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Does this round address the issue raised in \n>\n>   http://public-inbox.org/git/alpine.DEB.2.20.1611161041040.3746@virtualbox\n>\n> by Dscho?\n>\n> Even if you are not tracking a fifo, for example, your working tree\n> may have one created in t/trash* directory during testing, and\n> letting platform \"cp -r\" taking care of it (if that is possible---I\n> didn't look at the code that calls busybox copy to see if you are\n> doing something exotic or just blindly copying everything in the\n> directory) may turn out to be a more portable way to do this, and I\n> suspect that the cost of copying one whole-tree would dominate the\n> run_command() overhead.\n\nPlease do not take the above as me saying \"you must spawn the\nplatform cp -r\".  \n\nA more traditional alternative solution seen on this list is to work\ntogether, leveraging expertise of each participant.  From the build\nlog Dscho gave us, it seems that his Windows port lack at least\nPOSIX emulation for lchown, mknod, utimes and chown.  It is hard to\ndecide without involving Windows expert what the best way to deal\nwith it in the code (e.g. To stub or #ifdef out these calls?\nProvide suitable emulation in compat/?  Something else?), and what\nthings other than these four are still missing.\n\n"},{"id":"306518","messageId":"73836804-a581-85a4-b0c0-2aa46a7f9b8d@kdbg.org","threadId":"44470","inReplyTo":"xmqqshqbicga.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v2 00/11] git worktree (re)move","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2016-11-28T21:25:54Z","receivedAt":"2016-11-28T21:26:08Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 28.11.2016 um 21:20 schrieb Junio C Hamano:\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Does this round address the issue raised in\n>>\n>>   http://public-inbox.org/git/alpine.DEB.2.20.1611161041040.3746@virtualbox\n>>\n>> by Dscho?\n>>\n>> Even if you are not tracking a fifo, for example, your working tree\n>> may have one created in t/trash* directory during testing, and\n>> letting platform \"cp -r\" taking care of it (if that is possible---I\n>> didn't look at the code that calls busybox copy to see if you are\n>> doing something exotic or just blindly copying everything in the\n>> directory) may turn out to be a more portable way to do this, and I\n>> suspect that the cost of copying one whole-tree would dominate the\n>> run_command() overhead.\n>\n> Please do not take the above as me saying \"you must spawn the\n> platform cp -r\".\n\ncopy_dir_recursively is used in 'worktree move' when the move is across \nfile systems. My stance on it is to punt in this case. *I* would not \ntrust Git, or any other program that is not *specifically* made to copy \na whole directory structure, to get all cases right when a simple \nrename() is not sufficent. And, uh, oh, it does a \nremove_dir_recursively() after it has finshed copying. No, Git is not a \ntool to move directories, thank you very much!\n\n-- Hannes\n\n"},{"id":"306541","messageId":"CACsJy8DQDPzZGJXLpTVHVFUeupPpp5e=b9z4m7xceJWrxPfF3Q@mail.gmail.com","threadId":"44470","inReplyTo":"xmqqshqbicga.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v2 00/11] git worktree (re)move","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-29T12:08:16Z","receivedAt":"2016-11-29T12:08:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Nov 29, 2016 at 3:20 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Does this round address the issue raised in\n>>\n>>   http://public-inbox.org/git/alpine.DEB.2.20.1611161041040.3746@virtualbox\n>>\n>> by Dscho?\n\nIt does not (and is sort of expected), quoting from the commit message\n\n    copy.c: convert copy_file() to copy_dir_recursively()\n\n    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\n    comes from busybox, POSIXy (or even Linuxy) behavior is expected. More\n    changes may be needed for Windows support.\n\nI could \"#ifdef WINDOWS return -ENOSYS\" for now, which would make it\nbuild. \"git worktree move\" won't work on Windows of course...\n\n>> Even if you are not tracking a fifo, for example, your working tree\n>> may have one created in t/trash* directory during testing, and\n>> letting platform \"cp -r\" taking care of it (if that is possible---I\n>> didn't look at the code that calls busybox copy to see if you are\n>> doing something exotic or just blindly copying everything in the\n>> directory) may turn out to be a more portable way to do this, and I\n>> suspect that the cost of copying one whole-tree would dominate the\n>> run_command() overhead.\n>\n> Please do not take the above as me saying \"you must spawn the\n> platform cp -r\".\n\nFor the the record this was my first move but it will make it much\nharder to handle errors, and there's no native \"cp\" on Windows.\n-- \nDuy\n"},{"id":"306542","messageId":"CACsJy8DhAe3Br06p8i46UUhtnq5GzJGxHsp6AG4iXcg4P+g4nA@mail.gmail.com","threadId":"44470","inReplyTo":"73836804-a581-85a4-b0c0-2aa46a7f9b8d@kdbg.org","subject":"Re: [PATCH v2 00/11] git worktree (re)move","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-29T12:17:47Z","receivedAt":"2016-11-29T12:18:22Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Nov 29, 2016 at 4:25 AM, Johannes Sixt <j6t@kdbg.org> wrote:\n> Am 28.11.2016 um 21:20 schrieb Junio C Hamano:\n>>\n>> Junio C Hamano <gitster@pobox.com> writes:\n>>\n>>> Does this round address the issue raised in\n>>>\n>>>\n>>> http://public-inbox.org/git/alpine.DEB.2.20.1611161041040.3746@virtualbox\n>>>\n>>> by Dscho?\n>>>\n>>> Even if you are not tracking a fifo, for example, your working tree\n>>> may have one created in t/trash* directory during testing, and\n>>> letting platform \"cp -r\" taking care of it (if that is possible---I\n>>> didn't look at the code that calls busybox copy to see if you are\n>>> doing something exotic or just blindly copying everything in the\n>>> directory) may turn out to be a more portable way to do this, and I\n>>> suspect that the cost of copying one whole-tree would dominate the\n>>> run_command() overhead.\n>>\n>>\n>> Please do not take the above as me saying \"you must spawn the\n>> platform cp -r\".\n>\n>\n> copy_dir_recursively is used in 'worktree move' when the move is across file\n> systems. My stance on it is to punt in this case. *I* would not trust Git,\n> or any other program that is not *specifically* made to copy a whole\n> directory structure, to get all cases right when a simple rename() is not\n> sufficent.\n\nThis is why I did not write new  copy code. The code was from busybox,\nprobably battle tested for many years now. Of course bugs can slip in\nwhen I integrated it to git.\n\n> And, uh, oh, it does a remove_dir_recursively() after it has\n> finshed copying. No, Git is not a tool to move directories, thank you very\n> much!\n\nI guess you won't like my (unsent) patch for moving .git dir either\n;-) which does make me nervous. The thing is, these operations require\nsome more modification in .git. We can't ask the user to \"move this\ndirectory yourself, then come back to me and I will fix up the rest in\n.git\". First step \"only support moving within the same filesystem\"\nworks for me. But I don't know how rename() works on Windows...\n-- \nDuy\n"},{"id":"306545","messageId":"20161129135646.GA19797@ash","threadId":"44470","inReplyTo":"CACsJy8DQDPzZGJXLpTVHVFUeupPpp5e=b9z4m7xceJWrxPfF3Q@mail.gmail.com","subject":"Re: [PATCH v2 00/11] git worktree (re)move","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-29T13:56:46Z","receivedAt":"2016-11-29T13:56:59Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Tue, Nov 29, 2016 at 07:08:16PM +0700, Duy Nguyen wrote:\n> On Tue, Nov 29, 2016 at 3:20 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> > Junio C Hamano <gitster@pobox.com> writes:\n> >\n> >> Does this round address the issue raised in\n> >>\n> >>   http://public-inbox.org/git/alpine.DEB.2.20.1611161041040.3746@virtualbox\n> >>\n> >> by Dscho?\n> \n> It does not (and is sort of expected), quoting from the commit message\n> \n>     copy.c: convert copy_file() to copy_dir_recursively()\n> \n>     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\n>     comes from busybox, POSIXy (or even Linuxy) behavior is expected. More\n>     changes may be needed for Windows support.\n> \n> I could \"#ifdef WINDOWS return -ENOSYS\" for now, which would make it\n> build. \"git worktree move\" won't work on Windows of course...\n\nAnother way, as pointed out by j6t, is go with \"move within filesystem\nonly\", at least at the first step. Which is probably a good idea\nanyway so we can concentrate on git-specific stuff before going to\nminor and complicated copy/move details.\n\nIf you drop all the \"copy.c: \" patches and squash this to \"worktree\nmove: new command\", and if Windows rename() can move directories, then\ngit should build and new tests pass.\n\n-- 8< --\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex f114965..d8d0127 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -569,9 +569,9 @@ static int move_worktree(int ac, const char **av, const char *prefix)\n \t\t\t\t  wt->path, dst.buf);\n \n \t\t/* second try.. */\n-\t\tif (copy_dir_recursively(wt->path, dst.buf))\n-\t\t\tdie(_(\"failed to copy '%s' to '%s'\"),\n-\t\t\t    wt->path, dst.buf);\n+\t\tif (copy_dir_recursively(dst.buf, wt->path))\n+\t\t\tdie_errno(_(\"failed to copy '%s' to '%s'\"),\n+\t\t\t\t  wt->path, dst.buf);\n \t\telse {\n \t\t\tstruct strbuf sb = STRBUF_INIT;\n \ndiff --git a/cache.h b/cache.h\nindex a50a61a..2d4edf6 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1857,6 +1857,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 *dst, const char *src);\n \n extern void write_or_die(int fd, const void *buf, size_t count);\n extern void fsync_or_die(int fd, const char *);\ndiff --git a/copy.c b/copy.c\nindex 4de6a11..b232aec 100644\n--- a/copy.c\n+++ b/copy.c\n@@ -65,3 +65,9 @@ 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+int copy_dir_recursively(const char *dst, const char *src)\n+{\n+\terrno = ENOSYS;\n+\treturn -1;\n+}\n-- 8< --\n"},{"id":"306561","messageId":"xmqqfumaf5lf.fsf@gitster.mtv.corp.google.com","threadId":"44470","inReplyTo":"20161129135646.GA19797@ash","subject":"Re: [PATCH v2 00/11] git worktree (re)move","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-11-29T19:28:44Z","receivedAt":"2016-11-29T19:28:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> Another way, as pointed out by j6t, is go with \"move within filesystem\n> only\", at least at the first step. Which is probably a good idea\n> anyway so we can concentrate on git-specific stuff before going to\n> minor and complicated copy/move details.\n\nYup, that is a very sensible approach.\n"},{"id":"306572","messageId":"c73ec2ee-eed0-414c-63a1-a1bd5c864cef@kdbg.org","threadId":"44470","inReplyTo":"20161129135646.GA19797@ash","subject":"Re: [PATCH v2 00/11] git worktree (re)move","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2016-11-29T21:14:00Z","receivedAt":"2016-11-29T21:14:08Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 29.11.2016 um 14:56 schrieb Duy Nguyen:\n> If you drop all the \"copy.c: \" patches and squash this to \"worktree\n> move: new command\", and if Windows rename() can move directories, then\n> git should build and new tests pass.\n\nThanks! rename() can move directories on Windows, provided that \n*nothing* inside the directory is in any form of use by any process, \nparticularly also not as the \"current working directory\" (as per getcwd()).\n\n> diff --git a/copy.c b/copy.c\n> index 4de6a11..b232aec 100644\n> --- a/copy.c\n> +++ b/copy.c\n> @@ -65,3 +65,9 @@ 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> +int copy_dir_recursively(const char *dst, const char *src)\n> +{\n> +\terrno = ENOSYS;\n> +\treturn -1;\n> +}\n\nAn error message \"cannot move directories across devices\" or something \nwould be preferable over \"Function not implemented\", of course. Or did \nyou mean to set errno = EXDEV?\n\n-- Hannes\n\n"},{"id":"306581","messageId":"CACsJy8DNJw66n+h5uKtxGHibKJRFOQP5=V35gKo3bRarx2vDsA@mail.gmail.com","threadId":"44470","inReplyTo":"c73ec2ee-eed0-414c-63a1-a1bd5c864cef@kdbg.org","subject":"Re: [PATCH v2 00/11] git worktree (re)move","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-11-30T00:09:08Z","receivedAt":"2016-11-30T00:09:45Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Nov 30, 2016 at 4:14 AM, Johannes Sixt <j6t@kdbg.org> wrote:\n>> diff --git a/copy.c b/copy.c\n>> index 4de6a11..b232aec 100644\n>> --- a/copy.c\n>> +++ b/copy.c\n>> @@ -65,3 +65,9 @@ int copy_file_with_time(const char *dst, const char\n>> *src, int mode)\n>>                 return copy_times(dst, src);\n>>         return status;\n>>  }\n>> +\n>> +int copy_dir_recursively(const char *dst, const char *src)\n>> +{\n>> +       errno = ENOSYS;\n>> +       return -1;\n>> +}\n>\n>\n> An error message \"cannot move directories across devices\" or something would\n> be preferable over \"Function not implemented\", of course. Or did you mean to\n> set errno = EXDEV?\n\nThe exact message is not super important right now. Though I'm\nthinking of adding move_directory() that is a wrapper of rename(). We\ncan die(\"cannot move directories across devices\") then and hopefully\nbe able to move across devices at some point.\n-- \nDuy\n"},{"id":"306582","messageId":"CAGZ79kb21Ewm4Lnr87mpk-YdYiUaQUKtJQm+Voqq_4wfRExgYQ@mail.gmail.com","threadId":"44470","inReplyTo":"xmqqfumaf5lf.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v2 00/11] git worktree (re)move","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-11-30T00:18:36Z","receivedAt":"2016-11-30T00:18:42Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Tue, Nov 29, 2016 at 11:28 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Duy Nguyen <pclouds@gmail.com> writes:\n>\n>> Another way, as pointed out by j6t, is go with \"move within filesystem\n>> only\", at least at the first step. Which is probably a good idea\n>> anyway so we can concentrate on git-specific stuff before going to\n>> minor and complicated copy/move details.\n>\n> Yup, that is a very sensible approach.\n\nIn case you decided to go another way than a plain rename, please\nmake it easy to reuse (e.g. move_directory_safely exposed to the\nrest of the codebase). I'd find use for that in the series currently\nqueued as:\n\n  sb/submodule-intern-gitdir\n\nwhich moves git directories of submodules around (from inside the\nsubmodule/.git to the superprojects .git/modules/<name>)\n\nThanks,\nStefan\n"},{"id":"308093","messageId":"CAGZ79kbgJ=1j9V1QxmdzoKx60ZnqsJet2VwDH72Kz5PrDArOnA@mail.gmail.com","threadId":"44470","inReplyTo":"20161128094319.16176-11-pclouds@gmail.com","subject":"Re: [PATCH v2 10/11] worktree move: refuse to move worktrees with submodules","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-12-19T20:57:24Z","receivedAt":"2016-12-19T20:58:27Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Nov 28, 2016 at 1:43 AM, Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:\n> +\n> +                       if (S_ISGITLINK(ce->ce_mode)) {\n> +                               found_submodules = 1;\n> +                               break;\n> +                       }\n\nWhile I applaud being careful with submodules, I think this may be a bit\novereager, because empty (both not initialized as well as not populated)\nsubmodules are fine.\n\nIn origin/bw/grep-recurse-submodules^6 you find\n    int is_submodule_populated(const char *path)\n\nthat could be useful here, i.e. I'd imagine you'd change the condition to\n\n    if (S_ISGITLINK(ce->ce_mode)\n        && !is_submodule_populated(ce->name)) {\n        ...\n\nI guess that can come as a fixup later though.\n"}]}