GitoxideLabs/gitoxide · warning

prefix-stripping cannot fail as base is within our root

Error message

prefix-stripping cannot fail as base is within our root

What it means

A Rust `expect()` panic in the loose-refs directory iterator's `next()` (gix-ref/store/file/loose/iter.rs). For each file entry it strips the store's `base` directory prefix from the entry path, asserting the entry is always inside the base since both come from walking the same root. It panics if `self.base` and the walked root diverge — e.g. the base path differs from the directory handed to `std::fs::read_dir` (symlink resolution differences, non-canonicalized paths like `./refs` vs `refs`, or path casing/format mismatches on Windows).

Solutions

  1. Canonicalize both the base and the directory before constructing the iterator (`std::fs::canonicalize`) so `strip_prefix` matches.
  2. Ensure the exact same `PathBuf` value is used for the base and the traversal root — avoid rebuilding the path separately.
  3. Resolve symlinks (especially symlinked `.git` or `refs` directories) before iteration.
  4. If it occurs on canonicalized paths, report upstream with the OS and path strings — internal invariant violation.

Example fix

// before
let base = PathBuf::from("./.git/refs");
let iter = loose::Iter::at(dir_read_from_symlinked_path, base, ...); // panic
// after
let base = std::fs::canonicalize("./.git/refs")?;
let iter = loose::Iter::at(base.clone(), base, ...); // identical canonical paths
Defensive patterns

Strategy: validation

Validate before calling

let base = std::fs::canonicalize(".git/refs")?;
assert_eq!(base, dir_being_walked_canonicalized); // base must equal traversal root

Type guard

fn same_root(base: &Path, root: &Path) -> bool {
    std::fs::canonicalize(base).ok().as_deref() == std::fs::canonicalize(root).ok().as_deref()
}

Prevention

When it happens

Trigger: Creating a `loose::Iter` with a `base` that is not the canonicalized same path as the directory being traversed (e.g. base contains `..`, a symlink, or a trailing separator while read_dir resolves differently); platform-specific path normalization (`\\?\` prefixes, case differences) breaking `strip_prefix`.

Common situations: Passing relative or non-canonical repository paths (`./.git/refs`, symlinked `.git` dirs) into low-level loose-ref iteration; using a different path form for base vs the traversal root; Windows path quirks.

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

Appendix: source

Thrown at gix-ref/src/store/file/loose/iter.rs:59

            }),
        }
    }
}

impl Iterator for SortedLoosePaths {
    type Item = std::io::Result<(PathBuf, FullName)>;

    fn next(&mut self) -> Option<Self::Item> {
        for entry in self.file_walk.as_mut()?.by_ref() {
            match entry {
                Ok(entry) => {
                    if !entry.file_type().is_ok_and(|ft| ft.is_file()) {
                        continue;
                    }
                    let full_path = entry.path().into_owned();
                    let full_name = full_path
                        .strip_prefix(&self.base)
                        .expect("prefix-stripping cannot fail as base is within our root");
                    let Ok(full_name) = gix_path::try_into_bstr(full_name)
                        .map(|name| gix_path::to_unix_separators_on_windows(name).into_owned())
                    else {
                        continue;
                    };
                    if let Some(prefix) = &self.prefix
                        && !full_name.starts_with(prefix)
                    {
                        continue;
                    }
                    if let Some(suffix) = &self.suffix
                        && !full_name.ends_with(suffix)
                    {
                        continue;
                    }
                    if gix_validate::reference::name_partial(full_name.as_bstr()).is_ok() {
                        let name = FullName(full_name);
                        return Some(Ok((full_path, name)));

View on GitHub (pinned to e73179060b)