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 @@
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!(