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

[PATCH v2 4/4] worktree: add post-worktree-move hook

From
Domen Kožar <domen@cachix.org>
Date
Aug 4, 2026, 18:14 UTC
Message-ID
<625cef65-0197-4da3-81e0-de786f34a1cb@mtasv.net>
In-Reply-To
<20260804181358.532970-1-domen@cachix.org>

Tools that record worktree paths can keep their state up to date when a worktree is added or removed, but the mapping becomes stale when the worktree is moved. Services or other per-worktree state tied to the old path may also need to be relocated.

Introduce a post-worktree-move hook that runs after the working tree and its administrative files have been moved. The hook runs inside the new working tree with GIT_DIR and GIT_WORK_TREE cleared and receives the old absolute path as its sole argument. The new path and worktree identifier can be queried by running git from the hook's working directory.

This signature also lets one configured command handle all three worktree lifecycle hooks by argument count: post-worktree-add takes no arguments, post-worktree-move takes one, and post-worktree-remove takes two.

A failing hook does not undo the completed move, but its exit status becomes the exit status of "git worktree move".

Signed-off-by: Domen Kožar <domen@cachix.org>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 Documentation/config/hook.adoc |  1 +
 Documentation/githooks.adoc    | 17 +++++++++++++++++
 builtin/worktree.c             | 19 +++++++++++++++++--
 t/t2403-worktree-move.sh       | 29 +++++++++++++++++++++++++++++
 4 files changed, 64 insertions(+), 2 deletions(-)
diff --git a/Documentation/config/hook.adoc b/Documentation/config/hook.adoc
index e013bc1e40..32511b56fd 100644
--- a/Documentation/config/hook.adoc
+++ b/Documentation/config/hook.adoc
@@ -95,6 +95,7 @@ hook.jobs::
 `pre-commit`;;
 `post-checkout`;;
 `post-worktree-add`;;
+`post-worktree-move`;;
 `post-worktree-remove`;;
 `push-to-checkout`;;
 `post-commit`;;
diff --git a/Documentation/githooks.adoc b/Documentation/githooks.adoc
index fdf697b12f..0392454756 100644
--- a/Documentation/githooks.adoc
+++ b/Documentation/githooks.adoc
@@ -233,6 +233,23 @@ runs after the `post-checkout` hook, even 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-move
+~~~~~~~~~~~~~~~~~~
+
+This hook is invoked by linkgit:git-worktree[1] after `git worktree move`
+has moved a working tree and updated its administrative files. It is given
+one parameter: the absolute path of the working tree before it was moved.
+
+The hook's current working directory is the new working tree, so its new
+absolute path and identifier can be queried by running `git`.
+
+This hook cannot affect the outcome of `git worktree move`, other than
+that the hook's exit status becomes the exit status of the command. A
+failing hook does not undo the move.
+
+This hook can be used to update per-worktree development environments or
+registrations with external tools after their working tree has moved.
+
 post-worktree-remove
 ~~~~~~~~~~~~~~~~~~~~
 
diff --git a/builtin/worktree.c b/builtin/worktree.c
index e0c37039ac..55df3c6a8f 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -185,6 +185,17 @@ static int run_post_worktree_remove_hook(const char *path, const char *id)
 	return run_hooks_opt(the_repository, "post-worktree-remove", &hook_opt);
 }
 
