Skip to content

Commit 9f792bb

Browse files
pcloudsgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 9c620fc commit 9f792bb

File tree

4 files changed

+87
-7
lines changed

4 files changed

+87
-7
lines changed

Documentation/git-worktree.txt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ SYNOPSIS
1212
'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
1313
'git worktree list' [--porcelain]
1414
'git worktree lock' [--reason <string>] <worktree>
15+
'git worktree move' <worktree> <new-path>
1516
'git worktree prune' [-n] [-v] [--expire <expire>]
1617
'git worktree unlock' <worktree>
1718

@@ -34,10 +35,6 @@ The working tree's administrative files in the repository (see
3435
`git worktree prune` in the main or any linked working tree to
3536
clean up any stale administrative files.
3637

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-
4138
If a linked working tree is stored on a portable device or network share
4239
which is not always mounted, you can prevent its administrative files from
4340
being pruned by issuing the `git worktree lock` command, optionally
@@ -79,6 +76,11 @@ files from being pruned automatically. This also prevents it from
7976
being moved or deleted. Optionally, specify a reason for the lock
8077
with `--reason`.
8178

79+
move::
80+
81+
Move a working tree to a new location. Note that the main working tree
82+
cannot be moved.
83+
8284
prune::
8385

8486
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
196198
$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
197199
inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
198200

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
200202
in the entry's directory. For example, if a linked working tree is moved
201203
to `/newpath/test-next` and its `.git` file points to
202204
`/path/main/.git/worktrees/test-next`, then update
@@ -281,7 +283,6 @@ performed manually, such as:
281283

282284
- `remove` to remove a linked working tree and its administrative files (and
283285
warn if the working tree is dirty)
284-
- `mv` to move or rename a working tree and update its administrative files
285286

286287
GIT
287288
---

builtin/worktree.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ static const char * const worktree_usage[] = {
1717
N_("git worktree add [<options>] <path> [<branch>]"),
1818
N_("git worktree list [<options>]"),
1919
N_("git worktree lock [<options>] <path>"),
20+
N_("git worktree move <worktree> <new-path>"),
2021
N_("git worktree prune [<options>]"),
2122
N_("git worktree unlock <path>"),
2223
NULL
@@ -605,6 +606,56 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)
605606
return ret;
606607
}
607608

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+
608659
int cmd_worktree(int ac, const char **av, const char *prefix)
609660
{
610661
struct option options[] = {
@@ -627,5 +678,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
627678
return lock_worktree(ac - 1, av + 1, prefix);
628679
if (!strcmp(av[1], "unlock"))
629680
return unlock_worktree(ac - 1, av + 1, prefix);
681+
if (!strcmp(av[1], "move"))
682+
return move_worktree(ac - 1, av + 1, prefix);
630683
usage_with_options(worktree_usage, options);
631684
}

contrib/completion/git-completion.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3087,7 +3087,7 @@ _git_whatchanged ()
30873087

30883088
_git_worktree ()
30893089
{
3090-
local subcommands="add list lock prune unlock"
3090+
local subcommands="add list lock move prune unlock"
30913091
local subcommand="$(__git_find_on_cmdline "$subcommands")"
30923092
if [ -z "$subcommand" ]; then
30933093
__gitcomp "$subcommands"

t/t2028-worktree-move.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,30 @@ test_expect_success 'unlock worktree twice' '
5959
test_path_is_missing .git/worktrees/source/locked
6060
'
6161

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+
6288
test_done

0 commit comments

Comments
 (0)