GitoxideLabs/gitoxide · error
review finish cannot rewrite merge descendants
Error message
review finish cannot rewrite merge descendants
What it means
`review finish` rewrites the ancestry of the finished review's commits, which the rebase machinery can only do for linear history. When any descendant commit of the review has more than one parent (a merge commit), the rewrite cannot be expressed, so the library bails with this message.
Solutions
- Rebase the merge descendants onto a linearized version of the review branch first (flatten the merges), then run review finish
- Finish the review before downstream merges are created
- Ask downstream authors to rebase instead of merging onto the review branch
Example fix
// before: merge commit M(=parents[A_review, X]) on top of review repo.review_finish(review_id)?; // bails // after: linearize descendants first rebase_descendants_onto_linearized(&repo, review_tip)?; repo.review_finish(review_id)?;
Defensive patterns
Strategy: validation
Validate before calling
fn has_merge_descendants(graph: &Graph, tips: &[ObjectId]) -> bool {
tips.iter().all(|id| graph.parents_of(id).unwrap().len() <= 1)
} Try / catch
match result {
Err(e) if e.to_string().contains("merge descendants") => {
eprintln!("linearize downstream merges before finishing the review");
}
r => r?,
} Prevention
- Finish reviews before merging them downstream
- Prefer rebase over merge onto review branches
- Scan descendants for multi-parent commits before invoking finish
When it happens
Trigger: Finishing a review whose merged/branch-derived commits have descendants that are merge commits — detected via `graph.parents_of(id).len() > 1` for every review descendant.
Common situations: A developer branched off a review branch and merged it back before the review was finished; CI automation created merge commits on top of the review stack; a team policy of merge-instead-of-rebase downstream of review branches.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- the squash source must have exactly one parent
- the squash target must have exactly one parent
- descendant merge commits cannot be squashed
- copying a commit requires it to have exactly one parent
- review commits cannot be copied
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/debc9e97affd4255.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/rebase.rs:1427
})
.unwrap_or_default()
} else {
HashSet::new()
};
if !checkout_path.is_empty() {
reject_pending_checkout_path(
&repo,
checkout.as_ref().expect("a non-empty checkout path has a checkout").0,
)?;
}
for id in review_ids.iter().chain(&natural_ids) {
if graph
.parents_of(*id)
.context("a review descendant is incomplete")?
.len()
> 1
{
anyhow::bail!("review finish cannot rewrite merge descendants");
}
}
let mut rewritten = HashMap::<ObjectId, Option<ObjectId>>::new();
let mut note_rewrites = Vec::new();
let mut finished_review = None;
let mut conflict = None;
for old in &review_ids {
let old_parents = graph.parents_of(*old).context("a review descendant is incomplete")?;
let mut commit = repo.find_commit(*old)?.decode()?.into_owned()?;
let new_parents = if *old == review {
vec![tip]
} else {
old_parents
.iter()
.filter_map(|parent| rewritten.get(parent).copied().unwrap_or(Some(*parent)))
.collect()
};View on GitHub (pinned to e73179060b)