# How resolveForcePushDecision Uses Patch-ID Comparison in no-mistakes

> Learn how no-mistakes uses Git patch-ID comparison within resolveForcePushDecision to safely verify remote commits before force-pushing, preventing data loss.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: internals
- Published: 2026-06-30

---

**The `resolveForcePushDecision` function in no-mistakes uses Git's `--cherry-pick` patch-ID comparison to verify that remote commits are semantically incorporated before allowing a force-push, preventing accidental loss of upstream work.**

The no-mistakes repository implements a safety-first Git automation pipeline where force-pushing requires rigorous validation. At the heart of this system sits `resolveForcePushDecision`, a gatekeeper function defined in [`internal/pipeline/steps/forcepush.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/forcepush.go) (lines 45-66) that leverages **patch-ID comparison** to distinguish between harmless rebases and dangerous history rewrites. This mechanism ensures that force-pushes only occur when every remote commit's changes are already present in the new head, even if those commits have different SHA hashes.

## The Three-Phase Safety Algorithm

`resolveForcePushDecision` executes a three-phase algorithm to determine whether a forced push of a new head SHA is allowed.

### Phase 1: Remote State Inspection

The function first calls `lsRemoteSHA` (lines 96-107) to read the current remote tip for the target ref. If the ref does not exist remotely, the push is treated as a brand-new branch. If the remote already points at `newHeadSHA`, the function reports *up-to-date* and aborts any push operation.

### Phase 2: Fast-Path Lease Check

When the remote's SHA matches the **last-seen SHA** (the value the pipeline observed before the current run), the remote has not moved out-of-band. The function immediately returns a `forcePushDecision{remoteSHA: current}` at lines 77-80, allowing the force-push because the only history being rewritten is what the pipeline already knows about.

### Phase 3: Patch-ID Safety Check

If the remote has advanced to a commit the pipeline never produced, the code must ensure no remote-only commits would be lost. It invokes `remoteCommitsNotIncorporated` (lines 85-94), which performs the following:

- Fetches the remote tip into `FETCH_HEAD` without updating refs
- Executes `git rev-list --cherry-pick --right-only newHeadSHA...remoteSHA` to list commits reachable from `remoteSHA` but whose changes are not present (by patch-ID) in `newHeadSHA`
- Optionally excludes the known base history (`^baseSHA`) so intentional rewrites are not flagged

If the rev-list returns **zero commits**, the remote's changes are already incorporated and the force-push is safe. Otherwise, the function returns a `forcePushWouldDiscardError`, aborting the push.

## Why Patch-ID Comparison Matters

A plain ancestry check using `git merge-base` would deem a rebase as "different history" and incorrectly block legitimate rewrites. By using `--cherry-pick`, Git compares **patch-IDs**—the canonical hash of a commit's diff—rather than commit IDs.

- **Rebased commits** have identical changes but new SHAs; they are considered *already incorporated* because their patch-IDs match
- **Remote-only commits** contain changes that never existed in the pipeline's new head; they have distinct patch-IDs and surface as "dropped"

This logic is documented in the comment above `remoteCommitsNotIncorporated` at lines 16-20 and forms the core safety guarantee of the no-mistakes pipeline.

## Decision Outcomes and Return Values

The `forcePushDecision` struct returned by the function indicates the appropriate action:

- **`newBranch: true`** — The ref does not exist remotely; a normal push creates the branch
- **`upToDate: true`** — Remote already at `newHeadSHA`; nothing to do
- **`remoteSHA` set, no flags** — Remote is different; a forced push may proceed after the patch-ID safety check passes
- **Error (`*forcePushWouldDiscardError`)** — One or more remote commits would be lost; the pipeline aborts with a clear error message

## Implementation Examples

### Handling the Decision in Push Steps

The following pattern from the push step demonstrates how to consume the decision:

```go
remoteSHA, err := resolveForcePushDecision(
    gitRun,      // gitRunner bound to the repo directory
    pushURL,     // e.g., "git@github.com:owner/repo.git"
    ref,         // "refs/heads/feature-xyz"
    newHeadSHA,  // SHA of the commit to push
    lastSeenSHA, // SHA recorded during the rebase step
    baseSHA,     // SHA of the base commit for this run
)
if err != nil {
    // err will be *forcePushWouldDiscardError if commits would be lost
    log.Fatalf("cannot push: %v", err)
}
if decision.upToDate {
    return nil // nothing to push
}
if decision.newBranch {
    // plain git push for new branches
    return gitRun("push", pushURL, fmt.Sprintf("%s:%s", newHeadSHA, ref))
}

// Force-push with lease anchored at the verified remote SHA
return gitRun("push", "--force-with-lease="+decision.remoteSHA,
    pushURL, fmt.Sprintf("%s:%s", newHeadSHA, ref))

```

### Unit Testing the Safety Logic

The test suite verifies the patch-ID comparison path by simulating remote commits with differing patch-IDs:

```go
func TestResolveForcePushDecision_RejectsUnincorporatedCommits(t *testing.T) {
    // Arrange a fake git runner where remote has extra commits
    // not present in newHeadSHA (different patch-IDs)
    runner := fakeGitRunner{
        lsRemoteSHA: "abc123",          // remote tip
        revList:     "def456\ndef789", // commits not in newHeadSHA
    }

    _, err := resolveForcePushDecision(runner.run, "ssh://example.com/repo.git",
        "refs/heads/main", "newHeadSHA", "oldSeenSHA", "baseSHA")
    if err == nil {
        t.Fatalf("expected error, got nil")
    }
    if _, ok := err.(*forcePushWouldDiscardError); !ok {
        t.Fatalf("expected forcePushWouldDiscardError, got %T", err)
    }
}

```

### Manual Verification with Git

To manually verify the safety check that `remoteCommitsNotIncorporated` performs:

```bash

# Fetch the remote tip without updating refs

git fetch --no-tags origin refs/heads/main

# Check for unincorporated commits using patch-ID comparison

git rev-list --cherry-pick --right-only \
    newHeadSHA...origin/main ^baseSHA

```

If the command returns empty output, the push is safe. Any commit hashes in the output represent changes that would be discarded.

## Key Source Files

- **[`internal/pipeline/steps/forcepush.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/forcepush.go)** — Implements `resolveForcePushDecision`, `remoteCommitsNotIncorporated`, and the `forcePushWouldDiscardError` type
- **[`internal/pipeline/steps/forcepush_decision_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/forcepush_decision_test.go)** — Unit tests verifying lease logic and patch-ID safety checks
- **[`internal/pipeline/steps/forcepush_safety_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/forcepush_safety_test.go)** — End-to-end tests ensuring real git interactions respect the decision logic
- **[`internal/git/run.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/run.go)** — Provides the `gitRunner` wrapper for executing git commands with proper environment handling

## Summary

- `resolveForcePushDecision` in [`internal/pipeline/steps/forcepush.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/pipeline/steps/forcepush.go) acts as the safety gate for all force-push operations in no-mistakes
- The function uses `git rev-list --cherry-pick` to compare patch-IDs rather than commit SHAs, allowing legitimate rebases while blocking destructive rewrites
- Three execution phases—remote inspection, lease verification, and patch-ID comparison—ensure comprehensive safety
- When remote commits are not incorporated by patch-ID, the function returns `forcePushWouldDiscardError` to prevent data loss
- The implementation supports fast-path optimization when the remote matches the last-seen SHA, improving performance for unchanged remotes

