{"record":{"id":"07cf01d7666b8412","repo":"gitbutlerapp/gitbutler","slug":"cannot-uncommit-commits-that-would-result-in-merge","errorCode":null,"errorMessage":"Cannot uncommit commits that would result in merge conflicts","messagePattern":"Cannot uncommit commits that would result in merge conflicts","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but/src/command/legacy/squash.rs","lineNumber":1390,"sourceCode":"                    MoveCommittedFilesOperation { target, .. },\n                ) => SquashOutcome::Hunks { target, new_commit },\n            };\n            Ok((outcome, Some(ws)))\n        }\n        ExecutableSquashOperation::Uncommit(op) => {\n            if op.has_commit_sources() {\n                let but_api::commit::types::UncommitResult {\n                    workspace,\n                    uncommitted_ids: _,\n                } = but_api::commit::uncommit::commit_uncommit_only_with_perm(\n                    ctx,\n                    op.commit_sources().collect(),\n                    None,\n                    DryRun::Yes,\n                    perm,\n                )?;\n\n                anyhow::ensure!(\n                    !workspace.is_conflicted(),\n                    \"Cannot uncommit commits that would result in merge conflicts\"\n                );\n            }\n\n            let snapshot_details = SnapshotDetails::new(OperationKind::UndoCommit);\n            let ws = but_transaction::with_transaction_with_perm(\n                ctx,\n                meta,\n                perm,\n                snapshot_details,\n                DryRun::No,\n                |mut tx| {\n                    match &op {\n                        UncommitOperation::Commits { sources } => {\n                            if !sources.is_empty() {\n                                tx.uncommit_commits(sources.iter().map(|c| c.commit_id))?;\n                            }","sourceCodeStart":1372,"sourceCodeEnd":1408,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/command/legacy/squash.rs#L1372-L1408","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Uncommit fewer commits — start with only the topmost commit of the stack and add more incrementally","Uncommit the entire stack at once (the whole-range operation does not fight itself), or ship the later commits first and then uncommit","Commit or stash overlapping workspace changes so the dry-run workspace is clean, then retry","If the exact split is mandatory, do it with git-level tooling (revert/rebase) where you can resolve conflicts manually"],"exampleFix":"# before: uncommit an early commit whose lines later commits also touch\nbut squash --uncommit c1 c2 c5\n\n# after: take the tip only, then extend if the dry run stays clean\nbut squash --uncommit c5","handlingStrategy":"validation","validationCode":"let but_api::commit::types::UncommitResult { workspace, .. } =\n    but_api::commit::uncommit::commit_uncommit_only_with_perm(\n        ctx, ids.clone(), None, DryRun::Yes, perm,\n    )?;\nif workspace.is_conflicted() {\n    // shrink the selection instead of letting the command fail\n    anyhow::bail!(\"selection would conflict; uncommit fewer commits (start at the tip)\");\n}","typeGuard":"fn dry_run_is_conflicted(workspace: &but_graph::Workspace) -> bool {\n    workspace.is_conflicted()\n}","tryCatchPattern":"match run_uncommit(ctx, ids, perm) {\n    Ok(result) => { /* use result */ }\n    Err(err) if err.to_string().contains(\"would result in merge conflicts\") => {\n        // retry with a smaller set: only the tip commit, then extend one at a time\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Uncommit from the stack tip downward, one commit at a time, instead of arbitrary subsets","Keep the workspace clean before uncommit operations that overlap its files","When the dry run conflicts, ship or uncommit the dependent later commits first"],"tags":["rust","cli","git","uncommit","merge-conflict","dry-run","workspace"],"backgroundTag":"merge-conflict","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}