{"record":{"id":"0d77c12b228cb937","repo":"gitbutlerapp/gitbutler","slug":"branch-is-pushed-but-its-remote-ancestry-doe","errorCode":null,"errorMessage":"Branch `{}` is pushed, but its remote ancestry does not match the reviewed workspace stack; push it and its ancestors before creating a review","messagePattern":"Branch `(.+?)` is pushed, but its remote ancestry does not match the reviewed workspace stack; push it and its ancestors before creating a review","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-api/src/legacy/forge.rs","lineNumber":1886,"sourceCode":"    let repo = ctx.repo.get()?;\n\n    let mut reviewed_ancestors = stack.segments[selected_index + 1..]\n        .iter()\n        .rev()\n        .filter(|segment| review_number(segment).is_some())\n        .map(|segment| remote_head(&repo, segment))\n        .collect::<Result<Vec<_>>>()?;\n    let selected = remote_head(&repo, &stack.segments[selected_index])?;\n\n    for pair in reviewed_ancestors\n        .iter()\n        .map(|head| head.remote_tip)\n        .chain(std::iter::once(selected.remote_tip))\n        .collect::<Vec<_>>()\n        .windows(2)\n    {\n        if !remote_contains(&repo, pair[0], pair[1])? {\n            anyhow::bail!(\n                \"Branch `{}` is pushed, but its remote ancestry does not match the reviewed workspace stack; push it and its ancestors before creating a review\",\n                branch.shorten()\n            );\n        }\n    }\n\n    match reviewed_ancestors.pop() {\n        Some(nearest_reviewed_ancestor) => Ok(nearest_reviewed_ancestor.branch_name),\n        None => target_short_name(&ctx.project_meta()?, &repo),\n    }\n}\n\n#[derive(Debug)]\nstruct RemoteHead {\n    branch_name: String,\n    remote_tip: gix::ObjectId,\n}\n","sourceCodeStart":1868,"sourceCodeEnd":1904,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but-api/src/legacy/forge.rs#L1868-L1904","documentation":"Before creating a review, GitButler verifies that the remote tips of the reviewed ancestor branches plus the selected branch form a chain: each pair is checked with remote_contains, so each ancestor's remote tip must contain its parent's tip. A broken pair means remote history no longer matches the reviewed workspace stack, and review creation stops.","triggerScenarios":"Ancestor branches were pushed earlier from different commits — segments pushed independently, bottom commits rebased or amended after a previous push, or branches from different stacks mixed into one review — so a parent's remote tip is not contained in the child's remote tip.","commonSituations":"User amends or rebases lower commits after pushing; pushes individual branches rather than the whole stack; force-pushes only some ancestors; reviews spanning segments pushed out of order.","solutions":["Push the whole stack from the bottom up (GitButler push of the stack) so remote ancestry matches the workspace, then create the review","Force-push the outdated ancestor branches after the rebase/amend","Verify all segments up to the workspace base are pushed before selecting the branch for review"],"exampleFix":"# before: review a branch whose ancestors are stale on the remote\nbut review create my-branch   # fails: ancestry mismatch\n\n# after: push the stack including ancestors first, then review\nbut push --stack my-stack\nbut review create my-branch","handlingStrategy":"validation","validationCode":"let tips: Vec<gix::ObjectId> = reviewed_ancestor_tips.iter()\n    .map(|h| h.remote_tip)\n    .chain(std::iter::once(selected_tip))\n    .collect();\nfor pair in tips.windows(2) {\n    if !remote_contains(&repo, pair[0], pair[1])? {\n        // push the stack (with ancestors) before creating the review\n    }\n}","typeGuard":"fn remote_ancestry_is_chain(\n    tips: &[gix::ObjectId],\n    contains: impl Fn(&gix::ObjectId, &gix::ObjectId) -> bool,\n) -> bool {\n    tips.windows(2).all(|p| contains(&p[0], &p[1]))\n}","tryCatchPattern":"match create_review(ctx, branch).await {\n    Err(err) if err.to_string().contains(\"remote ancestry does not match\") => {\n        // prompt: push the full stack (ancestors included) and retry\n    }\n    other => other,\n}","preventionTips":["Push stacks as a unit instead of individual branches","After rebasing or amending pushed commits, force-push all affected ancestors before reviewing","Create reviews bottom-up so ancestors are always pushed first"],"tags":["forge","pull-request","push","remote","ancestry","force-push"],"backgroundTag":"diverged-remote-history","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}