How Fork Routing Works for GitHub Fork Contributions in no-mistakes
Fork routing in no-mistakes separates the push target from the PR base by storing an optional fork_url alongside the upstream_url, using Repo.PushURL() to route branches to the contributor's fork while creating pull requests against the parent repository via the gh pr create --head flag.
Fork routing for GitHub fork contributions in no-mistakes enables contributors to push branches to their personal forks while automatically opening pull requests against the upstream repository. This workflow relies on a dual-URL data model that distinguishes between the source of truth and the contributor's working copy. The implementation handles the complexity of GitHub's cross-repository PR workflow transparently through specific database fields and pipeline steps.
Understanding the Fork Routing Data Model
The system represents a repository using three related pieces of data defined in internal/db/repo.go:
| Field | Purpose |
|---|---|
repos.upstream_url |
URL of the parent repository that serves as the source of truth for PR base routing. |
repos.fork_url |
Optional URL of a fork owned by the contributor; this becomes the target for all branch pushes. |
Repo.PushURL() |
Helper method that returns fork_url when present, otherwise falls back to upstream_url【/cache/repos/github.com/kunchenguid/no-mistakes/main/internal/db/repo.go#L19-L27】. |
The PushURL() method implements the core routing logic:
// Simplified from internal/db/repo.go
func (r *Repo) PushURL() string {
if r.ForkURL != "" {
return r.ForkURL
}
return r.UpstreamURL
}
This abstraction ensures that push operations always target the contributor's fork when configured, while maintaining the upstream repository as the default fallback.
Initializing a Repository with Fork Support
To enable fork routing, contributors run the init command with the --fork-url flag:
no-mistakes init --fork-url git@github.com:myuser/project-fork.git
This command records the fork URL in the database while keeping the origin remote pointing at the parent repository. As documented in AGENTS.md, if the fork URL is omitted, the command preserves any existing fork URL for idempotent refresh operations【/cache/repos/github.com/kunchenguid/no-mistakes/main/AGENTS.md#L17-L22】.
Push Target Resolution
All push operations in the pipeline use the PushURL() method to determine the remote destination. In internal/pipeline/steps/push.go, the push step obtains the URL via sctx.Repo.PushURL() (approximately line 65)【/cache/repos/github.com/kunchenguid/no-mistakes/main/internal/pipeline/steps/push.go (line ≈ 65)】.
When a fork URL is configured, this ensures that branch updates land in the contributor's fork rather than directly in the upstream repository. The push step remains agnostic to whether it is targeting a fork or the upstream repository—it simply uses the resolved URL.
GitHub Pull Request Creation
The GitHub host builder in internal/pipeline/steps/host.go handles the cross-repository PR creation. The buildHost function extracts the fork owner from repos.fork_url using github.RepoSlug, then constructs a host configuration that:
- Sets
--repoto the parent repository (repos.upstream_url). - Supplies
--head <fork_owner>:<branch>to thegh pr createcommand, where<fork_owner>comes from the fork URL slug (the owner/component without the host prefix)【/cache/repos/github.com/kunchenguid/no-mistakes/main/internal/pipeline/steps/host.go#L41-L47】.
This approach opens a PR from the contributor's fork back to the parent repository while the underlying push target remains the fork URL. The resulting command structure is equivalent to:
gh pr create \
--repo owner/project \
--head myuser:my-feature-branch
Tests in internal/db/repo_test.go verify that PushURL() correctly prefers fork_url over upstream_url when both are present【/cache/repos/github.com/kunchenguid/no-mistakes/main/internal/db/repo_test.go#L59-L77】.
Provider Limitations
For GitLab, Bitbucket, and Azure DevOps, the code explicitly disables fork-based PR routing. While the push step may still utilize fork_url for branch pushes, PR creation is skipped entirely for these providers【/cache/repos/github.com/kunchenguid/no-mistakes/main/internal/pipeline/steps/host.go#L48-L66】. This restriction reflects the current GitHub-specific implementation of fork handling.
Summary
- Dual-URL architecture:
upstream_urldefines the PR base, whilefork_url(optional) defines the push target. - Automatic routing: The
Repo.PushURL()method ininternal/db/repo.gotransparently selects the fork URL when available. - GitHub-specific PR creation: Uses
--head <fork_owner>:<branch>to create cross-repository PRs while keeping--repopointed at the upstream. - Provider scope: Full fork routing is implemented only for GitHub; other providers lack PR creation support for forks.
Frequently Asked Questions
How do I configure no-mistakes to push to my fork while opening PRs against the parent repository?
Run no-mistakes init --fork-url git@github.com:yourusername/fork.git to record your fork URL. The system will automatically route pushes to your fork and create pull requests against the upstream repository using the gh pr create --head syntax. This separation is handled internally by the PushURL() method and the GitHub host builder.
What happens if I initialize without specifying a fork URL?
If you omit the --fork-url flag during initialization, the command preserves any existing fork URL already stored in the database. If no fork URL exists, all operations target the upstream repository directly. This behavior ensures idempotent configuration refreshes as described in the project documentation.
Why does no-mistakes use the --head flag instead of changing the --repo target?
The --head flag specifies the source branch for the pull request (including the fork owner), while --repo remains fixed on the upstream repository to define the PR's destination. This matches GitHub's CLI semantics for cross-repository contributions: the PR is created in the upstream repo from the fork branch. Hardcoding --repo to the upstream URL ensures the PR lands in the correct project regardless of where the branch was pushed.
Which Git providers support fork routing for PR creation?
Currently, only GitHub supports full fork routing including automated PR creation. For GitLab, Bitbucket, and Azure DevOps, the push step can still target a fork URL if configured, but the PR creation step is explicitly skipped in internal/pipeline/steps/host.go. Support for additional providers would require implementing provider-specific slug extraction and PR creation logic similar to the GitHub implementation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →