Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gitoxide-core/src/repository/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub fn list(
let mut last_meta = None;
let mut it = config.sections_and_postmatter().peekable();
while let Some((section, matter)) = it.next() {
if !filters.is_empty() && !filters.iter().any(|filter| filter.matches_section(section)) {
if !filters.is_empty() && !filters.iter().any(|filter| filter.matches_section(&section)) {
continue;
}

Expand Down
4 changes: 2 additions & 2 deletions gix-config-value/fuzz/fuzz_targets/fuzz_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use gix_config_value::{
Boolean, Color, Integer, Path,
};
use libfuzzer_sys::fuzz_target;
use std::{borrow::Cow, fmt::Write, hint::black_box, str::FromStr};
use std::{fmt::Write, hint::black_box, str::FromStr};

#[derive(Debug, Arbitrary)]
struct Ctx<'a> {
Expand Down Expand Up @@ -39,7 +39,7 @@ fn fuzz(ctx: Ctx) -> Result<()> {
let i = Integer::try_from(BStr::new(ctx.integer_str))?;
_ = black_box(i.to_decimal());

let p = Path::from(Cow::Borrowed(BStr::new(ctx.path_str)));
let p = Path::from(BStr::new(ctx.path_str));
_ = black_box(p.interpolate(Context::default()));

Ok(())
Expand Down
7 changes: 7 additions & 0 deletions gix-config-value/src/boolean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ impl TryFrom<Cow<'_, BStr>> for Boolean {
}
}

impl TryFrom<BString> for Boolean {
type Error = Error;
fn try_from(value: BString) -> Result<Self, Self::Error> {
Self::try_from(value.as_ref())
}
}

impl Display for Boolean {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
Expand Down
8 changes: 8 additions & 0 deletions gix-config-value/src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ impl TryFrom<Cow<'_, BStr>> for Color {
}
}

impl TryFrom<BString> for Color {
type Error = Error;

fn try_from(value: BString) -> Result<Self, Self::Error> {
Self::try_from(value.as_ref())
}
}

/// Discriminating enum for names of [`Color`] values.
///
/// `git-config` supports the eight standard colors, their bright variants, an
Expand Down
8 changes: 8 additions & 0 deletions gix-config-value/src/integer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ impl TryFrom<Cow<'_, BStr>> for Integer {
}
}

impl TryFrom<BString> for Integer {
type Error = Error;

fn try_from(value: BString) -> Result<Self, Self::Error> {
Self::try_from(value.as_ref())
}
}

/// Integer suffixes that are supported by `git-config`.
///
/// These values are base-2 unit of measurements, not the base-10 variants.
Expand Down
6 changes: 2 additions & 4 deletions gix-config-value/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
//! ## Examples
//!
//! ```
//! use std::borrow::Cow;
//!
//! use bstr::ByteSlice;
//! use gix_config_value::{Boolean, Integer, Path};
//!
Expand All @@ -14,9 +12,9 @@
//! let packed_limit = Integer::try_from("10m".as_bytes().as_bstr()).unwrap();
//! assert_eq!(packed_limit.to_decimal(), Some(10 * 1024 * 1024));
//!
//! let ignore_revs = Path::from(Cow::Borrowed(b":(optional)~/.git-blame-ignore-revs".as_bstr()));
//! let ignore_revs = Path::from(b":(optional)~/.git-blame-ignore-revs".as_bstr());
//! assert!(ignore_revs.is_optional);
//! assert_eq!(ignore_revs.value.as_ref(), b"~/.git-blame-ignore-revs".as_bstr());
//! assert_eq!(ignore_revs.value.as_bstr(), b"~/.git-blame-ignore-revs".as_bstr());
//! ```
//!
//! ## Feature Flags
Expand Down
65 changes: 31 additions & 34 deletions gix-config-value/src/path.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{borrow::Cow, path::PathBuf};
use std::path::PathBuf;

use bstr::BStr;
use bstr::{BStr, BString, ByteSlice};

use crate::Path;

Expand Down Expand Up @@ -86,44 +86,35 @@ pub mod interpolate {
}
}

impl std::ops::Deref for Path<'_> {
impl std::ops::Deref for Path {
type Target = BStr;

fn deref(&self) -> &Self::Target {
self.value.as_ref()
self.value.as_bstr()
}
}

impl AsRef<[u8]> for Path<'_> {
impl AsRef<[u8]> for Path {
fn as_ref(&self) -> &[u8] {
self.value.as_ref()
}
}

impl AsRef<BStr> for Path<'_> {
impl AsRef<BStr> for Path {
fn as_ref(&self) -> &BStr {
self.value.as_ref()
self.value.as_bstr()
}
}

