From 0ed94c751916fac43cc8a54a7eeb8786f9f7629c Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Thu, 20 Apr 2017 01:08:05 +0200 Subject: [PATCH] worktree: move subcommand This subcommands allow moving a linked worktree to a different location, updating administrative files as necessary. Currently only moves supported by rename(2) are supported. Signed-off-by: Giuseppe Bilotta --- Documentation/git-worktree.txt | 12 +++- builtin/worktree.c | 124 +++++++++++++++++++++++++++++++++ t/t2028-worktree-move.sh | 11 +++ 3 files changed, 146 insertions(+), 1 deletion(-) diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 553cf8413f..0ec3881c22 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -12,6 +12,7 @@ SYNOPSIS 'git worktree add' [-f] [--detach] [--checkout] [-b ] [] 'git worktree list' [--porcelain] 'git worktree lock' [--reason ] +'git worktree move' 'git worktree prune' [-n] [-v] [--expire ] 'git worktree unlock' @@ -34,7 +35,9 @@ The working tree's administrative files in the repository (see `git worktree prune` in the main or any linked working tree to clean up any stale administrative files. -If you move a linked working tree, you need to manually update the +To move a linked working tree to a different location, you can use +`git worktree move`, which takes care of updating the administrative files +as well. If you move one manually, you need to manually update the administrative files so that they do not get pruned automatically. See section "DETAILS" for more information. @@ -71,6 +74,13 @@ files from being pruned automatically. This also prevents it from being moved or deleted. Optionally, specify a reason for the lock with `--reason`. +move:: + +Move a worktree from its current location to a new path, updating the +administrative files as necessary. The new path must currently reside +on the same filesystem as the old one, per the limitations of the +`rename(2)` function. + prune:: Prune working tree information in $GIT_DIR/worktrees. diff --git a/builtin/worktree.c b/builtin/worktree.c index 9993ded41a..f25084899f 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -15,6 +15,7 @@ static const char * const worktree_usage[] = { N_("git worktree add [] []"), N_("git worktree list []"), N_("git worktree lock [] "), + N_("git worktree move [] "), N_("git worktree prune []"), N_("git worktree unlock "), NULL @@ -525,6 +526,127 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) return ret; } +static int move_worktree(int ac, const char **av, const char *prefix) +{ + const char *lock_reason; + struct option options[] = { + OPT__DRY_RUN(&show_only, N_("do not move, show only")), + OPT_END() + }; + struct worktree **worktrees, *wt; + char *path = NULL; + const char *name; + struct strbuf sb_path = STRBUF_INIT; + int len, ret = 0, ret2 = 0; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac != 2) + usage_with_options(worktree_usage, options); + + worktrees = get_worktrees(0); + + wt = find_worktree(worktrees, prefix, av[1]); + if (wt) + die(_("worktree '%s' exists already"), av[1]); + + path = prefix_filename(prefix, av[1]); + if (file_exists(path)) + die(_("'%s' already exists"), path); + + name = worktree_basename(path, &len); + strbuf_add_real_path(&sb_path, path); + + wt = find_worktree(worktrees, prefix, av[0]); + if (!wt) + die(_("'%s' is not a working tree"), av[0]); + /* Moving the main worktree could potentially be doable by changing all + * references from the associated worktrees too, but we don't support + * it presently */ + if (is_main_worktree(wt)) + die(_("The main working tree cannot be moved")); + + lock_reason = is_worktree_locked(wt); + if (lock_reason) { + if (*lock_reason) + die(_("'%s' is locked, reason: %s"), av[0], lock_reason); + die(_("'%s' is locked"), av[0]); + } + + if (show_only) { + printf("would lock %s\n", wt->id); + } else { + struct strbuf sb_lock = STRBUF_INIT; + strbuf_git_common_path(&sb_lock, "worktrees/%s/locked", wt->id); + write_file(sb_lock.buf, "moving\nfrom %s\nto %s", wt->path, sb_path.buf); + strbuf_reset(&sb_lock); + } + + if (show_only) { + printf("would move %s to %s\n", wt->path, sb_path.buf); + } else { + ret = rename(wt->path, sb_path.buf); + if (ret) + ret = errno; + } + + if (show_only) { + printf("would move worktrees/%s to worktrees/%s\n", wt->id, name); + } else { + if (!ret) { + struct strbuf wtold = STRBUF_INIT, wtnew = STRBUF_INIT; + strbuf_git_common_path(&wtold, "worktrees/%s", wt->id); + strbuf_git_common_path(&wtnew, "worktrees/%s", name); + ret = rename(wtold.buf, wtnew.buf); + if (ret) + ret = errno; + else { + free(wt->id); + wt->id = xstrdup(name); + } + strbuf_reset(&wtold); + strbuf_reset(&wtnew); + } + } + + if (show_only) { + printf("would fixup gitdir for %s", wt->id); + } else { + if (!ret) { + struct strbuf sb_gitdir = STRBUF_INIT, + sb_git = STRBUF_INIT, + sb_real_git = STRBUF_INIT; + strbuf_git_common_path(&sb_gitdir, "worktrees/%s/gitdir", wt->id); + strbuf_addf(&sb_git, "%s/.git", sb_path.buf); + strbuf_add_real_path(&sb_real_git, sb_gitdir.buf); + strbuf_remove(&sb_real_git, sb_real_git.len - 7, 7); + + write_file(sb_gitdir.buf, "%s", sb_git.buf); + write_file(sb_git.buf, "gitdir: %s", sb_real_git.buf); + + strbuf_reset(&sb_real_git); + strbuf_reset(&sb_git); + strbuf_reset(&sb_gitdir); + } + } + + if (show_only) { + printf("would unlock %s\n", wt->id); + } else { + struct strbuf sb_lock = STRBUF_INIT; + strbuf_git_common_path(&sb_lock, "worktrees/%s/locked", wt->id); + ret2 = unlink_or_warn(sb_lock.buf); + strbuf_reset(&sb_lock); + } + + free_worktrees(worktrees); + free(path); + + /* return the errno from the failed rename, if any, else the one from the + * unlock */ + return ret ? ret : ret2; +} + + int cmd_worktree(int ac, const char **av, const char *prefix) { struct option options[] = { @@ -545,6 +667,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix) return list(ac - 1, av + 1, prefix); if (!strcmp(av[1], "lock")) return lock_worktree(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "move")) + return move_worktree(ac - 1, av + 1, prefix); if (!strcmp(av[1], "unlock")) return unlock_worktree(ac - 1, av + 1, prefix); usage_with_options(worktree_usage, options); diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh index 8298aaf97f..32a549447d 100755 --- a/t/t2028-worktree-move.sh +++ b/t/t2028-worktree-move.sh @@ -59,4 +59,15 @@ test_expect_success 'unlock worktree twice' ' test_path_is_missing .git/worktrees/source/locked ' +test_expect_success 'move worktree' ' + git worktree move source dest && + git -C dest worktree list --porcelain | grep "^worktree" >actual && + cat <<-EOF >expected && + worktree $(pwd) + worktree $(pwd)/dest + worktree $(pwd)/elsewhere + EOF + test_cmp expected actual +' + test_done -- 2.32.0.93.g670b81a890