{"record":{"id":"0667d9a47d5c7769","repo":"gitbutlerapp/gitbutler","slug":"encountered-conflict-when-merging-tree-tree-to-me","errorCode":null,"errorMessage":"Encountered conflict when merging tree {tree_to_merge}{details}","messagePattern":"Encountered conflict when merging tree (.+?)(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-rebase/src/merge.rs","lineNumber":72,"sourceCode":"            Ok(but_core::Commit::from_id(commit_id.attach(repo))?\n                .tree_id_or_kind(TreeKind::Theirs)?\n                .detach())\n        })\n        .collect::<Result<Vec<_>, _>>()?\n        .into_iter();\n    let mut ours = trees_to_merge.next().expect(\"two or more trees\");\n    let (merge_options, unresolved) = repo.merge_options_fail_fast()?;\n    let mut successfully_merged = vec![ours];\n    for tree_to_merge in trees_to_merge {\n        let mut merge = repo.merge_trees(\n            merge_base,\n            ours,\n            tree_to_merge,\n            repo.default_merge_labels(),\n            merge_options.clone(),\n        )?;\n        if merge.has_unresolved_conflicts(unresolved) {\n            return Err(anyhow!(\n                \"Encountered conflict when merging tree {tree_to_merge}{details}\",\n                details = merge_conflict_details(&successfully_merged)\n            )\n            .context(ConflictErrorContext {\n                paths: merge\n                    .conflicts\n                    .iter()\n                    .map(|c| c.ours.location().to_owned())\n                    .collect(),\n            }));\n        }\n        successfully_merged.push(tree_to_merge);\n        ours = merge.tree.write()?.detach();\n    }\n    target_merge_commit.tree = ours;\n    if but_core::commit::Headers::try_from_commit(&target_merge_commit).is_none() {\n        let headers = but_core::commit::Headers::from_config(&repo.config_snapshot());\n        headers.set_in_commit(&mut target_merge_commit);","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-rebase/src/merge.rs#L54-L90","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Downcast the error to `ConflictErrorContext` and use its `paths` to tell the user exactly which files conflict","Have the user resolve those paths in their branches/workspace, then retry the operation","If operating programmatically, pre-check overlapping path modifications between the parents before attempting the merge","Do not retry unchanged — content conflicts are deterministic until the underlying edits change"],"exampleFix":"// before\nlet merge_id = but_rebase::merge::octopus(repo, commit, &mut graph)?; // opaque conflict error\n\n// after\nlet merge_id = match octopus(repo, commit, &mut graph) {\n    Ok(id) => id,\n    Err(err) => {\n        if let Some(ctx) = err.downcast_ref::<ConflictErrorContext>() {\n            return Err(err.context(format!(\"conflicted files: {:?}\", ctx.paths)));\n        }\n        return Err(err);\n    }\n};","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn is_conflict_error(err: &anyhow::Error) -> bool {\n    err.downcast_ref::<ConflictErrorContext>().is_some()\n}","tryCatchPattern":"match octopus(repo, commit, &mut graph) {\n    Ok(id) => Ok(id),\n    Err(err) if err.downcast_ref::<ConflictErrorContext>().is_some() => {\n        let paths: Vec<_> = err\n            .downcast_ref::<ConflictErrorContext>()\n            .map(|c| c.paths.clone())\n            .unwrap_or_default();\n        Err(err.context(format!(\"merge conflicts in: {paths:?}\"))) // surface to conflict-resolution UI\n    }\n    Err(err) => Err(err),\n}","preventionTips":["Always downcast for ConflictErrorContext instead of string-matching the message","Surface conflicted paths to the user immediately; conflicts need edits, not retries","Before integrating many branches, check for overlapping modified paths between them"],"tags":["rust","git","merge","conflict","rebase"],"backgroundTag":"merge-conflict","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}