gitbutlerapp/gitbutler · error · anyhow::Error
No merge-base found between '{ref_name}' and its tracking br
Error message
No merge-base found between '{ref_name}' and its tracking branch '{upstream_ref_name}' What it means
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.
Source
Thrown at crates/but-workspace/src/divergence.rs:66
/// preserved parentage consistently within the current operation.
///
/// Returns the local-only selectors, upstream-only selectors, and the selector
/// for their shared merge base.
pub(crate) fn get_commits_until_merge_base<'a, M: RefMetadata>(
ref_name: &'a gix::refs::FullNameRef,
upstream_ref_name: Cow<'a, gix::refs::FullNameRef>,
editor: &Editor<'_, '_, M>,
) -> Result<BranchMergeBaseCommits> {
let local_tip = tip_for_ref(editor, ref_name, editor.repo())
.with_context(|| format!("Could not determine tip commit for '{ref_name}'"))?;
let upstream_tip = tip_for_ref(editor, upstream_ref_name.as_ref(), editor.repo())
.with_context(|| {
format!("Could not determine tip commit for upstream '{upstream_ref_name}'")
})?;
let upstream_ancestor_ids = traverse_pick_ancestor_ids(editor, upstream_tip)?;
let merge_base = find_first_parent_merge_base(editor, local_tip, &upstream_ancestor_ids)?
.ok_or_else(|| {
anyhow::anyhow!(
"No merge-base found between '{ref_name}' and its tracking branch '{upstream_ref_name}'"
)
})?;
let merge_base_selector = editor.select_commit(merge_base)?;
let local_commits = first_parent_path_until(editor, local_tip, |selector| {
editor.lookup_pick(*selector).ok() == Some(merge_base)
})?
.into_iter()
.take_while(|selector| *selector != merge_base_selector)
.collect::<Vec<_>>();
let upstream_commits = first_parent_path_until(editor, upstream_tip, |selector| {
editor.lookup_pick(*selector).ok() == Some(merge_base)
})?
.into_iter()
.take_while(|selector| *selector != merge_base_selector)
.collect::<Vec<_>>();
Ok(BranchMergeBaseCommits {
local_commits,View on GitHub (pinned to caf1f223d3)
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
Example fix
# before — upstream rewritten, no shared history git fetch origin git merge-base main origin/main # → empty output (no merge base) # after — re-anchor the branch onto the new upstream git rebase --onto origin/main --root my-branch # or re-create branch from new upstream
Defensive patterns
Strategy: validation
Validate before calling
// before computing divergence, confirm the two tips share history
let local = tip_for_ref(editor, ref_name, repo)?;
let upstream = tip_for_ref(editor, upstream_ref_name, repo)?;
ensure!(find_first_parent_merge_base(editor, local, &traverse_pick_ancestor_ids(editor, upstream)?)?.is_some(),
"unrelated histories — fix tracking or re-anchor first"); Type guard
fn has_merge_base(editor: &Editor<'_, '_, M>, local: gix::Id, upstream: gix::Id) -> bool {
traverse_pick_ancestor_ids(editor, upstream).ok()
.and_then(|anc| find_first_parent_merge_base(editor, local, &anc).ok().flatten())
.is_some()
} Try / catch
match compute_divergence(...) {
Err(e) if e.to_string().contains("No merge-base found") => {
// treat as unrelated: prompt user to re-integrate onto new upstream or clear tracking
}
r => r,
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: Upstream repo did a history rewrite; someone deleted and recreated the remote branch; .git branch tracking metadata stale after a rename; fork relationships changed.
Related errors
- Integration steps cannot be empty
- line {line_number}: pick does not accept a message clause
- line {line_number}: pick requires exactly one commit
- line {line_number}: merge does not accept a message clause
- line {line_number}: merge requires exactly one commit
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/f89c964751993e1b.
Report an issue: GitHub.