GitoxideLabs/gitoxide · error
the review base must be an ancestor of the reviewed commit
Error message
the review base must be an ancestor of the reviewed commit
What it means
`review::start` validates that the review base is a strict ancestor of the reviewed commit: if `tip == base` or `graph.is_ancestor(base, tip)` is false, a review would have an empty or invalid diff, so it bails. This guarantees every review compares a base strictly below the tip in history.
Solutions
- Pass a base that is a proper ancestor of the tip (e.g. the merge-base of the branch and main).
- Swap the arguments if base and tip were reversed.
- Pick a different, earlier commit as the review base.
- If tip == base is intended, there is nothing to review — skip the review creation.
Example fix
// before: base may equal or exceed tip review::start(repo, tip_id, tip_id)?; // bails // after: derive a valid ancestor base let base = repo.merge_base(base_candidate, tip_id)?; assert!(base != tip_id); review::start(repo, base, tip_id)?;
Defensive patterns
Strategy: validation
Validate before calling
let graph = repo.rewrite_graph()?;
if tip_id == base_id || !graph.is_ancestor(base_id, tip_id) {
// pick a proper ancestor (e.g. merge-base) before starting the review
}
Type guard
fn is_valid_review_pair(graph: &Graph, base: ObjectId, tip: ObjectId) -> bool {
base != tip && graph.is_ancestor(base, tip)
}
Prevention
- Derive base from merge-base rather than hardcoding
- Always pass (base, tip) in ancestor-descendant order
- Refuse to review commits that equal the base (empty diff)
When it happens
Trigger: Calling `review::start` (via `starts_review_with_base_index_and_tip_worktree` and similar flows) with `base` equal to `tip`, or with a `base` that is not an ancestor of `tip` (diverged branches, swapped arguments, base on an unrelated line of history).
Common situations: Passing base/tip in the wrong order; reviewing a squashed/rebased commit against a base that was rewritten; accidentally selecting the same commit for both ends; reviewing across diverged branches.
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
- review commits can amend only staged paths
- review commits cannot be copied
- review commit names an invalid review reference
- has a pending rebase
- commit belongs to multiple unrelated review trees
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/cdfdd4cf7380fc77.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/review.rs:119
}
#[tracing::instrument(skip_all, fields(%tip, %base))]
pub(crate) fn start(
repository_path: &Path,
bare: bool,
graph: &history::HistoryGraph,
tip: ObjectId,
base: ObjectId,
) -> Result<Started> {
let repo = open_repository(repository_path, bare, false).context("could not open repository to start review")?;
let workdir = repo.workdir().context("review requires a worktree")?.to_owned();
let head = repo.head().context("could not read HEAD before review")?;
let restore = (
head.referent_name().map(ToOwned::to_owned),
head.id().map(gix::Id::detach),
);
if tip == base || !graph.is_ancestor(base, tip) {
anyhow::bail!("the review base must be an ancestor of the reviewed commit");
}
for (label, id) in [("reviewed commit", tip), ("review base", base)] {
let commit = repo
.find_commit(id)
.with_context(|| format!("could not find {label}"))?
.decode()?
.into_owned()?;
if super::rebase::is_pending(&commit) {
anyhow::bail!("{label} has a pending rebase");
}
}
ensure_clean(&workdir)?;
let departure_pin = match restore.1 {
Some(id) => {
let target = restore.0.clone().map_or(Target::Object(id), Target::Symbolic);
Some((
super::time_travel::create_pin(&repo, target, id, "tix review departure")?,View on GitHub (pinned to e73179060b)