gitbutlerapp/gitbutler · error

Cannot uncommit changes from a conflicted commit

Error message

Cannot uncommit changes from a conflicted commit

What it means

When removing selected changes from a commit without rebasing, the target commit is checked for merge-conflict state. A conflicted commit's tree cannot be safely reconstructed minus the requested diffs, so the operation is refused.

Solutions

  1. Resolve the conflict in the commit first (e.g. re-commit the commit with resolved content), then retry the uncommit
  2. Uncommit from a different, non-conflicted source commit or from the tree instead
  3. Use whole-workspace operations that don't target the conflicted commit

Example fix

// before
but.uncommit_changes_no_rebase(conflicted_commit, diffs); // bails
// after
let resolved = resolve_commit_conflicts(conflicted_commit)?;
but.uncommit_changes_no_rebase(resolved, diffs);
Defensive patterns

Strategy: validation

Validate before calling

let commit = but_core::Commit::from_id(id.attach(&repo))?;
if commit.is_conflicted() { resolve_conflicts_first()?; }

Type guard

fn is_unconflicted(commit: &but_core::Commit) -> bool { !commit.is_conflicted() }

Try / catch

match result {
    Err(e) if e.to_string().contains("conflicted commit") => {
        // surface conflict-resolution UI before retrying
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling uncommit_changes_no_rebase (via uncommit_changes_from_commits) with a commit target whose commit, when attached to the repo, reports is_conflicted() true.

Common situations: Workspaces containing unresolved merge conflicts from a prior merge/squash; stacked-branch flows where an ancestor merge left conflict markers; users attempting to partially uncommit from a conflict-commit on purpose.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at crates/but-workspace/src/commit/uncommit_changes.rs:209

    match uncommit_changes_no_rebase_inner(&mut editor, commit, changes, context_lines) {
        Ok(selector) => Ok((editor, selector)),
        Err(error) => Err(UncommitChangesNoRebaseError {
            into_editor: editor,
            error,
        }),
    }
}

fn uncommit_changes_no_rebase_inner<M: RefMetadata>(
    editor: &mut Editor<'_, '_, M>,
    commit: impl ToCommitSelector,
    changes: impl IntoIterator<Item = DiffSpec>,
    context_lines: u32,
) -> Result<Selector> {
    let (commit_selector, commit) = editor.find_selectable_commit(commit)?;

    if commit.clone().attach(editor.repo()).is_conflicted() {
        bail!("Cannot uncommit changes from a conflicted commit")
    }

    let (tree_without_changes, dropped_diffs) = create_tree_without_diff(
        editor.repo(),
        ChangesSource::Commit { id: commit.id },
        changes,
        context_lines,
    )?;

    if !dropped_diffs.is_empty() {
        bail!("Failed to remove specified changes from commit");
    }

    let new_commit_id = {
        let mut new_commit = commit.clone();
        new_commit.tree = tree_without_changes;
        editor.new_commit(new_commit, DateMode::CommitterUpdateAuthorKeep)?
    };

View on GitHub (pinned to 58e5313667)