{"record":{"id":"c4c67232b65ae4e2","repo":"gitbutlerapp/gitbutler","slug":"cannot-unapply-branch-by-checking-out-conflicted-c","errorCode":null,"errorMessage":"Cannot unapply branch by checking out conflicted commit {new_head_id}","messagePattern":"Cannot unapply branch by checking out conflicted commit (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/branch/unapply.rs","lineNumber":730,"sourceCode":"            if selected\n                .as_ref()\n                .is_none_or(|(best_generation, _)| generation < *best_generation)\n            {\n                selected = Some((generation, ref_to_checkout));\n            }\n        }\n        Ok(selected.map(|(_, ref_to_checkout)| ref_to_checkout))\n    }\n\n    /// Run `safe_checkout`, but provide better error messages if the commit to checkout\n    /// is conflicted.\n    fn safe_checkout(\n        repo: &gix::Repository,\n        new_head_id: gix::ObjectId,\n        options: but_core::worktree::checkout::Options,\n    ) -> anyhow::Result<but_core::worktree::checkout::Outcome> {\n        if but_core::Commit::from_id(new_head_id.attach(repo))?.is_conflicted() {\n            bail!(\"Cannot unapply branch by checking out conflicted commit {new_head_id}\");\n        }\n        but_core::worktree::safe_checkout_from_head(new_head_id, repo, options)\n    }\n\n    /// Check out `ref_to_checkout` using the workspace traversal entrypoint as the\n    /// current worktree/index source.\n    ///\n    /// `ws` must be the workspace projection for the currently checked-out `HEAD`.\n    /// Its graph entrypoint is therefore the commit the index and worktree are\n    /// expected to match before checkout. This matters when `HEAD` points at a\n    /// stack segment inside the workspace rather than the workspace tip.\n    ///\n    /// The helper only updates the index/worktree according to `options`; callers\n    /// remain responsible for any subsequent `HEAD`, reference, metadata, and\n    /// projection updates.\n    fn safe_checkout_ref_to_checkout(\n        repo: &gix::Repository,\n        ref_to_checkout: &RefToCheckout,","sourceCodeStart":712,"sourceCodeEnd":748,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-workspace/src/branch/unapply.rs#L712-L748","documentation":"Before checking out a new HEAD during unapply, safe_checkout verifies the target commit is not conflicted (carrying GitButler conflict markers per but_core::Commit::is_conflicted()). Checking out a conflicted commit would materialize conflict markers into the worktree, so the operation is refused. Resolve the conflicts (or pick a different target) before unapplying.","triggerScenarios":"Calling unapply paths that invoke safe_checkout with a new_head_id whose commit tree is in a conflicted state — typically a workspace that currently contains unresolved merge conflicts.","commonSituations":"Unapplying branches while the workspace still shows unresolved conflicts from an integration/sync; commits created by GitButler's conflict materialization left in place; retrying an unapply right after a conflicted upstream merge.","solutions":["Resolve outstanding conflicts in the workspace (or undo the conflicting operation) before unapplying.","Use GitButler undo to return to a pre-conflict state, then retry the unapply.","If automating, pre-check target commits with Commit::is_conflicted() and route the user to conflict resolution."],"exampleFix":"// before\nlet outcome = unapply(repo, meta, &ws, branch, opts)?;\n\n// after\nuse but_core::Commit;\nfor (_, seg) in ws.segments_with_commits() {\n    if Commit::from_id(seg.tip()?.id.attach(repo))?.is_conflicted() {\n        bail_user!(\"resolve conflicts before unapplying\");\n    }\n}\nlet outcome = unapply(repo, meta, &ws, branch, opts)?;","handlingStrategy":"validation","validationCode":"use but_core::Commit;\nlet target = Commit::from_id(new_head_id.attach(repo))?;\nif target.is_conflicted() {\n    // route the user to conflict resolution instead of unapplying\n    return prompt_conflict_resolution();\n}","typeGuard":"fn commit_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 unapply(repo, meta, &ws, branch, opts) {\n    Err(err) if err.to_string().contains(\"conflicted commit\") => {\n        ui::error(\"Resolve workspace conflicts before unapplying\");\n        Ok(default_outcome())\n    }\n    other => other,\n}","preventionTips":["Block unapply actions while the workspace reports unresolved conflicts.","Pre-check candidate head commits with but_core::Commit::is_conflicted().","Offer GitButler undo to escape conflicted states."],"tags":["gitbutler","unapply","merge-conflict","checkout","rust"],"backgroundTag":"conflicted-commit-checkout","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}