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
- Resolve the conflict in the commit first (e.g. re-commit the commit with resolved content), then retry the uncommit
- Uncommit from a different, non-conflicted source commit or from the tree instead
- 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
- Filter conflicted commits out of selectable change sources
- Resolve workspace conflicts promptly before further operations
- Surface conflict state in the UI before enabling per-commit edits
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
- The source of changes cannot have a conflicted 'after' side.
- Cannot amend a conflicted commit
- cannot amend into : the commit is immutable (not part of a…
- Cannot merge : merging into / resulted in conflicts. Rebase…
- Cannot squash commits that would result in merge conflicts
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)