Skip to content

Commit 4a016f1

Browse files
committed
Draft gix_path::relative_path::RelativePath
1 parent a32fa0e commit 4a016f1

File tree

9 files changed

+128
-28
lines changed

9 files changed

+128
-28
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-path/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ doctest = false
1616

1717
[dependencies]
1818
gix-trace = { version = "^0.1.12", path = "../gix-trace" }
19+
gix-validate = { version = "^0.9.4", path = "../gix-validate" }
1920
bstr = { version = "1.12.0", default-features = false, features = ["std"] }
2021
thiserror = "2.0.0"
2122
once_cell = "1.21.3"

gix-path/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,6 @@ pub use realpath::function::{realpath, realpath_opts};
6262

6363
/// Information about the environment in terms of locations of resources.
6464
pub mod env;
65+
66+
///
67+
pub mod relative_path;

gix-path/src/relative_path.rs

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use bstr::BStr;
2+
use bstr::BString;
3+
use bstr::ByteSlice;
4+
use gix_validate::path::component::Options;
5+
use std::borrow::Cow;
6+
use std::u8;
7+
8+
use crate::os_str_into_bstr;
9+
use crate::try_from_bstr;
10+
11+
/// A wrapper for `BStr`. It is used to enforce the following constraints:
12+
///
13+
/// - The path separator always is `/`, independent of the platform.
14+
/// - Only normal components are allowed.
15+
/// - It is always represented as a bunch of bytes.
16+
#[derive()]
17+
pub struct RelativePath {
18+
inner: BStr,
19+
}
20+
21+
impl RelativePath {
22+
/// TODO
23+
/// Needs docs.
24+
pub fn ends_with(&self, needle: &[u8]) -> bool {
25+
self.inner.ends_with(needle)
26+
}
27+
}
28+
29+
/// The error used in [`RelativePath`](RelativePath).
30+
#[derive(Debug, thiserror::Error)]
31+
#[allow(missing_docs)]
32+
pub enum Error {
33+
#[error(transparent)]
34+
ContainsInvalidComponent(#[from] gix_validate::path::component::Error),
35+
#[error(transparent)]
36+
IllegalUtf8(#[from] crate::Utf8Error),
37+
}
38+
39+
impl<'a> TryFrom<&'a BStr> for &'a RelativePath {
40+
type Error = Error;
41+
42+
fn try_from(value: &'a BStr) -> Result<Self, Self::Error> {
43+
let path: &std::path::Path = &try_from_bstr(value)?;
44+
let options: Options = Default::default();
45+
46+
for component in path.components() {
47+
let component = os_str_into_bstr(component.as_os_str())?;
48+
49+
gix_validate::path::component(component, None, options)?;
50+
}
51+
52+
todo!()
53+
}
54+
}
55+
56+
impl<'a, const N: usize> TryFrom<&'a [u8; N]> for &'a RelativePath {
57+
type Error = Error;
58+
59+
#[inline]
60+
fn try_from(_value: &'a [u8; N]) -> Result<Self, Self::Error> {
61+
todo!()
62+
}
63+
}
64+
65+
impl TryFrom<BString> for &RelativePath {
66+
type Error = Error;
67+
68+
fn try_from(_value: BString) -> Result<Self, Self::Error> {
69+
todo!()
70+
}
71+
}
72+
73+
/// This is required by a trait bound on [`from_str`](crate::from_bstr).
74+
impl<'a> From<&'a RelativePath> for Cow<'a, BStr> {
75+
#[inline]
76+
fn from(value: &'a RelativePath) -> Cow<'a, BStr> {
77+
Cow::Borrowed(&value.inner)
78+
}
79+
}
80+
81+
impl AsRef<[u8]> for RelativePath {
82+
#[inline]
83+
fn as_ref(&self) -> &[u8] {
84+
self.inner.as_bytes()
85+
}
86+
}

gix-ref/src/namespace.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::path::Path;
22

33
use gix_object::bstr::{BStr, BString, ByteSlice, ByteVec};
4+
use gix_path::relative_path::RelativePath;
45

56
use crate::{FullName, FullNameRef, Namespace, PartialNameRef};
67

