use anyhow::{Context, Result};
use endringer_core::types::{CommitId, SortOrder, TagInfo};
use gix::Repository;
use crate::util::{gix_id_to_commit_id, seconds_to_systemtime};
const TAGS_PREFIX: &str = "refs/tags/";
pub(crate) fn list_tags(repository: &Repository) -> Result<Vec<TagInfo>> {
let mut tags = Vec::new();
for reference in repository.references()?.prefixed(TAGS_PREFIX)? {
let mut reference = reference.map_err(|e| anyhow::anyhow!("{e}"))?;
let full_name = reference.name().as_bstr().to_string();
let name = full_name
.strip_prefix(TAGS_PREFIX)
.unwrap_or(&full_name)
.to_owned();
let commit = reference.peel_to_commit()?;
let commit_id: CommitId = gix_id_to_commit_id(commit.id);
let commit_summary = commit
.message()
.context("failed to read commit message")?
.summary()
.to_string();
let commit_timestamp =
seconds_to_systemtime(commit.time().context("failed to read commit timestamp")?.seconds);
tags.push(TagInfo { name, full_name, commit_id, commit_summary, commit_timestamp });
}
Ok(tags)
}
pub(crate) fn list_tags_sorted(repository: &Repository, order: SortOrder) -> Result<Vec<TagInfo>> {
let mut tags = list_tags(repository)?;
match order {
SortOrder::NewestFirst => tags.sort_by(|a, b| b.commit_timestamp.cmp(&a.commit_timestamp)),
SortOrder::OldestFirst => tags.sort_by(|a, b| a.commit_timestamp.cmp(&b.commit_timestamp)),
SortOrder::ByName => tags.sort_by(|a, b| a.name.cmp(&b.name)),
}
Ok(tags)
}
pub(crate) fn create_tag(repository: &Repository, name: &str) -> Result<()> {
let head_id = repository
.head()?
.id()
.ok_or_else(|| anyhow::anyhow!("HEAD is not pointing to a commit"))?;
repository
.reference(
format!("{}{}", TAGS_PREFIX, name).as_str(),
head_id.detach(),
gix::refs::transaction::PreviousValue::MustNotExist,
format!("tag: created lightweight tag {}", name),
)
.with_context(|| format!("failed to create tag '{}'", name))?;
Ok(())
}
pub(crate) fn create_annotated_tag(repository: &Repository, name: &str, message: &str) -> Result<()> {
let head_id = repository
.head()?
.id()
.ok_or_else(|| anyhow::anyhow!("HEAD is not pointing to a commit"))?
.detach();
let tagger = repository
.committer()
.ok_or_else(|| {
anyhow::anyhow!(
"no committer identity — set user.name and user.email in git config"
)
})?
.context("failed to resolve committer identity")?;
repository
.tag(
name,
&head_id,
gix::object::Kind::Commit,
Some(tagger),
message,
gix::refs::transaction::PreviousValue::MustNotExist,
)
.with_context(|| format!("failed to create annotated tag '{}'", name))?;
Ok(())
}
pub(crate) fn delete_tag(repository: &Repository, name: &str) -> Result<()> {
let full_ref = format!("{}{}", TAGS_PREFIX, name);
let edit = gix::refs::transaction::RefEdit {
change: gix::refs::transaction::Change::Delete {
expected: gix::refs::transaction::PreviousValue::Any,
log: gix::refs::transaction::RefLog::AndReference,
},
name: full_ref
.as_str()
.try_into()
.map_err(|_| anyhow::anyhow!("invalid ref name '{}'", full_ref))?,
deref: false,
};
repository
.edit_references(std::iter::once(edit))
.with_context(|| format!("failed to delete tag '{}'", name))?;
Ok(())
}