## Frequently Asked Questions

### What is a patch-ID in Git?

A **patch-ID** is a canonical hash computed from the diff content of a commit, ignoring metadata like timestamps, author names, or parent SHAs. Two commits with identical code changes but different commit IDs will share the same patch-ID, making patch-ID comparison ideal for detecting semantic equivalence across rebased histories.

### Why does no-mistakes use `--cherry-pick` instead of `merge-base`?

Using `git merge-base` only checks ancestry relationships, which would falsely flag rebased commits as "missing" even when their changes are present. The `--cherry-pick` flag enables patch-ID comparison, allowing `resolveForcePushDecision` to recognize that rebased commits are semantically incorporated despite having different SHAs, while still detecting truly new remote changes that would be lost.

### What happens if `remoteCommitsNotIncorporated` finds unincorporated commits?

When the function detects commits with unique patch-IDs in the remote that are absent from `newHeadSHA`, it returns a `*forcePushWouldDiscardError`. This error aborts the push operation immediately, preventing the pipeline from discarding upstream work and surfacing a clear failure message to the user.

### How does the fast-path lease check improve performance?

When the remote SHA matches the `lastSeenSHA` recorded at the start of the pipeline run, the function skips the expensive `git rev-list` patch-ID comparison entirely (lines 77-80). This optimization avoids unnecessary git command execution when the remote has not moved out-of-band, reducing latency for routine operations.