impl<'a> From<Cow<'a, BStr>> for Path<'a> {
fn from(value: Cow<'a, BStr>) -> Self {
impl From<BString> for Path {
fn from(mut value: BString) -> Self {
/// The prefix used to mark a path as optional in Git configuration files.
const OPTIONAL_PREFIX: &[u8] = b":(optional)";

if value.starts_with(OPTIONAL_PREFIX) {
// Strip the prefix while preserving the Cow variant for efficiency:
// - Borrowed data remains borrowed (no allocation)
// - Owned data is modified in-place using drain (no extra allocation)
let stripped = match value {
Cow::Borrowed(b) => Cow::Borrowed(&b[OPTIONAL_PREFIX.len()..]),
Cow::Owned(mut b) => {
b.drain(..OPTIONAL_PREFIX.len());
Cow::Owned(b)
}
};
value.drain(..OPTIONAL_PREFIX.len());
Path {
value: stripped,
value,
is_optional: true,
}
} else {
Expand All @@ -135,7 +126,19 @@ impl<'a> From<Cow<'a, BStr>> for Path<'a> {
}
}

impl<'a> Path<'a> {
impl From<&BStr> for Path {
fn from(value: &BStr) -> Self {
Path::from(value.to_owned())
}
}

impl From<&str> for Path {
fn from(value: &str) -> Self {
Path::from(BString::from(value))
}
}

impl Path {
/// Interpolates this path into a path usable on the file system.
///
/// If this path starts with `~/` or `~user/` or `%(prefix)/`
Expand All @@ -157,7 +160,7 @@ impl<'a> Path<'a> {
home_dir,
home_for_user,
}: interpolate::Context<'_>,
) -> Result<Cow<'a, std::path::Path>, interpolate::Error> {
) -> Result<PathBuf, interpolate::Error> {
if self.is_empty() {
return Err(interpolate::Error::Missing { what: "path" });
}
Expand All @@ -176,37 +179,31 @@ impl<'a> Path<'a> {
err,
}
})?;
Ok(git_install_dir.join(path_without_trailing_slash).into())
Ok(git_install_dir.join(path_without_trailing_slash))
} else if self.starts_with(USER_HOME) {
let home_path = home_dir.ok_or(interpolate::Error::Missing { what: "home dir" })?;
let (_prefix, val) = self.split_at(USER_HOME.len());
let val = gix_path::try_from_byte_slice(val).map_err(|err| interpolate::Error::Utf8Conversion {
what: "path past ~/",
err,
})?;
Ok(home_path.join(val).into())
Ok(home_path.join(val))
} else if self.starts_with(b"~") && self.contains(&b'/') {
self.interpolate_user(home_for_user.ok_or(interpolate::Error::Missing {
what: "home for user lookup",
})?)
} else {
Ok(gix_path::from_bstr(self.value))
Ok(gix_path::from_bstr(self.value.as_bstr()).into_owned())
}
}

#[cfg(any(target_os = "windows", target_os = "android"))]
fn interpolate_user(
self,
_home_for_user: fn(&str) -> Option<PathBuf>,
) -> Result<Cow<'a, std::path::Path>, interpolate::Error> {
fn interpolate_user(self, _home_for_user: fn(&str) -> Option<PathBuf>) -> Result<PathBuf, interpolate::Error> {
Err(interpolate::Error::UserInterpolationUnsupported)
}

#[cfg(not(any(target_os = "windows", target_os = "android")))]
fn interpolate_user(
self,
home_for_user: fn(&str) -> Option<PathBuf>,
) -> Result<Cow<'a, std::path::Path>, interpolate::Error> {
fn interpolate_user(self, home_for_user: fn(&str) -> Option<PathBuf>) -> Result<PathBuf, interpolate::Error> {
let (_prefix, val) = self.split_at("/".len());
let i = val
.iter()
Expand All @@ -222,6 +219,6 @@ impl<'a> Path<'a> {
err,
}
})?;
Ok(home.join(path_past_user_prefix).into())
Ok(home.join(path_past_user_prefix))
}
}
13 changes: 6 additions & 7 deletions gix-config-value/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub struct Boolean(pub bool);

/// Any value that can be interpreted as a path to a resource on disk.
///
/// Git represents file paths as byte arrays, modeled here as owned or borrowed byte sequences.
/// Git represents file paths as byte arrays, modeled here as an owned byte sequence.
///
/// ## Optional Paths
///
Expand All @@ -49,23 +49,22 @@ pub struct Boolean(pub bool);
/// configuration values like `blame.ignoreRevsFile` that may only exist in some repositories.
///
/// ```
/// use std::borrow::Cow;
/// use gix_config_value::Path;
/// use bstr::ByteSlice;
///
/// // Regular path - file is expected to exist
/// let path = Path::from(Cow::Borrowed(b"/etc/gitconfig".as_bstr()));
/// let path = Path::from(b"/etc/gitconfig".as_bstr());
/// assert!(!path.is_optional);
///
/// // Optional path - it's okay if the file doesn't exist
/// let path = Path::from(Cow::Borrowed(b":(optional)~/.gitignore".as_bstr()));
/// let path = Path::from(b":(optional)~/.gitignore".as_bstr());
/// assert!(path.is_optional);
/// assert_eq!(path.value.as_ref(), b"~/.gitignore"); // prefix is stripped
/// assert_eq!(path.value.as_bstr(), b"~/.gitignore"); // prefix is stripped
/// ```
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Path<'a> {
pub struct Path {
/// The path string, un-interpolated
pub value: std::borrow::Cow<'a, bstr::BStr>,
pub value: bstr::BString,
/// Whether this path was prefixed with `:(optional)`, indicating it's acceptable if the file doesn't exist.
///
/// Optional paths indicate that it's acceptable if the file doesn't exist.
Expand Down
8 changes: 0 additions & 8 deletions gix-config-value/tests/value/main.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
use std::borrow::Cow;

use bstr::{BStr, ByteSlice};

type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>;
fn b(s: &str) -> &bstr::BStr {
s.into()
}

pub fn cow_str(s: &str) -> Cow<'_, BStr> {
Cow::Borrowed(s.as_bytes().as_bstr())
}

mod boolean;
mod color;
mod integer;
Expand Down
Loading
Loading