{"record":{"id":"0c2ab13594f48412","repo":"gitbutlerapp/gitbutler","slug":"bug-we-use-matching-so-there-are-no-directories","errorCode":null,"errorMessage":"BUG: we use 'matching' so there are no directories","messagePattern":"BUG: we use 'matching' so there are no directories","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-core/src/diff/worktree.rs","lineNumber":860,"sourceCode":"        .kind())\n}\n\n/// Most importantly, this function allows to skip over untrackable entries, like named pipes, sockets and character devices, just like Git.\n/// `path` is needed for now while we have to stat the file again to learn about the executable bits.\n// TODO: remove `path` and provide the stat information or at least executable info with `gitoxide` - it has that info.\nfn disk_kind_to_entry_kind(\n    disk_kind: Option<gix::dir::entry::Kind>,\n    index_kind: Option<gix::dir::entry::Kind>,\n    path: PathBuf,\n) -> anyhow::Result<Option<EntryKind>> {\n    Ok(Some(\n        match disk_kind\n            .or(index_kind)\n            .context(\"Didn't have any type information for untracked item\")?\n        {\n            entry::Kind::Repository => EntryKind::Commit,\n            entry::Kind::Directory => {\n                unreachable!(\"BUG: we use 'matching' so there are no directories\")\n            }\n            entry::Kind::Untrackable => return Ok(None),\n            entry::Kind::File => {\n                let md = path.symlink_metadata()?;\n                if gix::fs::is_executable(&md) {\n                    EntryKind::BlobExecutable\n                } else {\n                    EntryKind::Blob\n                }\n            }\n            entry::Kind::Symlink => EntryKind::Link,\n        },\n    ))\n}\n\n/// Unified diffs\nimpl TreeChange {\n    /// Like [`Self::unified_patch()`], but also provides the file header for diffs like this:","sourceCodeStart":842,"sourceCodeEnd":878,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-core/src/diff/worktree.rs#L842-L878","documentation":"`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.","triggerScenarios":"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.","commonSituations":"Performance experiments that collapse untracked directories; gix upgrades altering traversal kinds; fixture builders that synthesize `gix::dir::Entry` values directly.","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!`."],"exampleFix":"// before\nentry::Kind::Directory => {\n    unreachable!(\"BUG: we use 'matching' so there are no directories\")\n}\n\n// after\nentry::Kind::Directory => {\n    anyhow::bail!(\"untracked traversal emitted a directory for '{path}' — matching mode required\");\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"fn is_leaf_kind(kind: gix::dir::entry::Kind) -> bool {\n    !matches!(kind, gix::dir::entry::Kind::Directory)\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["rust","but-core","gix","traversal-mode","untracked-files","internal-invariant"],"backgroundTag":"gix-traversal-config-mismatch","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}