git-ref-format: Copy over from radicle-git

This is the result of

    cp -r \
      ../radicle-git/radicle-git-ext/git-ref-format/core/src/{{cbor,check,deriv,lit,name,refspec,serde}.rs,{name,refspec}} \
      crates/radicle-git-ref-format/src/

with the radicle-git workspace at commit
8c5df5ecbd3f2507d323608b52e1fbf7b177d864.
This commit is contained in:
Lorenz Leutgeb
2026-07-24 16:27:02 +02:00
parent 36fc031da2
commit 8731d01bca
9 changed files with 2308 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
use std::convert::TryFrom;
use minicbor::{
decode,
encode::{self, Write},
Decode, Decoder, Encode, Encoder,
};
use crate::{
refspec::{PatternStr, PatternString},
Namespaced, Qualified, RefStr, RefString,
};
impl<'de: 'a, 'a> Decode<'de> for &'a RefStr {
#[inline]
fn decode(d: &mut Decoder<'de>) -> Result<Self, decode::Error> {
d.str()
.and_then(|s| Self::try_from(s).map_err(|e| decode::Error::Custom(Box::new(e))))
}
}
impl Encode for &RefStr {
#[inline]
fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
e.str(self.as_str())?;
Ok(())
}
}
impl<'de> Decode<'de> for RefString {
#[inline]
fn decode(d: &mut Decoder<'de>) -> Result<Self, decode::Error> {
Decode::decode(d).map(|s: &RefStr| s.to_owned())
}
}
impl Encode for RefString {
#[inline]
fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
self.as_refstr().encode(e)
}
}
impl<'de: 'a, 'a> Decode<'de> for &'a PatternStr {
#[inline]
fn decode(d: &mut Decoder<'de>) -> Result<Self, decode::Error> {
d.str()
.and_then(|s| Self::try_from(s).map_err(|e| decode::Error::Custom(Box::new(e))))
}
}
impl Encode for &PatternStr {
#[inline]
fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
e.str(self.as_str())?;
Ok(())
}
}
impl<'de> Decode<'de> for PatternString {
#[inline]
fn decode(d: &mut Decoder<'de>) -> Result<Self, decode::Error> {
Decode::decode(d).map(|s: &PatternStr| s.to_owned())
}
}
impl Encode for PatternString {
#[inline]
fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
self.as_pattern_str().encode(e)
}
}
impl<'de: 'a, 'a> Decode<'de> for Qualified<'a> {
#[inline]
fn decode(d: &mut Decoder<'de>) -> Result<Self, decode::Error> {
Decode::decode(d).and_then(|s: &RefStr| {
s.qualified()
.ok_or(decode::Error::Message("not a qualified ref"))
})
}
}
impl Encode for Qualified<'_> {
#[inline]
fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
self.as_str().encode(e)
}
}
impl<'de: 'a, 'a> Decode<'de> for Namespaced<'a> {
#[inline]
fn decode(d: &mut Decoder<'de>) -> Result<Self, decode::Error> {
Decode::decode(d).and_then(|s: &RefStr| {
s.to_namespaced()
.ok_or(decode::Error::Message("not a namespaced ref"))
})
}
}
impl Encode for Namespaced<'_> {
#[inline]
fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
self.as_str().encode(e)
}
}
+101
View File
@@ -0,0 +1,101 @@
use thiserror::Error;
pub struct Options {
/// If `false`, the refname must contain at least one `/`.
pub allow_onelevel: bool,
/// If `true`, the refname may contain exactly one `*` character.
pub allow_pattern: bool,
}
#[derive(Debug, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum Error {
#[error("empty input")]
Empty,
#[error("lone '@' character")]
LoneAt,
#[error("consecutive or trailing slash")]
Slash,
#[error("ends with '.lock'")]
DotLock,
#[error("consecutive dots ('..')")]
DotDot,
#[error("at-open-brace ('@{{')")]
AtOpenBrace,
#[error("invalid character {0:?}")]
InvalidChar(char),
#[error("component starts with '.'")]
StartsDot,
#[error("component ends with '.'")]
EndsDot,
#[error("control character")]
Control,
#[error("whitespace")]
Space,
#[error("must contain at most one '*'")]
Pattern,
#[error("must contain at least one '/'")]
OneLevel,
}
/// Validate that a string slice is a valid refname.
pub fn ref_format(opts: Options, s: &str) -> Result<(), Error> {
match s {
"" => Err(Error::Empty),
"@" => Err(Error::LoneAt),
"." => Err(Error::StartsDot),
_ => {
let mut globs = 0usize;
let mut parts = 0usize;
for x in s.split('/') {
if x.is_empty() {
return Err(Error::Slash);
}
parts += 1;
if x.ends_with(".lock") {
return Err(Error::DotLock);
}
let last_char = x.chars().count() - 1;
for (i, y) in x.chars().zip(x.chars().cycle().skip(1)).enumerate() {
match y {
('.', '.') => return Err(Error::DotDot),
('@', '{') => return Err(Error::AtOpenBrace),
('\0', _) => return Err(Error::InvalidChar('\0')),
('\\', _) => return Err(Error::InvalidChar('\\')),
('~', _) => return Err(Error::InvalidChar('~')),
('^', _) => return Err(Error::InvalidChar('^')),
(':', _) => return Err(Error::InvalidChar(':')),
('?', _) => return Err(Error::InvalidChar('?')),
('[', _) => return Err(Error::InvalidChar('[')),
('*', _) => globs += 1,
('.', _) if i == 0 => return Err(Error::StartsDot),
('.', _) if i == last_char => return Err(Error::EndsDot),
(' ', _) => return Err(Error::Space),
(z, _) if z.is_ascii_control() => return Err(Error::Control),
_ => continue,
}
}
}
if parts < 2 && !opts.allow_onelevel {
Err(Error::OneLevel)
} else if globs > 1 && opts.allow_pattern {
Err(Error::Pattern)
} else if globs > 0 && !opts.allow_pattern {
Err(Error::InvalidChar('*'))
} else {
Ok(())
}
}
}
}
+397
View File
@@ -0,0 +1,397 @@
use std::{
borrow::Cow,
fmt::{self, Display},
ops::Deref,
};
use crate::{
lit, name,
refspec::{PatternStr, QualifiedPattern},
Component, RefStr, RefString,
};
/// A fully-qualified refname.
///
/// A refname is qualified _iff_ it starts with "refs/" and has at least three
/// components. This implies that a [`Qualified`] ref has a category, such as
/// "refs/heads/main".
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Qualified<'a>(pub(crate) Cow<'a, RefStr>);
impl<'a> Qualified<'a> {
/// Infallibly create a [`Qualified`] from components.
///
/// Note that the "refs/" prefix is implicitly added, so `a` is the second
/// [`Component`]. Mirroring [`Self::non_empty_components`], providing
/// two [`Component`]s guarantees well-formedness of the [`Qualified`].
/// `tail` may be empty.
///
/// # Example
///
/// ```no_run
/// use git_ref_format::{component, Qualified};
///
/// assert_eq!(
/// "refs/heads/main",
/// Qualified::from_components(component::HEADS, component::MAIN, None).as_str()
/// )
/// ```
pub fn from_components<'b, 'c, 'd, A, B, C>(a: A, b: B, tail: C) -> Self
where
A: Into<Component<'b>>,
B: Into<Component<'c>>,
C: IntoIterator<Item = Component<'d>>,
{
let mut inner = name::REFS.join(a.into()).and(b.into());
inner.extend(tail);
Self(inner.into())
}
pub fn from_refstr(r: impl Into<Cow<'a, RefStr>>) -> Option<Self> {
Self::_from_refstr(r.into())
}
fn _from_refstr(r: Cow<'a, RefStr>) -> Option<Self> {
let mut iter = r.iter();
match (iter.next()?, iter.next()?, iter.next()?) {
("refs", _, _) => Some(Qualified(r)),
_ => None,
}
}
#[inline]
pub fn as_str(&self) -> &str {
self.as_ref()
}
#[inline]
pub fn join<'b, R>(&self, other: R) -> Qualified<'b>
where
R: AsRef<RefStr>,
{
Qualified(self.0.join(other).into())
}
pub fn to_pattern<P>(&'a self, pattern: P) -> QualifiedPattern<'a>
where
P: AsRef<PatternStr>,
{
QualifiedPattern(Cow::Owned(RefStr::to_pattern(self, pattern.as_ref())))
}
#[inline]
pub fn to_namespaced(&'a self) -> Option<Namespaced<'a>> {
self.0.as_ref().into()
}
/// Add a namespace.
///
/// Creates a new [`Namespaced`] by prefxing `self` with
/// `refs/namespaces/<ns>`.
pub fn with_namespace<'b>(&self, ns: Component<'b>) -> Namespaced<'a> {
Namespaced(Cow::Owned(
IntoIterator::into_iter([lit::Refs.into(), lit::Namespaces.into(), ns])
.chain(self.0.components())
.collect(),
))
}
/// Like [`Self::non_empty_components`], but with string slices.
pub fn non_empty_iter(&'a self) -> (&'a str, &'a str, &'a str, name::Iter<'a>) {
let mut iter = self.iter();
(
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
iter,
)
}
/// Return the first three [`Component`]s, and a possibly empty iterator
/// over the remaining ones.
///
/// A qualified ref is guaranteed to have at least three components, which
/// this method provides a witness of. This is useful eg. for pattern
/// matching on the prefix.
pub fn non_empty_components(
&'a self,
) -> (
Component<'a>,
Component<'a>,
Component<'a>,
name::Components<'a>,
) {
let mut cs = self.components();
(
cs.next().unwrap(),
cs.next().unwrap(),
cs.next().unwrap(),
cs,
)
}
#[inline]
pub fn to_owned<'b>(&self) -> Qualified<'b> {
Qualified(Cow::Owned(self.0.clone().into_owned()))
}
#[inline]
pub fn into_owned<'b>(self) -> Qualified<'b> {
Qualified(Cow::Owned(self.0.into_owned()))
}
#[inline]
pub fn into_refstring(self) -> RefString {
self.into()
}
}
impl Deref for Qualified<'_> {
type Target = RefStr;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<RefStr> for Qualified<'_> {
#[inline]
fn as_ref(&self) -> &RefStr {
self
}
}
impl AsRef<str> for Qualified<'_> {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<Self> for Qualified<'_> {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
impl<'a> From<Qualified<'a>> for Cow<'a, RefStr> {
#[inline]
fn from(q: Qualified<'a>) -> Self {
q.0
}
}
impl From<Qualified<'_>> for RefString {
#[inline]
fn from(q: Qualified) -> Self {
q.0.into_owned()
}
}
impl<T, U> From<(lit::Refs, T, U)> for Qualified<'_>
where
T: AsRef<RefStr>,
U: AsRef<RefStr>,
{
#[inline]
fn from((refs, cat, name): (lit::Refs, T, U)) -> Self {
let refs: &RefStr = refs.into();
Self(Cow::Owned(refs.join(cat).and(name)))
}
}
impl<T> From<lit::RefsHeads<T>> for Qualified<'_>
where
T: AsRef<RefStr>,
{
#[inline]
fn from((refs, heads, name): lit::RefsHeads<T>) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), heads.into()])
.collect::<RefString>()
.and(name),
))
}
}
impl<T> From<lit::RefsTags<T>> for Qualified<'_>
where
T: AsRef<RefStr>,
{
#[inline]
fn from((refs, tags, name): lit::RefsTags<T>) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), tags.into()])
.collect::<RefString>()
.and(name),
))
}
}
impl<T> From<lit::RefsNotes<T>> for Qualified<'_>
where
T: AsRef<RefStr>,
{
#[inline]
fn from((refs, notes, name): lit::RefsNotes<T>) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), notes.into()])
.collect::<RefString>()
.and(name),
))
}
}
impl<T> From<lit::RefsRemotes<T>> for Qualified<'_>
where
T: AsRef<RefStr>,
{
#[inline]
fn from((refs, remotes, name): lit::RefsRemotes<T>) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), remotes.into()])
.collect::<RefString>()
.and(name),
))
}
}
impl Display for Qualified<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
/// A [`Qualified`] ref under a git namespace.
///
/// A ref is namespaced if it starts with "refs/namespaces/", another path
/// component, and "refs" or "HEAD". Eg.
///
/// refs/namespaces/xyz/refs/heads/main
/// refs/namespaces/xyz/HEAD
///
/// Note that namespaces can be nested, so the result of
/// [`Namespaced::strip_namespace`] may be convertible to a [`Namespaced`]
/// again. For example:
///
/// ```no_run
/// let full = refname!("refs/namespaces/a/refs/namespaces/b/refs/heads/main");
/// let namespaced = full.namespaced().unwrap();
/// let strip_first = namespaced.strip_namespace();
/// let nested = strip_first.namespaced().unwrap();
/// let strip_second = nested.strip_namespace();
///
/// assert_eq!("a", namespaced.namespace().as_str());
/// assert_eq!("b", nested.namespace().as_str());
/// assert_eq!("refs/namespaces/b/refs/heads/main", strip_first.as_str());
/// assert_eq!("refs/heads/main", strip_second.as_str());
/// ```
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Namespaced<'a>(Cow<'a, RefStr>);
impl<'a> Namespaced<'a> {
pub fn namespace(&'a self) -> Component<'a> {
self.components().nth(2).unwrap()
}
pub fn strip_namespace<'b>(&self) -> Qualified<'b> {
const REFS_NAMESPACES: &RefStr = RefStr::from_str("refs/namespaces");
Qualified(Cow::Owned(
self.strip_prefix(REFS_NAMESPACES)
.unwrap()
.components()
.skip(1)
.collect(),
))
}
pub fn strip_namespace_recursive<'b>(&self) -> Qualified<'b> {
let mut strip = self.strip_namespace();
while let Some(ns) = strip.to_namespaced() {
strip = ns.strip_namespace();
}
strip
}
#[inline]
pub fn to_owned<'b>(&self) -> Namespaced<'b> {
Namespaced(Cow::Owned(self.0.clone().into_owned()))
}
#[inline]
pub fn into_owned<'b>(self) -> Namespaced<'b> {
Namespaced(Cow::Owned(self.0.into_owned()))
}
#[inline]
pub fn into_qualified(self) -> Qualified<'a> {
self.into()
}
}
impl Deref for Namespaced<'_> {
type Target = RefStr;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<RefStr> for Namespaced<'_> {
#[inline]
fn as_ref(&self) -> &RefStr {
self
}
}
impl AsRef<str> for Namespaced<'_> {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl<'a> From<Namespaced<'a>> for Qualified<'a> {
#[inline]
fn from(ns: Namespaced<'a>) -> Self {
Self(ns.0)
}
}
impl<'a> From<&'a RefStr> for Option<Namespaced<'a>> {
fn from(rs: &'a RefStr) -> Self {
let mut cs = rs.iter();
match (cs.next()?, cs.next()?, cs.next()?, cs.next()?) {
("refs", "namespaces", _, "refs" | "HEAD") => Some(Namespaced(Cow::from(rs))),
_ => None,
}
}
}
impl<'a, T> From<lit::RefsNamespaces<'_, T>> for Namespaced<'static>
where
T: Into<Component<'a>>,
{
#[inline]
fn from((refs, namespaces, namespace, name): lit::RefsNamespaces<T>) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([refs.into(), namespaces.into(), namespace.into()])
.collect::<RefString>()
.and(name),
))
}
}
impl Display for Namespaced<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
+192
View File
@@ -0,0 +1,192 @@
use crate::{name, Qualified, RefStr};
/// A literal [`RefStr`].
///
/// Types implementing [`Lit`] must be [`name::Component`]s, and provide a
/// conversion from a component _iff_ the component's [`RefStr`] representation
/// is equal to [`Lit::NAME`]. Because these morphisms can only be guaranteed
/// axiomatically, the trait can not currently be implemented by types outside
/// of this crate.
///
/// [`Lit`] types are useful for efficiently creating known-valid [`Qualified`]
/// refs, and sometimes for pattern matching.
pub trait Lit: Sized + sealed::Sealed {
const SELF: Self;
const NAME: &'static RefStr;
#[inline]
fn from_component(c: &name::Component) -> Option<Self> {
(c.as_ref() == Self::NAME).then_some(Self::SELF)
}
}
impl<T: Lit> From<T> for &'static RefStr {
#[inline]
fn from(_: T) -> Self {
T::NAME
}
}
mod sealed {
pub trait Sealed {}
}
/// All known literal [`RefStr`]s.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub enum KnownLit {
Refs,
Heads,
Namespaces,
Remotes,
Tags,
Notes,
}
impl KnownLit {
#[inline]
pub fn from_component(c: &name::Component) -> Option<Self> {
let c: &RefStr = c.as_ref();
if c == Refs::NAME {
Some(Self::Refs)
} else if c == Heads::NAME {
Some(Self::Heads)
} else if c == Namespaces::NAME {
Some(Self::Namespaces)
} else if c == Remotes::NAME {
Some(Self::Remotes)
} else if c == Tags::NAME {
Some(Self::Tags)
} else if c == Notes::NAME {
Some(Self::Notes)
} else {
None
}
}
}
impl From<KnownLit> for name::Component<'_> {
#[inline]
fn from(k: KnownLit) -> Self {
match k {
KnownLit::Refs => Refs.into(),
KnownLit::Heads => Heads.into(),
KnownLit::Namespaces => Namespaces.into(),
KnownLit::Remotes => Remotes.into(),
KnownLit::Tags => Tags.into(),
KnownLit::Notes => Notes.into(),
}
}
}
/// Either a [`KnownLit`] or a [`name::Component`]
pub enum SomeLit<'a> {
Known(KnownLit),
Any(name::Component<'a>),
}
impl SomeLit<'_> {
pub fn known(self) -> Option<KnownLit> {
match self {
Self::Known(k) => Some(k),
_ => None,
}
}
}
impl<'a> From<name::Component<'a>> for SomeLit<'a> {
#[inline]
fn from(c: name::Component<'a>) -> Self {
match KnownLit::from_component(&c) {
Some(k) => Self::Known(k),
None => Self::Any(c),
}
}
}
pub type RefsHeads<T> = (Refs, Heads, T);
pub type RefsTags<T> = (Refs, Tags, T);
pub type RefsNotes<T> = (Refs, Notes, T);
pub type RefsRemotes<T> = (Refs, Remotes, T);
pub type RefsNamespaces<'a, T> = (Refs, Namespaces, T, Qualified<'a>);
#[inline]
pub fn refs_heads<T: AsRef<RefStr>>(name: T) -> RefsHeads<T> {
(Refs, Heads, name)
}
#[inline]
pub fn refs_tags<T: AsRef<RefStr>>(name: T) -> RefsTags<T> {
(Refs, Tags, name)
}
#[inline]
pub fn refs_notes<T: AsRef<RefStr>>(name: T) -> RefsNotes<T> {
(Refs, Notes, name)
}
#[inline]
pub fn refs_remotes<T: AsRef<RefStr>>(name: T) -> RefsRemotes<T> {
(Refs, Remotes, name)
}
#[inline]
pub fn refs_namespaces<'a, 'b, T>(namespace: T, name: Qualified<'b>) -> RefsNamespaces<'b, T>
where
T: Into<name::Component<'a>>,
{
(Refs, Namespaces, namespace, name)
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Refs;
impl Lit for Refs {
const SELF: Self = Self;
const NAME: &'static RefStr = name::REFS;
}
impl sealed::Sealed for Refs {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Heads;
impl Lit for Heads {
const SELF: Self = Self;
const NAME: &'static RefStr = name::HEADS;
}
impl sealed::Sealed for Heads {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Namespaces;
impl Lit for Namespaces {
const SELF: Self = Self;
const NAME: &'static RefStr = name::NAMESPACES;
}
impl sealed::Sealed for Namespaces {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Remotes;
impl Lit for Remotes {
const SELF: Self = Self;
const NAME: &'static RefStr = name::REMOTES;
}
impl sealed::Sealed for Remotes {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Tags;
impl Lit for Tags {
const SELF: Self = Self;
const NAME: &'static RefStr = name::TAGS;
}
impl sealed::Sealed for Tags {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Notes;
impl Lit for Notes {
const SELF: Self = Self;
const NAME: &'static RefStr = name::NOTES;
}
impl sealed::Sealed for Notes {}
+464
View File
@@ -0,0 +1,464 @@
use std::{
borrow::{Borrow, Cow},
convert::TryFrom,
fmt::{self, Display},
iter::{Extend, FromIterator},
ops::Deref,
};
use crate::{
check,
refspec::{PatternStr, PatternString},
Namespaced, Qualified,
};
mod iter;
pub use iter::{component, Component, Components, Iter};
#[cfg(feature = "percent-encoding")]
pub use percent_encoding::PercentEncode;
pub const HEADS: &RefStr = RefStr::from_str(str::HEADS);
pub const MAIN: &RefStr = RefStr::from_str(str::MAIN);
pub const MASTER: &RefStr = RefStr::from_str(str::MASTER);
pub const NAMESPACES: &RefStr = RefStr::from_str(str::NAMESPACES);
pub const NOTES: &RefStr = RefStr::from_str(str::NOTES);
pub const ORIGIN: &RefStr = RefStr::from_str(str::ORIGIN);
pub const REFS: &RefStr = RefStr::from_str(str::REFS);
pub const REMOTES: &RefStr = RefStr::from_str(str::REMOTES);
pub const TAGS: &RefStr = RefStr::from_str(str::TAGS);
pub const REFS_HEADS_MAIN: &RefStr = RefStr::from_str(str::REFS_HEADS_MAIN);
pub const REFS_HEADS_MASTER: &RefStr = RefStr::from_str(str::REFS_HEADS_MASTER);
pub mod str {
pub const HEADS: &str = "heads";
pub const MAIN: &str = "main";
pub const MASTER: &str = "master";
pub const NAMESPACES: &str = "namespaces";
pub const NOTES: &str = "notes";
pub const ORIGIN: &str = "origin";
pub const REFS: &str = "refs";
pub const REMOTES: &str = "remotes";
pub const TAGS: &str = "tags";
pub const REFS_HEADS_MAIN: &str = "refs/heads/main";
pub const REFS_HEADS_MASTER: &str = "refs/heads/master";
}
pub mod bytes {
use super::str;
pub const HEADS: &[u8] = str::HEADS.as_bytes();
pub const MAIN: &[u8] = str::MAIN.as_bytes();
pub const MASTER: &[u8] = str::MASTER.as_bytes();
pub const NAMESPACES: &[u8] = str::NAMESPACES.as_bytes();
pub const NOTES: &[u8] = str::NOTES.as_bytes();
pub const ORIGIN: &[u8] = str::ORIGIN.as_bytes();
pub const REFS: &[u8] = str::REFS.as_bytes();
pub const REMOTES: &[u8] = str::REMOTES.as_bytes();
pub const TAGS: &[u8] = str::TAGS.as_bytes();
pub const REFS_HEADS_MAIN: &[u8] = str::REFS_HEADS_MAIN.as_bytes();
pub const REFS_HEADS_MASTER: &[u8] = str::REFS_HEADS_MASTER.as_bytes();
}
const CHECK_OPTS: check::Options = check::Options {
allow_pattern: false,
allow_onelevel: true,
};
#[repr(transparent)]
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct RefStr(str);
impl RefStr {
pub fn try_from_str(s: &str) -> Result<&RefStr, check::Error> {
TryFrom::try_from(s)
}
#[inline]
pub fn as_str(&self) -> &str {
self
}
#[inline]
pub fn to_ref_string(&self) -> RefString {
self.to_owned()
}
pub fn strip_prefix<P>(&self, base: P) -> Option<&RefStr>
where
P: AsRef<RefStr>,
{
self._strip_prefix(base.as_ref())
}
fn _strip_prefix(&self, base: &RefStr) -> Option<&RefStr> {
self.0
.strip_prefix(base.as_str())
.and_then(|s| s.strip_prefix('/'))
.map(Self::from_str)
}
/// Join `other` onto `self`, yielding a new [`RefString`].
///
/// Consider to use [`RefString::and`] when chaining multiple fragments
/// together, and the intermediate values are not needed.
pub fn join<R>(&self, other: R) -> RefString
where
R: AsRef<RefStr>,
{
self._join(other.as_ref())
}
fn _join(&self, other: &RefStr) -> RefString {
let mut buf = self.to_ref_string();
buf.push(other);
buf
}
pub fn to_pattern<P>(&self, pattern: P) -> PatternString
where
P: AsRef<PatternStr>,
{
self._to_pattern(pattern.as_ref())
}
fn _to_pattern(&self, pattern: &PatternStr) -> PatternString {
self.to_owned().with_pattern(pattern)
}
#[inline]
pub fn qualified<'a>(&'a self) -> Option<Qualified<'a>> {
Qualified::from_refstr(self)
}
#[inline]
pub fn to_namespaced<'a>(&'a self) -> Option<Namespaced<'a>> {
self.into()
}
pub fn iter<'a>(&'a self) -> Iter<'a> {
self.0.split('/')
}
pub fn components<'a>(&'a self) -> Components<'a> {
Components::from(self)
}
pub fn head<'a>(&'a self) -> Component<'a> {
self.components().next().expect("`RefStr` cannot be empty")
}
#[cfg(feature = "percent-encoding")]
pub fn percent_encode(&self) -> PercentEncode {
/// https://url.spec.whatwg.org/#fragment-percent-encode-set
const FRAGMENT_PERCENT_ENCODE_SET: &percent_encoding::AsciiSet =
&percent_encoding::CONTROLS
.add(b' ')
.add(b'"')
.add(b'<')
.add(b'>')
.add(b'`');
/// https://url.spec.whatwg.org/#path-percent-encode-set
const PATH_PERCENT_ENCODE_SET: &percent_encoding::AsciiSet = &FRAGMENT_PERCENT_ENCODE_SET
.add(b'#')
.add(b'?')
.add(b'{')
.add(b'}');
percent_encoding::utf8_percent_encode(self.as_str(), PATH_PERCENT_ENCODE_SET)
}
#[cfg(feature = "bstr")]
#[inline]
pub fn as_bstr(&self) -> &bstr::BStr {
self.as_ref()
}
pub(crate) const fn from_str(s: &str) -> &RefStr {
unsafe { &*(s as *const str as *const RefStr) }
}
}
impl Deref for RefStr {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<str> for RefStr {
#[inline]
fn as_ref(&self) -> &str {
self
}
}
#[cfg(feature = "bstr")]
impl AsRef<bstr::BStr> for RefStr {
#[inline]
fn as_ref(&self) -> &bstr::BStr {
use bstr::ByteSlice as _;
self.as_str().as_bytes().as_bstr()
}
}
impl AsRef<RefStr> for &RefStr {
#[inline]
fn as_ref(&self) -> &RefStr {
self
}
}
impl<'a> TryFrom<&'a str> for &'a RefStr {
type Error = check::Error;
#[inline]
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
check::ref_format(CHECK_OPTS, s).map(|()| RefStr::from_str(s))
}
}
impl<'a> From<&'a RefStr> for Cow<'a, RefStr> {
#[inline]
fn from(rs: &'a RefStr) -> Cow<'a, RefStr> {
Cow::Borrowed(rs)
}
}
impl Display for RefStr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self)
}
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct RefString(String);
impl RefString {
#[inline]
pub fn as_refstr(&self) -> &RefStr {
self
}
/// Join `other` onto `self` in place.
///
/// This is a consuming version of [`RefString::push`] which can be chained.
/// Prefer this over chaining calls to [`RefStr::join`] if the
/// intermediate values are not needed.
pub fn and<R>(self, other: R) -> Self
where
R: AsRef<RefStr>,
{
self._and(other.as_ref())
}
fn _and(mut self, other: &RefStr) -> Self {
self.push(other);
self
}
pub fn push<R>(&mut self, other: R)
where
R: AsRef<RefStr>,
{
self.0.push('/');
self.0.push_str(other.as_ref().as_str());
}
#[inline]
pub fn pop(&mut self) -> bool {
match self.0.rfind('/') {
None => false,
Some(idx) => {
self.0.truncate(idx);
true
}
}
}
/// Append a [`PatternStr`], turning self into a new [`PatternString`].
pub fn with_pattern<P>(self, pattern: P) -> PatternString
where
P: AsRef<PatternStr>,
{
self._with_pattern(pattern.as_ref())
}
fn _with_pattern(self, pattern: &PatternStr) -> PatternString {
let mut buf = self.0;
buf.push('/');
buf.push_str(pattern.as_str());
PatternString(buf)
}
#[inline]
pub fn into_qualified<'a>(self) -> Option<Qualified<'a>> {
Qualified::from_refstr(self)
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
self.0.reserve(additional)
}
#[inline]
pub fn shrink_to_fit(&mut self) {
self.0.shrink_to_fit()
}
#[cfg(feature = "bstr")]
#[inline]
pub fn into_bstring(self) -> bstr::BString {
self.into()
}
#[cfg(feature = "bstr")]
#[inline]
pub fn as_bstr(&self) -> &bstr::BStr {
self.as_ref()
}
}
impl Deref for RefString {
type Target = RefStr;
#[inline]
fn deref(&self) -> &Self::Target {
self.borrow()
}
}
impl AsRef<RefStr> for RefString {
#[inline]
fn as_ref(&self) -> &RefStr {
self
}
}
impl AsRef<str> for RefString {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
#[cfg(feature = "bstr")]
impl AsRef<bstr::BStr> for RefString {
#[inline]
fn as_ref(&self) -> &bstr::BStr {
use bstr::ByteSlice as _;
self.as_str().as_bytes().as_bstr()
}
}
impl Borrow<RefStr> for RefString {
#[inline]
fn borrow(&self) -> &RefStr {
RefStr::from_str(self.0.as_str())
}
}
impl ToOwned for RefStr {
type Owned = RefString;
#[inline]
fn to_owned(&self) -> Self::Owned {
RefString(self.0.to_owned())
}
}
impl TryFrom<&str> for RefString {
type Error = check::Error;
#[inline]
fn try_from(s: &str) -> Result<Self, Self::Error> {
RefStr::try_from_str(s).map(ToOwned::to_owned)
}
}
impl TryFrom<String> for RefString {
type Error = check::Error;
#[inline]
fn try_from(s: String) -> Result<Self, Self::Error> {
check::ref_format(CHECK_OPTS, s.as_str()).map(|()| RefString(s))
}
}
impl<'a> From<&'a RefString> for Cow<'a, RefStr> {
#[inline]
fn from(rs: &'a RefString) -> Cow<'a, RefStr> {
Cow::Borrowed(rs.as_refstr())
}
}
impl<'a> From<RefString> for Cow<'a, RefStr> {
#[inline]
fn from(rs: RefString) -> Cow<'a, RefStr> {
Cow::Owned(rs)
}
}
impl From<RefString> for String {
#[inline]
fn from(rs: RefString) -> Self {
rs.0
}
}
#[cfg(feature = "bstr")]
impl From<RefString> for bstr::BString {
#[inline]
fn from(rs: RefString) -> Self {
bstr::BString::from(rs.0.into_bytes())
}
}
impl Display for RefString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
impl<A> FromIterator<A> for RefString
where
A: AsRef<RefStr>,
{
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = A>,
{
let mut buf = String::new();
for c in iter {
buf.push_str(c.as_ref().as_str());
buf.push('/');
}
assert!(!buf.is_empty(), "empty iterator");
buf.truncate(buf.len() - 1);
Self(buf)
}
}
impl<A> Extend<A> for RefString
where
A: AsRef<RefStr>,
{
fn extend<T>(&mut self, iter: T)
where
T: IntoIterator<Item = A>,
{
for x in iter {
self.push(x)
}
}
}
@@ -0,0 +1,155 @@
use std::{
borrow::Cow,
fmt::{self, Display},
ops::Deref,
};
use super::RefStr;
use crate::lit;
pub type Iter<'a> = std::str::Split<'a, char>;
/// Iterator created by the [`RefStr::iter`] method.
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[derive(Clone)]
pub struct Components<'a> {
inner: std::str::Split<'a, char>,
}
impl<'a> Iterator for Components<'a> {
type Item = Component<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
.map(RefStr::from_str)
.map(Cow::from)
.map(Component)
}
}
impl DoubleEndedIterator for Components<'_> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.inner
.next_back()
.map(RefStr::from_str)
.map(Cow::from)
.map(Component)
}
}
impl<'a> From<&'a RefStr> for Components<'a> {
#[inline]
fn from(rs: &'a RefStr) -> Self {
Self {
inner: rs.as_str().split('/'),
}
}
}
/// A path component of a [`RefStr`].
///
/// A [`Component`] is a valid [`RefStr`] which does not contain any '/'
/// separators.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Component<'a>(Cow<'a, RefStr>);
impl<'a> Component<'a> {
#[inline]
pub fn from_refstr(r: impl Into<Cow<'a, RefStr>>) -> Option<Self> {
let r = r.into();
if !r.contains('/') {
Some(Self(r))
} else {
None
}
}
#[inline]
pub fn as_lit<T: lit::Lit>(&self) -> Option<T> {
T::from_component(self)
}
#[inline]
pub fn into_inner(self) -> Cow<'a, RefStr> {
self.0
}
}
impl Deref for Component<'_> {
type Target = RefStr;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<RefStr> for Component<'_> {
#[inline]
fn as_ref(&self) -> &RefStr {
self
}
}
impl<'a> From<&'a RefStr> for Option<Component<'a>> {
#[inline]
fn from(r: &'a RefStr) -> Self {
if !r.contains('/') {
Some(Component(Cow::from(r)))
} else {
None
}
}
}
impl<'a> From<Component<'a>> for Cow<'a, RefStr> {
#[inline]
fn from(c: Component<'a>) -> Self {
c.0
}
}
impl<T: lit::Lit> From<T> for Component<'static> {
#[inline]
fn from(_: T) -> Self {
Component(Cow::from(T::NAME))
}
}
impl<'a> From<lit::SomeLit<'a>> for Component<'a> {
#[inline]
fn from(s: lit::SomeLit<'a>) -> Self {
use lit::SomeLit::*;
match s {
Known(k) => k.into(),
Any(c) => c,
}
}
}
impl Display for Component<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.0.as_str())
}
}
pub mod component {
use super::Component;
use crate::name;
use std::borrow::Cow;
pub const HEADS: Component = Component(Cow::Borrowed(name::HEADS));
pub const MAIN: Component = Component(Cow::Borrowed(name::MAIN));
pub const MASTER: Component = Component(Cow::Borrowed(name::MASTER));
pub const NAMESPACES: Component = Component(Cow::Borrowed(name::NAMESPACES));
pub const NOTES: Component = Component(Cow::Borrowed(name::NOTES));
pub const ORIGIN: Component = Component(Cow::Borrowed(name::ORIGIN));
pub const REFS: Component = Component(Cow::Borrowed(name::REFS));
pub const REMOTES: Component = Component(Cow::Borrowed(name::REMOTES));
pub const TAGS: Component = Component(Cow::Borrowed(name::TAGS));
}
@@ -0,0 +1,605 @@
use std::{
borrow::{Borrow, Cow},
convert::TryFrom,
fmt::{self, Display, Write as _},
iter::FromIterator,
ops::Deref,
};
use thiserror::Error;
use crate::{check, lit, Namespaced, Qualified, RefStr, RefString};
mod iter;
pub use iter::{component, Component, Components, Iter};
pub const STAR: &PatternStr = PatternStr::from_str("*");
const CHECK_OPTS: check::Options = check::Options {
allow_onelevel: true,
allow_pattern: true,
};
#[repr(transparent)]
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct PatternStr(str);
impl PatternStr {
#[inline]
pub fn try_from_str(s: &str) -> Result<&Self, check::Error> {
TryFrom::try_from(s)
}
#[inline]
pub fn as_str(&self) -> &str {
self
}
pub fn join<R>(&self, other: R) -> PatternString
where
R: AsRef<RefStr>,
{
self._join(other.as_ref())
}
fn _join(&self, other: &RefStr) -> PatternString {
let mut buf = self.to_owned();
buf.push(other);
buf
}
#[inline]
pub fn qualified<'a>(&'a self) -> Option<QualifiedPattern<'a>> {
QualifiedPattern::from_patternstr(self)
}
#[inline]
pub fn to_namespaced<'a>(&'a self) -> Option<NamespacedPattern<'a>> {
self.into()
}
#[inline]
pub fn iter<'a>(&'a self) -> Iter<'a> {
self.0.split('/')
}
#[inline]
pub fn components<'a>(&'a self) -> Components<'a> {
Components::from(self)
}
pub(crate) const fn from_str(s: &str) -> &PatternStr {
unsafe { &*(s as *const str as *const PatternStr) }
}
}
impl Deref for PatternStr {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<str> for PatternStr {
#[inline]
fn as_ref(&self) -> &str {
self
}
}
impl AsRef<Self> for PatternStr {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
impl<'a> TryFrom<&'a str> for &'a PatternStr {
type Error = check::Error;
#[inline]
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
check::ref_format(CHECK_OPTS, s).map(|()| PatternStr::from_str(s))
}
}
impl<'a> From<&'a RefStr> for &'a PatternStr {
#[inline]
fn from(rs: &'a RefStr) -> Self {
PatternStr::from_str(rs.as_str())
}
}
impl<'a> From<&'a PatternStr> for Cow<'a, PatternStr> {
#[inline]
fn from(p: &'a PatternStr) -> Cow<'a, PatternStr> {
Cow::Borrowed(p)
}
}
impl Display for PatternStr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self)
}
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct PatternString(pub(crate) String);
impl PatternString {
#[inline]
pub fn as_str(&self) -> &str {
self.as_ref()
}
#[inline]
pub fn as_pattern_str(&self) -> &PatternStr {
self.as_ref()
}
#[inline]
pub fn from_components<'a, T>(iter: T) -> Result<Self, DuplicateGlob>
where
T: IntoIterator<Item = Component<'a>>,
{
iter.into_iter().collect()
}
#[inline]
pub fn and<R>(mut self, other: R) -> Self
where
R: AsRef<RefStr>,
{
self._push(other.as_ref());
self
}
#[inline]
pub fn push<R>(&mut self, other: R)
where
R: AsRef<RefStr>,
{
self._push(other.as_ref())
}
fn _push(&mut self, other: &RefStr) {
self.0.push('/');
self.0.push_str(other.as_str());
}
#[inline]
pub fn pop(&mut self) -> bool {
match self.0.rfind('/') {
None => false,
Some(idx) => {
self.0.truncate(idx);
true
}
}
}
}
impl Deref for PatternString {
type Target = PatternStr;
#[inline]
fn deref(&self) -> &Self::Target {
self.borrow()
}
}
impl AsRef<PatternStr> for PatternString {
#[inline]
fn as_ref(&self) -> &PatternStr {
self
}
}
impl AsRef<str> for PatternString {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl Borrow<PatternStr> for PatternString {
#[inline]
fn borrow(&self) -> &PatternStr {
PatternStr::from_str(self.0.as_str())
}
}
impl ToOwned for PatternStr {
type Owned = PatternString;
#[inline]
fn to_owned(&self) -> Self::Owned {
PatternString(self.0.to_owned())
}
}
impl From<RefString> for PatternString {
#[inline]
fn from(rs: RefString) -> Self {
Self(rs.into())
}
}
impl<'a> From<&'a PatternString> for Cow<'a, PatternStr> {
#[inline]
fn from(p: &'a PatternString) -> Cow<'a, PatternStr> {
Cow::Borrowed(p.as_ref())
}
}
impl From<PatternString> for String {
#[inline]
fn from(p: PatternString) -> Self {
p.0
}
}
impl TryFrom<&str> for PatternString {
type Error = check::Error;
#[inline]
fn try_from(s: &str) -> Result<Self, Self::Error> {
PatternStr::try_from_str(s).map(ToOwned::to_owned)
}
}
impl TryFrom<String> for PatternString {
type Error = check::Error;
#[inline]
fn try_from(s: String) -> Result<Self, Self::Error> {
check::ref_format(CHECK_OPTS, s.as_str()).map(|()| PatternString(s))
}
}
#[derive(Debug, Error)]
#[error("more than one '*' encountered")]
pub struct DuplicateGlob;
impl<'a> FromIterator<Component<'a>> for Result<PatternString, DuplicateGlob> {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = Component<'a>>,
{
use Component::*;
let mut buf = String::new();
let mut seen_glob = false;
for c in iter {
if let Glob(_) = c {
if seen_glob {
return Err(DuplicateGlob);
}
seen_glob = true;
}
buf.push_str(c.as_str());
buf.push('/');
}
buf.truncate(buf.len() - 1);
Ok(PatternString(buf))
}
}
impl Display for PatternString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
/// A fully-qualified refspec.
///
/// A refspec is qualified _iff_ it starts with "refs/" and has at least three
/// components. This implies that a [`QualifiedPattern`] ref has a category,
/// such as "refs/heads/*".
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct QualifiedPattern<'a>(pub(crate) Cow<'a, PatternStr>);
impl<'a> QualifiedPattern<'a> {
pub fn from_patternstr(r: impl Into<Cow<'a, PatternStr>>) -> Option<Self> {
Self::_from_patternstr(r.into())
}
fn _from_patternstr(r: Cow<'a, PatternStr>) -> Option<Self> {
let mut iter = r.iter();
match (iter.next()?, iter.next()?, iter.next()?) {
("refs", _, _) => Some(QualifiedPattern(r)),
_ => None,
}
}
#[inline]
pub fn as_str(&self) -> &str {
self.as_ref()
}
#[inline]
pub fn join<'b, R>(&self, other: R) -> QualifiedPattern<'b>
where
R: AsRef<RefStr>,
{
QualifiedPattern(Cow::Owned(self.0.join(other)))
}
#[inline]
pub fn to_namespaced(&'a self) -> Option<NamespacedPattern<'a>> {
self.0.as_ref().into()
}
/// Add a namespace.
///
/// Creates a new [`NamespacedPattern`] by prefxing `self` with
/// `refs/namespaces/<ns>`.
pub fn with_namespace<'b>(
&self,
ns: Component<'b>,
) -> Result<NamespacedPattern<'a>, DuplicateGlob> {
PatternString::from_components(
IntoIterator::into_iter([lit::Refs.into(), lit::Namespaces.into(), ns])
.chain(self.components()),
)
.map(|pat| NamespacedPattern(Cow::Owned(pat)))
}
/// Like [`Self::non_empty_components`], but with string slices.
pub fn non_empty_iter(&'a self) -> (&'a str, &'a str, &'a str, Iter<'a>) {
let mut iter = self.iter();
(
iter.next().unwrap(),
iter.next().unwrap(),
iter.next().unwrap(),
iter,
)
}
/// Return the first three [`Component`]s, and a possibly empty iterator
/// over the remaining ones.
///
/// A qualified ref is guaranteed to have at least three components, which
/// this method provides a witness of. This is useful eg. for pattern
/// matching on the prefix.
pub fn non_empty_components(
&'a self,
) -> (Component<'a>, Component<'a>, Component<'a>, Components<'a>) {
let mut cs = self.components();
(
cs.next().unwrap(),
cs.next().unwrap(),
cs.next().unwrap(),
cs,
)
}
#[inline]
pub fn to_owned<'b>(&self) -> QualifiedPattern<'b> {
QualifiedPattern(Cow::Owned(self.0.clone().into_owned()))
}
#[inline]
pub fn into_owned<'b>(self) -> QualifiedPattern<'b> {
QualifiedPattern(Cow::Owned(self.0.into_owned()))
}
#[inline]
pub fn into_patternstring(self) -> PatternString {
self.into()
}
}
impl Deref for QualifiedPattern<'_> {
type Target = PatternStr;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<PatternStr> for QualifiedPattern<'_> {
#[inline]
fn as_ref(&self) -> &PatternStr {
self
}
}
impl AsRef<str> for QualifiedPattern<'_> {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<Self> for QualifiedPattern<'_> {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
impl Display for QualifiedPattern<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'a> From<Qualified<'a>> for QualifiedPattern<'a> {
#[inline]
fn from(q: Qualified<'a>) -> Self {
Self(Cow::Owned(q.into_refstring().into()))
}
}
impl<'a> From<QualifiedPattern<'a>> for Cow<'a, PatternStr> {
#[inline]
fn from(q: QualifiedPattern<'a>) -> Self {
q.0
}
}
impl From<QualifiedPattern<'_>> for PatternString {
#[inline]
fn from(q: QualifiedPattern) -> Self {
q.0.into_owned()
}
}
/// A [`PatternString`] ref under a git namespace.
///
/// A ref is namespaced if it starts with "refs/namespaces/", another path
/// component, and "refs/". Eg.
///
/// refs/namespaces/xyz/refs/heads/main
///
/// Note that namespaces can be nested, so the result of
/// [`NamespacedPattern::strip_namespace`] may be convertible to a
/// [`NamespacedPattern`] again. For example:
///
/// ```no_run
/// let full = pattern!("refs/namespaces/a/refs/namespaces/b/refs/heads/*");
/// let namespaced = full.to_namespaced().unwrap();
/// let strip_first = namespaced.strip_namespace();
/// let nested = strip_first.namespaced().unwrap();
/// let strip_second = nested.strip_namespace();
///
/// assert_eq!("a", namespaced.namespace().as_str());
/// assert_eq!("b", nested.namespace().as_str());
/// assert_eq!("refs/namespaces/b/refs/heads/*", strip_first.as_str());
/// assert_eq!("refs/heads/*", strip_second.as_str());
/// ```
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct NamespacedPattern<'a>(Cow<'a, PatternStr>);
impl<'a> NamespacedPattern<'a> {
pub fn namespace(&'a self) -> Component<'a> {
self.components().nth(2).unwrap()
}
pub fn strip_namespace(&self) -> PatternString {
PatternString::from_components(self.components().skip(3))
.expect("BUG: NamespacedPattern was constructed with a duplicate glob")
}
pub fn strip_namespace_recursive(&self) -> PatternString {
let mut strip = self.strip_namespace();
while let Some(ns) = strip.to_namespaced() {
strip = ns.strip_namespace();
}
strip
}
#[inline]
pub fn to_owned<'b>(&self) -> NamespacedPattern<'b> {
NamespacedPattern(Cow::Owned(self.0.clone().into_owned()))
}
#[inline]
pub fn into_owned<'b>(self) -> NamespacedPattern<'b> {
NamespacedPattern(Cow::Owned(self.0.into_owned()))
}
#[inline]
pub fn into_qualified(self) -> QualifiedPattern<'a> {
self.into()
}
}
impl Deref for NamespacedPattern<'_> {
type Target = PatternStr;
#[inline]
fn deref(&self) -> &Self::Target {
self.borrow()
}
}
impl AsRef<PatternStr> for NamespacedPattern<'_> {
#[inline]
fn as_ref(&self) -> &PatternStr {
self.0.as_ref()
}
}
impl AsRef<str> for NamespacedPattern<'_> {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl Borrow<PatternStr> for NamespacedPattern<'_> {
#[inline]
fn borrow(&self) -> &PatternStr {
PatternStr::from_str(self.0.as_str())
}
}
impl<'a> From<Namespaced<'a>> for NamespacedPattern<'a> {
#[inline]
fn from(ns: Namespaced<'a>) -> Self {
NamespacedPattern(Cow::Owned(ns.to_ref_string().into()))
}
}
impl<'a> From<NamespacedPattern<'a>> for QualifiedPattern<'a> {
#[inline]
fn from(ns: NamespacedPattern<'a>) -> Self {
Self(ns.0)
}
}
impl<'a> From<&'a PatternStr> for Option<NamespacedPattern<'a>> {
fn from(rs: &'a PatternStr) -> Self {
let mut cs = rs.iter();
match (cs.next()?, cs.next()?, cs.next()?, cs.next()?) {
("refs", "namespaces", _, "refs") => Some(NamespacedPattern(Cow::from(rs))),
_ => None,
}
}
}
impl Display for NamespacedPattern<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
/// A Git [refspec].
///
/// **Note** that this is a simplified version of a [refspec] where
/// the `src` and `dst` are required and there is no way to construct
/// a negative refspec, e.g. `^refs/heads/no-thanks`.
///
/// [refspec]: https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Refspec<T, U> {
pub src: T,
pub dst: U,
pub force: bool,
}
impl<T, U> fmt::Display for Refspec<T, U>
where
T: fmt::Display,
U: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.force {
f.write_char('+')?;
}
write!(f, "{}:{}", self.src, self.dst)
}
}
@@ -0,0 +1,98 @@
use std::fmt::{self, Display};
use super::PatternStr;
use crate::{lit, RefStr};
pub type Iter<'a> = std::str::Split<'a, char>;
pub enum Component<'a> {
Glob(Option<&'a PatternStr>),
Normal(&'a RefStr),
}
impl Component<'_> {
#[inline]
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
impl AsRef<str> for Component<'_> {
#[inline]
fn as_ref(&self) -> &str {
match self {
Self::Glob(None) => "*",
Self::Glob(Some(x)) => x.as_str(),
Self::Normal(x) => x.as_str(),
}
}
}
impl Display for Component<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<T: lit::Lit> From<T> for Component<'static> {
#[inline]
fn from(_: T) -> Self {
Self::Normal(T::NAME)
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[derive(Clone)]
pub struct Components<'a> {
inner: Iter<'a>,
}
impl<'a> Iterator for Components<'a> {
type Item = Component<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|next| match next {
"*" => Component::Glob(None),
x if x.contains('*') => Component::Glob(Some(PatternStr::from_str(x))),
x => Component::Normal(RefStr::from_str(x)),
})
}
}
impl DoubleEndedIterator for Components<'_> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.inner.next_back().map(|next| match next {
"*" => Component::Glob(None),
x if x.contains('*') => Component::Glob(Some(PatternStr::from_str(x))),
x => Component::Normal(RefStr::from_str(x)),
})
}
}
impl<'a> From<&'a PatternStr> for Components<'a> {
#[inline]
fn from(p: &'a PatternStr) -> Self {
Self {
inner: p.as_str().split('/'),
}
}
}
pub mod component {
use super::Component;
use crate::name;
pub const STAR: Component = Component::Glob(None);
pub const HEADS: Component = Component::Normal(name::HEADS);
pub const MAIN: Component = Component::Normal(name::MAIN);
pub const MASTER: Component = Component::Normal(name::MASTER);
pub const NAMESPACES: Component = Component::Normal(name::NAMESPACES);
pub const NOTES: Component = Component::Normal(name::NOTES);
pub const ORIGIN: Component = Component::Normal(name::ORIGIN);
pub const REFS: Component = Component::Normal(name::REFS);
pub const REMOTES: Component = Component::Normal(name::REMOTES);
pub const TAGS: Component = Component::Normal(name::TAGS);
}
+190
View File
@@ -0,0 +1,190 @@
use std::convert::TryFrom;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use crate::{
refspec::{NamespacedPattern, PatternStr, PatternString, QualifiedPattern},
Namespaced, Qualified, RefStr, RefString,
};
impl<'de: 'a, 'a> Deserialize<'de> for &'a RefStr {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer)
.and_then(|s: &str| Self::try_from(s).map_err(de::Error::custom))
}
}
impl Serialize for &RefStr {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for RefString {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer)
.and_then(|x: String| Self::try_from(x).map_err(de::Error::custom))
}
}
impl Serialize for RefString {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de: 'a, 'a> Deserialize<'de> for &'a PatternStr {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer)
.and_then(|s: &str| Self::try_from(s).map_err(de::Error::custom))
}
}
impl Serialize for &PatternStr {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for PatternString {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer)
.and_then(|x: String| Self::try_from(x).map_err(de::Error::custom))
}
}
impl Serialize for PatternString {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for Qualified<'static> {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer).and_then(|s: String| {
let s = RefString::try_from(s).map_err(de::Error::custom)?;
s.qualified()
.ok_or_else(|| de::Error::custom("not a qualified ref"))
.map(|q| q.into_owned())
})
}
}
impl Serialize for Qualified<'_> {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for Namespaced<'static> {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer).and_then(|s: String| {
let s = RefString::try_from(s).map_err(de::Error::custom)?;
s.to_namespaced()
.ok_or_else(|| de::Error::custom("not a namespaced ref"))
.map(|ns| ns.into_owned())
})
}
}
impl Serialize for Namespaced<'_> {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for QualifiedPattern<'_> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer).and_then(|s: String| {
let s = PatternString::try_from(s).map_err(de::Error::custom)?;
s.qualified()
.ok_or_else(|| de::Error::custom("not a qualified ref"))
.map(|q| q.into_owned())
})
}
}
impl Serialize for QualifiedPattern<'_> {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for NamespacedPattern<'_> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer).and_then(|s: String| {
let s = PatternString::try_from(s).map_err(de::Error::custom)?;
s.to_namespaced()
.ok_or_else(|| de::Error::custom("not a qualified ref"))
.map(|q| q.into_owned())
})
}
}
impl Serialize for NamespacedPattern<'_> {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}