use std::path::Path;
use git2::Commit;
use lazy_static::lazy_static;
use regex::Regex;
use crate::workspace::errors;
#[cfg(test)]
#[path = "./repository_test.rs"]
mod repository_test;
lazy_static! {
static ref REPO_RE: Regex =
Regex::new(r#"github.com[:/](?P<user>[^/]+)/(?P<repo>.+?)(?:.git)?\n?$"#).unwrap();
}
pub struct Repository {
repo: git2::Repository,
}
impl Repository {
pub fn new<T: AsRef<Path>>(dir: T) -> Result<Self, errors::GRError> {
let repo = git2::Repository::open(dir).map_err(errors::GRError::Repository)?;
Ok(Self { repo })
}
pub fn latest_tag(&self) -> Result<String, errors::GRError> {
let tags = self
.repo
.tag_names(None)
.map_err(errors::GRError::TagNameList)?;
if let Some(Some(tag)) = tags.iter().last() {
Ok(tag.to_owned())
} else {
Err(errors::GRError::TagNotFound("latest".to_owned()))
}
}
pub fn validate_tag(&self, tag: &str) -> Result<git2::Oid, errors::GRError> {
let rev = self
.repo
.revparse(tag)
.map_err(errors::GRError::Repository)?;
Ok(rev
.from()
.ok_or_else(|| errors::GRError::TagNotFound(tag.to_owned()))?
.id())
}
pub fn previous_tag(&self, current: &str) -> Result<String, errors::GRError> {
self.validate_tag(current)?;
let tags = self
.repo
.tag_names(None)
.map_err(errors::GRError::TagNameList)?;
let mut tag = tags
.iter()
.rev()
.skip_while(|&hash| hash != Some(current))
.skip(1)
.take(1);
if let Some(Some(tag)) = tag.next() {
Ok(tag.to_owned())
} else {
let head = self.repo.head()?;
let id = head.peel_to_commit()?.id();
Ok(id.to_string())
}
}
pub fn commits_between_tags(
&self,
from: &str,
to: &str,
) -> Result<impl Iterator<Item = Commit>, errors::GRError> {
let from_obj = self.repo.revparse_single(from)?;
let to_obj = self.repo.revparse_single(to)?;
let from = from_obj.id();
let to = to_obj.id();
let from_obj = from_obj.peel_to_commit()?;
let to_obj = to_obj.peel_to_commit()?;
if from_obj.id() == to_obj.id() {
return Err(errors::GRError::TwinTags);
}
let mut res = self.repo.revwalk()?;
let range = format!("{from}..{to}");
res.push_range(&range)?;
res.set_sorting(git2::Sort::REVERSE)?;
let res = res.filter_map(Result::ok).filter_map(|oid| {
if let Ok(oid) = self.repo.find_commit(oid) {
Some(oid)
} else {
None
}
});
Ok(res)
}
fn repo_name_username(&self, remote: &str, index: usize) -> Result<String, errors::GRError> {
let url = self.repo.find_remote(remote)?;
let url = url.url().ok_or_else(|| {
errors::GRError::URLError("could not get the url of the repository".to_owned())
})?;
if let Some(caps) = REPO_RE.captures(url) {
return Ok(caps
.get(index)
.ok_or_else(|| {
errors::GRError::URLError(format!(
"could not find the setting from the url: {url}",
))
})?
.as_str()
.to_owned());
}
Ok(url.to_owned())
}
pub fn repo_name(&self, remote: &str) -> Result<String, errors::GRError> {
self.repo_name_username(remote, 2)
}
pub fn username(&self, remote: &str) -> Result<String, errors::GRError> {
self.repo_name_username(remote, 1)
}
}