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

[PATCH 25/25] worktree: add "remove" command

From
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Date
Apr 13, 2016, 13:15 UTC
Message-ID
<1460553346-12985-26-git-send-email-pclouds@gmail.com>
In-Reply-To
<1460553346-12985-1-git-send-email-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-worktree.txt         | 22 +++++----
 builtin/worktree.c                     | 81 ++++++++++++++++++++++++++++++++++
 contrib/completion/git-completion.bash |  5 ++-
 t/t2028-worktree-move.sh               | 26 +++++++++++
 4 files changed, 124 insertions(+), 10 deletions(-)
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index a302f0a..60ad465 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -14,6 +14,7 @@ SYNOPSIS
 'git worktree lock' [--reason <string>] <path>
 'git worktree move' <path> <new-path>
 'git worktree prune' [-n] [-v] [--expire <expire>]
+'git worktree remove' [--force] <path>
 'git worktree unlock' <path>
 
 DESCRIPTION
@@ -78,6 +79,14 @@ prune::
 
 Prune working tree information in $GIT_DIR/worktrees.
 
+remove::
+
+Remove a worktree. Only clean worktrees, no untracked files and no
+modification in tracked files, can be removed. Unclean worktrees can
+be removed with `--force`. Main worktree cannot be removed. It needs
+to be converted to a linked worktree first by moving the repository
+away.
+
 unlock::
 
 Unlock a worktree, allowing it to be pruned, moved or deleted.
@@ -87,9 +96,10 @@ OPTIONS
 
 -f::
 --force::
-	By default, `add` refuses to create a new working tree when `<branch>`
-	is already checked out by another working tree. This option overrides
-	that safeguard.
+	By default, `add` refuses to create a new working tree when
+	`<branch>` is already checked out by another working tree and
+	`remove` refuses to remove an unclean worktree. This option
+	overrides that safeguard.
 
 -b <new-branch>::
 -B <new-branch>::
@@ -234,12 +244,6 @@ Multiple checkout in general is still experimental, and the support
 for submodules is incomplete. It is NOT recommended to make multiple
 checkouts of a superproject.
 
-git-worktree could provide more automation for tasks currently
-performed manually, such as:
-
-- `remove` to remove a linked working tree and its administrative files (and
-  warn if the working tree is dirty)
-
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 5402a4e..084f8fd 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -17,6 +17,7 @@ static const char * const worktree_usage[] = {
 	N_("git worktree lock [<options>] <path>"),
 	N_("git worktree move <path> <new-path>"),
 	N_("git worktree prune [<options>]"),
+	N_("git worktree remove [<options>] <path>"),
 	N_("git worktree unlock <path>"),
 	NULL
 };
@@ -595,6 +596,84 @@ static int move_worktree(int ac, const char **av, const char *prefix)
 	return update_worktree_location(wt, dst.buf);
 }
 
