Expand description
Batch-publish a repository’s worktrees to their upstreams, force-pushing with a lease where history was rewritten (issue #1443).
This is the other half of worktree_rebase. A rebase rewrites history, so
every already-published branch it touches is now diverged from its upstream and
a plain git push is rejected — leaving the user to open a terminal in each
worktree and force-push by hand. This engine closes that loop for a whole batch.
Split of concerns (see ADR-0003 and ADR-0061). Every git read — the
checked-out branch, the upstream and its remote, the divergence, the repository’s
remote default branch — goes through git2. The one mutation, the push itself,
shells out to the user’s git: libgit2’s vendored build here has no reliable SSH
transport (issue #903), the shell inherits the user’s ssh-agent /
~/.ssh/config / credential-helper configuration for free, and only real git
implements the lease this engine depends on. The binary is resolved through
crate::git::resolve_git_binary rather than by name, because the daemon’s
PATH is minimal (ADR-0059 §3).
§Two rules this engine exists to enforce
1. Always --force-with-lease --force-if-includes, never --force.
A bare --force-with-lease leases against the local remote-tracking ref, so
any background fetch that refreshes refs/remotes/<remote>/<branch> silently
renews the lease and a teammate’s unseen commit becomes overwritable. Per
git-push(1), --force-if-includes additionally verifies that such implicitly
updated remote-tracking refs were actually integrated locally — and it is not
implied: it must be passed explicitly, and it is a documented no-op unless
--force-with-lease is given in its valueless (or refname-only) form. The hazard
is elevated in exactly the environment this ships into, since the built-in VS
Code Git extension’s git.autofetch is precisely such a background fetch.
--force is never emitted, and no option exposes it: a refused lease is the
feature working.
2. Never force-push the repository’s remote default branch. ADR-0060
dropped the rebase engine’s main-working-tree gate; a force-push must invert
that, and the gate is on the branch rather than the worktree’s structural
role. A rebase rewrites only local history and is git reflog-recoverable; a
force-push publishes that rewrite to everyone. A fast-forward onto the default
branch stays allowed, because it is an ordinary push.
§Planning does not touch the network
Unlike worktree_rebase::plan, plan performs no fetch — which is why it
takes no git binary at all. Classification reads the local
refs/remotes/<remote>/<branch>, and that is exactly the ref the lease is
checked against, so the plan the user confirms and the lease the push enforces
agree by construction. A fetch here would refresh that ref — renewing the very
lease rule 1 exists to protect. The cost is that a teammate’s unseen push reads
as PushResult::WouldFastForward and is then refused by the remote, which is
reported rather than forced.
Re-exports§
pub use crate::git::worktree_batch::Selection;
Structs§
- Plan
- The result of planning and (optionally) executing a batch push.
- Push
Options - Knobs for a batch push.
- Worktree
Outcome - What happened (or, in a plan, would happen) to one worktree.
Enums§
- Push
Result - The per-worktree classification and outcome.
- Skip
Reason - Why a worktree was skipped rather than pushed.