diff --git a/gix-fs/src/lib.rs b/gix-fs/src/lib.rs index 187097573cb..9f77d8e8614 100644 --- a/gix-fs/src/lib.rs +++ b/gix-fs/src/lib.rs @@ -83,6 +83,18 @@ pub fn is_executable(metadata: &std::fs::Metadata) -> bool { (metadata.mode() & 0o100) != 0 } +/// Classifiers for IO-errors. +pub mod io_err { + use std::io::ErrorKind; + + /// Return `true` if `err` indicates that the entry doesn't exist on disk. `raw` is used as well + /// for additional checks while the variants are outside the MSRV. + pub fn is_not_found(err: ErrorKind, raw_err: Option) -> bool { + // TODO: use variant once MSRV is 1.83 + err == ErrorKind::NotFound || raw_err == Some(20) + } +} + #[cfg(not(unix))] /// Returns whether a a file has the executable permission set. pub fn is_executable(_metadata: &std::fs::Metadata) -> bool { diff --git a/gix-status/src/index_as_worktree/function.rs b/gix-status/src/index_as_worktree/function.rs index 5dbf16715ac..8fb300abdb5 100644 --- a/gix-status/src/index_as_worktree/function.rs +++ b/gix-status/src/index_as_worktree/function.rs @@ -356,8 +356,10 @@ impl<'index> State<'_, 'index> { { let worktree_path = match self.path_stack.verified_path(gix_path::from_bstr(rela_path).as_ref()) { Ok(path) => path, - Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(Some(Change::Removed.into())), - Err(err) => return Err(Error::Io(err)), + Err(err) if gix_fs::io_err::is_not_found(err.kind(), err.raw_os_error()) => { + return Ok(Some(Change::Removed.into())) + } + Err(err) => return Err(err.into()), }; self.symlink_metadata_calls.fetch_add(1, Ordering::Relaxed); let metadata = match gix_index::fs::Metadata::from_path_no_follow(worktree_path) { @@ -379,7 +381,9 @@ impl<'index> State<'_, 'index> { } } Ok(metadata) => metadata, - Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(Some(Change::Removed.into())), + Err(err) if gix_fs::io_err::is_not_found(err.kind(), err.raw_os_error()) => { + return Ok(Some(Change::Removed.into())) + } Err(err) => { return Err(err.into()); } @@ -539,7 +543,7 @@ where // conversion to bstr can never fail because symlinks are only used // on unix (by git) so no reason to use the try version here let symlink_path = - gix_path::to_unix_separators_on_windows(gix_path::into_bstr(std::fs::read_link(self.path)?)); + gix_path::to_unix_separators_on_windows(gix_path::into_bstr(std::fs::read_link(self.path).unwrap())); self.buf.extend_from_slice(&symlink_path); self.worktree_bytes.fetch_add(self.buf.len() as u64, Ordering::Relaxed); Stream { diff --git a/gix-status/tests/fixtures/generated-archives/status_many.tar b/gix-status/tests/fixtures/generated-archives/status_many.tar index 8e656859d8e..2f9698d3133 100644 Binary files a/gix-status/tests/fixtures/generated-archives/status_many.tar and b/gix-status/tests/fixtures/generated-archives/status_many.tar differ diff --git a/gix-status/tests/fixtures/status_many.sh b/gix-status/tests/fixtures/status_many.sh index 9a11f3f4f8d..e7a67475920 100755 --- a/gix-status/tests/fixtures/status_many.sh +++ b/gix-status/tests/fixtures/status_many.sh @@ -39,3 +39,16 @@ cp -R changed-and-untracked changed-and-untracked-and-renamed echo change >> content-with-rewrite ) + +cp -R changed-and-untracked replace-dir-with-file +(cd replace-dir-with-file + git checkout executable + rm untracked dir/untracked + + mkdir dir/sub + touch dir/sub/nested + git add dir && git commit -m "add file in sub-directory" + + rm -Rf dir/ + touch dir +) diff --git a/gix-status/tests/status/index_as_worktree.rs b/gix-status/tests/status/index_as_worktree.rs index 27a3a272c0b..dc4b357902e 100644 --- a/gix-status/tests/status/index_as_worktree.rs +++ b/gix-status/tests/status/index_as_worktree.rs @@ -243,6 +243,31 @@ fn removed() { ); } +#[test] +fn replace_dir_with_file() { + let out = fixture_filtered_detailed( + "status_many", + "replace-dir-with-file", + &[], + &[ + (BStr::new(b"dir/content"), 0, status_removed()), + (BStr::new(b"dir/content2"), 1, status_removed()), + (BStr::new(b"dir/sub/nested"), 2, status_removed()), + ], + |_| {}, + false, + ); + assert_eq!( + out, + Outcome { + entries_to_process: 5, + entries_processed: 5, + symlink_metadata_calls: if cfg!(windows) { 5 } else { 4 }, + ..Default::default() + } + ); +} + #[test] fn subomdule_nochange() { assert_eq!(