+static int remove_worktree(int ac, const char **av, const char *prefix)
+{
+	int force = 0;
+	struct option options[] = {
+		OPT_BOOL(0, "force", &force,
+			 N_("force removing even if the worktree is dirty")),
+		OPT_END()
+	};
+	struct worktree **worktrees, *wt;
+	struct strbuf dst = STRBUF_INIT;
+	const char *reason;
+	int ret = 0;
+
+	ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+	if (ac != 1)
+		usage_with_options(worktree_usage, options);
+
+	strbuf_addstr(&dst, prefix_filename(prefix,
+					    strlen(prefix),
+					    av[0]));
+
+	worktrees = get_worktrees();
+	wt = find_worktree_by_path(worktrees, dst.buf);
+	if (!wt)
+		die(_("'%s' is not a working directory"), av[0]);
+	if (is_main_worktree(wt))
+		die(_("'%s' is a main working directory"), av[0]);
+	if ((reason = is_worktree_locked(wt))) {
+		if (*reason)
+			die(_("already locked, reason: %s"), reason);
+		die(_("already locked, no reason"));
+	}
+	if (validate_worktree(wt, 0))
+		return -1;
+
+	if (!force) {
+		struct argv_array child_env = ARGV_ARRAY_INIT;
+		struct child_process cp;
+		char buf[1];
+
+		argv_array_pushf(&child_env, "%s=%s/.git",
+				 GIT_DIR_ENVIRONMENT, wt->path);
+		argv_array_pushf(&child_env, "%s=%s",
+				 GIT_WORK_TREE_ENVIRONMENT, wt->path);
+		memset(&cp, 0, sizeof(cp));
+		argv_array_pushl(&cp.args, "status", "--porcelain", NULL);
+		cp.env = child_env.argv;
+		cp.git_cmd = 1;
+		cp.dir = wt->path;
+		cp.out = -1;
+		ret = start_command(&cp);
+		if (ret)
+			die_errno(_("failed to run git-status on '%s', code %d"),
+				  av[0], ret);
+		ret = xread(cp.out, buf, sizeof(buf));
+		if (ret)
+			die(_("'%s' is dirty, use --force to delete it"), av[0]);
+		close(cp.out);
+		ret = finish_command(&cp);
+		if (ret)
+			die_errno(_("failed to run git-status on '%s', code %d"),
+				  av[0], ret);
+	}
+	if (remove_dir_recursively(&dst, 0)) {
+		sys_error(_("failed to delete '%s'"), wt->path);
+		ret = -1;
+	}
+	strbuf_reset(&dst);
+	strbuf_addstr(&dst, git_common_path("worktrees/%s", wt->id));
+	if (remove_dir_recursively(&dst, 0)) {
+		sys_error(_("failed to delete '%s'"), dst.buf);
+		ret = -1;
+	}
+	strbuf_release(&dst);
+	free_worktrees(worktrees);
+	return ret;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
 	struct option options[] = {
@@ -617,5 +696,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
 		return unlock_worktree(ac - 1, av + 1, prefix);
 	if (!strcmp(av[1], "move"))
 		return move_worktree(ac - 1, av + 1, prefix);
+	if (!strcmp(av[1], "remove"))
+		return remove_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 022d990..6fb45ea 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -2596,7 +2596,7 @@ _git_whatchanged ()
 
 _git_worktree ()
 {
-	local subcommands="add list lock move prune unlock"
+	local subcommands="add list lock move prune remove unlock"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
 		__gitcomp "$subcommands"
@@ -2614,6 +2614,9 @@ _git_worktree ()
 		prune,--*)
 			__gitcomp "--dry-run --expire --verbose"
 			;;
+		remove,--*)
+			__gitcomp "--force"
+			;;
 		*)
 			;;
 		esac
diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh
index 83fbab5..668ee05 100755
--- a/t/t2028-worktree-move.sh
+++ b/t/t2028-worktree-move.sh
@@ -74,4 +74,30 @@ test_expect_success 'move main worktree' '
 	test_must_fail git worktree move . def
 '
 
+test_expect_success 'remove main worktree' '
+	test_must_fail git worktree remove .
+'
+
+test_expect_success 'remove locked worktree' '
+	git worktree lock destination &&
+	test_must_fail git worktree remove destination &&
+	git worktree unlock destination
+'
+
+test_expect_success 'remove worktree with dirty tracked file' '
+	echo dirty >>destination/init.t &&
+	test_must_fail git worktree remove destination
+'
+
+test_expect_success 'remove worktree with untracked file' '
+	git -C destination checkout init.t &&
+	: >destination/untracked &&
+	test_must_fail git worktree remove destination
+'
+
+test_expect_success 'force remove worktree with untracked file' '
+	git worktree remove --force destination &&
+	test_path_is_missing destination
+'
+
 test_done
