Skip to content

feat(runway): resolve merge targets per queue and provision their checkouts - #544

Merged
behinddwalls merged 1 commit into
mainfrom
sq/runway-merge-targets
Aug 12, 2026
Merged

feat(runway): resolve merge targets per queue and provision their checkouts#544
behinddwalls merged 1 commit into
mainfrom
sq/runway-merge-targets

Conversation

@behinddwalls

@behinddwalls behinddwalls commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Why?

Three things stopped Runway from performing a real merge. It resolved one merger for the whole process, so a deployment could serve only one repository. Nothing created the checkout the git merger requires, which it declares must already exist. And the container had no git binary, while the merger demanded three absolute runtime paths — so enabling it failed at startup with git runtime executable is required rather than working.

What?

Merge targets come from MERGE_CONFIG_PATH, a YAML file with a defaults block and per-queue overrides. Each git target is provisioned at startup — repository initialised, remote configured, credential written, target branch checked out — idempotently, so a restart against a persisted volume costs nothing and a rotated token takes effect. The existing MERGE_* environment variables still configure a single target when no file is given.

Two queues naming one checkout resolve to the same merger instance, which is what serializes them: a git merger locks the working tree it owns, and two instances over one tree would reset it out from under each other mid-merge. That instance is built from whichever queue is reached first, so queues sharing a checkout have to agree on every field it is built from — not merely on the target. Any disagreement is rejected at startup, naming the field, because the alternative is a queue that asked for SQUASH_REBASE quietly merging with REBASE and reporting nothing. The comparison is structural rather than a list of fields to remember, so a field added to the merger later is covered on its own.

The credential never enters the remote URL. The merger folds git's stderr into the errors it returns, so a URL-embedded token would be reprinted into logs and dead-letter payloads by any failed fetch. It is written as an HTTP Authorization header into a 0600 config fragment the repository includes — which keeps it off the command line too. SSH needs no code at all: the merger already passes SSH_AUTH_SOCK and GIT_SSH_COMMAND through its scrubbed environment.

The git runtime is derived from the installed git when unset — executable from PATH, exec path from git --exec-path, templates from the matching install prefix — so the pinning safeguard stops being a boot-time trap. git is installed in the image.

Test Plan

bazel test //service/runway/server:go_default_test — provisioning runs against real git (pinned, as the merger does): clone into an empty directory, idempotent re-run, drifted remote URL corrected, non-default target branch, credential written 0600 and absent from the remote URL, stale fragment removed, unset token rejected, and git-runtime derivation with and without the environment overrides.

✅ Sharing a checkout is now judged on the whole merger configuration, not just the target: two queues that agree everywhere still share one instance, two that disagree in defaultStrategy, updateHeadBranch, checkStaleness, maxPushAttempts, the committer identity or the refspecs are rejected at load, and a queue spelling a default explicitly still counts as agreeing.

@behinddwalls
behinddwalls marked this pull request as ready for review August 7, 2026 18:23
@behinddwalls
behinddwalls requested review from a team and sbalabanov as code owners August 7, 2026 18:23
@behinddwalls
behinddwalls force-pushed the sq/runway-merge-targets branch from 46d6ad4 to b976f26 Compare August 7, 2026 18:36
@behinddwalls
behinddwalls force-pushed the sq/runway-merge-targets branch from b976f26 to d7ebb8b Compare August 7, 2026 21:21
@behinddwalls
behinddwalls force-pushed the sq/runway-merge-targets branch from d7ebb8b to 33afcf6 Compare August 7, 2026 22:05
@behinddwalls
behinddwalls force-pushed the sq/runway-merge-targets branch from 33afcf6 to ed99818 Compare August 7, 2026 22:20
@behinddwalls
behinddwalls force-pushed the sq/runway-merge-targets branch from ed99818 to d1274fe Compare August 7, 2026 23:24
@behinddwalls
behinddwalls force-pushed the sq/runway-merge-targets branch from d1274fe to 34be0c1 Compare August 11, 2026 21:06
@behinddwalls
behinddwalls force-pushed the sq/runway-merge-targets branch from 34be0c1 to 4a9fc63 Compare August 11, 2026 22:39
@behinddwalls
behinddwalls force-pushed the sq/runway-merge-targets branch from 4a9fc63 to 5abb77f Compare August 11, 2026 22:52
@behinddwalls
behinddwalls force-pushed the sq/runway-merge-targets branch 2 times, most recently from 9350959 to 8ddf806 Compare August 12, 2026 05:47
@behinddwalls
behinddwalls force-pushed the sq/runway-merge-targets branch from 8ddf806 to 543fb1e Compare August 12, 2026 19:04

