gitbutlerapp/gitbutler · error
BUG: we use 'matching' so there are no directories
Error message
BUG: we use 'matching' so there are no directories
What it means
`disk_kind_to_entry_kind` maps an untracked item's `gix::dir::entry::Kind` to a `but_core::diff::EntryKind`. `Kind::Directory` is `unreachable!` because the untracked traversal runs in `matching` mode, where directories are not collapsed into entries — every emitted path is a leaf (file, symlink, repository). Seeing a Directory therefore means the traversal was run in a collapsing mode (`collapsed` untracked handling) or an index entry carried a directory kind, both of which this code explicitly rules out.
Source
Thrown at crates/but-core/src/diff/worktree.rs:860
.kind())
}
/// Most importantly, this function allows to skip over untrackable entries, like named pipes, sockets and character devices, just like Git.
/// `path` is needed for now while we have to stat the file again to learn about the executable bits.
// TODO: remove `path` and provide the stat information or at least executable info with `gitoxide` - it has that info.
fn disk_kind_to_entry_kind(
disk_kind: Option<gix::dir::entry::Kind>,
index_kind: Option<gix::dir::entry::Kind>,
path: PathBuf,
) -> anyhow::Result<Option<EntryKind>> {
Ok(Some(
match disk_kind
.or(index_kind)
.context("Didn't have any type information for untracked item")?
{
entry::Kind::Repository => EntryKind::Commit,
entry::Kind::Directory => {
unreachable!("BUG: we use 'matching' so there are no directories")
}
entry::Kind::Untrackable => return Ok(None),
entry::Kind::File => {
let md = path.symlink_metadata()?;
if gix::fs::is_executable(&md) {
EntryKind::BlobExecutable
} else {
EntryKind::Blob
}
}
entry::Kind::Symlink => EntryKind::Link,
},
))
}
/// Unified diffs
impl TreeChange {
/// Like [`Self::unified_patch()`], but also provides the file header for diffs like this:View on GitHub (pinned to caf1f223d3)
Solutions
- Keep the traversal feeding this function in matching mode (no directory collapse) as the caller configures today.
- Pin/verify the gix version after upgrades by running the worktree-diff snapshot tests.
- If directory entries must be supported, map them to an explicit error or recurse instead of `unreachable!`.
Example fix
// before
entry::Kind::Directory => {
unreachable!("BUG: we use 'matching' so there are no directories")
}
// after
entry::Kind::Directory => {
anyhow::bail!("untracked traversal emitted a directory for '{path}' — matching mode required");
} Defensive patterns
Strategy: type-guard
Type guard
fn is_leaf_kind(kind: gix::dir::entry::Kind) -> bool {
!matches!(kind, gix::dir::entry::Kind::Directory)
} Prevention
- Do not switch the untracked traversal feeding disk_kind_to_entry_kind away from matching mode.
- When testing, build gix::dir::Entry values with leaf kinds only (File, Symlink, Repository).
- Watch gix changelogs for changes to Kind assignment in matching traversal.
When it happens
Trigger: Switching the untracked collection from `matching` to `collapsed`/directory mode; a gix version changing when `Kind::Directory` is attached to disk or index metadata; constructing items by hand in tests with directory kinds.
Common situations: Performance experiments that collapse untracked directories; gix upgrades altering traversal kinds; fixture builders that synthesize `gix::dir::Entry` values directly.
Related errors
- we never return these as the status iteration is configured
- disabled
- BUG: this must have been deactivated
- BUG: `gix` disables this, as it knows we always need to be a
- worktree-changes are always set if there are hunks
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/0c2ab13594f48412.
Report an issue: GitHub.