GitoxideLabs/gitoxide · error

commit belongs to multiple unrelated review trees

Error message

commit belongs to multiple unrelated review trees

What it means

`review_tree` locates the single nearest review-tree root that is an ancestor of the given commit. If the commit is a descendant of two or more review roots where neither is an ancestor of the other, the roots are unrelated and there is no unique review tree to attribute the commit to. The library throws this instead of picking an arbitrary root.

Solutions

  1. Rebase or linearize the branch so the target commit descends from only one review root.
  2. Remove or re-create one of the conflicting review roots so the roots nest (one ancestor of the other).
  3. Time-travel to a commit that lies within a single review tree instead of a merge point.
  4. Restructure the history (e.g. squash the merge) so review roots form a chain.

Example fix

// before: commit is a merge of two unrelated review roots
let dest = repo.find_commit(merge_id)?;
review_tree(&repo, &graph, &roots, dest)?; // bails

// after: target a commit on one review line
let dest = repo.find_commit(side_a_tip)?;
review_tree(&repo, &graph, &roots, dest)?; // unique nearest root
Defensive patterns

Strategy: validation

Validate before calling

let review_roots: Vec<_> = roots.iter().copied()
    .filter(|r| graph.is_ancestor(*r, target_commit)).collect();
// must be 0 or 1, or a nesting chain of exactly one root
if review_roots.len() > 1 { /* restructure history first */ }

Try / catch

match review_tree(&repo, &graph, &roots, commit) {
    Ok(Some(tree)) => /* use tree */,
    Ok(None) => /* commit is outside any review tree */,
    Err(e) if e.to_string().contains("multiple unrelated review trees") => {
        // rebase/merge restructure, then retry
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `perform_reporting_rebased` (time-travel) targeting a commit whose ancestry includes two review-tree roots that don't nest (e.g. a merge commit joining two independent review branches, each with its own review root).

Common situations: Merging two independently-created review branches into one; a review root created on a branch that was later merged with another review line; accidental duplicate review roots on divergent histories.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/4025b452e5b21b37. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/edit/time_travel.rs:924

#[derive(Clone, Debug, Eq, PartialEq)]
struct ReviewTree {
    root: ObjectId,
    reference: gix::refs::FullName,
}

fn review_tree(
    repo: &gix::Repository,
    graph: &history::HistoryGraph,
    roots: &[ObjectId],
    commit: ObjectId,
) -> Result<Option<ReviewTree>> {
    let mut nearest = None;
    for root in roots.iter().copied().filter(|root| graph.is_ancestor(*root, commit)) {
        nearest = match nearest {
            None => Some(root),
            Some(current) if graph.is_ancestor(current, root) => Some(root),
            Some(current) if graph.is_ancestor(root, current) => Some(current),
            Some(_) => anyhow::bail!("commit belongs to multiple unrelated review trees"),
        };
    }
    let Some(root) = nearest else { return Ok(None) };
    let commit = repo.find_commit(root)?.decode()?.into_owned()?;
    let reference = super::review::reference(&commit)?.context("review root lost its review identity")?;
    Ok(Some(ReviewTree { root, reference }))
}

#[tracing::instrument(skip_all, fields(review = %review.reference))]
fn save_review_stash(
    repository_path: &Path,
    bare: bool,
    workdir: &Path,
    review: &ReviewTree,
) -> Result<Option<super::stash::SavedStash>> {
    if !super::review::is_dirty(workdir)? {
        return Ok(None);
    }

View on GitHub (pinned to e73179060b)