GitoxideLabs/gitoxide · warning

initial hunks are never ancestors

Error message

initial hunks are never ancestors

What it means

In the gix-merge built-in text merge driver, hunks are labeled by which side they were computed for (`Current`, `Other`, or `Ancestor`). When assembling the conflict hunk pairs, the code asserts that the 'filled hunks' side is never `Ancestor`, because initial (ancestor) hunks are integrated separately before this point. The panic fires only if that internal classification invariants is violated.

Solutions

  1. Ensure hunk-side classification assigns `Side::Current` or `Side::Other` when filling conflict hunks; ancestor hunks go through `write_ancestor`.
  2. Use the public merge API (`merge::blob::Platform::merge`) instead of internal driver functions.
  3. Report upstream with the inputs that triggered it if reproducible via the public API.
Defensive patterns

Strategy: validation

Validate before calling

// Ancestor-side hunks must be consumed before conflict assembly
if filled_hunks_side == Side::Ancestor {
    return Err(anyhow::anyhow!("ancestor hunks must be integrated before conflict output"));
}

Type guard

fn is_merge_side(side: Side) -> bool {
    matches!(side, Side::Current | Side::Other)
}

Prevention

When it happens

Trigger: Not reachable through public merge APIs (`gix::merge::blob` / platform `merge()`): it requires the internal `filled_hunks_side` computation to label hunks as belonging to the ancestor side when constructing Conflict::Remainder style output.

Common situations: Only during gix-merge internal development, changes to hunk classification, or custom drivers reusing internal helpers with mislabeled sides.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at gix-merge/src/blob/builtin_driver/text/function.rs:108

                let intersecting_range = before_range_from_hunks(&intersecting);
                let extended_range = filled_hunks_range.start..intersecting_range.end.max(filled_hunks_range.end);
                fill_ancestor(&extended_range, &mut current_hunks);
                fill_ancestor(&extended_range, &mut intersecting);
            }
            match conflict {
                Conflict::Keep { style, marker_size } => {
                    let marker_size = marker_size.get();
                    let (hunks_front_and_back, num_hunks_front) = match style {
                        ConflictStyle::Merge | ConflictStyle::ZealousDiff3 => {
                            zealously_contract_hunks(&mut current_hunks, &mut intersecting, input, current_tokens)
                        }
                        ConflictStyle::Diff3 => (Vec::new(), 0),
                    };
                    let (our_hunks, their_hunks) = match filled_hunks_side {
                        Side::Current => (&current_hunks, &intersecting),
                        Side::Other => (&intersecting, &current_hunks),
                        Side::Ancestor => {
                            unreachable!("initial hunks are never ancestors")
                        }
                    };
                    let (front_hunks, back_hunks) = hunks_front_and_back.split_at(num_hunks_front);
                    let first_hunk = first_hunk(front_hunks, our_hunks, their_hunks, back_hunks);
                    let last_hunk = last_hunk(front_hunks, our_hunks, their_hunks, back_hunks);
                    write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
                    write_hunks(front_hunks, input, current_tokens, out);
                    // DEVIATION: this makes tests (mostly) pass, but probably is very different from what Git does.
                    let hunk_storage;
                    let nl = detect_line_ending(
                        if front_hunks.is_empty() {
                            hunk_storage = Hunk {
                                before: ancestor_integrated_until..first_hunk.before.start,
                                after: Default::default(),
                                side: Side::Ancestor,
                            };
                            std::slice::from_ref(&hunk_storage)
                        } else {

View on GitHub (pinned to e73179060b)