How No-Mistakes Fork Routing and Push URL Configuration Work for GitHub Contributions
No-mistakes routes pushes to your personal fork while keeping pull request targets pinned to the upstream repository by storing a fork_url in SQLite and resolving the push target via the PushURL() method in internal/db/repo.go.
The kunchenguid/no-mistakes CLI automates Git contribution workflows by maintaining separate records for the original repository and your fork. Understanding no-mistakes fork routing and push URL configuration ensures your commits land in the right remote without accidentally modifying upstream history.
Database Layer: Fork and Upstream Storage
At the core of the routing logic lies the Repo struct defined in internal/db/repo.go. Each repository record tracks two critical URLs: the upstream (original) repository and an optional fork URL pointing to your personal copy.
The PushURL() method implements the failover logic:
// internal/db/repo.go – PushURL method (L19-L28)
func (r *Repo) PushURL() string {
if r == nil {
return ""
}
if strings.TrimSpace(r.ForkURL) != "" {
return r.ForkURL // ← fork wins
}
return r.UpstreamURL // ← otherwise use upstream
}
When ForkURL contains a non-empty string, the method returns it; otherwise, it falls back to UpstreamURL. This simple boolean check determines where the push step will send your commits.
Initializing Fork Configuration
You populate the fork_url column during repository initialization using the CLI flag:
no-mistakes init --fork-url git@github.com:myuser/myproject-fork.git
This command invokes InsertRepoWithFork or UpdateRepoForkURL (depending on existing records), persisting the value to the SQLite database. Once stored, subsequent pipeline runs automatically prefer the fork for push operations while retaining the upstream URL for fetch and pull request operations.
Host Construction and GitHub-Specific Logic
During pipeline execution, the scm.Host builder in internal/pipeline/steps/host.go constructs a provider-specific client. For GitHub, the code extracts the fork owner from the stored ForkURL and passes it to github.NewWithFork:
// internal/pipeline/steps/host.go – GitHub handling (L25-L48)
case scm.ProviderGitHub:
host := scm.ExtractHost(sctx.Repo.UpstreamURL)
repo := github.HostPrefixedSlug(sctx.Repo.UpstreamURL)
forkRepo := ""
if sctx.Repo.ForkURL != "" {
// Only the owner part is needed for PR creation.
forkRepo = github.RepoSlug(sctx.Repo.ForkURL)
}
return github.NewWithFork(cmdFactory, cliAvailable, host, repo, forkRepo), ""
The forkRepo variable captures the fork owner (e.g., myuser), enabling the gh CLI to create pull requests with --head myuser:branch while the --repo flag remains pointed at the upstream repository.
Pipeline Step Behaviors
The routing configuration affects three critical pipeline stages differently:
Rebase Step
When a fork URL exists, the rebase step fetches the fork's branch into refs/remotes/no-mistakes-push/<branch> instead of origin/<branch>. This ensures your local history aligns with your fork's state before rebasing onto upstream changes.
Push Step
The push step calls Repo.PushURL() to determine the remote target. According to the implementation in internal/pipeline/steps/push.go, the tool pushes to the fork URL when configured, and it refreshes the remote via git ls-remote before executing the push to validate state.
PR Step
For pull request creation, the tool keeps the gh --repo parameter pointed at the upstream repository while using the fork owner for the head reference. This creates PRs with --head <fork-owner>:<branch>, ensuring the contribution originates from your fork while the PR itself targets the correct upstream project.
Safety Guarantees and Platform Limitations
No-mistakes implements safeguards to prevent accidental upstream modification. The push step contains resolveForcePushDecision logic that refuses force-pushes unless the remote's current SHA matches the last-seen SHA or all remote commits are represented by patch-ids.
Important limitation: Fork routing is implemented exclusively for GitHub. For GitLab, Bitbucket, and Azure DevOps, the code in internal/pipeline/steps/host.go explicitly skips PR creation when ForkURL is set, preventing unsafe cross-repository operations on unsupported platforms.
Summary
- Storage: Fork and upstream URLs live in SQLite (
internal/db/repo.go), withPushURL()prioritizing the fork when present. - Initialization: Use
no-mistakes init --fork-url <url>to configure your fork remote. - Host Logic: GitHub-specific construction extracts the fork owner for PR head references while keeping the upstream as the PR target.
- Pipeline: Push goes to fork, PR targets upstream, and rebase fetches from the fork branch.
- Safety: Force-push protection and GitHub-only support prevent accidental upstream writes.
Frequently Asked Questions
How do I configure no-mistakes to push to my fork instead of upstream?
Run no-mistakes init --fork-url git@github.com:yourusername/repo-fork.git inside your cloned upstream repository. This persists your fork URL to the database, causing Repo.PushURL() to return the fork address for all subsequent push operations.
What happens if I don't set a fork URL?
Without a fork URL configured, PushURL() returns the upstream URL, and all pushes target the original repository. The pipeline behaves as if you have direct write access, which is suitable for maintainers but risky for external contributors.
Does fork routing work for GitLab or Bitbucket?
No. As implemented in internal/pipeline/steps/host.go, fork routing is GitHub-only. When ForkURL is set for non-GitHub providers, the pipeline skips PR creation entirely to prevent undefined cross-repository behaviors.
How does no-mistakes prevent me from accidentally force-pushing to upstream?
The push step includes resolveForcePushDecision logic that validates the remote's current SHA against the last-known state or verifies that all remote commits match local patch-IDs. If validation fails, the force-push is rejected, protecting upstream history from destructive operations.
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 →