git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 3/7] worktree move: new command

From
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Date
Jan 24, 2018, 09:53 UTC
Message-ID
<20180124095357.19645-4-pclouds@gmail.com>
In-Reply-To
<20180124095357.19645-1-pclouds@gmail.com>

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>
---
 Documentation/git-worktree.txt         |  7 ++++-
 builtin/worktree.c                     | 50 ++++++++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  2 +-
 t/t2028-worktree-move.sh               | 31 +++++++++++++++++++++
 4 files changed, 88 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 41585f535d..5d115b855a 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -12,6 +12,7 @@ SYNOPSIS
 'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
 'git worktree list' [--porcelain]
 'git worktree lock' [--reason <string>] <worktree>
+'git worktree move' <worktree> <new-path>
 'git worktree prune' [-n] [-v] [--expire <expire>]
 'git worktree unlock' <worktree>
 
@@ -79,6 +80,11 @@ 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 working tree to a new location. Note that the main working tree
+cannot be moved.
+
 prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
@@ -281,7 +287,6 @@ performed manually, such as:
 
 - `remove` to remove a linked working tree and its administrative files (and
   warn if the working tree is dirty)
-- `mv` to move or rename a working tree and update its administrative files
 
 GIT
 ---
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 7cef5b120b..2faa95430a 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -17,6 +17,7 @@ static const char * const worktree_usage[] = {
 	N_("git worktree add [<options>] <path> [<branch>]"),
 	N_("git worktree list [<options>]"),
 	N_("git worktree lock [<options>] <path>"),
+	N_("git worktree move <worktree> <new-path>"),
 	N_("git worktree prune [<options>]"),
 	N_("git worktree unlock <path>"),
 	NULL
@@ -605,6 +606,53 @@ 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)
+{
+	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);
+	if (file_exists(dst.buf))
+		die(_("target '%s' already exists"), av[1]);
+
+	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]);
+	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:\n%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);
+
+	return 0;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -627,5 +675,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 		return lock_worktree(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "unlock"))
 		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);
 }
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 3683c772c5..b87d387686 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -3087,7 +3087,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list lock prune unlock"
+	local subcommands="add list lock move prune unlock"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh
index 8298aaf97f..bef420a086 100755
--- a/t/t2028-worktree-move.sh
+++ b/t/t2028-worktree-move.sh
@@ -59,4 +59,35 @@ test_expect_success 'unlock worktree twice' '
 	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_must_fail git worktree move source destination &&
+	git worktree unlock source
+'
+
+test_expect_success 'move worktree' '
+	toplevel="$(pwd)" &&
+	git worktree move source destination &&
+	test_path_is_missing source &&
+	git worktree list --porcelain | grep "^worktree" >actual &&
+	cat <<-EOF >expected &&
+	worktree $toplevel
+	worktree $toplevel/destination
+	worktree $toplevel/elsewhere
+	EOF
+	test_cmp expected actual &&
+	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
-- 
2.16.0.47.g3d9b0fac3a
Previous: Duy NguyenNext: Eric Sunshine
Message 6 of 49 in “nd/worktree-move reboot”
  1. 0/7 nd/worktree-move rebootNguyễn Thái Ngọc Duy, Jan 24, 2018
  2. 1/7 worktree.c: add validate_worktree()Nguyễn Thái Ngọc Duy, Jan 24, 2018
  3. 2/7 worktree.c: add update_worktree_location()Nguyễn Thái Ngọc Duy, Jan 24, 2018
  4. Eric SunshineFeb 2, 2018
  5. Duy NguyenFeb 2, 2018
  6. 3/7 worktree move: new commandNguyễn Thái Ngọc Duy, Jan 24, 2018
  7. Eric SunshineFeb 2, 2018
  8. Eric SunshineFeb 2, 2018
  9. Duy NguyenFeb 5, 2018
  10. Jeff KingFeb 6, 2018
  11. Martin ÅgrenFeb 6, 2018
  12. Duy NguyenFeb 12, 2018
  13. Martin ÅgrenFeb 12, 2018
  14. Duy NguyenFeb 13, 2018
  15. Jeff KingFeb 14, 2018
  16. Duy NguyenFeb 14, 2018
  17. Junio C HamanoFeb 14, 2018
  18. t/known-leaky: add list of known-leaky test scriptsMartin Ågren, Feb 14, 2018
  19. Jeff KingFeb 19, 2018
  20. Martin ÅgrenFeb 20, 2018
  21. Jeff KingFeb 20, 2018
  22. Junio C HamanoFeb 21, 2018
  23. Jeff KingFeb 21, 2018
  24. Kaartic SivaraamFeb 25, 2018
  25. Martin ÅgrenFeb 26, 2018
  26. 4/7 worktree move: accept destination as directoryNguyễn Thái Ngọc Duy, Jan 24, 2018
  27. Eric SunshineFeb 2, 2018
  28. 5/7 worktree move: refuse to move worktrees with submodulesNguyễn Thái Ngọc Duy, Jan 24, 2018
  29. Eric SunshineFeb 2, 2018
  30. 6/7 worktree remove: new commandNguyễn Thái Ngọc Duy, Jan 24, 2018
  31. Eric SunshineFeb 2, 2018
  32. Duy NguyenFeb 12, 2018
  33. 7/7 worktree remove: allow it when $GIT_WORK_TREE is already goneNguyễn Thái Ngọc Duy, Jan 24, 2018
  34. Eric SunshineFeb 2, 2018
  35. Junio C HamanoJan 24, 2018
  36. 0/7 nd/worktree-move rebootNguyễn Thái Ngọc Duy, Feb 12, 2018
  37. 1/7 worktree.c: add validate_worktree()Nguyễn Thái Ngọc Duy, Feb 12, 2018
  38. 2/7 worktree.c: add update_worktree_location()Nguyễn Thái Ngọc Duy, Feb 12, 2018
  39. 4/7 worktree move: accept destination as directoryNguyễn Thái Ngọc Duy, Feb 12, 2018
  40. 5/7 worktree move: refuse to move worktrees with submodulesNguyễn Thái Ngọc Duy, Feb 12, 2018
  41. 6/7 worktree remove: new commandNguyễn Thái Ngọc Duy, Feb 12, 2018
  42. 7/7 worktree remove: allow it when $GIT_WORK_TREE is already goneNguyễn Thái Ngọc Duy, Feb 12, 2018
  43. 3/7 worktree move: new commandNguyễn Thái Ngọc Duy, Feb 12, 2018
  44. t2028: fix minor error and issues in newly-added "worktree move" testsEric Sunshine, Mar 4, 2018
  45. Duy NguyenMar 5, 2018
  46. SZEDER GáborMar 5, 2018
  47. Junio C HamanoMar 5, 2018
  48. Eric SunshineMar 5, 2018
  49. Eric SunshineMar 4, 2018

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.