require_signed_commits allows only fast-forward merges, so a merge request
whose target has moved must be rebased, force-pushed and merged again. The
refusal spells that procedure out; mr rebase <n> performs it.
In a clone of the target: fetch, git rebase origin/<target> <source>, push
--force-with-lease, then print the mr merge to run. The git work is local, so
the replayed commits are signed by whatever the user's git config signs with —
the server is never asked to vouch for a commit it did not receive already
signed.
Two corrections to the issue's premises, both checked rather than assumed:
- It says this "would be the first exception" to the CLI being a passthrough. It
is not —
mr checkout,repo cloneandinitalready run git locally through the samelocal()helper. The cost the issue was weighing does not exist. - Writing it surfaced a real gap in my first draft:
repo cloneapplies the instance's configured--ssh-options to git viaGIT_SSH_COMMANDand my fetch/push did not, so a non-default key or port would have been used by the CLI and not by the git it runs. Caught by the e2e test, which cannot authenticate any other way. Fixed.
Guards: a dirty tree is refused before any round trip; a merge request that is not open is refused; a source in a fork is refused naming the repository to run it in, since guessing which remote that is would be worse than saying so. A conflict leaves the rebase in progress on the right branch with the push command to finish with.
Not doing squash. The issue asks whether both land as one command with a strategy flag — rebase is the mechanical unblock the refusal names, while collapsing history is a different operation that needs a commit message and its own decision. Cheap to add later on this shape.
TestCLIMRRebase moves the target, confirms the ff merge is refused, rebases,
and confirms the same merge then lands. TestCLIMRRebaseRefusals covers the fork
and dirty-tree cases.
Stacked on !286.
Closes #175
retargeted from sweep-149 to main: !286 merged
2026-09-06 18:49 UTC