@@ -21,8 +22,8 @@ impl Namespace {
2122
///
2223
/// The prefix is a relative path with slash-separated path components.
2324
// TODO: use `RelativePath` type instead (see #1921), or a trait that helps convert into it.
24-
pub fn into_namespaced_prefix<'a>(mut self, prefix: impl Into<&'a BStr>) -> BString {
25-
self.0.push_str(prefix.into());
25+
pub fn into_namespaced_prefix<'a>(mut self, prefix: &'a RelativePath) -> BString {
26+
self.0.push_str(prefix);
2627
gix_path::to_unix_separators_on_windows(self.0).into_owned()
2728
}
2829
pub(crate) fn into_namespaced_name(mut self, name: &FullNameRef) -> FullName {

gix-ref/src/store/file/loose/iter.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ use std::path::{Path, PathBuf};
22

33
use gix_features::fs::walkdir::DirEntryIter;
44
use gix_object::bstr::ByteSlice;
5+
use gix_path::relative_path::RelativePath;
56

6-
use crate::{file::iter::LooseThenPacked, store_impl::file, BStr, BString, FullName};
7+
use crate::{file::iter::LooseThenPacked, store_impl::file, BString, FullName};
78

89
/// An iterator over all valid loose reference paths as seen from a particular base directory.
910
pub(in crate::store_impl::file) struct SortedLoosePaths {
@@ -89,7 +90,7 @@ impl file::Store {
8990
///
9091
/// Prefixes are relative paths with slash-separated components.
9192
// TODO: use `RelativePath` type instead (see #1921), or a trait that helps convert into it.
92-
pub fn loose_iter_prefixed<'a>(&self, prefix: impl Into<&'a BStr>) -> std::io::Result<LooseThenPacked<'_, '_>> {
93-
self.iter_prefixed_packed(prefix.into(), None)
93+
pub fn loose_iter_prefixed<'a>(&self, prefix: &'a RelativePath) -> std::io::Result<LooseThenPacked<'_, '_>> {
94+
self.iter_prefixed_packed(prefix, None)
9495
}
9596
}

gix-ref/src/store/file/overlay_iter.rs

+14-13
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use crate::{
1212
BStr, FullName, Namespace, Reference,
1313
};
1414

15+
use gix_path::relative_path::RelativePath;
16+
1517
/// An iterator stepping through sorted input of loose references and packed references, preferring loose refs over otherwise
1618
/// equivalent packed references.
1719
///
@@ -204,9 +206,9 @@ impl Platform<'_> {
204206
///
205207
/// Prefixes are relative paths with slash-separated components.
206208
// TODO: use `RelativePath` type instead (see #1921), or a trait that helps convert into it.
207-
pub fn prefixed<'a>(&self, prefix: impl Into<&'a BStr>) -> std::io::Result<LooseThenPacked<'_, '_>> {
209+
pub fn prefixed<'a>(&self, prefix: &'a RelativePath) -> std::io::Result<LooseThenPacked<'_, '_>> {
208210
self.store
209-
.iter_prefixed_packed(prefix.into(), self.packed.as_ref().map(|b| &***b))
211+
.iter_prefixed_packed(prefix, self.packed.as_ref().map(|b| &***b))
210212
}
211213
}
212214

@@ -291,13 +293,8 @@ impl<'a> IterInfo<'a> {
291293
.peekable()
292294
}
293295

