use std::collections::HashSet;
use anyhow::Result;
use git2::{Commit, Oid, Repository};
use radix_trie::Trie;
use crate::semver::Level;
pub use crate::semver::Version;
pub fn get_version(repository: &Repository) -> Result<Version> {
let tags = get_tags(repository)?;
let (version, height) = find_latest_versions(&tags, &repository)?
.into_iter()
.max_by(|(v1, _h1), (v2, _h2)| v1.cmp_precedence(v2))
.unwrap_or((Version::default(), 0));
if height == 0 {
Ok(version)
} else {
Ok(version
.with_height(height)
.without_metadata()
.with_incremented_level(Level::Patch))
}
}
fn find_latest_versions(
tags: &Trie<String, Version>,
repository: &Repository,
) -> Result<Vec<(Version, u32)>> {
let mut current_height: u32 = 0;
let mut results: Vec<(Version, u32)> = vec![];
let mut checked_commits: HashSet<Oid> = HashSet::new();
let mut commits_to_check = vec![repository.head()?.peel_to_commit()?];
while !commits_to_check.is_empty() {
let mut parent_commits: Vec<Vec<Commit>> = vec![];
for commit in commits_to_check {
if checked_commits.contains(&commit.id()) {
continue;
}
checked_commits.insert(commit.id());
match tags.get(&commit.id().to_string()) {
Some(v) => results.push((v.clone(), current_height)),
None => parent_commits.push(commit.parents().collect()),
}
}
commits_to_check = parent_commits.into_iter().flatten().collect();
current_height = current_height + 1;
}
Ok(results)
}
fn get_tags(repository: &Repository) -> Result<Trie<String, Version>> {
let mut trie = Trie::new();
let tags = repository.tag_names(None)?;
tags.iter()
.filter_map(std::convert::identity)
.map(|tag_name| {
Ok((
Version::parse(tag_name)?,
get_tagged_commit(&repository, tag_name)?,
))
})
.filter_map(|result: Result<(Version, Commit)>| result.ok())
.for_each(|(version, commit)| {
trie.insert(commit.id().to_string(), version);
});
Ok(trie)
}
fn get_tagged_commit<'a>(repository: &'a Repository, tag_name: &'a str) -> Result<Commit<'a>> {
let object = repository.revparse_single(&format!("refs/tags/{}", tag_name))?;
Ok(object.peel_to_commit()?)
}