feat(merge): remote-aware push with divergence reconciliation (BRW-IJBPWQ) - #1
Closed
JonathanWorks wants to merge 1 commit into
Closed
feat(merge): remote-aware push with divergence reconciliation (BRW-IJBPWQ)#1JonathanWorks wants to merge 1 commit into
JonathanWorks wants to merge 1 commit into
Conversation
…BPWQ)
Adds a built-in wt merge phase that pushes the feature branch to origin
with divergence-aware reconciliation, replacing the need for a project-
level `[[pre-merge]] push = "git push -u origin HEAD"` hook that fails
non-fast-forward when the remote has intermediate CI-visibility commits.
Problem
-------
In agent-only workflows, feature branches are pushed to origin during
development because CI, PR review, and multi-session handoffs all need
the commits resolvable on the remote. When `wt merge` later locally
squashes that branch, the existing push phase hits non-fast-forward.
Force-push is disallowed by convention in governed repos (pwm-os has
this hook-blocked unconditionally).
The previous workaround was a manual `wt-restack` dance — create a
`-vN` branch from the squash, close the old PR, open a new PR — costing
~15 minutes per merge. This change lifts that flow into wt itself.
Mechanism
---------
New module `src/commands/worktree/remote_reconcile.rs`:
- `RemoteState` — snapshot of local-vs-remote (Absent / InSync /
Diverges{behind,ahead} / Ahead), produced by
`classify_remote_state(repo, branch)`.
- `reconcile_and_push(repo, branch, target, strategy, auto_open_pr)`
dispatches on state. Absent → first push. InSync → no-op. Ahead →
rebase local onto remote, push (fast-forward). Diverges → apply the
configured `RemoteDivergenceStrategy`:
* `RemoteSquash` — find/open PR, `gh pr merge --squash
--delete-branch`, fetch target to local. Zero force-pushes.
* `Restack` — create `<branch>-vN`, push, close old PR with
supersession note, open new PR with "Supersedes #N" body.
* `Abort` — return a structured error naming recovery commands.
Config (`src/config/user/sections.rs`):
- `RemoteDivergenceStrategy` enum: `RemoteSquash | Restack | Abort`.
- `MergeConfig` gains three optional fields, all defaulting to the
pre-0.38 behavior (off):
* `push_to_origin: Option<bool>` (default false) — master gate.
* `on_diverged_remote: Option<RemoteDivergenceStrategy>`
(default `RemoteSquash`).
* `auto_open_pr_if_missing: Option<bool>` (default true) — lets
`RemoteSquash` auto-open a draft PR when one doesn't exist.
- Exported through `worktrunk::config::RemoteDivergenceStrategy`.
- `Merge` impl updated to layer the new fields.
Wiring (`src/commands/merge.rs`):
- After rebase, before pre-merge hooks: if `push_to_origin = true`,
call `reconcile_and_push` and log the outcome. When the outcome is
`RemoteSquashed`, return Ok early — GitHub has already merged the
feature into target and deleted the branch; the local merge phase
would find nothing to do.
Invariants preserved
--------------------
- No `--force` push in any path. Every reconciliation creates new
commits (server-side via GitHub, or a fresh `-vN` branch locally)
rather than rewriting shared history.
- `gh pr merge` is only invoked from inside wt; the developer-facing
"never use gh pr merge" governance rule is unchanged.
- Default behavior unchanged: `push_to_origin = false` preserves the
pre-0.38 project-level push-hook model exactly.
Tests
-----
Seven new unit tests for pure helpers (PR-number parsing, supersession
body formatting, abort message content). End-to-end tests against a
live gh + origin are tracked as a follow-up; today's unit coverage is
sufficient to catch regressions in the pure logic.
Full `cargo test --lib` passes except one pre-existing unrelated
mock_commands flake (reproduces on main without any changes). Full
`cargo test --bin wt` passes 584/584. `cargo clippy --all-targets
-- -D warnings` clean. `cargo fmt` applied.
References
----------
- brainwrap card BRW-IJBPWQ (WT project) — design doc
- pwm-os sessions 2026-04-22 PR max-sixty#695→max-sixty#703 and max-sixty#709→max-sixty#714 — two manual
wt-restack events that this change automates.
Author
|
Superseded by replacement branch after local-squash vs remote-history collision — the exact scenario this PR fixes. Recreated on feat/merge-remote-reconcile-v2 with identical tree content + one squash commit. New PR #2 incoming. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lifts the manual `wt-restack` recovery flow into a built-in wt merge phase so agent-only workflows no longer spend ~15 min per merge recovering from the non-fast-forward collision when a feature branch has intermediate CI-visibility commits.
Why
In agent-only workflows (pwm-os, DIRTMAN, brainwrap itself), feature branches are pushed to origin while work is in progress because CI, PR review, and multi-session handoffs all need the commits resolvable on the remote. When `wt merge` later locally squashes the branch, a plain `git push` hits non-fast-forward. Force-push is hook-blocked in governed repos. The workaround was a manual `wt-restack` — create a `-vN` branch from the squash, close the old PR, open a new PR. Two such events in a single pwm-os session on 2026-04-22 (PRs 695→703 and 709→714) motivated this change.
What
A new module `src/commands/worktree/remote_reconcile.rs` classifies local-vs-remote state into four variants and dispatches to a configured reconciliation strategy:
Config
Three new optional fields on `MergeConfig`, all defaulting to the pre-0.38 behavior:
```toml
[merge]
master gate; when false, origin push remains a project pre-merge hook responsibility
push_to_origin = true
default "remote-squash" when unset
on_diverged_remote = "remote-squash"
lets remote-squash auto-open a draft PR if none exists (default true)
auto_open_pr_if_missing = true
```
Invariants preserved
Tests
Rollout plan
References