{"record":{"id":"ca947f94b134d69e","repo":"gitbutlerapp/gitbutler","slug":"cannot-amend-a-conflicted-commit","errorCode":null,"errorMessage":"Cannot amend a conflicted commit","messagePattern":"Cannot amend a conflicted commit","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/commit/commit_amend.rs","lineNumber":66,"sourceCode":"/// have been written to the shared object database at that point; it is\n/// unreachable and gets garbage-collected eventually.\n///\n/// `context_lines` define how many diff context lines are being used for\n/// this particular function call. The provided `context_lines` MUST align\n/// with the `context_lines` value used to generate the `DiffSpec`s passed\n/// in the `changes` parameter.\npub fn commit_amend<'ws, 'meta, M: RefMetadata>(\n    mut editor: Editor<'ws, 'meta, M>,\n    commit: impl ToCommitSelector,\n    changes: Vec<DiffSpec>,\n    context_lines: u32,\n    source: ChangeSource<'_>,\n) -> Result<CommitAmendOutcome<'ws, 'meta, M>> {\n    let (target_selector, target) = editor.find_selectable_commit(commit)?;\n\n    let target_id = target.id;\n    if target.attach(editor.repo()).is_conflicted() {\n        bail!(\"Cannot amend a conflicted commit\")\n    }\n    // An immutable pick would be replaced in the step graph while the rebase copies\n    // its descendants verbatim and never moves the (immutable) refs pointing at it -\n    // the amended commit would be written but stay unreachable, with this function\n    // still reporting success. Fail fast instead.\n    let Step::Pick(target_pick) = editor.lookup_step(target_selector)? else {\n        bail!(\"BUG: Expected pick step from commit selector. This should never happen\");\n    };\n    if !target_pick.mutable {\n        bail!(\n            \"cannot amend into {target_id}: the commit is immutable (not part of a mutable branch)\"\n        );\n    }\n\n    // Clone before `create_commit` consumes the vec — needed afterwards\n    // to determine which changes were consumed (not rejected).\n    let all_changes = changes.clone();\n    let create_out = create_commit(","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-workspace/src/commit/commit_amend.rs#L48-L84","documentation":"commit_amend refuses when the target commit's tree is conflicted (but_core::Commit::is_conflicted()), because amending a commit that contains conflict markers would bake the unresolved state into history. The check runs on the resolved target commit before any rebase step is built, so nothing is mutated. Resolve the conflicts (or amend a different commit) first.","triggerScenarios":"Calling commit_amend(editor, commit, ...) where the selectable commit found via find_selectable_commit is conflicted — e.g. amending a commit created during a conflicted GitButler integration.","commonSituations":"Amending a commit produced by GitButler's conflict materialization; syncing an upstream branch with conflicts and then trying to amend the auto-created conflicted commit.","solutions":["Resolve the conflicts in that commit (or in the workspace) before amending.","Undo the conflicted operation and amend its pre-conflict ancestor instead.","Pre-check with but_core::Commit::is_conflicted() and surface a conflict-resolution prompt instead of an error."],"exampleFix":"// before\nlet outcome = commit_amend(editor, commit_id, changes, 3, source)?;\n\n// after\nuse but_core::Commit;\nlet target = editor.find_commit(commit_id)?;\nensure!(!target.attach(editor.repo()).is_conflicted(), \"resolve conflicts before amending\");\nlet outcome = commit_amend(editor, commit_id, changes, 3, source)?;","handlingStrategy":"validation","validationCode":"let (_, target) = editor.find_selectable_commit(commit)?;\nif target.attach(editor.repo()).is_conflicted() {\n    // amend would bake conflict markers into history; resolve first\n    return prompt_conflict_resolution();\n}","typeGuard":"fn amend_target_is_clean(repo: &gix::Repository, id: gix::ObjectId) -> bool {\n    but_core::Commit::from_id(id.attach(repo))\n        .map(|c| !c.is_conflicted())\n        .unwrap_or(false)\n}","tryCatchPattern":"match commit_amend(editor, commit_id, changes, 3, source) {\n    Err(err) if err.to_string() == \"Cannot amend a conflicted commit\" => {\n        ui::error(\"Resolve conflicts in this commit before amending\");\n        Ok(default_outcome())\n    }\n    other => other,\n}","preventionTips":["Disable amend on commits flagged conflicted by but_core::Commit::is_conflicted().","After a conflicted sync, resolve or undo before edit operations.","In tests, cover amending conflicted-commit fixtures to assert the guard."],"tags":["gitbutler","amend","merge-conflict","rust"],"backgroundTag":"conflicted-commit-amend","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}