gitbutlerapp/gitbutler · error

Cannot squash commits that would result in merge conflicts

Error message

Cannot squash commits that would result in merge conflicts

What it means

Squashing commits in a GitButler workspace merges the subject commits' changes into the target commit via a tree-level three-way merge (using a force-ours merge strategy). If the resulting tree still contains conflict markers, the library refuses to produce a squash, because the squash would silently embed conflicted content in the commit.

Solutions

  1. First resolve the overlap: uncommit one of the conflicting changes, resolve it in the working directory, and re-commit before squashing
  2. Squash the commits one at a time instead of all at once, resolving between steps
  3. Verify the involved commits don't both modify identical hunks (e.g. via git diff between them)
  4. If appropriate for the semantics, apply the changes manually and create a single new commit rather than using squash

Example fix

// before
but.squash_commits([commit_a, commit_b]); // fails: same lines edited in both
// after
let outcome = but.uncommit_changes_from_commits([commit_b]);
// re-apply conflict-free changes, then:
but.squash_commits([commit_a]);
Defensive patterns

Strategy: validation

Validate before calling

let overlaps = detect_overlapping_hunks(&subject_commits, &target_commit)?;
if !overlaps.is_empty() { /* resolve or split before squashing */ }

Try / catch

match result {
    Err(e) if e.to_string().contains("merge conflicts") => {
        // uncommit one side, resolve, retry squash
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling squash_commits where the target commit's tree and any subject commit's tree touch the same lines/regions so that even merge_options_force_ours cannot cleanly merge them, leaving squashed_tree.conflict set to Some.

Common situations: Users try to squash two workspace commits that edited the same file regions; stacked changes where a later commit rewrites lines an earlier commit rewrote; uncommitted-hunk-level edits overlapping the commit's content.

Understand the failure class

Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/146fc4fa8b15a6c2. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-workspace/src/commit/squash_commits.rs:177

            continue;
        }
        subject_selectors.push(subject_commit_selector);
    }

    let subject_commit_ids = subject_selectors
        .iter()
        .map(|commit_selector| {
            let (_, commit) = editor.find_selectable_commit(*commit_selector)?;
            Ok(commit.id)
        })
        .collect::<Result<Vec<_>>>()?;
    let squashed_tree = editor.merge_commit_changes_to_tree(
        target_commit_obj.id,
        subject_commit_ids,
        editor.repo().merge_options_force_ours()?,
    )?;
    if squashed_tree.conflict.is_some() {
        bail!("Cannot squash commits that would result in merge conflicts");
    }

    let mut combined_message = Vec::new();
    match how_to_combine_messages {
        MessageCombinationStrategy::KeepSubject => {
            for source_id in subject_selectors.iter().copied() {
                let (_, source_commit) = editor.find_selectable_commit(source_id)?;
                push_message_with_spacing(&mut combined_message, source_commit.message.as_ref());
            }
        }
        MessageCombinationStrategy::KeepTarget => {
            push_message_with_spacing(&mut combined_message, target_commit_obj.message.as_ref());
        }
        MessageCombinationStrategy::KeepBoth => {
            push_message_with_spacing(&mut combined_message, target_commit_obj.message.as_ref());
            for source_id in subject_selectors.iter().copied() {
                let (_, source_commit) = editor.find_selectable_commit(source_id)?;
                push_message_with_spacing(&mut combined_message, source_commit.message.as_ref());

View on GitHub (pinned to 58e5313667)