gitbutlerapp/gitbutler · error · anyhow::Error

Cannot uncommit commits that would result in merge conflicts

Error message

Cannot uncommit commits that would result in merge conflicts

What it means

Guard in the legacy squash flow's uncommit path: before performing any real mutation, the selected commits are simulated with `commit_uncommit_only_with_perm(..., DryRun::Yes)` and the resulting workspace is inspected. If `workspace.is_conflicted()` is true — i.e. moving those commits' changes back to the workspace would collide with other content — the operation is aborted before the `DryRun::No` transaction runs.

Source

Thrown at crates/but/src/command/legacy/squash.rs:1390

                    MoveCommittedFilesOperation { target, .. },
                ) => SquashOutcome::Hunks { target, new_commit },
            };
            Ok((outcome, Some(ws)))
        }
        ExecutableSquashOperation::Uncommit(op) => {
            if op.has_commit_sources() {
                let but_api::commit::types::UncommitResult {
                    workspace,
                    uncommitted_ids: _,
                } = but_api::commit::uncommit::commit_uncommit_only_with_perm(
                    ctx,
                    op.commit_sources().collect(),
                    None,
                    DryRun::Yes,
                    perm,
                )?;

                anyhow::ensure!(
                    !workspace.is_conflicted(),
                    "Cannot uncommit commits that would result in merge conflicts"
                );
            }

            let snapshot_details = SnapshotDetails::new(OperationKind::UndoCommit);
            let ws = but_transaction::with_transaction_with_perm(
                ctx,
                meta,
                perm,
                snapshot_details,
                DryRun::No,
                |mut tx| {
                    match &op {
                        UncommitOperation::Commits { sources } => {
                            if !sources.is_empty() {
                                tx.uncommit_commits(sources.iter().map(|c| c.commit_id))?;
                            }

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Uncommit fewer commits — start with only the topmost commit of the stack and add more incrementally
  2. Uncommit the entire stack at once (the whole-range operation does not fight itself), or ship the later commits first and then uncommit
  3. Commit or stash overlapping workspace changes so the dry-run workspace is clean, then retry
  4. If the exact split is mandatory, do it with git-level tooling (revert/rebase) where you can resolve conflicts manually

Example fix

# before: uncommit an early commit whose lines later commits also touch
but squash --uncommit c1 c2 c5

# after: take the tip only, then extend if the dry run stays clean
but squash --uncommit c5
Defensive patterns

Strategy: validation

Validate before calling

let but_api::commit::types::UncommitResult { workspace, .. } =
    but_api::commit::uncommit::commit_uncommit_only_with_perm(
        ctx, ids.clone(), None, DryRun::Yes, perm,
    )?;
if workspace.is_conflicted() {
    // shrink the selection instead of letting the command fail
    anyhow::bail!("selection would conflict; uncommit fewer commits (start at the tip)");
}

Type guard

fn dry_run_is_conflicted(workspace: &but_graph::Workspace) -> bool {
    workspace.is_conflicted()
}

Try / catch

match run_uncommit(ctx, ids, perm) {
    Ok(result) => { /* use result */ }
    Err(err) if err.to_string().contains("would result in merge conflicts") => {
        // retry with a smaller set: only the tip commit, then extend one at a time
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Uncommitting a set of commits whose inverse changes overlap with lines modified by commits that stay in the stack, or with existing workspace changes — the dry-run workspace comes out conflicted, so the CLI refuses.

Common situations: Undoing an early commit in a stack whose later commits touch the same lines; uncommitting while the workspace already holds edits to the same files; selecting a non-contiguous commit range that interleaves with kept commits.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/07cf01d7666b8412. Report an issue: GitHub.