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_HEADwithout updating refs - Executes
git rev-list --cherry-pick --right-only newHeadSHA...remoteSHAto list commits reachable fromremoteSHAbut whose changes are not present (by patch-ID) innewHeadSHA - 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 branchupToDate: true— Remote already atnewHeadSHA; nothing to doremoteSHAset, 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
internal/pipeline/steps/forcepush.go— ImplementsresolveForcePushDecision,remoteCommitsNotIncorporated, and theforcePushWouldDiscardErrortypeinternal/pipeline/steps/forcepush_decision_test.go— Unit tests verifying lease logic and patch-ID safety checksinternal/pipeline/steps/forcepush_safety_test.go— End-to-end tests ensuring real git interactions respect the decision logicinternal/git/run.go— Provides thegitRunnerwrapper for executing git commands with proper environment handling
Summary
resolveForcePushDecisionininternal/pipeline/steps/forcepush.goacts as the safety gate for all force-push operations in no-mistakes- The function uses
git rev-list --cherry-pickto 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
forcePushWouldDiscardErrorto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →