From: "Domen Kožar" <domen@cachix.org>
To: git@vger.kernel.org
Cc: "Phillip Wood" <phillip.wood123@gmail.com>,
"Eric Sunshine" <sunshine@sunshineco.com>,
"Patrick Steinhardt" <ps@pks.im>,
"Ævar Arnfjörð Bjarmason" <avarab@gmail.com>,
"Caleb White" <cdwhite3@pm.me>,
"Junio C Hamano" <gitster@pobox.com>,
"Domen Kožar" <domen@cachix.org>
Subject: [PATCH v2 4/4] worktree: add post-worktree-move hook
Date: Tue, 04 Aug 2026 18:14:04 +0000 [thread overview]
Message-ID: <625cef65-0197-4da3-81e0-de786f34a1cb@mtasv.net> (raw)
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
prev parent reply other threads:[~2026-08-04 18:17 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-09 23:36 [PATCH v1 0/3] worktree: add post-worktree-add and post-worktree-remove hooks Domen Kožar
2026-07-10 9:34 ` Phillip Wood
[not found] ` <CAMvcdZS=ZYbLmjKaGJvjQ_fWYhVbOzwMvYq+MMENWPYi_RiqvQ@mail.gmail.com>
2026-07-13 13:19 ` Phillip Wood
2026-08-04 18:14 ` [PATCH v2 0/4] worktree: add lifecycle hooks Domen Kožar
2026-08-04 19:03 ` Caleb White
2026-08-04 20:28 ` Junio C Hamano
[not found] ` <20260804181358.532970-1-domen@cachix.org>
2026-08-04 18:14 ` [PATCH v2 1/4] worktree: add post-worktree-add hook Domen Kožar
2026-08-04 20:03 ` Caleb White
2026-08-04 18:14 ` [PATCH v2 2/4] worktree: add post-worktree-remove hook Domen Kožar
2026-08-04 18:14 ` [PATCH v2 3/4] worktree: run post-worktree-remove hook when pruning Domen Kožar
2026-08-04 18:14 ` Domen Kožar [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=625cef65-0197-4da3-81e0-de786f34a1cb@mtasv.net \
--to=domen@cachix.org \
--cc=avarab@gmail.com \
--cc=cdwhite3@pm.me \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=phillip.wood123@gmail.com \
--cc=ps@pks.im \
--cc=sunshine@sunshineco.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox