feat(runway): git merger PROMOTE - #462
Open
behinddwalls wants to merge 1 commit into
Open
Conversation
This was referenced Jul 29, 2026
behinddwalls
force-pushed
the
preetam/runway-git-merger-promote
branch
from
July 30, 2026 16:12
5514286 to
7c2728f
Compare
behinddwalls
force-pushed
the
preetam/runway-git-merger-promote
branch
from
July 30, 2026 16:16
7c2728f to
ddfaebd
Compare
behinddwalls
force-pushed
the
preetam/runway-git-merger-promote
branch
from
July 30, 2026 16:51
ddfaebd to
d305166
Compare
behinddwalls
marked this pull request as ready for review
July 30, 2026 17:06
`PROMOTE` is the last strategy in the wire contract without an apply path. It is also the one that does not fit the shared machinery: the transforming strategies build new commits locally and push `HEAD:target`, while PROMOTE advances the target to a commit that already exists, unchanged. Adds the `promote` path, dispatched directly from `process` rather than through `applyTransforming`. **Fast-forward only.** After resetting to the remote tip, promote classifies the named commit three ways. Already the tip, or contained in it — idempotent success, no push. A strict descendant of the tip — a genuine fast-forward, pushed as `<sha>:refs/heads/<target>`. Anything else has diverged and is a terminal `ErrConflict`; PROMOTE never creates a commit to reconcile the two. Because it moves the ref to an existing commit, a change of any size arrives whole by construction — its ancestry comes with it, so PROMOTE needs none of the range machinery the picking strategies do. **Exclusivity.** `resolveAndValidate` rejects a PROMOTE that is not the entire request — one step, one change, one URI — as `ErrInvalidRequest`. Two reasons, both structural: a pre-existing commit cannot descend from commits an earlier transforming step just produced, and the push targets an exact SHA rather than the locally-built HEAD, so there is nothing for a preceding step to contribute. **Its own availability checks.** promote bypasses `tryApply`, so it performs the object-availability and staleness checks itself. Without them a commit the remote cannot supply makes every containment query fail with a plain error, which the consumer retries forever rather than reporting a request that can never succeed. **Contention.** The same bounded retry as the transforming path, but the loop re-runs the classification rather than the apply: if the push is rejected the tip may have moved, and the commit that was a fast-forward a moment ago may now be contained (success) or divergent (conflict). The push is a single atomic ref update, so PROMOTE needs no separate atomicity argument. A dry-run check performs the identical classification and returns without pushing, reporting no output. With this the merger implements every strategy in the contract; `isConcreteStrategy` now admits all four. ✅ `bazel test //runway/extension/merger/git:go_default_test` — passes (61s) New cases: fast-forward promote, promote of a commit already contained in the tip, divergent promote rejected as a conflict, a multi-commit change promoted whole to the exact named commit, an unavailable commit reported as an invalid request rather than retried, both dry-run classifications, and the two composition rules (PROMOTE with a second step, PROMOTE with a second URI) rejected as invalid requests.
behinddwalls
force-pushed
the
preetam/runway-git-merger-promote
branch
from
July 30, 2026 17:13
d305166 to
9d0e8b7
Compare
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.
PROMOTEis the last strategy in the wire contract without an apply path. It is also the one that does not fit the shared machinery: the transforming strategies build new commits locally and pushHEAD:target, while PROMOTE advances the target to a commit that already exists, unchanged.Adds the
promotepath, dispatched directly fromprocessrather than throughapplyTransforming.Fast-forward only. After resetting to the remote tip, promote classifies the named commit three ways. Already the tip, or contained in it — idempotent success, no push. A strict descendant of the tip — a genuine fast-forward, pushed as
<sha>:refs/heads/<target>. Anything else has diverged and is a terminalErrConflict; PROMOTE never creates a commit to reconcile the two.Because it moves the ref to an existing commit, a change of any size arrives whole by construction — its ancestry comes with it, so PROMOTE needs none of the range machinery the picking strategies do.
Exclusivity.
resolveAndValidaterejects a PROMOTE that is not the entire request — one step, one change, one URI — asErrInvalidRequest. Two reasons, both structural: a pre-existing commit cannot descend from commits an earlier transforming step just produced, and the push targets an exact SHA rather than the locally-built HEAD, so there is nothing for a preceding step to contribute.Its own availability checks. promote bypasses
tryApply, so it performs the object-availability and staleness checks itself. Without them a commit the remote cannot supply makes every containment query fail with a plain error, which the consumer retries forever rather than reporting a request that can never succeed.Contention. The same bounded retry as the transforming path, but the loop re-runs the classification rather than the apply: if the push is rejected the tip may have moved, and the commit that was a fast-forward a moment ago may now be contained (success) or divergent (conflict).
The push is a single atomic ref update, so PROMOTE needs no separate atomicity argument. A dry-run check performs the identical classification and returns without pushing, reporting no output.
With this the merger implements every strategy in the contract;
isConcreteStrategynow admits all four.✅
bazel test //runway/extension/merger/git:go_default_test— passes (61s)New cases: fast-forward promote, promote of a commit already contained in the tip, divergent promote rejected as a conflict, a multi-commit change promoted whole to the exact named commit, an unavailable commit reported as an invalid request rather than retried, both dry-run classifications, and the two composition rules (PROMOTE with a second step, PROMOTE with a second URI) rejected as invalid requests.