{"record":{"id":"646c9a4e40b21fe4","repo":"gitbutlerapp/gitbutler","slug":"cannot-uncommit-changes-from-a-conflicted-commit","errorCode":null,"errorMessage":"Cannot uncommit changes from a conflicted commit","messagePattern":"Cannot uncommit changes from a conflicted commit","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/commit/uncommit_changes.rs","lineNumber":209,"sourceCode":"    match uncommit_changes_no_rebase_inner(&mut editor, commit, changes, context_lines) {\n        Ok(selector) => Ok((editor, selector)),\n        Err(error) => Err(UncommitChangesNoRebaseError {\n            into_editor: editor,\n            error,\n        }),\n    }\n}\n\nfn uncommit_changes_no_rebase_inner<M: RefMetadata>(\n    editor: &mut Editor<'_, '_, M>,\n    commit: impl ToCommitSelector,\n    changes: impl IntoIterator<Item = DiffSpec>,\n    context_lines: u32,\n) -> Result<Selector> {\n    let (commit_selector, commit) = editor.find_selectable_commit(commit)?;\n\n    if commit.clone().attach(editor.repo()).is_conflicted() {\n        bail!(\"Cannot uncommit changes from a conflicted commit\")\n    }\n\n    let (tree_without_changes, dropped_diffs) = create_tree_without_diff(\n        editor.repo(),\n        ChangesSource::Commit { id: commit.id },\n        changes,\n        context_lines,\n    )?;\n\n    if !dropped_diffs.is_empty() {\n        bail!(\"Failed to remove specified changes from commit\");\n    }\n\n    let new_commit_id = {\n        let mut new_commit = commit.clone();\n        new_commit.tree = tree_without_changes;\n        editor.new_commit(new_commit, DateMode::CommitterUpdateAuthorKeep)?\n    };","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-workspace/src/commit/uncommit_changes.rs#L191-L227","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nbut.uncommit_changes_no_rebase(conflicted_commit, diffs); // bails\n// after\nlet resolved = resolve_commit_conflicts(conflicted_commit)?;\nbut.uncommit_changes_no_rebase(resolved, diffs);","handlingStrategy":"validation","validationCode":"let commit = but_core::Commit::from_id(id.attach(&repo))?;\nif commit.is_conflicted() { resolve_conflicts_first()?; }","typeGuard":"fn is_unconflicted(commit: &but_core::Commit) -> bool { !commit.is_conflicted() }","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"conflicted commit\") => {\n        // surface conflict-resolution UI before retrying\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["git","merge-conflict","precondition"],"backgroundTag":"invalid-state-transition","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}