GitoxideLabs/gitoxide · error

continued refs have a target

Error message

continued refs have a target

What it means

This `expect` panic (gix-tix/src/edit/rebase.rs:200) occurs while rebuilding `ExpectedRef` entries for a rebase continuation. For each expected ref not present in `final_refs`, the code asserts that `expected.new` is `Some(target)`, i.e. every ref that is still being carried forward has a known new target. A `None` means the plan recorded a ref whose target is unknown, which would corrupt the ref-update plan.

Solutions

  1. Delete the stale rebase continuation state and restart the rebase from scratch.
  2. Filter refs with `new: None` out of the continuation instead of asserting (treat them as already resolved).
  3. Validate the continuation plan on load: reject or repair plans containing target-less expected refs before resuming.

Example fix

// before
target: expected.new.expect("continued refs have a target"),
// after
match expected.new {
    Some(target) => target,
    None => continue, // skip refs whose target was never recorded; they are already resolved
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Before resuming a continuation, validate the plan:
fn plan_is_resumable(plan: &Plan) -> bool {
    plan.expected_refs.iter()
        .filter(|e| !plan.final_refs.contains(&e.name))
        .all(|e| e.new.is_some())
}

Type guard

fn continuable(e: &ExpectedRef, final_refs: &HashSet<...>) -> Option<ExpectedRef> {
    let target = e.new?;
    (!final_refs.contains(&e.name)).then_some(ExpectedRef { target, ..e.clone() })
}

Try / catch

// All panics here are process-fatal in Rust; guard by validating before resume:
if !plan_is_resumable(&plan) {
    eprintln!("stale/incomplete rebase continuation; restart the rebase");
}

Prevention

When it happens

Trigger: Running a rebase continuation (resume) where the saved plan's `expected_refs` contain an entry with `new: None` that is not in `final_refs`. This can happen if the continuation state was produced by an older/buggy version, was hand-edited, or if plan construction allowed pending refs without targets to be marked as continued.

Common situations: Resuming an interrupted rebase whose on-disk continuation file was written by a different gix-tix version (schema drift), or a crash mid-write leaving a truncated/incomplete continuation state.

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/d6be9c1553dd51b1. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/edit/rebase.rs:200

    pub(crate) fn persist_objects(&mut self) -> Result<()> {
        self.conflict.prepared.persist_objects()
    }

    pub(crate) fn map(&self, id: ObjectId) -> Option<ObjectId> {
        self.rewritten.get(&id).copied().unwrap_or(Some(id))
    }

    pub(crate) fn continuation_plan(&self) -> Plan {
        let expected_refs = self
            .plan
            .expected_refs
            .iter()
            .filter(|expected| !self.final_refs.contains(&expected.name))
            .map(|expected| ExpectedRef {
                name: expected.name.clone(),
                old: expected.old,
                target: expected.new.expect("continued refs have a target"),
                new: expected.new,
                follows_tip: expected.follows_tip,
                editable: expected.editable,
                placement: expected.placement.map(|target| match target {
                    PlanParent::Existing(id) => PlanParent::Existing(id),
                    PlanParent::Step(index) if index < self.continuation_start => {
                        PlanParent::Existing(self.produced[index])
                    }
                    PlanParent::Step(index) => PlanParent::Step(index - self.continuation_start),
                }),
            })
            .collect();
        let mut scope = self.produced[self.continuation_start..].to_vec();
        scope.extend(self.remaining_squash.iter().flatten().copied());
        let base = match self.plan.steps[self.continuation_start].parent {
            PlanParent::Existing(id) => id,
            PlanParent::Step(parent) => self.produced[parent],
        };

View on GitHub (pinned to e73179060b)