worktree move: new command
This command allows to relocate linked worktrees. Main worktree cannot (yet) be moved. There are two options to move the main worktree, but both have complications, so it's not implemented yet. Anyway the options are: - convert the main worktree to a linked one and move it away, leave the git repository where it is. The repo essentially becomes bare after this move. - move the repository with the main worktree. The tricky part is make sure all file descriptors to the repository are closed, or it may fail on Windows. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Nguyễn Thái Ngọc Duy committed
Feb 12, 2018 at 16:49 UTC
9f792bb4721066ce27ee98c4ad38d6d146b64fcf
4 files changed
+87
-7
Documentation/git-worktree.txt
+7
-6
@@ -12,6 +12,7 @@ SYNOPSIS
12
'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
13
'git worktree list' [--porcelain]
14
'git worktree lock' [--reason <string>] <worktree>
15
+'git worktree move' <worktree> <new-path>
16
'git worktree prune' [-n] [-v] [--expire <expire>]
17
'git worktree unlock' <worktree>
18
@@ -34,10 +35,6 @@ The working tree's administrative files in the repository (see
35
`git worktree prune` in the main or any linked working tree to
36
clean up any stale administrative files.
37
37
-If you move a linked working tree, you need to manually update the
38
-administrative files so that they do not get pruned automatically. See
39
-section "DETAILS" for more information.
40
-
38
If a linked working tree is stored on a portable device or network share
39
which is not always mounted, you can prevent its administrative files from
40
being pruned by issuing the `git worktree lock` command, optionally
@@ -79,6 +76,11 @@ files from being pruned automatically. This also prevents it from
76
being moved or deleted. Optionally, specify a reason for the lock
77
with `--reason`.
78
79
+move::
80
+
81
+Move a working tree to a new location. Note that the main working tree
82
+cannot be moved.
83
+
84
prune::
85
86
Prune working tree information in $GIT_DIR/worktrees.
@@ -196,7 +198,7 @@ thumb is do not make any assumption about whether a path belongs to
198
$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
199
inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
200
199
-If you move a linked working tree, you need to update the 'gitdir' file
201
+If you manually move a linked working tree, you need to update the 'gitdir' file
202
in the entry's directory. For example, if a linked working tree is moved
203
to `/newpath/test-next` and its `.git` file points to
204
`/path/main/.git/worktrees/test-next`, then update
@@ -281,7 +283,6 @@ performed manually, such as:
283
284
- `remove` to remove a linked working tree and its administrative files (and
285
warn if the working tree is dirty)
284
-- `mv` to move or rename a working tree and update its administrative files
286
287
GIT
288
---
builtin/worktree.c
+53
@@ -17,6 +17,7 @@ static const char * const worktree_usage[] = {
17
N_("git worktree add [<options>] <path> [<branch>]"),
18
N_("git worktree list [<options>]"),
19
N_("git worktree lock [<options>] <path>"),
20
+ N_("git worktree move <worktree> <new-path>"),
21
N_("git worktree prune [<options>]"),
22
N_("git worktree unlock <path>"),
23
NULL
@@ -605,6 +606,56 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)
606
return ret;
607
}
608
609
+static int move_worktree(int ac, const char **av, const char *prefix)
610
+{
611
+ struct option options[] = {
612
+ OPT_END()
613
+ };
614
+ struct worktree **worktrees, *wt;
615
+ struct strbuf dst = STRBUF_INIT;
616
+ struct strbuf errmsg = STRBUF_INIT;
617
+ const char *reason;
618
+ char *path;
619
+
620
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
621
+ if (ac != 2)
622
+ usage_with_options(worktree_usage, options);
623
+
624
+ path = prefix_filename(prefix, av[1]);
625
+ strbuf_addstr(&dst, path);
626
+ free(path);
627
+
628
+ worktrees = get_worktrees(0);
629
+ wt = find_worktree(worktrees, prefix, av[0]);
630
+ if (!wt)
631
+ die(_("'%s' is not a working tree"), av[0]);
632
+ if (is_main_worktree(wt))
633
+ die(_("'%s' is a main working tree"), av[0]);
634
+ if (file_exists(dst.buf))
635
+ die(_("target '%s' already exists"), av[1]);
636
+
637
+ reason = is_worktree_locked(wt);
638
+ if (reason) {
639
+ if (*reason)
640
+ die(_("cannot move a locked working tree, lock reason: %s"),
641
+ reason);
642
+ die(_("cannot move a locked working tree"));
643
+ }
644
+ if (validate_worktree(wt, &errmsg))
645
+ die(_("validation failed, cannot move working tree: %s"),
646
+ errmsg.buf);
647
+ strbuf_release(&errmsg);
648
+
649
+ if (rename(wt->path, dst.buf) == -1)
650
+ die_errno(_("failed to move '%s' to '%s'"), wt->path, dst.buf);
651
+
652
+ update_worktree_location(wt, dst.buf);
653
+
654
+ strbuf_release(&dst);
655
+ free_worktrees(worktrees);
656
+ return 0;
657
+}
658
+
659
int cmd_worktree(int ac, const char **av, const char *prefix)
660
{
661
struct option options[] = {
@@ -627,5 +678,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
678
return lock_worktree(ac - 1, av + 1, prefix);
679
if (!strcmp(av[1], "unlock"))
680
return unlock_worktree(ac - 1, av + 1, prefix);
681
+ if (!strcmp(av[1], "move"))
682
+ return move_worktree(ac - 1, av + 1, prefix);
683
usage_with_options(worktree_usage, options);
684
}
contrib/completion/git-completion.bash
+1
-1
@@ -3087,7 +3087,7 @@ _git_whatchanged ()
3087
3088
_git_worktree ()
3089
{
3090
- local subcommands="add list lock prune unlock"
3090
+ local subcommands="add list lock move prune unlock"
3091
local subcommand="$(__git_find_on_cmdline "$subcommands")"
3092
if [ -z "$subcommand" ]; then
3093
__gitcomp "$subcommands"
t/t2028-worktree-move.sh
+26
@@ -59,4 +59,30 @@ test_expect_success 'unlock worktree twice' '
59
test_path_is_missing .git/worktrees/source/locked
60
'
61
62
+test_expect_success 'move non-worktree' '
63
+ mkdir abc &&
64
+ test_must_fail git worktree move abc def
65
+'
66
+
67
+test_expect_success 'move locked worktree' '
68
+ git worktree lock source &&
69
+ test_when_finished "git worktree unlock source" &&
70
+ test_must_fail git worktree move source destination
71
+'
72
+
73
+test_expect_success 'move worktree' '
74
+ toplevel="$(pwd)" &&
75
+ git worktree move source destination &&
76
+ test_path_is_missing source &&
77
+ git worktree list --porcelain | grep "^worktree.*/destination" &&
78
+ ! git worktree list --porcelain | grep "^worktree.*/source" >empty &&
79
+ git -C destination log --format=%s >actual2 &&
80
+ echo init >expected2 &&
81
+ test_cmp expected2 actual2
82
+'
83
+
84
+test_expect_success 'move main worktree' '
85
+ test_must_fail git worktree move . def
86
+'
87
+
88
test_done