gitbutlerapp/gitbutler · error · anyhow::Error

Conflict {} of "{}" was addressed more than once

Error message

Conflict {} of "{}" was addressed more than once

What it means

Before anything is written, validate_specs() maps each ResolutionSpec to exactly one (file, 1-based hunk) slot in a per-file BTreeMap. Two specs addressing the same hunk of the same path would give one conflict two different resolutions, so the second insert bails and the whole resolve call is rejected atomically (nothing is written to the repo).

Source

Thrown at crates/but-api/src/resolve/apply.rs:80

) -> anyhow::Result<PicksPerFile> {
    let files_by_path = index_files_by_path(request)?;
    let mut picks: PicksPerFile = vec![BTreeMap::new(); request.files.len()];

    for spec in specs {
        let (file_index, hunk_index) = locate_hunk(request, &files_by_path, &spec.path, spec.hunk)?;
        let file = &request.files[file_index];
        let pick = match &spec.resolution {
            HunkResolution::Ours => HunkPick::Ours,
            HunkResolution::Theirs => HunkPick::Theirs,
            HunkResolution::Content(content) => {
                ensure_no_markers(content, &file.path)?;
                HunkPick::Content(content.clone())
            }
            // Replaced with `Content` by `resolve_ai_specs()` before validation.
            HunkResolution::Ai => bail!("AI resolutions must be materialized before validation"),
        };
        if picks[file_index].insert(hunk_index, pick).is_some() {
            bail!(
                "Conflict {} of \"{}\" was addressed more than once",
                spec.hunk,
                file.path
            );
        }
    }

    Ok(picks)
}

/// Resolve a `(path, 1-based hunk)` address against the request, returning
/// the file index and 0-based hunk index.
pub(crate) fn locate_hunk(
    request: &ResolutionRequest,
    files_by_path: &BTreeMap<String, usize>,
    path: &str,
    hunk: usize,
) -> anyhow::Result<(usize, usize)> {

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Dedupe specs by (normalize_path(path), hunk) before submitting, keeping the intended winner (usually the last)
  2. Fix the double-add in the caller - e.g. a retry path that re-appends already-present specs
  3. Use one canonical path spelling (the exact path returned by commit_conflicts) for every spec

Example fix

// before
const specs = [...selectionA, ...selectionB]; // may contain dup (path, hunk)
await api.resolveCommitConflictHunks(commitId, specs);

// after
const seen = new Set();
const specs = [...selectionA, ...selectionB].filter(s => {
  const key = `${normalizePath(s.path)}:${s.hunk}`;
  if (seen.has(key)) return false;
  seen.add(key);
  return true;
});
await api.resolveCommitConflictHunks(commitId, specs);
Defensive patterns

Strategy: validation

Validate before calling

// Dedupe specs by normalized (path, hunk), last one wins
function normalizePath(p: string): string {
  return p.trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/\+/g, '/');
}
const byKey = new Map<string, Spec>();
for (const s of specs) byKey.set(`${normalizePath(s.path)}:${s.hunk}`, s);
const uniqueSpecs = [...byKey.values()];

Prevention

When it happens

Trigger: Calling resolve_commit_conflict_hunks with specs containing the same {path, hunk} pair twice - e.g. a UI double-submit, a retry that appends to the previous spec list instead of replacing it, or two spellings of the same path ('a/b' and '.\\a\\b' / 'a//b') that normalize_path maps to the same file.

Common situations: Caller accumulates per-checkbox selections and merges arrays from two views; batched 'resolve all with ours' then a per-hunk override both firing; path-format differences (backslashes, leading ./) making the caller's dedupe key miss.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/33235e24474a8c9b. Report an issue: GitHub.