How detectBundledLocalDefaultCommits Prevents Silent Data Loss on Rebase

The detectBundledLocalDefaultCommits function in the no-mistakes repository blocks rebases when a feature branch contains unpushed commits from the local default branch, preventing unrelated work from being silently merged into pull requests.

The no-mistakes tool provides a robust CI/CD pipeline that guards against common Git workflow errors. When developers create feature branches from their local default branch instead of the remote, they risk bundling unpushed work into subsequent rebases. The detectBundledLocalDefaultCommits step specifically targets this vulnerability by analyzing branch ancestry before any rebase operation proceeds.

The Risk of Bundled Local Default Commits

When a contributor branches from main instead of origin/main, any unpushed commits on their local default branch become embedded in the feature branch's history. During a later rebase onto origin/main, Git silently incorporates these commits into the pull request. This silent data loss occurs when another developer merges the PR, effectively claiming authorship of work that was never intended for review.

How detectBundledLocalDefaultCommits Blocks Unsafe Rebases

The detection logic in internal/pipeline/steps/rebase.go employs a six-step verification process to identify bundled commits before they contaminate the rebase.

1. Scope Validation

The function first verifies it is operating on a non-default branch. It returns immediately if the current branch is empty or matches the default branch name (e.g., main or master).

2. Local and Remote State Comparison

The implementation queries the current tip of the local default branch using git rev-parse refs/heads/<default>. It simultaneously confirms the existence of the remote default branch (origin/<default>) to establish a comparison baseline.

3. Ancestry Verification

The function performs two critical reachability tests using an isAncestor helper:

  • Verifies the local default tip is not already reachable from the remote default tip
  • Confirms the current branch actually contains the local default tip (isAncestor(localTip, HEAD))

4. Unpushed Commit Enumeration

When the local tip diverges from the remote, the function lists all commits between the remote tip and local tip, including the specific files each commit modifies.

5. Blocking Outcome Generation

If unpushed commits are detected, the function constructs a StepOutcome struct with NeedsApproval: true and AutoFixable: false. This halts the pipeline and requires manual intervention.

Implementation in the Rebase Pipeline

In internal/pipeline/steps/rebase.go, the detection occurs at lines 74-81, immediately before any rebase logic executes. The function signature accepts the branch name and default branch identifier:

func detectBundledLocalDefaultCommits(ctx context.Context, stepCtx *StepContext, branch, defaultBranch string) *StepOutcome

The Git operations leverage wrappers from the internal/git package, including git.Run and git.DiffNameOnly, to execute repository queries safely.

Practical Usage Example

When the detector identifies bundled commits, it produces actionable output for the developer:

// Example: Handling a blocked rebase outcome
outcome := detectBundledLocalDefaultCommits(ctx, stepCtx, "feature-xyz", "main")
if outcome != nil {
    fmt.Println("Rebase blocked:", outcome.Findings)
    return outcome, nil
}

Typical console output warns the user of specific bundled commits:

⚠️  branch bundles 3 unpushed main commit(s)
- a1b2c3d Fix typo in docs
- d4e5f6g Add experimental API
- h7i8j9k Refactor helper functions

Push main to origin, or rebase your branch onto origin/main before gating.

This explicit enumeration prevents the silent incorporation of Fix typo in docs or Add experimental API into the pull request.

Summary

  • detectBundledLocalDefaultCommits in internal/pipeline/steps/rebase.go prevents silent data loss by intercepting rebases that would bundle unpushed default branch commits
  • The function verifies ancestry relationships between local and remote default branches before allowing rebase operations to proceed
  • It generates a blocking StepOutcome with NeedsApproval: true when unpushed commits are detected, forcing manual resolution
  • The detection integrates directly into the rebase pipeline at lines 74-81, ensuring early failure before any destructive Git operations occur
  • Console output explicitly lists affected commits and files, directing developers to push their default branch or rebase onto the remote default first

Frequently Asked Questions

What triggers the detectBundledLocalDefaultCommits check?

The check triggers when processing a non-default branch that contains commits from the local default branch (e.g., main) that do not exist on the remote default (origin/main). Specifically, when isAncestor(localTip, HEAD) returns true but isAncestor(localTip, remoteTip) returns false, the function identifies bundled unpushed work.

How does the function differ from standard Git pre-rebase hooks?

Unlike client-side hooks that rely on local configuration, detectBundledLocalDefaultCommits operates within the no-mistakes pipeline step context, providing consistent enforcement across all contributors. It performs granular ancestry checks using git rev-parse and isAncestor validation rather than relying solely on ref comparisons, offering precise detection of commit reachability.

Can the rebase proceed automatically if bundled commits are detected?

No. When bundled commits are found, the function returns a StepOutcome with AutoFixable: false and NeedsApproval: true. This design requires explicit human approval to proceed, ensuring developers consciously review the unpushed commits before they can be incorporated into the rebase operation.

Where does the detection logic reside in the no-mistakes codebase?

The core implementation lives in internal/pipeline/steps/rebase.go at lines 84-112, with the invocation site at lines 74-81. Supporting Git operations are abstracted in the internal/git package, which provides the git.Run and git.DiffNameOnly wrappers used for commit enumeration and file diffing.

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 →