Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/src/content/docs/hook.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The most common creation hook is `post-start` — it runs background tasks (dev
| `pre-remove` | Cleanup before worktree deletion: saving test artifacts, backing up state. Runs in the worktree being removed |
| `post-remove` | Stopping dev servers, removing containers, notifying external systems. Template variables reference the removed worktree |

During `wt merge`, hooks run in this order: pre-commit → post-commit → pre-merge → pre-remove → post-remove + post-merge. See [`wt merge`](/merge/#pipeline) for the complete pipeline.
During `wt merge`, the blocking hooks run in this order: pre-commit → pre-merge → pre-remove. The `post-*` hooks all start together once the merge finishes, each in the worktree it is anchored on — post-merge, post-switch and post-remove in the destination, post-commit in the worktree the commit was made in. So `post-commit` doesn't run on a merge that removes that worktree. Use `pre-remove` for work that must finish there, or `--no-remove` to keep the worktree. See [`wt merge`](/merge/#pipeline) for the complete pipeline.

# Security

Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ $ wt merge --no-commit --no-rebase

`wt merge` runs these steps:

1. **Commit** — Pre-commit hooks run, then uncommitted changes are committed. Post-commit hooks run in background. Skipped when squashing (the default) — changes are staged during the squash step instead. With `--no-squash`, this is the only commit step.
1. **Commit** — Pre-commit hooks run, then uncommitted changes are committed. Post-commit hooks run in the background, unless step 7 removes the worktree they are anchored on. This step is skipped when squashing (the default) — changes are staged during the squash step instead. With `--no-squash`, this is the only commit step.
2. **Squash** — Combines all commits since target into one (like GitHub's "Squash and merge"). Use `--stage` to control what gets staged: `all` (default), `tracked`, or `none`. Working-tree changes swept into the squash are backed up first to `refs/wt-backup/<branch>`. With `--no-squash`, individual commits are preserved.
3. **Rebase** — Rebases onto target, skipping when nothing needs replaying ([`wt step rebase`](/step/#wt-step-rebase) gives the conditions). A conflict stops the merge with the rebase left open in the worktree, to resolve or abort. With `--no-rebase`, the graph produced by earlier commit/squash steps is preserved and the target must be able to fast-forward to its tip.
4. **Pre-merge hooks** — Hooks run after rebase, before merge. Failures abort. See [`wt hook`](/hook/).
Expand Down
2 changes: 1 addition & 1 deletion plugins/worktrunk/skills/worktrunk/reference/hook.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion plugins/worktrunk/skills/worktrunk/reference/merge.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion skills/worktrunk/reference/hook.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion skills/worktrunk/reference/merge.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1446,7 +1446,7 @@ $ wt merge --no-commit --no-rebase

`wt merge` runs these steps:

1. **Commit** — Pre-commit hooks run, then uncommitted changes are committed. Post-commit hooks run in background. Skipped when squashing (the default) — changes are staged during the squash step instead. With `--no-squash`, this is the only commit step.
1. **Commit** — Pre-commit hooks run, then uncommitted changes are committed. Post-commit hooks run in the background, unless step 7 removes the worktree they are anchored on. This step is skipped when squashing (the default) — changes are staged during the squash step instead. With `--no-squash`, this is the only commit step.
2. **Squash** — Combines all commits since target into one (like GitHub's "Squash and merge"). Use `--stage` to control what gets staged: `all` (default), `tracked`, or `none`. Working-tree changes swept into the squash are backed up first to `refs/wt-backup/<branch>`. With `--no-squash`, individual commits are preserved.
3. **Rebase** — Rebases onto target, skipping when nothing needs replaying ([`wt step rebase`](/step/#wt-step-rebase) gives the conditions). A conflict stops the merge with the rebase left open in the worktree, to resolve or abort. With `--no-rebase`, the graph produced by earlier commit/squash steps is preserved and the target must be able to fast-forward to its tip.
4. **Pre-merge hooks** — Hooks run after rebase, before merge. Failures abort. See [`wt hook`](/hook/).
Expand Down Expand Up @@ -1593,7 +1593,7 @@ The most common creation hook is `post-start` — it runs background tasks (dev
| `pre-remove` | Cleanup before worktree deletion: saving test artifacts, backing up state. Runs in the worktree being removed |
| `post-remove` | Stopping dev servers, removing containers, notifying external systems. Template variables reference the removed worktree |

During `wt merge`, hooks run in this order: pre-commit → post-commit → pre-merge → pre-remove → post-remove + post-merge. See [`wt merge`](/merge/#pipeline) for the complete pipeline.
During `wt merge`, the blocking hooks run in this order: pre-commit → pre-merge → pre-remove. The `post-*` hooks all start together once the merge finishes, each in the worktree it is anchored on — post-merge, post-switch and post-remove in the destination, post-commit in the worktree the commit was made in. So `post-commit` doesn't run on a merge that removes that worktree. Use `pre-remove` for work that must finish there, or `--no-remove` to keep the worktree. See [`wt merge`](/merge/#pipeline) for the complete pipeline.
Comment thread
max-sixty marked this conversation as resolved.
Resolved

# Security

Expand Down
15 changes: 15 additions & 0 deletions src/commands/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,21 @@ pub fn handle_merge(opts: MergeOptions<'_>) -> anyhow::Result<()> {
// (from auto-commit or squash), post-remove + post-switch (from worktree
// removal), and post-merge share a single `◎ Running …` line flushed at
// the end.
//
// Every background hook runs in the worktree it is anchored on, and only
// post-commit is anchored on the feature worktree (the other three anchor
// on the destination). Flushing after `finish_after_merge` therefore spawns
// post-commit into a directory the removal has just deleted, and its runner
// logs `failed to open repository for pipeline` to
// `.git/wt/logs/<branch>/user|project/post-commit/runner.log`, which
// nothing reads back — the `◎ Running post-commit` line is the only trace.
Comment thread
max-sixty marked this conversation as resolved.
Resolved
// Accepted rather than fixed: the commit it would fire on is squashed and
// rebased before the merge lands, `pre-remove` already covers work that
// must finish in the feature worktree, and spawning early only narrows the
// window (removal succeeds against a live cwd). post-commit still runs where
// the worktree survives — `--no-remove`, merging on the target branch,
// merging from the primary worktree — and on `wt step commit` /
// `wt step squash`.
let mut announcer = HookAnnouncer::new(repo, false);

// The project commit-append is gated independently of hook approval:
Expand Down
16 changes: 12 additions & 4 deletions tests/integration_tests/user_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,10 @@ sync = "echo merged"
/// post-commit phase should join post-remove + post-switch + post-merge on
/// one combined announce line — the non-squash sibling of
/// [`test_merge_squash_combines_post_commit_post_remove_post_switch_post_merge`].
///
/// The announce line is all this pins. post-commit is announced but never
/// runs on a merge that removes the worktree, which `handle_merge`'s announcer
/// comment covers.
#[rstest]
fn test_merge_auto_commit_combines_post_commit_post_remove_post_switch_post_merge(
mut repo: TestRepo,
Expand Down Expand Up @@ -1081,10 +1085,14 @@ sync = "echo merged"
);
}

/// `wt merge --squash` fires post-commit (from the squash phase), post-remove,
/// post-switch (from worktree removal), and post-merge. All four should share
/// one `Running …` announce line so the user sees a single status line for
/// the whole command, not four.
/// `wt merge --squash` announces post-commit (from the squash phase),
/// post-remove, post-switch (from worktree removal), and post-merge. All four
/// should share one `Running …` announce line so the user sees a single status
/// line for the whole command, not four.
///
/// That line is all this pins. post-commit is announced but never runs on a
/// merge that removes the worktree, which `handle_merge`'s announcer comment
/// covers.
#[rstest]
fn test_merge_squash_combines_post_commit_post_remove_post_switch_post_merge(mut repo: TestRepo) {
// Squash needs >1 commit ahead of main to actually run.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ $ wt merge --no-commit --no-rebase

`wt merge` runs these steps:

1. **Commit** — Pre-commit hooks run, then uncommitted changes are committed. Post-commit hooks run in background. Skipped when squashing (the default) — changes are staged during the squash step instead. With `--no-squash`, this is the only commit step.
1. **Commit** — Pre-commit hooks run, then uncommitted changes are committed. Post-commit hooks run in the background, unless step 7 removes the worktree they are anchored on. This step is skipped when squashing (the default) — changes are staged during the squash step instead. With `--no-squash`, this is the only commit step.
2. **Squash** — Combines all commits since target into one (like GitHub's "Squash and merge"). Use `--stage` to control what gets staged: `all` (default), `tracked`, or `none`. Working-tree changes swept into the squash are backed up first to `refs/wt-backup/<branch>`. With `--no-squash`, individual commits are preserved.
3. **Rebase** — Rebases onto target, skipping when nothing needs replaying ([`wt step rebase`](/step/#wt-step-rebase) gives the conditions). A conflict stops the merge with the rebase left open in the worktree, to resolve or abort. With `--no-rebase`, the graph produced by earlier commit/squash steps is preserved and the target must be able to fast-forward to its tip.
4. **Pre-merge hooks** — Hooks run after rebase, before merge. Failures abort. See [`wt hook`](/hook/).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Preserve the exact clean commit graph and tip:

wt merge runs these steps:

1. Commit — Pre-commit hooks run, then uncommitted changes are committed. Post-commit hooks run in background. Skipped when squashing (the default) — changes are staged during the squash step instead. With --no-squash, this is the only commit step.
1. Commit — Pre-commit hooks run, then uncommitted changes are committed. Post-commit hooks run in the background, unless step 7 removes the worktree they are anchored on. This step is skipped when squashing (the default) — changes are staged during the squash step instead. With --no-squash, this is the only commit step.
2. Squash — Combines all commits since target into one (like GitHub's "Squash and merge"). Use --stage to control what gets staged: all (default), tracked, or none. Working-tree changes swept into the squash are backed up first to refs/wt-backup/<branch>. With --no-squash, individual commits are preserved.
3. Rebase — Rebases onto target, skipping when nothing needs replaying (wt step rebase gives the conditions). A conflict stops the merge with the rebase left open in the worktree, to resolve or abort. With --no-rebase, the graph produced by earlier commit/squash steps is preserved and the target must be able to fast-forward to its tip.
4. Pre-merge hooks — Hooks run after rebase, before merge. Failures abort. See wt hook.
Expand Down
Loading