GitoxideLabs/gitoxide · error

we exit as soon as everything is consumed

Error message

we exit as soon as everything is consumed

What it means

`write_at_pathbuf` in `gix-object`'s `TreeEditor` loops consuming pending parent entries, and each iteration either finishes (returns Ok) or re-queues parents. The trailing `unreachable!` asserts the loop always exits. Firing means the pending-queue reduction logic failed to converge, an internal editor invariant break.

Solutions

  1. Report upstream with the sequence of `upsert`/`remove` edits and the base tree that reproduce it.
  2. Update `gix-object` to the latest version.
  3. If maintaining, add a bounded-iteration guard returning an error instead of panicking.

Example fix

// before
unreachable!("we exit as soon as everything is consumed")
// after
Err(message("tree editor write did not converge; internal error"))
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate edited paths are unique and well-formed before writing
// Use BString paths without duplicate entries; check for conflicting upserts on the same path

Try / catch

let result = std::panic::catch_unwind(|| editor.write(buf));
// treat panic as a bug: file an issue with the edit sequence

Prevention

When it happens

Trigger: Only via a bug in `TreeEditor::write`/`upsert` bookkeeping (e.g. a cycle or un-reduced parent entry), not via user-provided paths; users just call `editor.upsert(...)` then `editor.write(...)`.

Common situations: Should never occur; if it does, it happens while writing deeply nested or conflicting path edits to a tree.

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

Appendix: source

Thrown at gix-object/src/tree/editor.rs:266

                                WriteMode::FromCursor => {}
                            }
                            self.trees.insert(rela_path, tree);
                            return Ok(root_tree_id);
                        }
                        Err(err) => {
                            self.trees.insert(rela_path, tree);
                            return Err(err);
                        }
                    }
                } else if !tree.entries.is_empty() {
                    out(&tree)?;
                }
            } else {
                parents.push((parent_idx, rela_path, tree));
            }
        }

        unreachable!("we exit as soon as everything is consumed")
    }

    fn upsert_or_remove_at_pathbuf<I, C>(&mut self, rela_path: I, edit: EditMode) -> Result<&mut Self, Error>
    where
        I: IntoIterator<Item = C>,
        C: AsRef<BStr>,
    {
        let mut path_buf = self.path_buf.borrow_mut();
        let mut cursor = self.trees.get_mut(path_buf.as_bstr()).expect("root is always present");
        let mut rela_path = rela_path.into_iter().peekable();
        let new_kind_is_tree = matches!(edit, EditMode::Upsert(EntryKind::Tree, _, _));
        while let Some(name) = rela_path.next() {
            let name = name.as_ref();
            if name.is_empty() {
                return Err(Error::EmptyPathComponent);
            }
            let is_last = rela_path.peek().is_none();
            let mut needs_sorting = false;

View on GitHub (pinned to e73179060b)