Skip to content

Commit 5cb5337

Browse files
authored
Merge pull request #1909 from cruessler/take-to-components-in-fs-stack
Accept `ToComponents` in `gix_fs::Stack`
2 parents bfa3253 + 1f98edb commit 5cb5337

File tree

20 files changed

+177
-102
lines changed

20 files changed

+177
-102
lines changed

.github/workflows/msrv.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
env:
3232
# dictated by `firefox` to support the `helix` editor, but now probably effectively be controlled by `jiff`, which also aligns with `regex`.
3333
# IMPORTANT: adjust etc/msrv-badge.svg as well
34-
rust_version: 1.74.0
34+
rust_version: 1.75.0
3535

3636
steps:
3737
- uses: actions/checkout@v4

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

etc/msrv-badge.svg

Lines changed: 3 additions & 3 deletions
Loading

gitoxide-core/src/repository/attributes/query.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ pub struct Options {
88
}
99

1010
pub(crate) mod function {
11-
use std::{borrow::Cow, io, path::Path};
12-
1311
use anyhow::bail;
1412
use gix::bstr::BStr;
13+
use std::borrow::Cow;
14+
use std::{io, path::Path};
1515

1616
use crate::{
1717
is_dir_to_mode,
@@ -44,7 +44,7 @@ pub(crate) mod function {
4444
.ok()
4545
.map(|m| is_dir_to_mode(m.is_dir()));
4646

47-
let entry = cache.at_entry(path.as_slice(), mode)?;
47+
let entry = cache.at_entry(&path, mode)?;
4848
if !entry.matching_attributes(&mut matches) {
4949
continue;
5050
}

gitoxide-core/src/repository/exclude.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub fn query(
4848
.metadata()
4949
.ok()
5050
.map(|m| is_dir_to_mode(m.is_dir()));
51-
let entry = cache.at_entry(path.as_slice(), mode)?;
51+
let entry = cache.at_entry(&path, mode)?;
5252
let match_ = entry
5353
.matching_exclude_pattern()
5454
.and_then(|m| (show_ignore_patterns || !m.pattern.is_negative()).then_some(m));

gix-fs/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
88
description = "A crate providing file system specific utilities to `gitoxide`"
99
authors = ["Sebastian Thiel <[email protected]>"]
1010
edition = "2021"
11-
rust-version = "1.70"
11+
rust-version = "1.75"
1212
include = ["src/**/*", "LICENSE-*"]
1313

1414
[lib]
@@ -19,8 +19,11 @@ doctest = false
1919
serde = ["dep:serde"]
2020

2121
[dependencies]
22+
bstr = "1.5.0"
23+
gix-path = { version = "^0.10.14", path = "../gix-path" }
2224
gix-features = { version = "^0.40.0", path = "../gix-features", features = ["fs-read-dir"] }
2325
gix-utils = { version = "^0.1.14", path = "../gix-utils" }
26+
thiserror = "2.0.0"
2427
serde = { version = "1.0.114", optional = true, default-features = false, features = ["std", "derive"] }
2528

2629
# For `Capabilities` to assure parallel operation works.

gix-fs/src/stack.rs

Lines changed: 93 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,80 @@
1+
use crate::Stack;
2+
use bstr::{BStr, BString, ByteSlice};
3+
use std::ffi::OsStr;
14
use std::path::{Component, Path, PathBuf};
25

3-
use crate::Stack;
6+
///
7+
pub mod to_normal_path_components {
8+
use std::ffi::OsString;
9+
10+
/// The error used in [`ToNormalPathComponents::to_normal_path_components()`](super::ToNormalPathComponents::to_normal_path_components()).
11+
#[derive(Debug, thiserror::Error)]
12+
#[allow(missing_docs)]
13+
pub enum Error {
14+
#[error("Input path \"{path}\" contains relative or absolute components", path = std::path::Path::new(.0.as_os_str()).display())]
15+
NotANormalComponent(OsString),
16+
#[error("Could not convert to UTF8 or from UTF8 due to ill-formed input")]
17+
IllegalUtf8,
18+
}
19+
}
20+
21+
/// Obtain an iterator over `OsStr`-components which are normal, none-relative and not absolute.
22+
pub trait ToNormalPathComponents {
23+
/// Return an iterator over the normal components of a path, without the separator.
24+
fn to_normal_path_components(&self) -> impl Iterator<Item = Result<&OsStr, to_normal_path_components::Error>>;
25+
}
26+
27+
impl ToNormalPathComponents for &Path {
28+
fn to_normal_path_components(&self) -> impl Iterator<Item = Result<&OsStr, to_normal_path_components::Error>> {
29+
self.components().map(|component| match component {
30+
Component::Normal(os_str) => Ok(os_str),
31+
_ => Err(to_normal_path_components::Error::NotANormalComponent(
32+
self.as_os_str().to_owned(),
33+
)),
34+
})
35+
}
36+
}
37+
38+
impl ToNormalPathComponents for PathBuf {
39+
fn to_normal_path_components(&self) -> impl Iterator<Item = Result<&OsStr, to_normal_path_components::Error>> {
40+
self.components().map(|component| match component {
41+
Component::Normal(os_str) => Ok(os_str),
42+
_ => Err(to_normal_path_components::Error::NotANormalComponent(
43+
self.as_os_str().to_owned(),
44+
)),
45+
})
46+
}
47+
}
48+
49+
impl ToNormalPathComponents for &BStr {
50+
fn to_normal_path_components(&self) -> impl Iterator<Item = Result<&OsStr, to_normal_path_components::Error>> {
51+
self.split(|b| *b == b'/').filter(|c| !c.is_empty()).map(|component| {
52+
gix_path::try_from_byte_slice(component.as_bstr())
53+
.map_err(|_| to_normal_path_components::Error::IllegalUtf8)
54+
.map(Path::as_os_str)
55+
})
56+
}
57+
}
58+
59+
impl ToNormalPathComponents for &str {
60+
fn to_normal_path_components(&self) -> impl Iterator<Item = Result<&OsStr, to_normal_path_components::Error>> {
61+
self.split('/').filter(|c| !c.is_empty()).map(|component| {
62+
gix_path::try_from_byte_slice(component.as_bytes())
63+
.map_err(|_| to_normal_path_components::Error::IllegalUtf8)
64+
.map(Path::as_os_str)
65+
})
66+
}
67+
}
68+
69+
impl ToNormalPathComponents for &BString {
70+
fn to_normal_path_components(&self) -> impl Iterator<Item = Result<&OsStr, to_normal_path_components::Error>> {
71+
self.split(|b| *b == b'/').filter(|c| !c.is_empty()).map(|component| {
72+
gix_path::try_from_byte_slice(component.as_bstr())
73+
.map_err(|_| to_normal_path_components::Error::IllegalUtf8)
74+
.map(Path::as_os_str)
75+
})
76+
}
77+
}
478

579
/// Access
680
impl Stack {
@@ -62,8 +136,13 @@ impl Stack {
62136
/// `relative` paths are terminal, so point to their designated file or directory.
63137
/// The path is also expected to be normalized, and should not contain extra separators, and must not contain `..`
64138
/// or have leading or trailing slashes (or additionally backslashes on Windows).
65-
pub fn make_relative_path_current(&mut self, relative: &Path, delegate: &mut dyn Delegate) -> std::io::Result<()> {
66-
if self.valid_components != 0 && relative.as_os_str().is_empty() {
139+
pub fn make_relative_path_current(
140+
&mut self,
141+
relative: impl ToNormalPathComponents,
142+
delegate: &mut dyn Delegate,
143+
) -> std::io::Result<()> {
144+
let mut components = relative.to_normal_path_components().peekable();
145+
if self.valid_components != 0 && components.peek().is_none() {
67146
return Err(std::io::Error::new(
68147
std::io::ErrorKind::Other,
69148
"empty inputs are not allowed",
@@ -73,15 +152,19 @@ impl Stack {
73152
delegate.push_directory(self)?;
74153
}
75154

76-
let mut components = relative.components().peekable();
77155
let mut existing_components = self.current_relative.components();
78156
let mut matching_components = 0;
79157
while let (Some(existing_comp), Some(new_comp)) = (existing_components.next(), components.peek()) {
80-
if existing_comp == *new_comp {
81-
components.next();
82-
matching_components += 1;
83-
} else {
84-
break;
158+
match new_comp {
159+
Ok(new_comp) => {
160+
if existing_comp.as_os_str() == *new_comp {
161+
components.next();
162+
matching_components += 1;
163+
} else {
164+
break;
165+
}
166+
}
167+
Err(err) => return Err(std::io::Error::other(format!("{err}"))),
85168
}
86169
}
87170

@@ -100,15 +183,7 @@ impl Stack {
100183
}
101184

102185
while let Some(comp) = components.next() {
103-
if !matches!(comp, Component::Normal(_)) {
104-
return Err(std::io::Error::new(
105-
std::io::ErrorKind::Other,
106-
format!(
107-
"Input path \"{}\" contains relative or absolute components",
108-
relative.display()
109-
),
110-
));
111-
}
186+
let comp = comp.map_err(std::io::Error::other)?;
112187
let is_last_component = components.peek().is_none();
113188
self.current_is_directory = !is_last_component;
114189
self.current.push(comp);

0 commit comments

Comments
 (0)