use std::{convert::TryFrom, str};
use git_ext::{
ref_format::{component, lit, Qualified, RefStr, RefString},
Oid,
};
use crate::{refs::refstr_join, Author};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Tag {
Light {
id: Oid,
name: RefString,
},
Annotated {
id: Oid,
target: Oid,
name: RefString,
tagger: Option<Author>,
message: Option<String>,
},
}
impl Tag {
pub fn id(&self) -> Oid {
match self {
Self::Light { id, .. } => *id,
Self::Annotated { id, .. } => *id,
}
}
pub fn short_name(&self) -> &RefString {
match &self {
Tag::Light { name, .. } => name,
Tag::Annotated { name, .. } => name,
}
}
pub fn refname(&self) -> Qualified {
lit::refs_tags(self.short_name()).into()
}
}
pub mod error {
use std::str;
use radicle_git_ext::ref_format::{self, RefString};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum FromTag {
#[error(transparent)]
RefFormat(#[from] ref_format::Error),
#[error(transparent)]
Utf8(#[from] str::Utf8Error),
}
#[derive(Debug, Error)]
pub enum FromReference {
#[error(transparent)]
FromTag(#[from] FromTag),
#[error(transparent)]
Git(#[from] git2::Error),
#[error("the refname '{0}' did not begin with 'refs/tags'")]
NotQualified(String),
#[error("the refname '{0}' did not begin with 'refs/tags'")]
NotTag(RefString),
#[error(transparent)]
RefFormat(#[from] ref_format::Error),
#[error(transparent)]
Utf8(#[from] str::Utf8Error),
}
}
impl TryFrom<&git2::Tag<'_>> for Tag {
type Error = error::FromTag;
fn try_from(tag: &git2::Tag) -> Result<Self, Self::Error> {
let id = tag.id().into();
let target = tag.target_id().into();
let name = {
let name = str::from_utf8(tag.name_bytes())?;
RefStr::try_from_str(name)?.to_ref_string()
};
let tagger = tag.tagger().map(Author::try_from).transpose()?;
let message = tag
.message_bytes()
.map(str::from_utf8)
.transpose()?
.map(|message| message.into());
Ok(Tag::Annotated {
id,
target,
name,
tagger,
message,
})
}
}
impl TryFrom<&git2::Reference<'_>> for Tag {
type Error = error::FromReference;
fn try_from(reference: &git2::Reference) -> Result<Self, Self::Error> {
let name = reference_name(reference)?;
match reference.peel_to_tag() {
Ok(tag) => Tag::try_from(&tag).map_err(error::FromReference::from),
Err(err)
if err.class() == git2::ErrorClass::Object
&& err.code() == git2::ErrorCode::InvalidSpec =>
{
let commit = reference.peel_to_commit()?;
Ok(Tag::Light {
id: commit.id().into(),
name,
})
}
Err(err) => Err(err.into()),
}
}
}
pub(crate) fn reference_name(
reference: &git2::Reference,
) -> Result<RefString, error::FromReference> {
let name = str::from_utf8(reference.name_bytes())?;
let name = RefStr::try_from_str(name)?
.qualified()
.ok_or_else(|| error::FromReference::NotQualified(name.to_string()))?;
let (_refs, tags, c, cs) = name.non_empty_components();
if tags == component::TAGS {
Ok(refstr_join(c, cs))
} else {
Err(error::FromReference::NotTag(name.into()))
}
}