Skip to content

feat(runway): git merger PROMOTE - #462

Open
behinddwalls wants to merge 1 commit into
preetam/runway-git-merger-squash-mergefrom
preetam/runway-git-merger-promote
Open

feat(runway): git merger PROMOTE#462
behinddwalls wants to merge 1 commit into
preetam/runway-git-merger-squash-mergefrom
preetam/runway-git-merger-promote

Conversation

@behinddwalls

@behinddwalls behinddwalls commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

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.

`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
behinddwalls force-pushed the preetam/runway-git-merger-promote branch from d305166 to 9d0e8b7 Compare July 30, 2026 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant