mirror of
https://github.com/git/git
synced 2024-08-27 11:39:22 +00:00
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>
This commit is contained in:
parent
9c620fc7a6
commit
9f792bb472
|
@ -12,6 +12,7 @@ SYNOPSIS
|
||||||
'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
|
'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
|
||||||
'git worktree list' [--porcelain]
|
'git worktree list' [--porcelain]
|
||||||
'git worktree lock' [--reason <string>] <worktree>
|
'git worktree lock' [--reason <string>] <worktree>
|
||||||
|
'git worktree move' <worktree> <new-path>
|
||||||
'git worktree prune' [-n] [-v] [--expire <expire>]
|
'git worktree prune' [-n] [-v] [--expire <expire>]
|
||||||
'git worktree unlock' <worktree>
|
'git worktree unlock' <worktree>
|
||||||
|
|
||||||
|
@ -34,10 +35,6 @@ The working tree's administrative files in the repository (see
|
||||||
`git worktree prune` in the main or any linked working tree to
|
`git worktree prune` in the main or any linked working tree to
|
||||||
clean up any stale administrative files.
|
clean up any stale administrative files.
|
||||||
|
|
||||||
If you move a linked working tree, you need to manually update the
|
|
||||||
administrative files so that they do not get pruned automatically. See
|
|
||||||
section "DETAILS" for more information.
|
|
||||||
|
|
||||||
If a linked working tree is stored on a portable device or network share
|
If a linked working tree is stored on a portable device or network share
|
||||||
which is not always mounted, you can prevent its administrative files from
|
which is not always mounted, you can prevent its administrative files from
|
||||||
being pruned by issuing the `git worktree lock` command, optionally
|
being pruned by issuing the `git worktree lock` command, optionally
|
||||||
|
@ -79,6 +76,11 @@ files from being pruned automatically. This also prevents it from
|
||||||
being moved or deleted. Optionally, specify a reason for the lock
|
being moved or deleted. Optionally, specify a reason for the lock
|
||||||
with `--reason`.
|
with `--reason`.
|
||||||
|
|
||||||
|
move::
|
||||||
|
|
||||||
|
Move a working tree to a new location. Note that the main working tree
|
||||||
|
cannot be moved.
|
||||||
|
|
||||||
prune::
|
prune::
|
||||||
|
|
||||||
Prune working tree information in $GIT_DIR/worktrees.
|
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
|
||||||
$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
|
$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
|
||||||
inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
|
inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
|
||||||
|
|
||||||
If you move a linked working tree, you need to update the 'gitdir' file
|
If you manually move a linked working tree, you need to update the 'gitdir' file
|
||||||
in the entry's directory. For example, if a linked working tree is moved
|
in the entry's directory. For example, if a linked working tree is moved
|
||||||
to `/newpath/test-next` and its `.git` file points to
|
to `/newpath/test-next` and its `.git` file points to
|
||||||
`/path/main/.git/worktrees/test-next`, then update
|
`/path/main/.git/worktrees/test-next`, then update
|
||||||
|
@ -281,7 +283,6 @@ performed manually, such as:
|
||||||
|
|
||||||
- `remove` to remove a linked working tree and its administrative files (and
|
- `remove` to remove a linked working tree and its administrative files (and
|
||||||
warn if the working tree is dirty)
|
warn if the working tree is dirty)
|
||||||
- `mv` to move or rename a working tree and update its administrative files
|
|
||||||
|
|
||||||
GIT
|
GIT
|
||||||
---
|
---
|
||||||
|
|
|
@ -17,6 +17,7 @@ static const char * const worktree_usage[] = {
|
||||||
N_("git worktree add [<options>] <path> [<branch>]"),
|
N_("git worktree add [<options>] <path> [<branch>]"),
|
||||||
N_("git worktree list [<options>]"),
|
N_("git worktree list [<options>]"),
|
||||||
N_("git worktree lock [<options>] <path>"),
|
N_("git worktree lock [<options>] <path>"),
|
||||||
|
N_("git worktree move <worktree> <new-path>"),
|
||||||
N_("git worktree prune [<options>]"),
|
N_("git worktree prune [<options>]"),
|
||||||
N_("git worktree unlock <path>"),
|
N_("git worktree unlock <path>"),
|
||||||
NULL
|
NULL
|
||||||
|
@ -605,6 +606,56 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int move_worktree(int ac, const char **av, const char *prefix)
|
||||||
|
{
|
||||||
|
struct option options[] = {
|
||||||
|
OPT_END()
|
||||||
|
};
|
||||||
|
struct worktree **worktrees, *wt;
|
||||||
|
struct strbuf dst = STRBUF_INIT;
|
||||||
|
struct strbuf errmsg = STRBUF_INIT;
|
||||||
|
const char *reason;
|
||||||
|
char *path;
|
||||||
|
|
||||||
|
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
|
||||||
|
if (ac != 2)
|
||||||
|
usage_with_options(worktree_usage, options);
|
||||||
|
|
||||||
|
path = prefix_filename(prefix, av[1]);
|
||||||
|
strbuf_addstr(&dst, path);
|
||||||
|
free(path);
|
||||||
|
|
||||||
|
worktrees = get_worktrees(0);
|
||||||
|
wt = find_worktree(worktrees, prefix, av[0]);
|
||||||
|
if (!wt)
|
||||||
|
die(_("'%s' is not a working tree"), av[0]);
|
||||||
|
if (is_main_worktree(wt))
|
||||||
|
die(_("'%s' is a main working tree"), av[0]);
|
||||||
|
if (file_exists(dst.buf))
|
||||||
|
die(_("target '%s' already exists"), av[1]);
|
||||||
|
|
||||||
|
reason = is_worktree_locked(wt);
|
||||||
|
if (reason) {
|
||||||
|
if (*reason)
|
||||||
|
die(_("cannot move a locked working tree, lock reason: %s"),
|
||||||
|
reason);
|
||||||
|
die(_("cannot move a locked working tree"));
|
||||||
|
}
|
||||||
|
if (validate_worktree(wt, &errmsg))
|
||||||
|
die(_("validation failed, cannot move working tree: %s"),
|
||||||
|
errmsg.buf);
|
||||||
|
strbuf_release(&errmsg);
|
||||||
|
|
||||||
|
if (rename(wt->path, dst.buf) == -1)
|
||||||
|
die_errno(_("failed to move '%s' to '%s'"), wt->path, dst.buf);
|
||||||
|
|
||||||
|
update_worktree_location(wt, dst.buf);
|
||||||
|
|
||||||
|
strbuf_release(&dst);
|
||||||
|
free_worktrees(worktrees);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int cmd_worktree(int ac, const char **av, const char *prefix)
|
int cmd_worktree(int ac, const char **av, const char *prefix)
|
||||||
{
|
{
|
||||||
struct option options[] = {
|
struct option options[] = {
|
||||||
|
@ -627,5 +678,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
|
||||||
return lock_worktree(ac - 1, av + 1, prefix);
|
return lock_worktree(ac - 1, av + 1, prefix);
|
||||||
if (!strcmp(av[1], "unlock"))
|
if (!strcmp(av[1], "unlock"))
|
||||||
return unlock_worktree(ac - 1, av + 1, prefix);
|
return unlock_worktree(ac - 1, av + 1, prefix);
|
||||||
|
if (!strcmp(av[1], "move"))
|
||||||
|
return move_worktree(ac - 1, av + 1, prefix);
|
||||||
usage_with_options(worktree_usage, options);
|
usage_with_options(worktree_usage, options);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3087,7 +3087,7 @@ _git_whatchanged ()
|
||||||
|
|
||||||
_git_worktree ()
|
_git_worktree ()
|
||||||
{
|
{
|
||||||
local subcommands="add list lock prune unlock"
|
local subcommands="add list lock move prune unlock"
|
||||||
local subcommand="$(__git_find_on_cmdline "$subcommands")"
|
local subcommand="$(__git_find_on_cmdline "$subcommands")"
|
||||||
if [ -z "$subcommand" ]; then
|
if [ -z "$subcommand" ]; then
|
||||||
__gitcomp "$subcommands"
|
__gitcomp "$subcommands"
|
||||||
|
|
|
@ -59,4 +59,30 @@ test_expect_success 'unlock worktree twice' '
|
||||||
test_path_is_missing .git/worktrees/source/locked
|
test_path_is_missing .git/worktrees/source/locked
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'move non-worktree' '
|
||||||
|
mkdir abc &&
|
||||||
|
test_must_fail git worktree move abc def
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'move locked worktree' '
|
||||||
|
git worktree lock source &&
|
||||||
|
test_when_finished "git worktree unlock source" &&
|
||||||
|
test_must_fail git worktree move source destination
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'move worktree' '
|
||||||
|
toplevel="$(pwd)" &&
|
||||||
|
git worktree move source destination &&
|
||||||
|
test_path_is_missing source &&
|
||||||
|
git worktree list --porcelain | grep "^worktree.*/destination" &&
|
||||||
|
! git worktree list --porcelain | grep "^worktree.*/source" >empty &&
|
||||||
|
git -C destination log --format=%s >actual2 &&
|
||||||
|
echo init >expected2 &&
|
||||||
|
test_cmp expected2 actual2
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'move main worktree' '
|
||||||
|
test_must_fail git worktree move . def
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
Loading…
Reference in a new issue