diff --git a/gitoxide-core/src/repository/clean.rs b/gitoxide-core/src/repository/clean.rs index b9bac90d9f3..a21bbe25a6c 100644 --- a/gitoxide-core/src/repository/clean.rs +++ b/gitoxide-core/src/repository/clean.rs @@ -163,7 +163,7 @@ pub(crate) mod function { .join(gix::path::from_bstr(entry.rela_path.as_bstr())) .metadata() .ok() - .map(|e| e.file_type().into()); + .and_then(|e| gix::dir::entry::Kind::try_from_file_type(e.file_type())); } let mut disk_kind = entry.disk_kind.expect("present if not pruned"); if !keep { diff --git a/gix-dir/Cargo.toml b/gix-dir/Cargo.toml index d29b58cafaa..e0d6472f6f5 100644 --- a/gix-dir/Cargo.toml +++ b/gix-dir/Cargo.toml @@ -12,6 +12,7 @@ rust-version = "1.65" [lib] doctest = false +test = false [dependencies] gix-trace = { version = "^0.1.11", path = "../gix-trace" } diff --git a/gix-dir/src/entry.rs b/gix-dir/src/entry.rs index 4bfc02e02a4..a34885b1b75 100644 --- a/gix-dir/src/entry.rs +++ b/gix-dir/src/entry.rs @@ -147,15 +147,20 @@ impl Entry { } } -impl From for Kind { - fn from(value: std::fs::FileType) -> Self { - if value.is_dir() { +impl Kind { + /// Try to represent the file type `t` as `Entry`, or return `None` if it cannot be represented. + /// + /// The latter can happen if it's a `pipe` for instance. + pub fn try_from_file_type(t: std::fs::FileType) -> Option { + Some(if t.is_dir() { Kind::Directory - } else if value.is_symlink() { + } else if t.is_symlink() { Kind::Symlink - } else { + } else if t.is_file() { Kind::File - } + } else { + return None; + }) } } diff --git a/gix-dir/src/walk/classify.rs b/gix-dir/src/walk/classify.rs index b87c893e7c7..e289307f792 100644 --- a/gix-dir/src/walk/classify.rs +++ b/gix-dir/src/walk/classify.rs @@ -20,7 +20,10 @@ pub fn root( let mut last_length = None; let mut path_buf = worktree_root.to_owned(); // These initial values kick in if worktree_relative_root.is_empty(); - let file_kind = path_buf.symlink_metadata().map(|m| m.file_type().into()).ok(); + let file_kind = path_buf + .symlink_metadata() + .ok() + .and_then(|m| entry::Kind::try_from_file_type(m.file_type())); let mut out = path(&mut path_buf, buf, 0, file_kind, || None, options, ctx)?; let worktree_root_is_repository = out .disk_kind @@ -32,7 +35,10 @@ pub fn root( } path_buf.push(component); buf.extend_from_slice(gix_path::os_str_into_bstr(component.as_os_str()).expect("no illformed UTF8")); - let file_kind = path_buf.symlink_metadata().map(|m| m.file_type().into()).ok(); + let file_kind = path_buf + .symlink_metadata() + .ok() + .and_then(|m| entry::Kind::try_from_file_type(m.file_type())); out = path( &mut path_buf, @@ -122,6 +128,8 @@ impl<'a> EntryRef<'a> { /// /// Returns `(status, file_kind, pathspec_matches_how)` to identify the `status` on disk, along with a classification `file_kind`, /// and if `file_kind` is not a directory, the way the pathspec matched with `pathspec_matches_how`. +/// +/// Note that non-files are pruned by default. pub fn path( path: &mut PathBuf, rela_path: &mut BString, diff --git a/gix-dir/src/walk/readdir.rs b/gix-dir/src/walk/readdir.rs index ab1e9370344..cde07fa0ea6 100644 --- a/gix-dir/src/walk/readdir.rs +++ b/gix-dir/src/walk/readdir.rs @@ -64,7 +64,7 @@ pub(super) fn recursive( current_bstr, if prev_len == 0 { 0 } else { prev_len + 1 }, None, - || entry.file_type().ok().map(Into::into), + || entry.file_type().ok().and_then(entry::Kind::try_from_file_type), opts, ctx, )?; diff --git a/gix-dir/tests/dir.rs b/gix-dir/tests/dir/main.rs similarity index 65% rename from gix-dir/tests/dir.rs rename to gix-dir/tests/dir/main.rs index 81d7d83341e..ec8a0eff6b9 100644 --- a/gix-dir/tests/dir.rs +++ b/gix-dir/tests/dir/main.rs @@ -1,4 +1,5 @@ pub use gix_testtools::Result; mod walk; +#[path = "../walk_utils/mod.rs"] pub mod walk_utils; diff --git a/gix-dir/tests/walk/mod.rs b/gix-dir/tests/dir/walk.rs similarity index 98% rename from gix-dir/tests/walk/mod.rs rename to gix-dir/tests/dir/walk.rs index e4d08f7b0cf..36b6bd591f4 100644 --- a/gix-dir/tests/walk/mod.rs +++ b/gix-dir/tests/dir/walk.rs @@ -18,6 +18,94 @@ use gix_dir::walk::EmissionMode::*; use gix_dir::walk::ForDeletionMode; use gix_ignore::Kind::*; +#[test] +#[cfg(unix)] +fn root_is_fifo() { + let root = fixture_in("fifo", "top-level"); + + let err = try_collect(&root, None, |keep, ctx| { + walk( + &root, + ctx, + gix_dir::walk::Options { + emit_ignored: Some(Matching), + ..options() + }, + keep, + ) + }) + .unwrap_err(); + assert!( + matches!(err, gix_dir::walk::Error::WorktreeRootIsFile { .. }), + "roots simply need to be directories to work" + ); +} + +#[test] +#[cfg(unix)] +fn one_top_level_fifo() { + let root = fixture_in("fifo", "single-top-level-fifo"); + + let ((out, _root), entries) = collect(&root, None, |keep, ctx| { + walk( + &root, + ctx, + gix_dir::walk::Options { + emit_pruned: false, + ..options() + }, + keep, + ) + }); + assert_eq!( + out, + walk::Outcome { + read_dir_calls: 1, + returned_entries: entries.len(), + seen_entries: 2, + } + ); + + assert_eq!(entries, &[], "Non-files are simply pruned by default"); +} + +#[test] +#[cfg(unix)] +fn fifo_in_traversal() { + let root = fixture_in("fifo", "two-fifos-two-files"); + + let ((out, _root), entries) = collect(&root, None, |keep, ctx| { + walk( + &root, + ctx, + gix_dir::walk::Options { + emit_pruned: true, + ..options() + }, + keep, + ) + }); + assert_eq!( + out, + walk::Outcome { + read_dir_calls: 3, + returned_entries: entries.len(), + seen_entries: 5, + } + ); + + assert_eq!( + entries, + &[ + entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always), + entry("dir-with-file/nested-file", Untracked, File), + entry("file", Untracked, File), + ], + "Non-files are not even pruned, they are ignored entirely.\ + If one day this isn't what we want, we can create an own filetype for them" + ); +} + #[test] fn symlink_to_dir_can_be_excluded() -> crate::Result { let root = fixture_in("many-symlinks", "excluded-symlinks-to-dir"); diff --git a/gix-dir/tests/dir_walk_cwd.rs b/gix-dir/tests/dir_cwd.rs similarity index 100% rename from gix-dir/tests/dir_walk_cwd.rs rename to gix-dir/tests/dir_cwd.rs diff --git a/gix-dir/tests/fixtures/fifo.sh b/gix-dir/tests/fixtures/fifo.sh new file mode 100755 index 00000000000..9c59c234464 --- /dev/null +++ b/gix-dir/tests/fixtures/fifo.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -eu -o pipefail + +mkfifo top-level + +git init single-top-level-fifo +(cd single-top-level-fifo + mkfifo top +) + +git init two-fifos-two-files +(cd two-fifos-two-files + mkdir dir dir-with-file + touch file dir-with-file/nested-file + + mkfifo top + mkfifo dir/nested +) diff --git a/gix-dir/tests/fixtures/generated-archives/.gitignore b/gix-dir/tests/fixtures/generated-archives/.gitignore index 75186c14a92..d025b4d2cf9 100644 --- a/gix-dir/tests/fixtures/generated-archives/.gitignore +++ b/gix-dir/tests/fixtures/generated-archives/.gitignore @@ -1,2 +1,3 @@ many.tar -many-symlinks.tar \ No newline at end of file +many-symlinks.tar +fifo.tar \ No newline at end of file