gitbutlerapp/gitbutler · error · anyhow::Error

Cannot reconcile {source}: branch name '{name}' occurs more

Error message

Cannot reconcile {source}: branch name '{name}' occurs more than once

What it means

During metadata reconciliation, `ensure_unique_branch_names()` walks all workspace branch refs and rejects the operation if the same full ref name (`refs/heads/...`) appears twice. Duplicate workspace branches would make stack/branch reconciliation ambiguous, so it refuses rather than guessing. The `source` string names which reconciliation step failed.

Source

Thrown at crates/but-core/src/ref_metadata.rs:533

    config: &mut gix::config::File,
    key: &str,
    value: Option<impl AsRef<str>>,
) -> anyhow::Result<()> {
    match value {
        Some(value) => git_config::set_config_value(config, key, value.as_ref())?,
        None => git_config::remove_config_value(config, key)?,
    }
    Ok(())
}

fn ensure_unique_branch_names<'a>(
    names: impl IntoIterator<Item = &'a gix::refs::FullNameRef>,
    source: &str,
) -> Result<()> {
    let mut seen = Vec::<gix::refs::FullName>::new();
    for name in names {
        if seen.iter().any(|seen| seen.as_ref() == name) {
            bail!("Cannot reconcile {source}: branch name '{name}' occurs more than once");
        }
        seen.push(name.to_owned());
    }
    Ok(())
}

fn remove_branch_from_stacks(
    stacks: &mut [WorkspaceStack],
    preferred_stack_idx: usize,
    name: &gix::refs::FullNameRef,
) -> Option<WorkspaceStackBranch> {
    if let Some(stack) = stacks.get_mut(preferred_stack_idx)
        && let Some(branch_idx) = stack
            .branches
            .iter()
            .position(|branch| branch.ref_name.as_ref() == name)
    {
        return Some(stack.branches.remove(branch_idx));

View on GitHub (pinned to caf1f223d3)

Solutions

  1. List the workspace stacks and find the duplicated ref name (the error names it), then delete or rename one of the stacks/branches so each full ref occurs once.
  2. Rename the duplicate branch in one stack before reconciling.
  3. If both copies are stale, remove the redundant stack via GitButler UI/CLI and retry the operation that triggered reconciliation.
  4. Report to GitButler if the duplication arose without manual recovery steps — the writer allowed an invalid state.
Defensive patterns

Strategy: validation

Validate before calling

// Rust — detect duplicates before triggering reconciliation
fn find_duplicate_branch_names<'a>(
    names: impl IntoIterator<Item = &'a gix::refs::FullNameRef>,
) -> Option<String> {
    let mut seen = std::collections::HashSet::new();
    for name in names {
        if !seen.insert(name.as_bstr().to_owned()) {
            return Some(name.to_string());
        }
    }
    None
}

Try / catch

if let Some(dup) = find_duplicate_branch_names(all_refs) {
    // offer the user a rename/remove of one duplicate before reconciling
    return prompt_resolve_duplicate(dup);
}
reconcile(all_refs)?;

Prevention

When it happens

Trigger: Calling the reconcile path when two stacks (or two segments) in the workspace metadata reference the same full branch ref — typically after a sync bug duplicated a stack, metadata was restored from a backup on top of existing state, or branches were merged between stacks manually.

Common situations: Two GitButler clients writing concurrently before locking was airtight; copying project data directories between machines; recovering `metadata.toml`/database from a snapshot while refs also exist in git; importing the same branch into two stacks.

Related errors


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