{"record":{"id":"5b82c1be647c7a22","repo":"gitbutlerapp/gitbutler","slug":"validation","errorCode":"Validation","errorMessage":"Commit {commit_id} is not conflicted","messagePattern":"Commit (.+?) is not conflicted","errorType":"validation","errorClass":"but_error::Code","httpStatus":null,"severity":"error","filePath":"crates/but-api/src/resolve/context.rs","lineNumber":134,"sourceCode":"}\n\n#[cfg(feature = \"export-schema\")]\nbut_schemars::register_sdk_type!(ManualConflict);\n\n/// Re-merge the conflict trees of `commit_id` and extract all conflict hunks.\n///\n/// Conflicts with no marker block to splice a resolution into — side deletions,\n/// non-blob entries, binary or oversized files — are reported in `manual`\n/// rather than failing the request, so the rest of the commit stays workable.\npub fn build_request(\n    repo: &gix::Repository,\n    commit_id: gix::ObjectId,\n) -> anyhow::Result<ResolutionRequest> {\n    use gix::prelude::ObjectIdExt as _;\n\n    let commit = but_core::Commit::from_id(commit_id.attach(repo))?;\n    let Some((base, ours, theirs)) = commit.conflicted_tree_ids()? else {\n        bail!(\n            anyhow::anyhow!(Code::Validation)\n                .context(format!(\"Commit {commit_id} is not conflicted\"))\n        );\n    };\n\n    let commit_message = but_core::commit::strip_conflict_markers(commit.message.as_ref())\n        .to_str_lossy()\n        .into_owned();\n    let parent_message = commit\n        .parents\n        .first()\n        .and_then(|parent_id| but_core::Commit::from_id(parent_id.attach(repo)).ok())\n        .map(|parent| commit_title(&parent));\n\n    let (base, ours, theirs) = (base.detach(), ours.detach(), theirs.detach());\n    let repo = repo.clone().for_tree_diffing()?;\n    // Merge without favoring a side to reproduce the actual conflicts, and\n    // force diff3-style markers with the sentinel labels so every hunk carries","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but-api/src/resolve/context.rs#L116-L152","documentation":"but-api's conflict-resolution request builder refuses commits that carry no conflict data: but_core::Commit::conflicted_tree_ids() returned None, so the commit has no base/ours/theirs tree triple and is not one of GitButler's conflicted workspace commits. The error carries the Validation code — the request itself was inconsistent, not an IO or git failure. Note the doc comment: conflicts that exist but cannot be spliced (side deletions, binary, oversized files) go to 'manual' instead; only fully non-conflicted commits fail here.","triggerScenarios":"Calling build_request (crates/but-api/src/resolve/context.rs:118) with a plain commit, an already-resolved workspace commit, or a stale commit id — e.g. after a rebase/snapshot produced a new commit underneath the caller.","commonSituations":"UI retrying the resolve flow after the user already saved resolutions; commit ids cached from a previous workspace state; scripts driving the resolve API against arbitrary SHAs.","solutions":["Verify conflicted-ness first: re-read the commit and require conflicted_tree_ids() to be Some.","Refresh the commit id from current workspace state instead of reusing a cached one.","If conflicts were already resolved, route to the normal apply flow rather than resolution.","Map Code::Validation to a user-facing 'nothing to resolve' response instead of a hard error."],"exampleFix":"// before\nlet request = build_request(&repo, commit_id)?;\n\n// after\nlet commit = but_core::Commit::from_id(commit_id.attach(&repo))?;\nanyhow::ensure!(\n    commit.conflicted_tree_ids()?.is_some(),\n    \"commit {commit_id} has no conflicts to resolve\"\n);\nlet request = build_request(&repo, commit_id)?;","handlingStrategy":"validation","validationCode":"let commit = but_core::Commit::from_id(commit_id.attach(&repo))?;\nif commit.conflicted_tree_ids()?.is_none() {\n    anyhow::bail!(\"commit {commit_id} has no conflicts to resolve\");\n}","typeGuard":null,"tryCatchPattern":"match build_request(&repo, commit_id) {\n    Err(err) if err.chain().any(|c| c.to_string().contains(\"is not conflicted\")) => {\n        refresh_workspace_state(&ctx)?; // id may be stale\n        build_request(&repo, refreshed_commit_id)\n    }\n    r => r,\n}?","preventionTips":["Derive 'is conflicted' from the same commit object the resolve flow will use.","Disable resolve UI as soon as resolutions are written back.","Treat stale SHAs after rebase/snapshot as a signal to refresh state, not to retry blindly."],"tags":["git","merge-conflict","validation","resolve","state-machine"],"backgroundTag":"merge-conflict-state-invalid","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}