294-
fn from_prefix(
295-
base: &'a Path,
296-
prefix: impl Into<Cow<'a, BStr>>,
297-
precompose_unicode: bool,
298-
) -> std::io::Result<Self> {
299-
let prefix = prefix.into();
300-
let prefix_path = gix_path::from_bstr(prefix.as_ref());
296+
fn from_prefix(base: &'a Path, prefix: &'a RelativePath, precompose_unicode: bool) -> std::io::Result<Self> {
297+
let prefix_path = gix_path::from_bstr(prefix);
301298
if prefix_path.is_absolute() {
302299
return Err(std::io::Error::new(
303300
std::io::ErrorKind::InvalidInput,
@@ -326,7 +323,7 @@ impl<'a> IterInfo<'a> {
326323
.to_owned();
327324
Ok(IterInfo::ComputedIterationRoot {
328325
base,
329-
prefix,
326+
prefix: prefix.into(),
330327
iter_root,
331328
precompose_unicode,
332329
})
@@ -380,7 +377,7 @@ impl file::Store {
380377
// TODO: use `RelativePath` type instead (see #1921), or a trait that helps convert into it.
381378
pub fn iter_prefixed_packed<'a, 's, 'p>(
382379
&'s self,
383-
prefix: impl Into<&'a BStr>,
380+
prefix: &'a RelativePath,
384381
packed: Option<&'p packed::Buffer>,
385382
) -> std::io::Result<LooseThenPacked<'p, 's>> {
386383
let prefix = prefix.into();
@@ -394,8 +391,12 @@ impl file::Store {
394391
self.iter_from_info(git_dir_info, common_dir_info, packed)
395392
}
396393
Some(namespace) => {
397-
let prefix = namespace.to_owned().into_namespaced_prefix(prefix);
398-
let git_dir_info = IterInfo::from_prefix(self.git_dir(), prefix.clone(), self.precompose_unicode)?;
394+
let prefix = namespace
395+
.to_owned()
396+
.into_namespaced_prefix(prefix)
397+
.try_into()
398+
.expect("TODO");
399+
let git_dir_info = IterInfo::from_prefix(self.git_dir(), prefix, self.precompose_unicode)?;
399400
let common_dir_info = self
400401
.common_dir()
401402
.map(|base| IterInfo::from_prefix(base, prefix, self.precompose_unicode))

gix/src/open/repository.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#![allow(clippy::result_large_err)]
22
use super::{Error, Options};
33
use crate::{
4-
bstr,
54
bstr::BString,
65
config,
76
config::{
@@ -347,10 +346,9 @@ impl ThreadSafeRepository {
347346
refs.namespace.clone_from(&config.refs_namespace);
348347
let replacements = replacement_objects_refs_prefix(&config.resolved, lenient_config, filter_config_section)?
349348
.and_then(|prefix| {
350-
use bstr::ByteSlice;
351349
let _span = gix_trace::detail!("find replacement objects");
352350
let platform = refs.iter().ok()?;
353-
let iter = platform.prefixed(prefix.as_bstr()).ok()?;
351+
let iter = platform.prefixed(prefix.clone().try_into().expect("TODO")).ok()?;
354352
let replacements = iter
355353
.filter_map(Result::ok)
356354
.filter_map(|r: gix_ref::Reference| {

gix/src/reference/iter.rs

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
//!
22
#![allow(clippy::empty_docs)]
3-
use std::borrow::Cow;
43

5-
use gix_ref::{bstr::BStr, file::ReferenceExt};
4+
use gix_path::relative_path::RelativePath;
5+
use gix_ref::file::ReferenceExt;
66

77
/// A platform to create iterators over references.
88
#[must_use = "Iterators should be obtained from this iterator platform"]
@@ -45,32 +45,38 @@ impl Platform<'_> {
4545
/// These are of the form `refs/heads/` or `refs/remotes/origin`, and must not contain relative paths components like `.` or `..`.
4646
// TODO: Create a custom `Path` type that enforces the requirements of git naturally, this type is surprising possibly on windows
4747
// and when not using a trailing '/' to signal directories.
48-
pub fn prefixed<'a>(&self, prefix: impl Into<Cow<'a, BStr>>) -> Result<Iter<'_>, init::Error> {
49-
Ok(Iter::new(self.repo, self.platform.prefixed(prefix.into().as_ref())?))
48+
pub fn prefixed<'a>(&self, prefix: &'a RelativePath) -> Result<Iter<'_>, init::Error> {
49+
Ok(Iter::new(self.repo, self.platform.prefixed(prefix)?))
5050
}
5151

5252
// TODO: tests
5353
/// Return an iterator over all references that are tags.
5454
///
5555
/// They are all prefixed with `refs/tags`.
5656
pub fn tags(&self) -> Result<Iter<'_>, init::Error> {
57-
Ok(Iter::new(self.repo, self.platform.prefixed(b"refs/tags/")?))
57+
Ok(Iter::new(self.repo, self.platform.prefixed(b"refs/tags/".try_into()?)?))
5858
}
5959

6060
// TODO: tests
6161
/// Return an iterator over all local branches.
6262
///
6363
/// They are all prefixed with `refs/heads`.
6464
pub fn local_branches(&self) -> Result<Iter<'_>, init::Error> {
65-
Ok(Iter::new(self.repo, self.platform.prefixed(b"refs/heads/")?))
65+
Ok(Iter::new(
66+
self.repo,
67+
self.platform.prefixed(b"refs/heads/".try_into()?)?,
68+
))
6669
}
6770

6871
// TODO: tests
6972
/// Return an iterator over all remote branches.
7073
///
7174
/// They are all prefixed with `refs/remotes`.
7275
pub fn remote_branches(&self) -> Result<Iter<'_>, init::Error> {
73-
Ok(Iter::new(self.repo, self.platform.prefixed(b"refs/remotes/")?))
76+
Ok(Iter::new(
77+
self.repo,
78+
self.platform.prefixed(b"refs/remotes/".try_into()?)?,
79+
))
7480
}
7581
}
7682

@@ -123,6 +129,8 @@ pub mod init {
123129
pub enum Error {
124130
#[error(transparent)]
125131
Io(#[from] std::io::Error),
132+
#[error(transparent)]
133+
RelativePath(#[from] gix_path::relative_path::Error),
126134
}
127135
}
128136

0 commit comments

Comments
 (0)