Thread (1 message) 1 message, 1 author, 3d ago
DORMANTno replies

[PATCH v1 2/3] worktree: add post-worktree-remove hook

From: Domen Kožar <hidden>
Date: 2026-07-09 23:39:13
Subsystem: documentation, the rest · Maintainers: Jonathan Corbet, Linus Torvalds

External tooling has no way to learn that a working tree is gone:
"git worktree remove" deletes both the working tree and its
administrative directory without running any hook.

Introduce a post-worktree-remove hook that runs after "git worktree
remove" has deleted a working tree. It is given the former absolute
path of the working tree and its identifier as arguments. The hook
also runs when only the administrative entry is deleted because the
working tree directory itself had already disappeared, since the
worktree is deregistered either way.

Because the working tree no longer exists at that point, no special
working directory or environment is set up; the hook runs wherever
the command ran, like other post-command hooks.

The hook runs once deletion is underway even if parts of it fail,
since there is no going back at that point, but it does not run when
the removal is refused (locked or dirty working tree, failed
validation). It cannot affect the outcome of the command other than
its exit status being reflected in the exit status of "git worktree
remove".

Signed-off-by: Domen Kožar <redacted>
Co-Authored-By: Claude Fable 5 [off-list ref]
---
 Documentation/githooks.adoc | 18 +++++++++++++++
 builtin/worktree.c          | 10 +++++++++
 t/t2403-worktree-move.sh    | 44 +++++++++++++++++++++++++++++++++++++
 3 files changed, 72 insertions(+)
diff --git a/Documentation/githooks.adoc b/Documentation/githooks.adoc
index 2778f73f30..22b3263ff7 100644
--- a/Documentation/githooks.adoc
+++ b/Documentation/githooks.adoc
@@ -235,6 +235,24 @@ runs after the `post-checkout` hook, and is skipped if that hook fails.
 This hook can be used to set up per-worktree development environments
 or to register the new working tree with external tools.
 
+post-worktree-remove
+~~~~~~~~~~~~~~~~~~~~
+
+This hook is invoked by linkgit:git-worktree[1] after a working tree
+has been deleted by `git worktree remove`. The hook is given two
+parameters: the absolute path of the removed working tree and its
+identifier (the name of its former administrative directory in
+`$GIT_DIR/worktrees/`).
+
+The working tree no longer exists when the hook runs.
+
+This hook cannot affect the outcome of `git worktree remove`, other
+than that the hook's exit status becomes the exit status of the
+command.
+
+This hook can be used to tear down per-worktree development
+environments or to unregister the working tree from external tools.
+
 post-merge
 ~~~~~~~~~~
 
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 7b9d337234..01b62ed2fc 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -168,6 +168,14 @@ static void delete_worktrees_dir_if_empty(void)
 	free(path);
 }
 
+static int run_post_worktree_remove_hook(const char *path, const char *id)
+{
+	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_FORCE_SERIAL;
+
+	strvec_pushl(&hook_opt.args, path, id, NULL);
+	return run_hooks_opt(the_repository, "post-worktree-remove", &hook_opt);
+}
+
 static void prune_worktree(const char *id, const char *reason)
 {
 	if (show_only || verbose)
@@ -1437,6 +1445,8 @@ static int remove_worktree(int ac, const char **av, const char *prefix,
 	ret |= delete_git_dir(wt->id);
 	delete_worktrees_dir_if_empty();
 
+	ret |= run_post_worktree_remove_hook(wt->path, wt->id);
+
 	free_worktrees(worktrees);
 	return ret;
 }
diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh
index 0bb33e8b1b..b94f00e426 100755
--- a/t/t2403-worktree-move.sh
+++ b/t/t2403-worktree-move.sh
@@ -246,6 +246,50 @@ test_expect_success 'not remove a repo with initialized submodule' '
 	)
 '
 
+test_expect_success '"remove" invokes post-worktree-remove hook' '
+	test_hook post-worktree-remove <<-\EOF &&
+	echo $* >hook.actual
+	EOF
+	git worktree add --detach wt-hooked &&
+	git worktree remove wt-hooked &&
+	echo $(pwd)/wt-hooked wt-hooked >hook.expect &&
+	test_cmp hook.expect hook.actual
+'
+
+test_expect_success '"remove" of missing worktree invokes post-worktree-remove hook' '
+	test_when_finished "rm -rf wt-moved-away" &&
+	test_hook post-worktree-remove <<-\EOF &&
+	echo $* >hook.actual
+	EOF
+	rm -f hook.actual &&
+	git worktree add --detach wt-elsewhere &&
+	mv wt-elsewhere wt-moved-away &&
+	git worktree remove wt-elsewhere &&
+	echo $(pwd)/wt-elsewhere wt-elsewhere >hook.expect &&
+	test_cmp hook.expect hook.actual
+'
+
+test_expect_success 'refused "remove" does not invoke post-worktree-remove hook' '
+	git worktree add --detach wt-kept &&
+	test_when_finished "git worktree remove --force --force wt-kept || :" &&
+	test_hook post-worktree-remove <<-\EOF &&
+	>hook.ran
+	EOF
+	git worktree lock wt-kept &&
+	test_must_fail git worktree remove wt-kept &&
+	test_path_is_missing hook.ran
+'
+
+test_expect_success 'failing post-worktree-remove hook fails "remove", worktree is gone' '
+	test_hook post-worktree-remove <<-\EOF &&
+	exit 1
+	EOF
+	git worktree add --detach wt-doomed &&
+	test_must_fail git worktree remove wt-doomed &&
+	test_path_is_missing wt-doomed &&
+	test_path_is_missing .git/worktrees/wt-doomed
+'
+
 test_expect_success 'move worktree with absolute path to relative path' '
 	test_config worktree.useRelativePaths false &&
 	git worktree add ./absolute &&
-- 
2.54.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help