gitbutlerapp/gitbutler · error

non-empty conflicts map contains a commit

Error message

non-empty conflicts map contains a commit

What it means

Invariant in the 'but resolve' output path: the code runs inside a block that only executes when at least one conflict was recorded for a branch, and it then takes the first commit of that branch's resolution queue to suggest the next 'but resolve <id>'. The expect fires if the per-branch queue is empty while the aggregate conflicts map is not - an internal desync between the two structures.

Source

Thrown at crates/but/src/command/legacy/resolve.rs:1284

                    "  {} {}",
                    t.important.paint("Branch:"),
                    t.local_branch.paint(branch_name)
                )?;
                for commit in commits {
                    writeln!(
                        human_out,
                        "    {} {} {}",
                        t.sym().dot.error(),
                        t.hint.paint(&commit.commit_short_id),
                        commit.commit_message
                    )?;
                }
            }

            let next_commit = resolution_queue
                .first()
                .copied()
                .expect("non-empty conflicts map contains a commit");
            writeln!(
                human_out,
                "Resolve the next commit with {}.",
                t.command_suggestion
                    .paint(format!("but resolve {}", next_commit.commit_short_id))
            )?;
        }
    }

    if let Some(json_out) = out.for_json() {
        let conflicts_by_branch = conflicts_after
            .iter()
            .map(|(branch_name, commits)| {
                (
                    branch_name,
                    commits
                        .iter()
                        .map(|commit| {

View on GitHub (pinned to 2497b8007a)

Solutions

  1. Re-run 'but resolve' (or its listing subcommand) to refresh conflict state from the workspace
  2. Report the scenario - queue/map desync is an internal bug, not a user error
  3. Maintainer: derive next_commit from the same iteration that printed the branch header instead of a second lookup

Example fix

// before
let next_commit = resolution_queue.first().copied()
    .expect("non-empty conflicts map contains a commit");

// after
let Some(next_commit) = resolution_queue.first().copied() else {
    tracing::warn!(branch = %branch_name, "conflicts recorded but resolution queue empty");
    continue;
};
Defensive patterns

Strategy: validation

Validate before calling

// Keep the conflicts map and resolution queue in one structure so they cannot desync
struct BranchConflicts {
    branch: BranchName,
    queue: NonEmpty<ConflictedCommit>, // empty-by-construction is impossible
}

Prevention

When it happens

Trigger: Conflicts are recorded under a branch key whose resolution queue ended up empty after earlier steps consumed or filtered it; a refactor moves queue population so the map and queue are no longer filled together.

Common situations: Long-running resolve sessions resuming from persisted state; workspace updates landing between listing conflicts and printing suggestions.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17). Data as JSON: /api/errors/152bbf5b36406a44. Report an issue: GitHub.