How resolveForcePushDecision Uses Patch-ID Comparison in no-mistakes

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 (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:

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:

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:


# 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

Summary

  • resolveForcePushDecision in 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →