{"record":{"id":"e7c53bc0f648c92b","repo":"gitbutlerapp/gitbutler","slug":"commit-prefix-spec-is-ambiguous","errorCode":null,"errorMessage":"commit prefix '{spec}' is ambiguous","messagePattern":"commit prefix '(.+?)' is ambiguous","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/branch/integrate_branch_upstream/parsing.rs","lineNumber":161,"sourceCode":"                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}\n\nfn parse_quoted_string(input: &str) -> Result<String> {","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-workspace/src/branch/integrate_branch_upstream/parsing.rs#L143-L179","documentation":"Raised by `resolve_commit` when a commit spec is a prefix that matches two or more commits in the allowed divergence set (string prefix match against full hex ObjectIds). The parser refuses to guess which commit was meant.","triggerScenarios":"Using a very short prefix like `4f` when two divergence commits both start with `4f`; prefixes shorter than the unique-prefix length of the current divergence.","commonSituations":"Users shorten SHAs to 1-4 chars as on CLI git; automated editors that truncate SHAs for readability.","solutions":["Lengthen the prefix until it is unique (7+ hex chars is usually enough; full 40-char SHA always works).","Copy the exact prefixes emitted by `render_integration_steps_script`, which uses `short_id` and is what the UI displays."],"exampleFix":"# before\nsquash 4f 991b\n\n# after\nsquash 4f2a9c1 991b3aa","handlingStrategy":"validation","validationCode":"// Enforce unique prefixes up front:\nfn unique_prefix_len(ids: &HashSet<gix::ObjectId>, spec: &str) -> bool {\n    ids.iter().filter(|id| id.to_string().starts_with(spec)).count() == 1\n}","typeGuard":null,"tryCatchPattern":"if let Err(err) = parse_integration_steps_script(&script, &divergence) {\n    if err.to_string().contains(\"ambiguous\") {\n        // ask user to lengthen the prefix on the reported line\n    }\n}","preventionTips":["Emit scripts via render_integration_steps_script (uses short_id) rather than hand-shortening SHAs.","Prefer 7+ hex characters; full SHAs never conflict."],"tags":["rust","git","but-workspace","integration","todo-script","commit-resolution","ambiguous"],"backgroundTag":"ambiguous-commit-prefix","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}