# How No-Mistakes Fork Routing and Push URL Configuration Work for GitHub Contributions

> Learn how no-mistakes fork routing and push URL configuration streamline GitHub contributions. Route pushes to your fork while keeping pull request targets on upstream. Understand the underlying mechanics.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: how-to-guide
- Published: 2026-07-04

---

**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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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:

```go
// 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:

```bash
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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`:

```go
// 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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/repo.go)), with `PushURL()` 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`](https://github.com/kunchenguid/no-mistakes/blob/main/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.