@mnoah1 mnoah1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving - comment below seems like an edge case we may want to consider.

Comment thread service/runway/server/main.go
@behinddwalls
behinddwalls force-pushed the sq/runway-merge-targets branch 2 times, most recently from 123dfdb to 9e5bb3b Compare August 12, 2026 20:47
Base automatically changed from sq/merger-headbranch to main August 12, 2026 21:29
…ckouts

## Summary

### Why?

Three things stopped Runway from performing a real merge. It resolved one merger for the whole process, so a deployment could serve only one repository. Nothing created the checkout the git merger requires, which it declares must already exist. And the container had no `git` binary, while the merger demanded three absolute runtime paths — so enabling it failed at startup with `git runtime executable is required` rather than working.

### What?

Merge targets come from `MERGE_CONFIG_PATH`, a YAML file with a `defaults` block and per-queue overrides. Each git target is provisioned at startup — repository initialised, remote configured, credential written, target branch checked out — idempotently, so a restart against a persisted volume costs nothing and a rotated token takes effect. The existing `MERGE_*` environment variables still configure a single target when no file is given.

Two queues naming one checkout resolve to the *same* merger instance, which is what serializes them: a git merger locks the working tree it owns, and two instances over one tree would reset it out from under each other mid-merge. That instance is built from whichever queue is reached first, so queues sharing a checkout have to agree on every field it is built from — not merely on the target. Any disagreement is rejected at startup, naming the field, because the alternative is a queue that asked for `SQUASH_REBASE` quietly merging with `REBASE` and reporting nothing. The comparison is structural rather than a list of fields to remember, so a field added to the merger later is covered on its own.

The credential never enters the remote URL. The merger folds git's stderr into the errors it returns, so a URL-embedded token would be reprinted into logs and dead-letter payloads by any failed fetch. It is written as an HTTP `Authorization` header into a `0600` config fragment the repository includes — which keeps it off the command line too. SSH needs no code at all: the merger already passes `SSH_AUTH_SOCK` and `GIT_SSH_COMMAND` through its scrubbed environment.

The git runtime is derived from the installed git when unset — executable from `PATH`, exec path from `git --exec-path`, templates from the matching install prefix — so the pinning safeguard stops being a boot-time trap. `git` is installed in the image.

## Test Plan

✅ `bazel test //service/runway/server:go_default_test` — provisioning runs against real git (pinned, as the merger does): clone into an empty directory, idempotent re-run, drifted remote URL corrected, non-default target branch, credential written `0600` and absent from the remote URL, stale fragment removed, unset token rejected, and git-runtime derivation with and without the environment overrides.

✅ Sharing a checkout is now judged on the whole merger configuration, not just the target: two queues that agree everywhere still share one instance, two that disagree in `defaultStrategy`, `updateHeadBranch`, `checkStaleness`, `maxPushAttempts`, the committer identity or the refspecs are rejected at load, and a queue spelling a default explicitly still counts as agreeing.
@behinddwalls
behinddwalls force-pushed the sq/runway-merge-targets branch from 9e5bb3b to 3347419 Compare August 12, 2026 21:29
@behinddwalls
behinddwalls added this pull request to the merge queue Aug 12, 2026
Merged via the queue into main with commit d9a3cfe Aug 12, 2026
15 checks passed
@behinddwalls
behinddwalls deployed to stack-rebase August 12, 2026 21:41 — with GitHub Actions Active
@behinddwalls
behinddwalls deleted the sq/runway-merge-targets branch August 12, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants