{"record":{"id":"7d18c93483827c4c","repo":"gitbutlerapp/gitbutler","slug":"two-conflicted-paths-normalize-to-the-same-value","errorCode":null,"errorMessage":"Two conflicted paths normalize to the same value ({:?}); resolve this commit manually instead","messagePattern":"Two conflicted paths normalize to the same value \\((.+?)\\); resolve this commit manually instead","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-api/src/resolve/apply.rs","lineNumber":141,"sourceCode":"            file.hunks.len(),\n            if file.hunks.len() == 1 { \"\" } else { \"s\" },\n            hunk\n        );\n    }\n    Ok((file_index, hunk - 1))\n}\n\n/// Map normalized request paths to file indices, rejecting collisions.\npub(crate) fn index_files_by_path(\n    request: &ResolutionRequest,\n) -> anyhow::Result<BTreeMap<String, usize>> {\n    let mut files_by_path = BTreeMap::new();\n    for (index, file) in request.files.iter().enumerate() {\n        if files_by_path\n            .insert(normalize_path(&file.path), index)\n            .is_some()\n        {\n            bail!(\n                \"Two conflicted paths normalize to the same value ({:?}); resolve this commit manually instead\",\n                normalize_path(&file.path)\n            );\n        }\n    }\n    Ok(files_by_path)\n}\n\n/// Reject content that contains a conflict-marker-shaped line.\npub(crate) fn ensure_no_markers(content: &str, path: &str) -> anyhow::Result<()> {\n    if let Some(marker) = content\n        .lines()\n        .map(|line| line.strip_suffix('\\r').unwrap_or(line))\n        .find(|line| is_marker_shaped(line))\n    {\n        bail!(\"The resolution for \\\"{path}\\\" contains a conflict marker ({marker:?})\");\n    }\n    Ok(())","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-api/src/resolve/apply.rs#L123-L159","documentation":"index_files_by_path() keys every conflicted file of the commit by its normalized path (trim, backslashes to slashes, strip leading ./, collapse duplicate slashes). If two distinct conflicted paths normalize to the same key, a path-addressed spec would be ambiguous between two files, so the whole request is rejected with advice to resolve manually. This is a property of the commit itself, not of the specs - every resolve call on such a commit fails.","triggerScenarios":"A conflicted commit containing two paths that differ only in separator style or redundant components - e.g. 'dir/file' and '.\\\\dir\\\\file', or 'a//b' and 'a/b' - which can appear after rename/rename conflicts on case-insensitive filesystems or unusual merge tooling. Any resolve_commit_conflict_hunks / AI resolve call on that commit then bails during validation.","commonSituations":"Case-renames on macOS/Windows producing both spellings; merge commits created by external tools that mixed path separators; repos with paths that only differ pre-normalization.","solutions":["Resolve this particular commit manually, as the message says (edit mode or git mergetool outside the API)","Check the commit's conflicted paths for separator-only or './'-only differences and normalize them in the merge","If reproducible, report it - it is a defensive guard against ambiguous path addressing, and the input commit shape is worth a bug report"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await api.resolveCommitConflictHunks(commitId, specs);\n} catch (err) {\n  if (String(err).includes('normalize to the same value')) {\n    // commit-level ambiguity: no spec shape can fix it\n    routeToManualResolution(commitId);\n  } else throw err;\n}","preventionTips":["Expect this on commits mixing path separators (rename/rename on case-insensitive filesystems) - route them to manual resolution proactively","When creating conflicted commits programmatically, emit one canonical path spelling","Do not retry with different path spellings; the collision is between the commit's own files"],"tags":["conflict-resolution","path-normalization","ambiguous-path","validation"],"backgroundTag":"ambiguous-path-identifier","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}