+static int run_post_worktree_move_hook(const char *old_path,
+				       const char *new_path)
+{
+	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_FORCE_SERIAL;
+
+	strvec_pushl(&hook_opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
+	strvec_push(&hook_opt.args, old_path);
+	hook_opt.dir = new_path;
+	return run_hooks_opt(the_repository, "post-worktree-move", &hook_opt);
+}
+
 static int prune_worktree(const char *id, const char *dotgit,
 			  const char *reason)
 {
@@ -1306,7 +1317,8 @@ static int move_worktree(int ac, const char **av, const char *prefix,
 	struct strbuf dst = STRBUF_INIT;
 	struct strbuf errmsg = STRBUF_INIT;
 	const char *reason = NULL;
-	char *path;
+	char *old_path, *path;
+	int ret;
 
 	ac = parse_options(ac, av, prefix, options, git_worktree_move_usage,
 			   0);
@@ -1349,14 +1361,17 @@ static int move_worktree(int ac, const char **av, const char *prefix,
 		    errmsg.buf);
 	strbuf_release(&errmsg);
 
+	old_path = xstrdup(wt->path);
 	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, use_relative_paths);
+	ret = run_post_worktree_move_hook(old_path, wt->path);
 
+	free(old_path);
 	strbuf_release(&dst);
 	free_worktrees(worktrees);
-	return 0;
+	return ret;
 }
 
 /*
diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh
index b94f00e426..0ffcfe88f7 100755
--- a/t/t2403-worktree-move.sh
+++ b/t/t2403-worktree-move.sh
@@ -82,6 +82,35 @@ test_expect_success 'move worktree' '
 	test_cmp expected2 actual2
 '
 
+test_expect_success '"move" invokes post-worktree-move hook' '
+	test_hook post-worktree-move <<-\EOF &&
+	test "$#" = 1 &&
+	{
+		echo "$1" &&
+		git rev-parse --git-dir --show-toplevel
+	} >hook.actual
+	EOF
+	git worktree add --detach hook-source &&
+	git worktree move hook-source hook-destination &&
+	{
+		echo "$(pwd)/hook-source" &&
+		echo "$(pwd)/.git/worktrees/hook-source" &&
+		echo "$(pwd)/hook-destination"
+	} >hook.expect &&
+	test_cmp hook.expect hook-destination/hook.actual
+'
+
+test_expect_success 'failing post-worktree-move hook leaves worktree moved' '
+	test_hook post-worktree-move <<-\EOF &&
+	exit 1
+	EOF
+	git worktree add --detach hook-failing-source &&
+	test_must_fail git worktree move hook-failing-source hook-failing-destination &&
+	test_path_is_missing hook-failing-source &&
+	git -C hook-failing-destination status --porcelain >actual &&
+	test_must_be_empty actual
+'
+
 test_expect_success 'move main worktree' '
 	test_must_fail git worktree move . def
 '
-- 
2.54.0
Previous: Caleb WhiteNext: Domen Kožar
Message 16 of 21 in “worktree: add post-worktree-add and post-worktree-remove hooks”
  1. 0/3 worktree: add post-worktree-add and post-worktree-remove hooksDomen Kožar, Jul 9, 2026
  2. Phillip WoodJul 10, 2026
  3. 0/4 worktree: add lifecycle hooksDomen Kožar, Aug 4, 2026
  4. Caleb WhiteAug 4, 2026
  5. Junio C HamanoAug 4, 2026
  6. Domen KožarAug 30, 2026
  7. Domen KožarSep 7, 2026
  8. Kristoffer HaugsbakkSep 7, 2026
  9. Domen KožarSep 7, 2026
  10. Maciej CiemborowiczOct 3, 2026
  11. 0/2 worktree: add post-worktree lifecycle hookDomen Kožar, Oct 4, 2026
  12. Junio C HamanoOct 5, 2026
  13. Phillip WoodJul 13, 2026
  14. 1/4 worktree: add post-worktree-add hookDomen Kožar, Aug 4, 2026
  15. Caleb WhiteAug 4, 2026
  16. 4/4 worktree: add post-worktree-move hookDomen Kožar, Aug 4, 2026
  17. 2/4 worktree: add post-worktree-remove hookDomen Kožar, Aug 4, 2026
  18. 3/4 worktree: run post-worktree-remove hook when pruningDomen Kožar, Aug 4, 2026
  19. 1/2 worktree: add post-worktree lifecycle hookDomen Kožar, Oct 4, 2026
  20. Phillip WoodOct 6, 2026
  21. 2/2 worktree: notify post-worktree hook when pruningDomen Kožar, Oct 4, 2026

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.