GitoxideLabs/gitoxide · warning

visit_non_tree() called us

Error message

visit_non_tree() called us

What it means

This is a caller-contract assertion inside the index-writing tree visitor: `add_entry()` only ever expects to be called with non-tree entries, because tree entries are routed to the tree-recursion callback (`visit_tree`) by the `gix_traverse::tree` visitor trait. The library panics if a `tree::EntryRef` with `EntryKind::Tree` reaches `add_entry`, which can only happen if the visitor dispatch contract is broken.

Solutions

  1. If you wrote a custom `gix_traverse::tree::Visit`, route `EntryKind::Tree` entries to the `visit_tree` callback, not `visit_nontree`/`add_entry`.
  2. Update or bisect gix-index/gix-traverse versions; this indicates an internal bug worth reporting upstream.
  3. As a workaround, filter entries by mode kind before handing them to the visitor pipeline.

Example fix

// before (custom Visit impl)
fn visit_nontree(&mut self, entry: tree::EntryRef<'_>) -> Action {
    self.add_entry(&entry);
    Action::Continue
}

// after
fn visit_nontree(&mut self, entry: tree::EntryRef<'_>) -> Action {
    if entry.mode.kind() != EntryKind::Tree {
        self.add_entry(&entry);
    }
    Action::Continue
}
Defensive patterns

Strategy: type-guard

Validate before calling

if entry.mode.kind() == EntryKind::Tree {
    return Err(anyhow::anyhow!("tree entries must be routed to visit_tree, not add_entry"));
}

Type guard

fn is_non_tree(entry: &gix_traverse::tree::EntryRef<'_>) -> bool {
    entry.mode.kind() != gix_object::tree::EntryKind::Tree
}

Try / catch

// Panic-based invariant; guard at the call site:
if is_non_tree(entry) { visitor.add_entry(entry); }

Prevention

When it happens

Trigger: Only via a bug in the traversal wiring: the tree visitor implementation calls `add_entry` for an entry whose mode kind is `Tree` instead of routing it through the tree callback. End users of `gix_index` cannot trigger it directly through public APIs.

Common situations: Encountered during internal refactors of the tree-visit/index-write path, or when embedding a custom `gix_traverse::tree::Visit` implementation that forwards all entries to the non-tree callback.

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

Appendix: source

Thrown at gix-index/src/init.rs:154

        fn push_element(&mut self, name: &BStr) {
            if name.is_empty() {
                return;
            }
            if !self.path.is_empty() {
                self.path.push(b'/');
            }
            self.path.push_str(name);
            if self.invalid_path.is_none()
                && let Err(err) = gix_validate::path::component(name, None, self.validate)
            {
                self.invalid_path = Some((self.path.clone(), err));
            }
        }

        pub fn add_entry(&mut self, entry: &tree::EntryRef<'_>) {
            let mode = match entry.mode.kind() {
                EntryKind::Tree => unreachable!("visit_non_tree() called us"),
                EntryKind::Blob => Mode::FILE,
                EntryKind::BlobExecutable => Mode::FILE_EXECUTABLE,
                EntryKind::Link => Mode::SYMLINK,
                EntryKind::Commit => Mode::COMMIT,
            };
            // There are leaf-names that require special validation, specific to their mode.
            // Double-validate just for this case, as the previous validation didn't know the mode yet.
            if self.invalid_path.is_none() {
                let start = self.path.rfind_byte(b'/').map(|pos| pos + 1).unwrap_or_default();
                if let Err(err) = gix_validate::path::component(
                    self.path[start..].as_ref(),
                    (entry.mode.kind() == EntryKind::Link).then_some(gix_validate::path::component::Mode::Symlink),
                    self.validate,
                ) {
                    self.invalid_path = Some((self.path.clone(), err));
                }
            }

View on GitHub (pinned to e73179060b)