{"record":{"id":"b9686648cd8212a1","repo":"gitbutlerapp/gitbutler","slug":"cannot-hash-directory-entries-that-aren-t-files-or-symlinks","errorCode":null,"errorMessage":"Cannot hash directory entries that aren't files or symlinks","messagePattern":"Cannot hash directory entries that aren't files or symlinks","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-core/src/diff/worktree.rs","lineNumber":988,"sourceCode":"                &mut stream,\n                md.len(),\n                &mut gix::progress::Discard,\n                &gix::interrupt::IS_INTERRUPTED,\n            )?,\n            ToGitOutcome::Process(mut stream) => {\n                let mut buf = repo.empty_reusable_buffer();\n                stream.read_to_end(&mut buf)?;\n                gix::objs::compute_hash(repo.object_hash(), gix::object::Kind::Blob, &buf)?\n            }\n            ToGitOutcome::Buffer(buf) => {\n                gix::objs::compute_hash(repo.object_hash(), gix::object::Kind::Blob, buf)?\n            }\n        }\n    } else if md.is_symlink() {\n        let bytes = gix::path::os_string_into_bstring(std::fs::read_link(path)?.into())?;\n        gix::objs::compute_hash(repo.object_hash(), gix::object::Kind::Blob, &bytes)?\n    } else {\n        bail!(\"Cannot hash directory entries that aren't files or symlinks\");\n    };\n    Ok(id)\n}\n\nfn into_tree_entry_kind(mode: gix::index::entry::Mode) -> anyhow::Result<EntryKind> {\n    Ok(mode\n        .to_tree_entry_mode()\n        .with_context(|| format!(\"Entry contained invalid entry mode: {mode:?}\"))?\n        .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,","sourceCodeStart":970,"sourceCodeEnd":1006,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-core/src/diff/worktree.rs#L970-L1006","documentation":"id_or_hash_from_worktree() hashes a worktree path's content to an object id. Directories cannot be hashed as blobs, so when the path is neither a regular file nor a symlink (e.g. a directory or special file), the function bails. It is used when computing effective merged changes.","triggerScenarios":"Calling merged_change_is_effective (or the hashing helper) on a path that resolves to a directory at hash time — commonly because the file was deleted and replaced by a directory, or the path is a git submodule/fifo/socket.","commonSituations":"A branch swap where a formerly-tracked file path became a directory; submodule checkout state making a path a directory; race where the file is replaced between stat and hash; special files on Linux.","solutions":["Skip directory-typed paths before hashing (check metadata kind first)","Handle submodule entries through their commit-id path rather than content hashing","Re-check worktree state and retry if the file was concurrently replaced","Ensure the expected entry is actually a file: git status / re-read the index"],"exampleFix":"// before\nlet id = id_or_hash_from_worktree(repo, path)?;\n// after\nlet id = if path.is_dir() {\n    eprintln!(\"{path:?} is a directory; skipping hash\");\n    None\n} else {\n    Some(id_or_hash_from_worktree(repo, path)?)\n};","handlingStrategy":"validation","validationCode":"fn hashable(path: &Path) -> bool {\n    path.symlink_metadata().map(|m| m.is_file() || m.is_symlink()).unwrap_or(false)\n}","typeGuard":"if path.is_dir() { skip(); } else { hash(path)?; }","tryCatchPattern":"match id_or_hash_from_worktree(repo, path) {\n    Err(e) if e.to_string().contains(\"aren't files or symlinks\") => skip_entry(),\n    other => other?,\n}","preventionTips":["Check entry kind before hashing","Handle submodule paths via their commit ids","Re-stat paths after branch switches to avoid races","Exclude special files (fifos/sockets) from change computation"],"tags":["git","worktree","hashing"],"backgroundTag":"unsupported-operation","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}