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