use chrono::DateTime;
use super::{ListOptions, Tag};
use crate::{Error, Result};
pub async fn list_tags(source: &url::Url, options: &ListOptions<'_>) -> Result<Vec<Tag>> {
let tmp_dir = tempfile::TempDir::with_prefix("adnv").map_err(Error::TempDirectory)?;
let repo = tokio::task::spawn_blocking({
let url = source
.as_str()
.strip_prefix("git+")
.unwrap_or(source.as_str())
.to_string();
let path = tmp_dir.path().to_path_buf();
move || {
git2::build::RepoBuilder::new()
.bare(true)
.clone(&url, &path)
}
})
.await
.map_err(|err| Error::io(std::io::Error::other(err), "Clone task panicked"))?
.map_err(|err| {
Error::git(
err,
format!(
"Could not clone source repository from {:?}",
source.as_str()
),
)
})?;
let versions: Vec<_> = repo
.tag_names(None)
.map_err(|err| {
Error::git(
err,
format!("Could not list tags from {:?}", source.as_str()),
)
})?
.iter_bytes()
.filter_map(|name| match String::from_utf8(name.to_vec()) {
Ok(name) => Some(name),
Err(err) => {
tracing::warn!("Bad tag name: {err}");
None
}
})
.filter_map(|name| match get_tag(&repo, &name) {
Ok(version) => Some(version),
Err(err) => {
tracing::warn!("{}", miette::Report::new(err));
None
}
})
.collect();
Ok(options.filter_tags(versions))
}
fn get_tag(repo: &git2::Repository, name: &str) -> Result<Tag> {
let reference = repo
.resolve_reference_from_short_name(name)
.map_err(|err| Error::git(err, format!("Could not find reference `{name}`")))?;
if let Some(tagger) = reference
.peel_to_tag()
.ok()
.and_then(|tag| tag.tagger().map(|tagger| tagger.to_owned()))
{
let time = git_time_to_chrono(tagger.when()).ok_or_else(|| {
Error::git(
git2::Error::from_str("Invalid time"),
"Could not convert time",
)
})?;
Ok(Tag::new(name, time))
} else if let Ok(commit) = reference.peel_to_commit() {
let time = git_time_to_chrono(commit.time()).ok_or_else(|| {
Error::git(
git2::Error::from_str("Invalid time"),
"Could not convert time",
)
})?;
Ok(Tag::new(name, time))
} else {
Err(Error::git(
git2::Error::from_str("Bad reference"),
"Could not resolve repository version",
))
}
}
fn git_time_to_chrono(time: git2::Time) -> Option<DateTime<chrono::Utc>> {
use chrono::{DateTime, FixedOffset, TimeZone, Utc};
let timestamp = time.seconds();
let dt = DateTime::<Utc>::from_timestamp(timestamp, 0)?;
Some(
FixedOffset::west_opt(time.offset_minutes() * 60)?
.from_utc_datetime(&dt.naive_utc())
.into(),
)
}