{"record":{"id":"7a7eb6731553c0fe","repo":"affaan-m/ECC","slug":"git-merge-tree-failed-stderr","errorCode":null,"errorMessage":"git merge-tree failed: {stderr}","messagePattern":"git merge-tree failed: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"ecc2/src/worktree/mod.rs","lineNumber":808,"sourceCode":"        let overflow = conflicts.len().saturating_sub(3);\n        let detail = if overflow > 0 {\n            format!(\"{conflict_summary}, +{overflow} more\")\n        } else {\n            conflict_summary\n        };\n\n        return Ok(MergeReadiness {\n            status: MergeReadinessStatus::Conflicted,\n            summary: format!(\n                \"Merge blocked between {left_branch} and {right_branch} by {} conflict(s): {detail}\",\n                conflicts.len()\n            ),\n            conflicts,\n        });\n    }\n\n    let stderr = String::from_utf8_lossy(&output.stderr);\n    anyhow::bail!(\"git merge-tree failed: {stderr}\");\n}\n\npub fn branch_conflict_preview(\n    left: &WorktreeInfo,\n    right: &WorktreeInfo,\n    max_lines: usize,\n) -> Result<Option<BranchConflictPreview>> {\n    if left.base_branch != right.base_branch {\n        return Ok(None);\n    }\n\n    let repo_root = base_checkout_path(left)?;\n    let readiness = merge_readiness_for_branches(&repo_root, &left.branch, &right.branch)?;\n    if readiness.status != MergeReadinessStatus::Conflicted {\n        return Ok(None);\n    }\n\n    Ok(Some(BranchConflictPreview {","sourceCodeStart":790,"sourceCodeEnd":826,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/worktree/mod.rs#L790-L826","documentation":"merge_readiness_for_branches runs `git merge-tree --write-tree <left> <right>` (the modern git 2.38+ form). If git exits non-zero AND the output yielded zero parseable conflict paths, the function gives up and bails with git's stderr. The 'conflicts but no conflict lines' branch means git failed outright rather than reporting a merge conflict.","triggerScenarios":"Either branch ref does not exist; git version is older than 2.38 and does not support `merge-tree --write-tree`; object database corruption; refs are missing because fetch/pull never happened for one side.","commonSituations":"CI image pinned to an old git (e.g. 2.30); worktree branch not yet pushed so the ref is unknown to a shallow/partial clone; `base_branch` was deleted on the remote; corrupted loose object after interrupted gc.","solutions":["Confirm git >= 2.38: `git --version`; bump the image if older.","Verify both refs resolve locally: `git rev-parse --verify <left>` and `git rev-parse --verify <right>`.","Run `git fetch --all` (or fetch the base) so the base_branch ref is present.","Run `git fsck` if refs resolve but merge-tree still errors."],"exampleFix":"// before\nlet readiness = merge_readiness_for_branches(&root, &base, &branch)?;\n\n// after\n// ensure both refs resolve before asking merge-tree about them\nfor r in [&base, &branch] {\n    let ok = Command::new(\"git\").arg(\"-C\").arg(&root)\n        .args([\"rev-parse\", \"--verify\", r]).output()?.status.success();\n    if !ok { anyhow::bail!(\"ref does not exist: {r}\"); }\n}\nlet readiness = merge_readiness_for_branches(&root, &base, &branch)?;","handlingStrategy":"validation","validationCode":"use std::process::Command;\n\nfn git_supports_write_tree() -> bool {\n    // merge-tree --write-tree landed in git 2.38\n    let out = Command::new(\"git\").arg(\"--version\").output();\n    matches!(out, Ok(o) if String::from_utf8_lossy(&o.stdout).contains(\"git version 2.38\")\n        || newer_than_2_38(&String::from_utf8_lossy(&o.stdout)))\n}\n\nfn both_refs_resolve(root: &Path, left: &str, right: &str) -> bool {\n    for r in [left, right] {\n        let ok = Command::new(\"git\").arg(\"-C\").arg(root)\n            .args([\"rev-parse\", \"--verify\", r]).output()\n            .map(|o| o.status.success()).unwrap_or(false);\n        if !ok { return false; }\n    }\n    true\n}\n\nif !git_supports_write_tree() { anyhow::bail!(\"git >= 2.38 required for merge readiness\"); }\nif !both_refs_resolve(&root, left, right) { anyhow::bail!(\"one or both refs do not exist locally\"); }\nlet readiness = merge_readiness_for_branches(&root, left, right)?;","typeGuard":null,"tryCatchPattern":"match merge_readiness_for_branches(&root, left, right) {\n    Ok(r) => Ok(r),\n    Err(e) => {\n        let m = format!(\"{e:#}\");\n        if m.contains(\"usage: git merge-tree\") || m.contains(\"unknown option\") {\n            // ancient git; fall back to a dry-run merge in a temp worktree\n        } else if m.contains(\"Not a valid object name\") {\n            // fetch and retry once\n        }\n        Err(e)\n    }\n}","preventionTips":["Pin CI/runtime images to git >= 2.38 if you use merge-readiness features.","Always `git fetch` the base branch before computing readiness so the ref is present.","Run `git fsck` if both refs resolve but merge-tree still errors."],"tags":["git","subprocess","worktree","merge","version-compat"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}