{"record":{"id":"f89c964751993e1b","repo":"gitbutlerapp/gitbutler","slug":"no-merge-base-found-between-ref-name-and-its-t","errorCode":null,"errorMessage":"No merge-base found between '{ref_name}' and its tracking branch '{upstream_ref_name}'","messagePattern":"No merge-base found between '(.+?)' and its tracking branch '(.+?)'","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/divergence.rs","lineNumber":66,"sourceCode":"/// preserved parentage consistently within the current operation.\n///\n/// Returns the local-only selectors, upstream-only selectors, and the selector\n/// for their shared merge base.\npub(crate) fn get_commits_until_merge_base<'a, M: RefMetadata>(\n    ref_name: &'a gix::refs::FullNameRef,\n    upstream_ref_name: Cow<'a, gix::refs::FullNameRef>,\n    editor: &Editor<'_, '_, M>,\n) -> Result<BranchMergeBaseCommits> {\n    let local_tip = tip_for_ref(editor, ref_name, editor.repo())\n        .with_context(|| format!(\"Could not determine tip commit for '{ref_name}'\"))?;\n    let upstream_tip = tip_for_ref(editor, upstream_ref_name.as_ref(), editor.repo())\n        .with_context(|| {\n            format!(\"Could not determine tip commit for upstream '{upstream_ref_name}'\")\n        })?;\n    let upstream_ancestor_ids = traverse_pick_ancestor_ids(editor, upstream_tip)?;\n    let merge_base = find_first_parent_merge_base(editor, local_tip, &upstream_ancestor_ids)?\n        .ok_or_else(|| {\n            anyhow::anyhow!(\n                \"No merge-base found between '{ref_name}' and its tracking branch '{upstream_ref_name}'\"\n            )\n        })?;\n    let merge_base_selector = editor.select_commit(merge_base)?;\n    let local_commits = first_parent_path_until(editor, local_tip, |selector| {\n        editor.lookup_pick(*selector).ok() == Some(merge_base)\n    })?\n    .into_iter()\n    .take_while(|selector| *selector != merge_base_selector)\n    .collect::<Vec<_>>();\n    let upstream_commits = first_parent_path_until(editor, upstream_tip, |selector| {\n        editor.lookup_pick(*selector).ok() == Some(merge_base)\n    })?\n    .into_iter()\n    .take_while(|selector| *selector != merge_base_selector)\n    .collect::<Vec<_>>();\n    Ok(BranchMergeBaseCommits {\n        local_commits,","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-workspace/src/divergence.rs#L48-L84","documentation":"The divergence computation walks the upstream's first-parent ancestors and uses a first-parent merge-base search to relate a branch to its tracking branch. If no merge-base exists, the two histories are unrelated and divergence cannot be computed — typically because the upstream was force-pushed to a completely different history, was recreated, or tracks the wrong ref.","triggerScenarios":"Computing divergence for a branch whose upstream ref no longer shares any history: upstream recreated from an orphan root, hard force-push replacing all commits, or a tracking configuration pointing at an unrelated branch (e.g. upstream renamed and a new unrelated branch took the old name).","commonSituations":"Upstream repo did a history rewrite; someone deleted and recreated the remote branch; .git branch tracking metadata stale after a rename; fork relationships changed.","solutions":["Fetch and inspect: git merge-base <branch> <branch>@{upstream} to confirm unrelated histories","If the upstream was legitimately rewritten, re-target or re-create the branch on top of the new upstream (e.g. re-integrate/anchor onto the new tip)","Fix wrong tracking config: git branch --set-upstream-to=<correct-remote-branch>","If the old upstream is gone, remove the upstream association for the branch"],"exampleFix":"# before — upstream rewritten, no shared history\ngit fetch origin\ngit merge-base main origin/main   # → empty output (no merge base)\n# after — re-anchor the branch onto the new upstream\ngit rebase --onto origin/main --root my-branch  # or re-create branch from new upstream","handlingStrategy":"validation","validationCode":"// before computing divergence, confirm the two tips share history\nlet local = tip_for_ref(editor, ref_name, repo)?;\nlet upstream = tip_for_ref(editor, upstream_ref_name, repo)?;\nensure!(find_first_parent_merge_base(editor, local, &traverse_pick_ancestor_ids(editor, upstream)?)?.is_some(),\n    \"unrelated histories — fix tracking or re-anchor first\");","typeGuard":"fn has_merge_base(editor: &Editor<'_, '_, M>, local: gix::Id, upstream: gix::Id) -> bool {\n    traverse_pick_ancestor_ids(editor, upstream).ok()\n        .and_then(|anc| find_first_parent_merge_base(editor, local, &anc).ok().flatten())\n        .is_some()\n}","tryCatchPattern":"match compute_divergence(...) {\n    Err(e) if e.to_string().contains(\"No merge-base found\") => {\n        // treat as unrelated: prompt user to re-integrate onto new upstream or clear tracking\n    }\n    r => r,\n}","preventionTips":["After upstream force-pushes or recreates a branch, re-anchor local branches before asking for divergence","Verify tracking points at the intended remote branch after renames (git branch -vv)","Detect empty `git merge-base` in automation before invoking divergence-based features"],"tags":["rust","git","merge-base","unrelated-histories","force-push","but-workspace"],"backgroundTag":"no-merge-base","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}