diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml index 1cf308f85b1..9d96b3db67c 100644 --- a/.github/workflows/msrv.yml +++ b/.github/workflows/msrv.yml @@ -31,7 +31,7 @@ jobs: env: # dictated by `firefox` to support the `helix` editor, but now probably effectively be controlled by `jiff`, which also aligns with `regex`. # IMPORTANT: adjust etc/msrv-badge.svg as well - rust_version: 1.74.0 + rust_version: 1.75.0 steps: - uses: actions/checkout@v4 diff --git a/Cargo.lock b/Cargo.lock index 5a0c621181e..097826a9048 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1901,13 +1901,16 @@ dependencies = [ name = "gix-fs" version = "0.13.0" dependencies = [ + "bstr", "crossbeam-channel", "fastrand", "gix-features 0.40.0", + "gix-path 0.10.14", "gix-utils 0.1.14", "is_ci", "serde", "tempfile", + "thiserror 2.0.12", ] [[package]] diff --git a/etc/msrv-badge.svg b/etc/msrv-badge.svg index c346e1cb307..184a0688c61 100644 --- a/etc/msrv-badge.svg +++ b/etc/msrv-badge.svg @@ -1,5 +1,5 @@ - rustc: 1.74.0+ + rustc: 1.75.0+ @@ -15,7 +15,7 @@ rustc - - 1.74.0+ + + 1.75.0+ diff --git a/gitoxide-core/src/repository/attributes/query.rs b/gitoxide-core/src/repository/attributes/query.rs index af0b90f9185..b12421e77df 100644 --- a/gitoxide-core/src/repository/attributes/query.rs +++ b/gitoxide-core/src/repository/attributes/query.rs @@ -8,10 +8,10 @@ pub struct Options { } pub(crate) mod function { - use std::{borrow::Cow, io, path::Path}; - use anyhow::bail; use gix::bstr::BStr; + use std::borrow::Cow; + use std::{io, path::Path}; use crate::{ is_dir_to_mode, @@ -44,7 +44,7 @@ pub(crate) mod function { .ok() .map(|m| is_dir_to_mode(m.is_dir())); - let entry = cache.at_entry(path.as_slice(), mode)?; + let entry = cache.at_entry(&path, mode)?; if !entry.matching_attributes(&mut matches) { continue; } diff --git a/gitoxide-core/src/repository/exclude.rs b/gitoxide-core/src/repository/exclude.rs index f2cd35be0d9..608d81bd949 100644 --- a/gitoxide-core/src/repository/exclude.rs +++ b/gitoxide-core/src/repository/exclude.rs @@ -48,7 +48,7 @@ pub fn query( .metadata() .ok() .map(|m| is_dir_to_mode(m.is_dir())); - let entry = cache.at_entry(path.as_slice(), mode)?; + let entry = cache.at_entry(&path, mode)?; let match_ = entry .matching_exclude_pattern() .and_then(|m| (show_ignore_patterns || !m.pattern.is_negative()).then_some(m)); diff --git a/gix-fs/Cargo.toml b/gix-fs/Cargo.toml index a11252b2c52..12a7523338d 100644 --- a/gix-fs/Cargo.toml +++ b/gix-fs/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" description = "A crate providing file system specific utilities to `gitoxide`" authors = ["Sebastian Thiel "] edition = "2021" -rust-version = "1.70" +rust-version = "1.75" include = ["src/**/*", "LICENSE-*"] [lib] @@ -19,8 +19,11 @@ doctest = false serde = ["dep:serde"] [dependencies] +bstr = "1.5.0" +gix-path = { version = "^0.10.14", path = "../gix-path" } gix-features = { version = "^0.40.0", path = "../gix-features", features = ["fs-read-dir"] } gix-utils = { version = "^0.1.14", path = "../gix-utils" } +thiserror = "2.0.0" serde = { version = "1.0.114", optional = true, default-features = false, features = ["std", "derive"] } # For `Capabilities` to assure parallel operation works. diff --git a/gix-fs/src/stack.rs b/gix-fs/src/stack.rs index c5cf73ca459..da1f8457a13 100644 --- a/gix-fs/src/stack.rs +++ b/gix-fs/src/stack.rs @@ -1,6 +1,80 @@ +use crate::Stack; +use bstr::{BStr, BString, ByteSlice}; +use std::ffi::OsStr; use std::path::{Component, Path, PathBuf}; -use crate::Stack; +/// +pub mod to_normal_path_components { + use std::ffi::OsString; + + /// The error used in [`ToNormalPathComponents::to_normal_path_components()`](super::ToNormalPathComponents::to_normal_path_components()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("Input path \"{path}\" contains relative or absolute components", path = std::path::Path::new(.0.as_os_str()).display())] + NotANormalComponent(OsString), + #[error("Could not convert to UTF8 or from UTF8 due to ill-formed input")] + IllegalUtf8, + } +} + +/// Obtain an iterator over `OsStr`-components which are normal, none-relative and not absolute. +pub trait ToNormalPathComponents { + /// Return an iterator over the normal components of a path, without the separator. + fn to_normal_path_components(&self) -> impl Iterator>; +} + +impl ToNormalPathComponents for &Path { + fn to_normal_path_components(&self) -> impl Iterator> { + self.components().map(|component| match component { + Component::Normal(os_str) => Ok(os_str), + _ => Err(to_normal_path_components::Error::NotANormalComponent( + self.as_os_str().to_owned(), + )), + }) + } +} + +impl ToNormalPathComponents for PathBuf { + fn to_normal_path_components(&self) -> impl Iterator> { + self.components().map(|component| match component { + Component::Normal(os_str) => Ok(os_str), + _ => Err(to_normal_path_components::Error::NotANormalComponent( + self.as_os_str().to_owned(), + )), + }) + } +} + +impl ToNormalPathComponents for &BStr { + fn to_normal_path_components(&self) -> impl Iterator> { + self.split(|b| *b == b'/').filter(|c| !c.is_empty()).map(|component| { + gix_path::try_from_byte_slice(component.as_bstr()) + .map_err(|_| to_normal_path_components::Error::IllegalUtf8) + .map(Path::as_os_str) + }) + } +} + +impl ToNormalPathComponents for &str { + fn to_normal_path_components(&self) -> impl Iterator> { + self.split('/').filter(|c| !c.is_empty()).map(|component| { + gix_path::try_from_byte_slice(component.as_bytes()) + .map_err(|_| to_normal_path_components::Error::IllegalUtf8) + .map(Path::as_os_str) + }) + } +} + +impl ToNormalPathComponents for &BString { + fn to_normal_path_components(&self) -> impl Iterator> { + self.split(|b| *b == b'/').filter(|c| !c.is_empty()).map(|component| { + gix_path::try_from_byte_slice(component.as_bstr()) + .map_err(|_| to_normal_path_components::Error::IllegalUtf8) + .map(Path::as_os_str) + }) + } +} /// Access impl Stack { @@ -62,8 +136,13 @@ impl Stack { /// `relative` paths are terminal, so point to their designated file or directory. /// The path is also expected to be normalized, and should not contain extra separators, and must not contain `..` /// or have leading or trailing slashes (or additionally backslashes on Windows). - pub fn make_relative_path_current(&mut self, relative: &Path, delegate: &mut dyn Delegate) -> std::io::Result<()> { - if self.valid_components != 0 && relative.as_os_str().is_empty() { + pub fn make_relative_path_current( + &mut self, + relative: impl ToNormalPathComponents, + delegate: &mut dyn Delegate, + ) -> std::io::Result<()> { + let mut components = relative.to_normal_path_components().peekable(); + if self.valid_components != 0 && components.peek().is_none() { return Err(std::io::Error::new( std::io::ErrorKind::Other, "empty inputs are not allowed", @@ -73,15 +152,19 @@ impl Stack { delegate.push_directory(self)?; } - let mut components = relative.components().peekable(); let mut existing_components = self.current_relative.components(); let mut matching_components = 0; while let (Some(existing_comp), Some(new_comp)) = (existing_components.next(), components.peek()) { - if existing_comp == *new_comp { - components.next(); - matching_components += 1; - } else { - break; + match new_comp { + Ok(new_comp) => { + if existing_comp.as_os_str() == *new_comp { + components.next(); + matching_components += 1; + } else { + break; + } + } + Err(err) => return Err(std::io::Error::other(format!("{err}"))), } } @@ -100,15 +183,7 @@ impl Stack { } while let Some(comp) = components.next() { - if !matches!(comp, Component::Normal(_)) { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!( - "Input path \"{}\" contains relative or absolute components", - relative.display() - ), - )); - } + let comp = comp.map_err(std::io::Error::other)?; let is_last_component = components.peek().is_none(); self.current_is_directory = !is_last_component; self.current.push(comp); diff --git a/gix-fs/tests/fs/stack.rs b/gix-fs/tests/fs/stack.rs index 11fadd8fe68..f3ba1d70751 100644 --- a/gix-fs/tests/fs/stack.rs +++ b/gix-fs/tests/fs/stack.rs @@ -182,7 +182,7 @@ fn empty_paths_are_noop_if_no_path_was_pushed_before() { let mut s = Stack::new(root.clone()); let mut r = Record::default(); - s.make_relative_path_current("".as_ref(), &mut r).unwrap(); + s.make_relative_path_current("", &mut r).unwrap(); assert_eq!( s.current_relative().to_string_lossy(), "", @@ -196,7 +196,7 @@ fn relative_components_are_invalid() { let mut s = Stack::new(root.clone()); let mut r = Record::default(); - let err = s.make_relative_path_current("a/..".as_ref(), &mut r).unwrap_err(); + let err = s.make_relative_path_current(p("a/.."), &mut r).unwrap_err(); assert_eq!( err.to_string(), format!( @@ -205,7 +205,7 @@ fn relative_components_are_invalid() { ) ); - s.make_relative_path_current("a/./b".as_ref(), &mut r) + s.make_relative_path_current(p("a/./b"), &mut r) .expect("dot is ignored"); assert_eq!( r, @@ -221,7 +221,7 @@ fn relative_components_are_invalid() { if cfg!(windows) { r".\a\b" } else { "./a/b" }, "dot is silently ignored" ); - s.make_relative_path_current("a//b/".as_ref(), &mut r) + s.make_relative_path_current(p("a//b/"), &mut r) .expect("multiple-slashes are ignored"); assert_eq!( r, @@ -240,19 +240,19 @@ fn absolute_paths_are_invalid() -> crate::Result { let mut s = Stack::new(root.clone()); let mut r = Record::default(); - let err = s.make_relative_path_current("/".as_ref(), &mut r).unwrap_err(); + let err = s.make_relative_path_current(p("/"), &mut r).unwrap_err(); assert_eq!( err.to_string(), r#"Input path "/" contains relative or absolute components"#, "a leading slash is always considered absolute" ); - s.make_relative_path_current("a/".as_ref(), &mut r)?; + s.make_relative_path_current(p("a/"), &mut r)?; assert_eq!( s.current(), p("./a/"), "trailing slashes aren't a problem at this stage, as they cannot cause a 'breakout'" ); - s.make_relative_path_current(r"b\".as_ref(), &mut r)?; + s.make_relative_path_current(p(r"b\"), &mut r)?; assert_eq!( s.current(), p(r"./b\"), @@ -261,7 +261,7 @@ fn absolute_paths_are_invalid() -> crate::Result { #[cfg(windows)] { - let err = s.make_relative_path_current(r"\".as_ref(), &mut r).unwrap_err(); + let err = s.make_relative_path_current(Path::new(r"\"), &mut r).unwrap_err(); assert_eq!( err.to_string(), r#"Input path "\" contains relative or absolute components"#, @@ -269,20 +269,20 @@ fn absolute_paths_are_invalid() -> crate::Result { hence they are forbidden." ); - let err = s.make_relative_path_current("c:".as_ref(), &mut r).unwrap_err(); + let err = s.make_relative_path_current(Path::new("c:"), &mut r).unwrap_err(); assert_eq!( err.to_string(), r#"Input path "c:" contains relative or absolute components"#, "on Windows, drive-letters without trailing backslash or slash are also absolute (even though they ought to be relative)" ); - let err = s.make_relative_path_current(r"c:\".as_ref(), &mut r).unwrap_err(); + let err = s.make_relative_path_current(Path::new(r"c:\"), &mut r).unwrap_err(); assert_eq!( err.to_string(), r#"Input path "c:\" contains relative or absolute components"#, "on Windows, drive-letters are absolute, which is expected" ); - s.make_relative_path_current("֍:".as_ref(), &mut r)?; + s.make_relative_path_current(Path::new("֍:"), &mut r)?; assert_eq!( s.current().to_string_lossy(), ".\\֍:", @@ -290,7 +290,7 @@ fn absolute_paths_are_invalid() -> crate::Result { but we just turn it into a presumably invalid path which is fine, i.e. we get a joined path" ); let err = s - .make_relative_path_current(r"\\localhost\hello".as_ref(), &mut r) + .make_relative_path_current(Path::new(r"\\localhost\hello"), &mut r) .unwrap_err(); assert_eq!( err.to_string(), @@ -298,7 +298,9 @@ fn absolute_paths_are_invalid() -> crate::Result { "there is UNC paths as well" ); - let err = s.make_relative_path_current(r#"\\?\C:"#.as_ref(), &mut r).unwrap_err(); + let err = s + .make_relative_path_current(Path::new(r#"\\?\C:"#), &mut r) + .unwrap_err(); assert_eq!( err.to_string(), r#"Input path "\\?\C:" contains relative or absolute components"#, @@ -314,10 +316,10 @@ fn delegate_calls_are_consistent() -> crate::Result { let mut s = Stack::new(root.clone()); assert_eq!(s.current(), root); - assert_eq!(s.current_relative(), Path::new("")); + assert_eq!(s.current_relative(), p("")); let mut r = Record::default(); - s.make_relative_path_current("a/b".as_ref(), &mut r)?; + s.make_relative_path_current("a/b", &mut r)?; let mut dirs = vec![root.clone(), root.join("a")]; assert_eq!( r, @@ -329,7 +331,7 @@ fn delegate_calls_are_consistent() -> crate::Result { "it pushes the root-directory first, then the intermediate one" ); - s.make_relative_path_current("a/b2".as_ref(), &mut r)?; + s.make_relative_path_current("a/b2", &mut r)?; assert_eq!( r, Record { @@ -340,7 +342,7 @@ fn delegate_calls_are_consistent() -> crate::Result { "dirs remain the same as b2 is a leaf/file, hence the new `push`" ); - s.make_relative_path_current("c/d/e".as_ref(), &mut r)?; + s.make_relative_path_current("c/d/e", &mut r)?; dirs.pop(); dirs.extend([root.join("c"), root.join("c").join("d")]); assert_eq!( @@ -354,7 +356,7 @@ fn delegate_calls_are_consistent() -> crate::Result { ); dirs.push(root.join("c").join("d").join("x")); - s.make_relative_path_current("c/d/x/z".as_ref(), &mut r)?; + s.make_relative_path_current("c/d/x/z", &mut r)?; assert_eq!( r, Record { @@ -366,8 +368,8 @@ fn delegate_calls_are_consistent() -> crate::Result { ); dirs.drain(1..).count(); - s.make_relative_path_current("f".as_ref(), &mut r)?; - assert_eq!(s.current_relative(), Path::new("f")); + s.make_relative_path_current("f", &mut r)?; + assert_eq!(s.current_relative(), p("f")); assert_eq!( r, Record { @@ -379,7 +381,7 @@ fn delegate_calls_are_consistent() -> crate::Result { ); dirs.push(root.join("x")); - s.make_relative_path_current("x/z".as_ref(), &mut r)?; + s.make_relative_path_current("x/z", &mut r)?; assert_eq!( r, Record { @@ -391,7 +393,7 @@ fn delegate_calls_are_consistent() -> crate::Result { ); dirs.push(root.join("x").join("z")); - s.make_relative_path_current("x/z/a".as_ref(), &mut r)?; + s.make_relative_path_current("x/z/a", &mut r)?; assert_eq!( r, Record { @@ -404,7 +406,7 @@ fn delegate_calls_are_consistent() -> crate::Result { dirs.push(root.join("x").join("z").join("a")); dirs.push(root.join("x").join("z").join("a").join("b")); - s.make_relative_path_current("x/z/a/b/c".as_ref(), &mut r)?; + s.make_relative_path_current("x/z/a/b/c", &mut r)?; assert_eq!( r, Record { @@ -416,7 +418,7 @@ fn delegate_calls_are_consistent() -> crate::Result { ); dirs.drain(1 /*root*/ + 1 /*x*/ + 1 /*x/z*/ ..).count(); - s.make_relative_path_current("x/z".as_ref(), &mut r)?; + s.make_relative_path_current("x/z", &mut r)?; assert_eq!( r, Record { @@ -432,7 +434,7 @@ fn delegate_calls_are_consistent() -> crate::Result { "the stack is state so keeps thinking it's a directory which is consistent. Git does it differently though." ); - let err = s.make_relative_path_current("".as_ref(), &mut r).unwrap_err(); + let err = s.make_relative_path_current(p(""), &mut r).unwrap_err(); assert_eq!( err.to_string(), "empty inputs are not allowed", @@ -440,7 +442,7 @@ fn delegate_calls_are_consistent() -> crate::Result { and besides that really shouldn't happen" ); - s.make_relative_path_current("leaf".as_ref(), &mut r)?; + s.make_relative_path_current("leaf", &mut r)?; dirs.drain(1..).count(); assert_eq!( r, @@ -452,7 +454,7 @@ fn delegate_calls_are_consistent() -> crate::Result { "reset as much as possible, with just a leaf-component and the root directory" ); - s.make_relative_path_current("a//b".as_ref(), &mut r)?; + s.make_relative_path_current(p("a//b"), &mut r)?; dirs.push(root.join("a")); assert_eq!( r, @@ -466,7 +468,7 @@ fn delegate_calls_are_consistent() -> crate::Result { #[cfg(not(windows))] { - s.make_relative_path_current(r"\/b".as_ref(), &mut r)?; + s.make_relative_path_current(r"\/b", &mut r)?; dirs.pop(); dirs.push(root.join(r"\")); assert_eq!( @@ -479,7 +481,7 @@ fn delegate_calls_are_consistent() -> crate::Result { "a backslash is a normal character outside of Windows, so it's fine to have it as component" ); - s.make_relative_path_current(r"\".as_ref(), &mut r)?; + s.make_relative_path_current(r"\", &mut r)?; assert_eq!( r, Record { @@ -494,7 +496,7 @@ fn delegate_calls_are_consistent() -> crate::Result { r"a backslash can also be a valid leaf component - here we only popped the 'b', leaving the \ 'directory'" ); - s.make_relative_path_current(r"\\".as_ref(), &mut r)?; + s.make_relative_path_current(r"\\", &mut r)?; dirs.pop(); assert_eq!( r, @@ -513,7 +515,7 @@ fn delegate_calls_are_consistent() -> crate::Result { #[cfg(windows)] { - s.make_relative_path_current(r"c\/d".as_ref(), &mut r)?; + s.make_relative_path_current(Path::new(r"c\/d"), &mut r)?; dirs.pop(); dirs.push(root.join("c")); assert_eq!( diff --git a/gix-protocol/src/fetch/response/async_io.rs b/gix-protocol/src/fetch/response/async_io.rs index aee078d19e1..eea71bb6086 100644 --- a/gix-protocol/src/fetch/response/async_io.rs +++ b/gix-protocol/src/fetch/response/async_io.rs @@ -124,7 +124,7 @@ impl Response { io::ErrorKind::UnexpectedEof, "Could not read message headline", ))); - }; + } match line.trim_end() { "acknowledgments" => { diff --git a/gix-status/src/stack.rs b/gix-status/src/stack.rs index 7c72c83f4c0..75eabf66c43 100644 --- a/gix-status/src/stack.rs +++ b/gix-status/src/stack.rs @@ -1,10 +1,10 @@ +use crate::SymlinkCheck; use bstr::BStr; +use gix_fs::stack::ToNormalPathComponents; use gix_fs::Stack; use std::borrow::Cow; use std::path::{Path, PathBuf}; -use crate::SymlinkCheck; - impl SymlinkCheck { /// Create a new stack that starts operating at `root`. pub fn new(root: PathBuf) -> Self { @@ -24,7 +24,7 @@ impl SymlinkCheck { /// ### Note /// /// On windows, no verification is performed, instead only the combined path is provided as usual. - pub fn verified_path(&mut self, relative_path: &Path) -> std::io::Result<&Path> { + pub fn verified_path(&mut self, relative_path: impl ToNormalPathComponents) -> std::io::Result<&Path> { self.inner.make_relative_path_current(relative_path, &mut Delegate)?; Ok(self.inner.current()) } @@ -34,7 +34,7 @@ impl SymlinkCheck { /// For convenience, this incarnation is tuned to be easy to use with Git paths, i.e. slash-separated `BString` path. pub fn verified_path_allow_nonexisting(&mut self, relative_path: &BStr) -> std::io::Result> { let rela_path = gix_path::try_from_bstr(relative_path).map_err(std::io::Error::other)?; - if let Err(err) = self.verified_path(&rela_path) { + if let Err(err) = self.verified_path(rela_path.as_ref()) { if err.kind() == std::io::ErrorKind::NotFound { Ok(Cow::Owned(self.inner.root().join(rela_path))) } else { diff --git a/gix-status/tests/status/stack.rs b/gix-status/tests/status/stack.rs index 362f21261a3..b65d871a4df 100644 --- a/gix-status/tests/status/stack.rs +++ b/gix-status/tests/status/stack.rs @@ -25,7 +25,7 @@ fn paths_not_going_through_symlink_directories_are_ok_and_point_to_correct_item( ("dir/dirlink", is_symlinked_dir), ] { assert!( - expectation(&stack.verified_path(rela_path.as_ref())?.symlink_metadata()?), + expectation(&stack.verified_path(rela_path)?.symlink_metadata()?), "{rela_path:?} expectation failed" ); } @@ -35,7 +35,7 @@ fn paths_not_going_through_symlink_directories_are_ok_and_point_to_correct_item( #[test] fn leaf_file_does_not_have_to_exist() -> crate::Result { - assert!(!stack().verified_path("dir/does-not-exist".as_ref())?.exists()); + assert!(!stack().verified_path("dir/does-not-exist")?.exists()); Ok(()) } @@ -43,10 +43,7 @@ fn leaf_file_does_not_have_to_exist() -> crate::Result { #[cfg(not(windows))] fn intermediate_directories_have_to_exist_or_not_found_error() -> crate::Result { assert_eq!( - stack() - .verified_path("nonexisting-dir/file".as_ref()) - .unwrap_err() - .kind(), + stack().verified_path("nonexisting-dir/file").unwrap_err().kind(), std::io::ErrorKind::NotFound ); Ok(()) @@ -55,7 +52,7 @@ fn intermediate_directories_have_to_exist_or_not_found_error() -> crate::Result #[test] #[cfg(windows)] fn intermediate_directories_do_not_have_exist_for_success() -> crate::Result { - assert!(stack().verified_path("nonexisting-dir/file".as_ref()).is_ok()); + assert!(stack().verified_path("nonexisting-dir/file").is_ok()); Ok(()) } @@ -67,16 +64,13 @@ fn intermediate_directories_do_not_have_exist_for_success() -> crate::Result { fn paths_leading_through_symlinks_are_rejected() { let mut stack = stack(); assert_eq!( - stack - .verified_path("root-dirlink/file-in-dir".as_ref()) - .unwrap_err() - .kind(), + stack.verified_path("root-dirlink/file-in-dir").unwrap_err().kind(), std::io::ErrorKind::Other, "root-dirlink is a symlink to a directory" ); assert_eq!( - stack.verified_path("dir/dirlink/nothing".as_ref()).unwrap_err().kind(), + stack.verified_path("dir/dirlink/nothing").unwrap_err().kind(), std::io::ErrorKind::Other, "root-dirlink is a symlink to a directory" ); diff --git a/gix-worktree-state/src/checkout/entry.rs b/gix-worktree-state/src/checkout/entry.rs index 602aa280308..c52a8a1ce6b 100644 --- a/gix-worktree-state/src/checkout/entry.rs +++ b/gix-worktree-state/src/checkout/entry.rs @@ -80,7 +80,7 @@ where let dest_relative = gix_path::try_from_bstr(entry_path).map_err(|_| crate::checkout::Error::IllformedUtf8 { path: entry_path.to_owned(), })?; - let path_cache = path_cache.at_path(dest_relative, Some(entry.mode), &*objects)?; + let path_cache = path_cache.at_path(dest_relative.as_ref(), Some(entry.mode), &*objects)?; let dest = path_cache.path(); let object_size = match entry.mode { diff --git a/gix-worktree/Cargo.toml b/gix-worktree/Cargo.toml index 6eb7254545e..7d23341a85e 100644 --- a/gix-worktree/Cargo.toml +++ b/gix-worktree/Cargo.toml @@ -9,7 +9,7 @@ description = "A crate of the gitoxide project for shared worktree related types authors = ["Sebastian Thiel "] edition = "2021" include = ["src/**/*", "LICENSE-*"] -rust-version = "1.70" +rust-version = "1.74" autotests = false [lib] diff --git a/gix-worktree/src/stack/mod.rs b/gix-worktree/src/stack/mod.rs index 43939fed58a..76c9d446f81 100644 --- a/gix-worktree/src/stack/mod.rs +++ b/gix-worktree/src/stack/mod.rs @@ -113,7 +113,7 @@ impl Stack { /// Provide access to cached information for that `relative` path via the returned platform. pub fn at_path( &mut self, - relative: impl AsRef, + relative: impl ToNormalPathComponents, mode: Option, objects: &dyn gix_object::Find, ) -> std::io::Result> { @@ -127,8 +127,7 @@ impl Stack { case: self.case, statistics: &mut self.statistics, }; - self.stack - .make_relative_path_current(relative.as_ref(), &mut delegate)?; + self.stack.make_relative_path_current(relative, &mut delegate)?; Ok(Platform { parent: self, is_dir: mode_is_dir(mode), @@ -148,15 +147,8 @@ impl Stack { objects: &dyn gix_object::Find, ) -> std::io::Result> { let relative = relative.into(); - let relative_path = gix_path::try_from_bstr(relative).map_err(|_err| { - std::io::Error::new( - std::io::ErrorKind::Other, - format!("The path \"{relative}\" contained invalid UTF-8 and could not be turned into a path"), - ) - })?; - self.at_path( - relative_path, + relative, mode.or_else(|| relative.ends_with_str("/").then_some(gix_index::entry::Mode::DIR)), objects, ) @@ -210,6 +202,7 @@ impl Stack { /// pub mod delegate; use delegate::StackDelegate; +use gix_fs::stack::ToNormalPathComponents; mod platform; /// diff --git a/gix-worktree/tests/worktree/stack/create_directory.rs b/gix-worktree/tests/worktree/stack/create_directory.rs index 65b4b04fd7d..fd35cc6a329 100644 --- a/gix-worktree/tests/worktree/stack/create_directory.rs +++ b/gix-worktree/tests/worktree/stack/create_directory.rs @@ -92,7 +92,7 @@ fn symlinks_or_files_in_path_are_forbidden_or_unlinked_when_forced() -> crate::R let relative_path = format!("{dirname}/file"); assert_eq!( cache - .at_path(&relative_path, IS_FILE, &gix_object::find::Never) + .at_path(&*relative_path, IS_FILE, &gix_object::find::Never) .unwrap_err() .kind(), std::io::ErrorKind::AlreadyExists @@ -112,7 +112,9 @@ fn symlinks_or_files_in_path_are_forbidden_or_unlinked_when_forced() -> crate::R *unlink_on_collision = true; } let relative_path = format!("{dirname}/file"); - let path = cache.at_path(&relative_path, IS_FILE, &gix_object::find::Never)?.path(); + let path = cache + .at_path(&*relative_path, IS_FILE, &gix_object::find::Never)? + .path(); assert!(path.parent().unwrap().is_dir(), "directory was forcefully created"); assert!(!path.exists()); } diff --git a/gix-worktree/tests/worktree/stack/ignore.rs b/gix-worktree/tests/worktree/stack/ignore.rs index 7b618560e43..a1ad67eb07e 100644 --- a/gix-worktree/tests/worktree/stack/ignore.rs +++ b/gix-worktree/tests/worktree/stack/ignore.rs @@ -1,10 +1,10 @@ +use crate::{hex_to_id, worktree::stack::probe_case}; use bstr::{BStr, ByteSlice}; +use gix_fs::stack::ToNormalPathComponents; use gix_index::entry::Mode; use gix_worktree::{stack::state::ignore::Source, Stack}; use std::fs::Metadata; -use crate::{hex_to_id, worktree::stack::probe_case}; - struct IgnoreExpectations<'a> { lines: bstr::Lines<'a>, } @@ -170,7 +170,10 @@ fn check_against_baseline() -> crate::Result { // OK: we provide negative patterns that matched on paths if there was no other match, while git doesn't. } (actual, expected) => { - panic!("actual {actual:?} didn't match {expected:?} at '{relative_entry}'"); + panic!( + "actual {actual:?} didn't match {expected:?} at '{relative_entry}': {components:?}", + components = relative_entry.to_normal_path_components().collect::>() + ); } } } diff --git a/gix/Cargo.toml b/gix/Cargo.toml index 825ffe6ae3f..8da874b8d6f 100644 --- a/gix/Cargo.toml +++ b/gix/Cargo.toml @@ -9,7 +9,7 @@ version = "0.70.0" authors = ["Sebastian Thiel "] edition = "2021" include = ["src/**/*", "LICENSE-*"] -rust-version = "1.70" +rust-version = "1.75" [lib] doctest = false diff --git a/gix/src/attribute_stack.rs b/gix/src/attribute_stack.rs index d9921f58af3..4f1da159672 100644 --- a/gix/src/attribute_stack.rs +++ b/gix/src/attribute_stack.rs @@ -1,7 +1,7 @@ +use crate::{types::AttributeStack, Repository}; +use gix_fs::stack::ToNormalPathComponents; use std::ops::{Deref, DerefMut}; -use crate::{bstr::BStr, types::AttributeStack, Repository}; - /// Lifecycle impl<'repo> AttributeStack<'repo> { /// Create a new instance from a `repo` and the underlying pre-configured `stack`. @@ -44,18 +44,18 @@ impl AttributeStack<'_> { relative: impl AsRef, mode: Option, ) -> std::io::Result> { - self.inner.at_path(relative, mode, &self.repo.objects) + self.inner.at_path(relative.as_ref(), mode, &self.repo.objects) } /// Obtain a platform for attribute or ignore lookups from a repo-`relative` path, typically obtained from an index entry. /// `mode` should reflect whether it's a directory or not, or left at `None` if unknown. /// /// If `relative` ends with `/` and `mode` is `None`, it is automatically assumed to be a directory. - pub fn at_entry<'r>( + pub fn at_entry( &mut self, - relative: impl Into<&'r BStr>, + relative: impl ToNormalPathComponents, mode: Option, ) -> std::io::Result> { - self.inner.at_entry(relative, mode, &self.repo.objects) + self.inner.at_path(relative, mode, &self.repo.objects) } } diff --git a/gix/src/id.rs b/gix/src/id.rs index 90aed00db05..317f9340b50 100644 --- a/gix/src/id.rs +++ b/gix/src/id.rs @@ -64,7 +64,7 @@ impl<'repo> Id<'repo> { fn calculate_auto_hex_len(num_packed_objects: u64) -> usize { let mut len = 64 - num_packed_objects.leading_zeros(); - len = (len + 1) / 2; + len = len.div_ceil(2); len.max(7) as usize } diff --git a/tests/it/src/commands/copy_royal.rs b/tests/it/src/commands/copy_royal.rs index d68d3b348b6..3cebae566df 100644 --- a/tests/it/src/commands/copy_royal.rs +++ b/tests/it/src/commands/copy_royal.rs @@ -29,7 +29,7 @@ pub(super) mod function { { let rela_path = gix::path::from_bstr(rela_path); let src = worktree_dir.join(&rela_path); - stack.make_relative_path_current(&rela_path, &mut create_dir)?; + stack.make_relative_path_current(&*rela_path, &mut create_dir)?; let dst = stack.current(); eprintln!(