-- 
2.8.0.rc0.210.gd302cd2
Previous: Johannes SixtNext: Junio C Hamano
Message 32 of 35 in “worktree lock, move, remove and unlock”
  1. 00/25 worktree lock, move, remove and unlockNguyễn Thái Ngọc Duy, Apr 13, 2016
  2. 01/25 usage.c: move format processing out of die_errno()Nguyễn Thái Ngọc Duy, Apr 13, 2016
  3. 02/25 usage.c: add sys_error() that prints strerror() automaticallyNguyễn Thái Ngọc Duy, Apr 13, 2016
  4. 03/25 copy.c: import copy_file() from busyboxNguyễn Thái Ngọc Duy, Apr 13, 2016
  5. 04/25 copy.c: delete unused code in copy_file()Nguyễn Thái Ngọc Duy, Apr 13, 2016
  6. 05/25 copy.c: convert bb_(p)error_msg to (sys_)errorNguyễn Thái Ngọc Duy, Apr 13, 2016
  7. 06/25 copy.c: style fixNguyễn Thái Ngọc Duy, Apr 13, 2016
  8. 07/25 copy.c: convert copy_file() to copy_dir_recursively()Nguyễn Thái Ngọc Duy, Apr 13, 2016
  9. 08/25 completion: support git-worktreeNguyễn Thái Ngọc Duy, Apr 13, 2016
  10. 09/25 git-worktree.txt: keep subcommand listing in alphabetical orderNguyễn Thái Ngọc Duy, Apr 13, 2016
  11. 10/25 path.c: add git_common_path() and strbuf_git_common_path()Nguyễn Thái Ngọc Duy, Apr 13, 2016
  12. 11/25 worktree.c: use is_dot_or_dotdot()Nguyễn Thái Ngọc Duy, Apr 13, 2016
  13. 12/25 worktree.c: store "id" instead of "git_dir"Nguyễn Thái Ngọc Duy, Apr 13, 2016
  14. 13/25 worktree.c: add clear_worktree()Nguyễn Thái Ngọc Duy, Apr 13, 2016
  15. 14/25 worktree.c: add find_worktree_by_path()Nguyễn Thái Ngọc Duy, Apr 13, 2016
  16. 15/25 worktree.c: add is_main_worktree()Nguyễn Thái Ngọc Duy, Apr 13, 2016
  17. 16/25 worktree.c: add validate_worktree()Nguyễn Thái Ngọc Duy, Apr 13, 2016
  18. 17/25 worktree.c: add update_worktree_location()Nguyễn Thái Ngọc Duy, Apr 13, 2016
  19. 18/25 worktree.c: add is_worktree_locked()Nguyễn Thái Ngọc Duy, Apr 13, 2016
  20. 19/25 worktree: avoid 0{40}, too many zeroes, hard to readNguyễn Thái Ngọc Duy, Apr 13, 2016
  21. 20/25 worktree: simplify prefixing pathsNguyễn Thái Ngọc Duy, Apr 13, 2016
  22. 21/25 worktree: add "lock" commandNguyễn Thái Ngọc Duy, Apr 13, 2016
  23. 22/25 worktree: add "unlock" commandNguyễn Thái Ngọc Duy, Apr 13, 2016
  24. 23/25 worktree: add "move" commmandNguyễn Thái Ngọc Duy, Apr 13, 2016
  25. 24/25 worktree move: accept destination as directoryNguyễn Thái Ngọc Duy, Apr 13, 2016
  26. Eric SunshineMay 11, 2016
  27. Duy NguyenMay 11, 2016
  28. Eric SunshineMay 11, 2016
  29. Johannes SixtMay 11, 2016
  30. Junio C HamanoMay 11, 2016
  31. Johannes SixtMay 12, 2016
  32. 25/25 worktree: add "remove" commandNguyễn Thái Ngọc Duy, Apr 13, 2016
  33. Junio C HamanoApr 14, 2016
  34. Duy NguyenApr 15, 2016
  35. Junio C HamanoApr 15, 2016

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.