use std::{convert, convert::Infallible, ffi::OsStr, path::Path}; use git_object::bstr::{BStr, BString, ByteSlice, ByteVec}; use crate::{Category, FullName, FullNameRef, PartialName, PartialNameRef}; /// The error used in the [`PartialNameRef`][super::PartialNameRef]::try_from(…) implementations.pub type Error = git_validate::reference::name::Error; impl<'a> Category<'a> { /// Return the prefix that would contain all references of our kind, or an empty string if the reference would /// be directly inside of the [`git_dir()`][crate::file::Store::git_dir()]. pub fn prefix(&self) -> &BStr { match self { Category::Tag => b"refs/tags/".as_bstr(), Category::LocalBranch => b"refs/heads/".as_bstr(), Category::RemoteBranch => b"refs/remotes/".as_bstr(), Category::Note => b"refs/notes/".as_bstr(), Category::MainPseudoRef => b"main-worktree/".as_bstr(), Category::MainRef => b"main-worktree/refs/".as_bstr(), Category::PseudoRef => b"".as_bstr(), Category::LinkedPseudoRef { .. } => b"worktrees/".as_bstr(), Category::LinkedRef { .. } => b"worktrees/".as_bstr(), Category::Bisect => b"refs/bisect/".as_bstr(), Category::Rewritten => b"refs/rewritten/".as_bstr(), Category::WorktreePrivate => b"refs/worktree/".as_bstr(), } } /// Returns true if the category is private to their worktrees, and never shared with other worktrees. pub fn is_worktree_private(&self) -> bool { matches!( self, Category::MainPseudoRef | Category::PseudoRef | Category::LinkedPseudoRef { .. } | Category::WorktreePrivate | Category::Rewritten | Category::Bisect ) }} impl FullNameRef { pub(crate) fn new_unchecked(v: &BStr) -> &Self { // SAFETY: FullNameRef is transparent and equivalent to a &BStr if provided as reference #[allow(unsafe_code)] unsafe { std::mem::transmute(v) } }} impl PartialNameRef { pub(crate) fn new_unchecked(v: &BStr) -> &Self { // SAFETY: PartialNameRef is transparent and equivalent to a &BStr if provided as reference #[allow(unsafe_code)] unsafe { std::mem::transmute(v) } }} impl PartialNameRef { pub(crate) fn looks_like_full_name(&self) -> bool { let name = self.0.as_bstr(); name.starts_with_str("refs/") || name.starts_with(Category::MainPseudoRef.prefix()) || name.starts_with(Category::LinkedPseudoRef { name: "".into() }.prefix()) || is_pseudo_ref(name) } pub(crate) fn construct_full_name_ref<'buf>( &self, add_refs_prefix: bool, inbetween: &str, buf: &'buf mut BString, ) -> &'buf FullNameRef { buf.clear(); if add_refs_prefix && !self.looks_like_full_name() { buf.push_str("refs/"); } if !inbetween.is_empty() { buf.push_str(inbetween); buf.push_byte(b'/'); } buf.extend_from_slice(&self.0); FullNameRef::new_unchecked(buf.as_bstr()) }} impl PartialNameRef { /// Convert this name into the relative path possibly identifying the reference location. /// Note that it may be only a partial path though. pub fn to_partial_path(&self) -> &Path { git_path::from_byte_slice(self.0.as_bstr()) } /// Provide the name as binary string which is known to be a valid partial ref name. pub fn as_bstr(&self) -> &BStr { &self.0 }} impl PartialName { /// Append the `component` to ourselves and validate the newly created partial path. pub fn join(self, component: impl AsRef<[u8]>) -> Result<Self, Error> { let mut b = self.0; b.push_byte(b'/'); b.extend(component.as_ref()); git_validate::reference::name_partial(b.as_ref())?; Ok(PartialName(b)) }} impl<'a> convert::TryFrom<&'a BStr> for &'a FullNameRef { type Error = Error; fn try_from(v: &'a BStr) -> Result<Self, Self::Error> { Ok(FullNameRef::new_unchecked(git_validate::reference::name(v)?)) }} impl<'a> From<&'a FullNameRef> for &'a PartialNameRef { fn from(v: &'a FullNameRef) -> Self { PartialNameRef::new_unchecked(v.0.as_bstr()) }} impl<'a> convert::TryFrom<&'a OsStr> for &'a PartialNameRef { type Error = Error; fn try_from(v: &'a OsStr) -> Result<Self, Self::Error> { let v = git_path::os_str_into_bstr(v).map_err(|_| { Error::Tag(git_validate::tag::name::Error::InvalidByte { byte: "<unknown encoding>".into(), }) })?; Ok(PartialNameRef::new_unchecked(git_validate::reference::name_partial( v.as_bstr(), )?)) }} mod impls { use std::borrow::Borrow; use crate::{bstr::ByteSlice, PartialName, PartialNameRef}; impl Borrow<PartialNameRef> for PartialName { #[inline] fn borrow(&self) -> &PartialNameRef { PartialNameRef::new_unchecked(self.0.as_bstr()) } } impl AsRef<PartialNameRef> for PartialName { fn as_ref(&self) -> &PartialNameRef { self.borrow() } } impl ToOwned for PartialNameRef { type Owned = PartialName; fn to_owned(&self) -> Self::Owned { PartialName(self.0.to_owned()) } }} impl<'a> convert::TryFrom<&'a BString> for &'a PartialNameRef { type Error = Error; fn try_from(v: &'a BString) -> Result<Self, Self::Error> { Ok(PartialNameRef::new_unchecked(git_validate::reference::name_partial( v.as_ref(), )?)) }} impl<'a> convert::TryFrom<&'a BStr> for &'a PartialNameRef { type Error = Error; fn try_from(v: &'a BStr) -> Result<Self, Self::Error> { Ok(PartialNameRef::new_unchecked(git_validate::reference::name_partial(v)?)) }} impl<'a> convert::TryFrom<&'a PartialName> for &'a PartialNameRef { type Error = Error; fn try_from(v: &'a PartialName) -> Result<Self, Self::Error> { Ok(PartialNameRef::new_unchecked(v.0.as_bstr())) }} impl<'a> convert::TryFrom<&'a str> for &'a FullNameRef { type Error = Error; fn try_from(v: &'a str) -> Result<Self, Self::Error> { let v = v.as_bytes().as_bstr(); Ok(FullNameRef::new_unchecked(git_validate::reference::name(v)?)) }} impl<'a> convert::TryFrom<&'a str> for &'a PartialNameRef { type Error = Error; fn try_from(v: &'a str) -> Result<Self, Self::Error> { let v = v.as_bytes().as_bstr(); Ok(PartialNameRef::new_unchecked(git_validate::reference::name_partial(v)?)) }} impl<'a> convert::TryFrom<&'a str> for PartialName { type Error = Error; fn try_from(v: &'a str) -> Result<Self, Self::Error> { let v = v.as_bytes().as_bstr(); Ok(PartialName(git_validate::reference::name_partial(v)?.to_owned())) }} impl<'a> convert::TryFrom<&'a FullName> for &'a PartialNameRef { type Error = Infallible; fn try_from(v: &'a FullName) -> Result<Self, Self::Error> { Ok(v.as_ref().as_partial_name()) }} impl<'a> convert::TryFrom<&'a String> for &'a FullNameRef { type Error = Error; fn try_from(v: &'a String) -> Result<Self, Self::Error> { let v = v.as_bytes().as_bstr(); Ok(FullNameRef::new_unchecked(git_validate::reference::name(v)?)) }} impl<'a> convert::TryFrom<&'a String> for &'a PartialNameRef { type Error = Error; fn try_from(v: &'a String) -> Result<Self, Self::Error> { let v = v.as_bytes().as_bstr(); Ok(PartialNameRef::new_unchecked(git_validate::reference::name_partial(v)?)) }} impl convert::TryFrom<String> for PartialName { type Error = Error; fn try_from(v: String) -> Result<Self, Self::Error> { git_validate::reference::name_partial(v.as_bytes().as_bstr())?; Ok(PartialName(v.into())) }} impl convert::TryFrom<BString> for PartialName { type Error = Error; fn try_from(v: BString) -> Result<Self, Self::Error> { git_validate::reference::name_partial(v.as_ref())?; Ok(PartialName(v)) }} /// Note that this method is disagreeing with git_validate as it allows dashes '-' for some reason./// Since partial names cannot be created with dashes inside we adjusted this as it's probably unintended or git creates pseudo-refs/// which wouldn't pass its safety checks.pub(crate) fn is_pseudo_ref<'a>(name: impl Into<&'a BStr>) -> bool { name.into().bytes().all(|b| b.is_ascii_uppercase() || b == b'_')}
use std::{convert, convert::Infallible, ffi::OsStr, path::Path};
use git_object::bstr::{BStr, BString, ByteSlice, ByteVec};
use crate::{Category, FullName, FullNameRef, PartialName, PartialNameRef};
/// The error used in the [`PartialNameRef`][super::PartialNameRef]::try_from(…) implementations.
pub type Error = git_validate::reference::name::Error;
impl<'a> Category<'a> {
/// Return the prefix that would contain all references of our kind, or an empty string if the reference would
/// be directly inside of the [`git_dir()`][crate::file::Store::git_dir()].
pub fn prefix(&self) -> &BStr {
match self {
Category::Tag => b"refs/tags/".as_bstr(),
Category::LocalBranch => b"refs/heads/".as_bstr(),
Category::RemoteBranch => b"refs/remotes/".as_bstr(),
Category::Note => b"refs/notes/".as_bstr(),
Category::MainPseudoRef => b"main-worktree/".as_bstr(),
Category::MainRef => b"main-worktree/refs/".as_bstr(),
Category::PseudoRef => b"".as_bstr(),
Category::LinkedPseudoRef { .. } => b"worktrees/".as_bstr(),
Category::LinkedRef { .. } => b"worktrees/".as_bstr(),
Category::Bisect => b"refs/bisect/".as_bstr(),
Category::Rewritten => b"refs/rewritten/".as_bstr(),
Category::WorktreePrivate => b"refs/worktree/".as_bstr(),
}
}
/// Returns true if the category is private to their worktrees, and never shared with other worktrees.
pub fn is_worktree_private(&self) -> bool {
matches!(
self,
Category::MainPseudoRef
| Category::PseudoRef
| Category::LinkedPseudoRef { .. }
| Category::WorktreePrivate
| Category::Rewritten
| Category::Bisect
)
}
}
impl FullNameRef {
pub(crate) fn new_unchecked(v: &BStr) -> &Self {
// SAFETY: FullNameRef is transparent and equivalent to a &BStr if provided as reference
#[allow(unsafe_code)]
unsafe {
std::mem::transmute(v)
}
}
}
impl PartialNameRef {
pub(crate) fn new_unchecked(v: &BStr) -> &Self {
// SAFETY: PartialNameRef is transparent and equivalent to a &BStr if provided as reference
#[allow(unsafe_code)]
unsafe {
std::mem::transmute(v)
}
}
}
impl PartialNameRef {
pub(crate) fn looks_like_full_name(&self) -> bool {
let name = self.0.as_bstr();
name.starts_with_str("refs/")
|| name.starts_with(Category::MainPseudoRef.prefix())
|| name.starts_with(Category::LinkedPseudoRef { name: "".into() }.prefix())
|| is_pseudo_ref(name)
}
pub(crate) fn construct_full_name_ref<'buf>(
&self,
add_refs_prefix: bool,
inbetween: &str,
buf: &'buf mut BString,
) -> &'buf FullNameRef {
buf.clear();
if add_refs_prefix && !self.looks_like_full_name() {
buf.push_str("refs/");
}
if !inbetween.is_empty() {
buf.push_str(inbetween);
buf.push_byte(b'/');
}
buf.extend_from_slice(&self.0);
FullNameRef::new_unchecked(buf.as_bstr())
}
}
impl PartialNameRef {
/// Convert this name into the relative path possibly identifying the reference location.
/// Note that it may be only a partial path though.
pub fn to_partial_path(&self) -> &Path {
git_path::from_byte_slice(self.0.as_bstr())
}
/// Provide the name as binary string which is known to be a valid partial ref name.
pub fn as_bstr(&self) -> &BStr {
&self.0
}
}
impl PartialName {
/// Append the `component` to ourselves and validate the newly created partial path.
pub fn join(self, component: impl AsRef<[u8]>) -> Result<Self, Error> {
let mut b = self.0;
b.push_byte(b'/');
b.extend(component.as_ref());
git_validate::reference::name_partial(b.as_ref())?;
Ok(PartialName(b))
}
}
impl<'a> convert::TryFrom<&'a BStr> for &'a FullNameRef {
type Error = Error;
fn try_from(v: &'a BStr) -> Result<Self, Self::Error> {
Ok(FullNameRef::new_unchecked(git_validate::reference::name(v)?))
}
}
impl<'a> From<&'a FullNameRef> for &'a PartialNameRef {
fn from(v: &'a FullNameRef) -> Self {
PartialNameRef::new_unchecked(v.0.as_bstr())
}
}
impl<'a> convert::TryFrom<&'a OsStr> for &'a PartialNameRef {
type Error = Error;
fn try_from(v: &'a OsStr) -> Result<Self, Self::Error> {
let v = git_path::os_str_into_bstr(v).map_err(|_| {
Error::Tag(git_validate::tag::name::Error::InvalidByte {
byte: "<unknown encoding>".into(),
})
})?;
Ok(PartialNameRef::new_unchecked(git_validate::reference::name_partial(
v.as_bstr(),
)?))
}
}
mod impls {
use std::borrow::Borrow;
use crate::{bstr::ByteSlice, PartialName, PartialNameRef};
impl Borrow<PartialNameRef> for PartialName {
#[inline]
fn borrow(&self) -> &PartialNameRef {
PartialNameRef::new_unchecked(self.0.as_bstr())
}
}
impl AsRef<PartialNameRef> for PartialName {
fn as_ref(&self) -> &PartialNameRef {
self.borrow()
}
}
impl ToOwned for PartialNameRef {
type Owned = PartialName;
fn to_owned(&self) -> Self::Owned {
PartialName(self.0.to_owned())
}
}
}
impl<'a> convert::TryFrom<&'a BString> for &'a PartialNameRef {
type Error = Error;
fn try_from(v: &'a BString) -> Result<Self, Self::Error> {
Ok(PartialNameRef::new_unchecked(git_validate::reference::name_partial(
v.as_ref(),
)?))
}
}
impl<'a> convert::TryFrom<&'a BStr> for &'a PartialNameRef {
type Error = Error;
fn try_from(v: &'a BStr) -> Result<Self, Self::Error> {
Ok(PartialNameRef::new_unchecked(git_validate::reference::name_partial(v)?))
}
}
impl<'a> convert::TryFrom<&'a PartialName> for &'a PartialNameRef {
type Error = Error;
fn try_from(v: &'a PartialName) -> Result<Self, Self::Error> {
Ok(PartialNameRef::new_unchecked(v.0.as_bstr()))
}
}
impl<'a> convert::TryFrom<&'a str> for &'a FullNameRef {
type Error = Error;
fn try_from(v: &'a str) -> Result<Self, Self::Error> {
let v = v.as_bytes().as_bstr();
Ok(FullNameRef::new_unchecked(git_validate::reference::name(v)?))
}
}
impl<'a> convert::TryFrom<&'a str> for &'a PartialNameRef {
type Error = Error;
fn try_from(v: &'a str) -> Result<Self, Self::Error> {
let v = v.as_bytes().as_bstr();
Ok(PartialNameRef::new_unchecked(git_validate::reference::name_partial(v)?))
}
}
impl<'a> convert::TryFrom<&'a str> for PartialName {
type Error = Error;
fn try_from(v: &'a str) -> Result<Self, Self::Error> {
let v = v.as_bytes().as_bstr();
Ok(PartialName(git_validate::reference::name_partial(v)?.to_owned()))
}
}
impl<'a> convert::TryFrom<&'a FullName> for &'a PartialNameRef {
type Error = Infallible;
fn try_from(v: &'a FullName) -> Result<Self, Self::Error> {
Ok(v.as_ref().as_partial_name())
}
}
impl<'a> convert::TryFrom<&'a String> for &'a FullNameRef {
type Error = Error;
fn try_from(v: &'a String) -> Result<Self, Self::Error> {
let v = v.as_bytes().as_bstr();
Ok(FullNameRef::new_unchecked(git_validate::reference::name(v)?))
}
}
impl<'a> convert::TryFrom<&'a String> for &'a PartialNameRef {
type Error = Error;
fn try_from(v: &'a String) -> Result<Self, Self::Error> {
let v = v.as_bytes().as_bstr();
Ok(PartialNameRef::new_unchecked(git_validate::reference::name_partial(v)?))
}
}
impl convert::TryFrom<String> for PartialName {
type Error = Error;
fn try_from(v: String) -> Result<Self, Self::Error> {
git_validate::reference::name_partial(v.as_bytes().as_bstr())?;
Ok(PartialName(v.into()))
}
}
impl convert::TryFrom<BString> for PartialName {
type Error = Error;
fn try_from(v: BString) -> Result<Self, Self::Error> {
git_validate::reference::name_partial(v.as_ref())?;
Ok(PartialName(v))
}
}
/// Note that this method is disagreeing with git_validate as it allows dashes '-' for some reason.
/// Since partial names cannot be created with dashes inside we adjusted this as it's probably unintended or git creates pseudo-refs
/// which wouldn't pass its safety checks.
pub(crate) fn is_pseudo_ref<'a>(name: impl Into<&'a BStr>) -> bool {
name.into().bytes().all(|b| b.is_ascii_uppercase() || b == b'_')
}