gitbutlerapp/gitbutler · error

Encountered conflict when merging tree {tree_to_merge}{detai

Error message

Encountered conflict when merging tree {tree_to_merge}{details}

What it means

During octopus/merge re-merging, `but_rebase::merge` three-way-merges each parent tree of a merge commit into the accumulating result using fail-fast merge options; any unresolved conflict aborts with this error. The error is wrapped with a `ConflictErrorContext` carrying the conflicted paths (`c.ours.location()`), so callers can downcast to recover the exact files. By design there is no conflict hiding: workspace-tip merges must apply cleanly.

Source

Thrown at crates/but-rebase/src/merge.rs:72

            Ok(but_core::Commit::from_id(commit_id.attach(repo))?
                .tree_id_or_kind(TreeKind::Theirs)?
                .detach())
        })
        .collect::<Result<Vec<_>, _>>()?
        .into_iter();
    let mut ours = trees_to_merge.next().expect("two or more trees");
    let (merge_options, unresolved) = repo.merge_options_fail_fast()?;
    let mut successfully_merged = vec![ours];
    for tree_to_merge in trees_to_merge {
        let mut merge = repo.merge_trees(
            merge_base,
            ours,
            tree_to_merge,
            repo.default_merge_labels(),
            merge_options.clone(),
        )?;
        if merge.has_unresolved_conflicts(unresolved) {
            return Err(anyhow!(
                "Encountered conflict when merging tree {tree_to_merge}{details}",
                details = merge_conflict_details(&successfully_merged)
            )
            .context(ConflictErrorContext {
                paths: merge
                    .conflicts
                    .iter()
                    .map(|c| c.ours.location().to_owned())
                    .collect(),
            }));
        }
        successfully_merged.push(tree_to_merge);
        ours = merge.tree.write()?.detach();
    }
    target_merge_commit.tree = ours;
    if but_core::commit::Headers::try_from_commit(&target_merge_commit).is_none() {
        let headers = but_core::commit::Headers::from_config(&repo.config_snapshot());
        headers.set_in_commit(&mut target_merge_commit);

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Downcast the error to `ConflictErrorContext` and use its `paths` to tell the user exactly which files conflict
  2. Have the user resolve those paths in their branches/workspace, then retry the operation
  3. If operating programmatically, pre-check overlapping path modifications between the parents before attempting the merge
  4. Do not retry unchanged — content conflicts are deterministic until the underlying edits change

Example fix

// before
let merge_id = but_rebase::merge::octopus(repo, commit, &mut graph)?; // opaque conflict error

// after
let merge_id = match octopus(repo, commit, &mut graph) {
    Ok(id) => id,
    Err(err) => {
        if let Some(ctx) = err.downcast_ref::<ConflictErrorContext>() {
            return Err(err.context(format!("conflicted files: {:?}", ctx.paths)));
        }
        return Err(err);
    }
};
Defensive patterns

Strategy: try-catch

Type guard

fn is_conflict_error(err: &anyhow::Error) -> bool {
    err.downcast_ref::<ConflictErrorContext>().is_some()
}

Try / catch

match octopus(repo, commit, &mut graph) {
    Ok(id) => Ok(id),
    Err(err) if err.downcast_ref::<ConflictErrorContext>().is_some() => {
        let paths: Vec<_> = err
            .downcast_ref::<ConflictErrorContext>()
            .map(|c| c.paths.clone())
            .unwrap_or_default();
        Err(err.context(format!("merge conflicts in: {paths:?}"))) // surface to conflict-resolution UI
    }
    Err(err) => Err(err),
}

Prevention

When it happens

Trigger: Rebasing/re-merging a merge commit whose parent trees modify the same lines incompatibly (classic content conflict); merging more than two parents where an intermediate result conflicts with the next tree; changes to the same file on multiple branches being integrated into one workspace merge commit.

Common situations: GitButler integrating multiple virtual branches that touch the same lines; rebasing a merge commit across a base where one side edited a file the other deleted or rewrote; binary files modified in multiple parents.

Related errors


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