{"record":{"id":"72b2d380a7eef885","repo":"gitbutlerapp/gitbutler","slug":"commit-spec-is-not-part-of-the-editable-diverg","errorCode":null,"errorMessage":"commit '{spec}' is not part of the editable divergence","messagePattern":"commit '(.+?)' is not part of the editable divergence","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/branch/integrate_branch_upstream/parsing.rs","lineNumber":159,"sourceCode":"        .filter(|commit| {\n            matches!(\n                commit.target_relation,\n                IntegrationDivergenceTargetRelation::NotIntegrated\n            )\n        })\n        .map(|commit| commit.id)\n        .chain(divergence.upstream_only.iter().map(|commit| commit.id))\n        .collect()\n}\n\nfn resolve_commit(spec: &str, allowed_commits: &HashSet<gix::ObjectId>) -> Result<gix::ObjectId> {\n    let matches = allowed_commits\n        .iter()\n        .copied()\n        .filter(|commit_id| commit_id.to_string().starts_with(spec))\n        .collect::<Vec<_>>();\n    match matches.as_slice() {\n        [] => bail!(\"commit '{spec}' is not part of the editable divergence\"),\n        [commit_id] => Ok(*commit_id),\n        _ => bail!(\"commit prefix '{spec}' is ambiguous\"),\n    }\n}\n\nfn split_message_clause(line: &str) -> Result<(&str, Option<&str>)> {\n    let Some((command_part, message_part)) = line.split_once('|') else {\n        return Ok((line, None));\n    };\n    Ok((command_part.trim_end(), Some(message_part.trim_start())))\n}\n\nfn parse_message_clause(clause: &str) -> Result<String> {\n    let value = clause\n        .strip_prefix(\"message=\")\n        .context(\"expected `message=` after `|`\")?;\n    parse_quoted_string(value)\n}","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-workspace/src/branch/integrate_branch_upstream/parsing.rs#L141-L177","documentation":"Raised by `resolve_commit` inside `parse_integration_steps_script` when a commit spec prefix-matches zero commits in `allowed_commits` — the set built from the divergence's editable commits (local-only commits chained with `upstream_only` commits). Anything outside that divergence (merge-base ancestors, commits on other branches, typos) is rejected so the integration cannot pull in foreign history.","triggerScenarios":"A script line references a SHA that is not one of the local-only or upstream-only divergence commits — e.g. copied from `git log` of the main branch, a full SHA from another stack, or a typo/hand-typed prefix. Also stale scripts: the divergence changed (branch updated, commits added) between rendering the script and parsing it back.","commonSituations":"User pastes a commit from elsewhere in the repo into the integration editor; the editor session is stale after the branch moved (script rendered before a fetch/update, parsed after); truncated SHAs that don't correspond to any allowed commit.","solutions":["Re-open/re-render the integration proposal so the script's commit list matches the current divergence, then re-apply edits.","Copy commit prefixes only from the script itself or the divergence display shown alongside it.","If a specific commit must be included, first bring it into the branch (so it appears in the local-only divergence), then restart integration."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Verify every spec in the script resolves against the current divergence before applying:\nlet allowed: HashSet<gix::ObjectId> = /* local_only + upstream_only commit ids from divergence */;\nfor (i, line) in script.lines().enumerate() {\n    for spec in line.split_whitespace().skip(1) {\n        let spec = spec.trim_start_matches('#');\n        if !allowed.iter().any(|id| id.to_string().starts_with(spec)) {\n            return Err(anyhow::anyhow!(\"line {}: commit {spec} not in divergence (stale script?)\", i + 1));\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"if let Err(err) = parse_integration_steps_script(&script, &divergence) {\n    if err.to_string().contains(\"not part of the editable divergence\") {\n        // prompt: divergence changed, re-render the proposal and re-apply edits\n    }\n}","preventionTips":["Bind the editor session to a snapshot of the divergence; if the branch updated since rendering, re-render before applying.","Only let users reference commits listed in the rendered script/divergence view."],"tags":["rust","git","but-workspace","integration","todo-script","commit-resolution"],"backgroundTag":"commit-not-in-range","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}