GitoxideLabs/gitoxide · error

never without parent

Error message

never without parent

What it means

Panic raised by `.expect("never without parent")` on `reflog_path.parent()` while cleaning up empty reflog directories after a ref deletion. The invariant is that a reflog path always has a parent directory component; a `None` would mean the path was somehow just a root/file name, which cannot happen for valid reflog paths produced by the store.

Solutions

  1. Report the panic to the gitoxide project with the ref name and full stack trace.
  2. Check the deleted ref's name for pathological components that could break path construction.
  3. Update to a gix-ref version where reflog path handling is correct.
Defensive patterns

Strategy: validation

Validate before calling

// sanity-check the reflog path before deleting refs with reflogs
let path = store.reflog_path(name);
assert!(path.parent().is_some(), "reflog path must have a parent");

Prevention

When it happens

Trigger: Committing a ref transaction with deletions whose reflog cleanup path computation yields a parentless path — per the invariant, only from an internal bug in reflog path construction, not normal usage.

Common situations: Practically never; possibly exposed by exotic ref names or a bug in reflog path derivation after a gix-ref upgrade.

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

Appendix: source

Thrown at gix-ref/src/store/file/transaction/commit.rs:135

        for change in &mut updates {
            let (reflog_root, relative_name) = self.store.reflog_base_and_relative_path(change.update.name.as_ref());
            match &change.update.change {
                Change::Update { .. } => {}
                Change::Delete { .. } => {
                    // Reflog deletion happens first in case it fails a ref without log is less terrible than
                    // a log without a reference.
                    let reflog_path = reflog_root.join(relative_name);
                    if let Err(err) = std::fs::remove_file(&reflog_path) {
                        if err.kind() != std::io::ErrorKind::NotFound {
                            return Err(Error::DeleteReflog {
                                source: err,
                                full_name: change.name(),
                            });
                        }
                    } else {
                        gix_tempfile::remove_dir::empty_upward_until_boundary(
                            reflog_path.parent().expect("never without parent"),
                            &reflog_root,
                        )
                        .ok();
                    }
                }
            }
        }

        if let Some(t) = self.packed_transaction {
            t.commit().map_err(Error::PackedTransactionCommit)?;
            // Always refresh ourselves right away to avoid races. We ignore errors as there may be many reasons this fails, and it's not
            // critical to be done here. In other words, the pack may be refreshed at a later time and then it might work.
            self.store.force_refresh_packed_buffer().ok();
        }

        for change in &mut updates {
            let take_lock_and_delete = match &change.update.change {
                Change::Update {

View on GitHub (pinned to e73179060b)