GitoxideLabs/gitoxide · error

history traversal did not produce a graph

Error message

history traversal did not produce a graph

What it means

A history traversal is expected to emit exactly one `history::Event::Complete(value)` carrying the commit graph; the local `graph` variable is `Option` and `ok_or_else` converts `None` into this error. It is an invariant check: the traversal callback sets `graph = Some(value)` when the complete event arrives, so absence means the traversal ended without a completion event.

Solutions

  1. Verify the repository has at least one reachable commit not excluded by hidden refs/pins/worktrees — unhide a tip and retry.
  2. Check that the traversal callback's `Event::Complete` arm is actually reached (add logging/assertion before `ok_or_else`).
  3. Trace whether the traversal aborted early and returned an error that was swallowed; propagate it instead.
  4. Guard the call site: refuse to run the overview when the filtered tip set is empty.

Example fix

// before
let graph = graph.ok_or_else(|| anyhow::anyhow!("history traversal did not produce a graph"))?;
// after
let graph = graph.ok_or_else(|| anyhow::anyhow!(
    "history traversal did not produce a graph (no reachable commits after applying hidden refs)"
))?;
Defensive patterns

Strategy: validation

Validate before calling

fn traversal_can_produce_graph(repo: &gix::Repository, hidden: &[gix::RefSpec]) -> bool {
    repo.head().ok().map(|h| h.is_detached() || h.referent().is_ok()).unwrap_or(false)
        && repo.references().map(|r| r.all().map(|i| i.count() > 0).unwrap_or(false)).unwrap_or(false)
}

Type guard

fn has_complete_event(events: &[crate::history::Event]) -> bool {
    events.iter().any(|e| matches!(e, crate::history::Event::Complete(_)))
}

Try / catch

match build_overview(&repo, ...) {
    Err(e) if e.to_string().contains("history traversal did not produce a graph") => {
        eprintln!("No reachable commits after hidden refs; unhide a tip or check the repo is non-empty");
    }
    res => res?,
}

Prevention

When it happens

Trigger: The traversal producing the overview graph (ref_tree.rs:1515) finished without ever yielding `Event::Complete` — e.g. the traversal aborted early on error, the callback filtered/ignored the event, or the traversal produced zero commits because no reachable tips remained after hiding refs/pins/worktrees.

Common situations: An empty repository or one where every tip is hidden by refs/pins/worktree filters, so nothing is traversed; an early traversal error path that still returns `Ok` upstream; a regression in the history producer no longer emitting the complete event.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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

Appendix: source

Thrown at gix-tix/src/ref_tree.rs:1515

    let authors = gix::features::threading::OwnShared::new(gix::features::threading::Mutable::new(
        crate::history::Authors::default(),
    ));
    let mut graph = None;
    crate::history::load(
        repository,
        &visible_revisions,
        hidden,
        true,
        &authors,
        &AtomicBool::new(false),
        |event| {
            if let crate::history::Event::Complete(value) = event {
                graph = Some(value);
            }
            true
        },
    )?;
    let graph = graph.ok_or_else(|| anyhow::anyhow!("history traversal did not produce a graph"))?;
    let hidden_refs = hidden_refs.into_keys().collect();
    let decorations = crate::history::decorations_excluding(repository, &refs.pins, &refs.worktrees, &hidden_refs)?;
    refs.hidden_tips.clear();
    let overview = Overview::new(&graph, &refs, &decorations, show_tags);
    let labels = overview
        .nodes
        .iter()
        .filter(|node| node.raw_tip && node.decorations.is_empty())
        .map(|node| Ok((node.id, crate::change_id::display(repository, node.id, 7)?)))
        .collect::<anyhow::Result<HashMap<_, _>>>()?;
    Ok(render_overview(&overview, unicode, &labels))
}

fn render_overview(overview: &Overview, unicode: bool, commit_labels: &HashMap<ObjectId, String>) -> String {
    if overview.nodes.is_empty() {
        return String::new();
    }
    let placed = place_rail(overview, None);

View on GitHub (pinned to e73179060b)