use std::{marker::PhantomData, path::Path};
use crate::{Commit, Error, GitUrl};
mod utils;
pub struct Remote;
pub struct Local;
pub struct Repository<Location = Local> {
repo: git2::Repository,
location: PhantomData<Location>,
}
impl Repository<Local> {
pub fn new<P>(path: P) -> Result<Self, Error>
where
P: AsRef<Path>,
{
if !path.as_ref().is_dir() {
return Err(Error::PathError("{path} is not a directory".to_string()));
}
let git_repo = git2::Repository::open(path).map_err(Error::Git)?;
Ok(Self {
repo: git_repo,
location: PhantomData::<Local>,
})
}
pub fn raw(&self) -> &git2::Repository {
&self.repo
}
pub fn traverse_commits(
&self,
) -> Result<impl Iterator<Item = Result<Commit<'_>, Error>>, Error> {
let mut walker = self.raw().revwalk().map_err(Error::Git)?;
walker.push_head().map_err(Error::Git)?;
self.iterate_walker(walker)
}
pub fn traverse_from(
&self,
oid: &str,
) -> Result<impl Iterator<Item = Result<Commit<'_>, Error>>, Error> {
let oid = git2::Oid::from_str(oid).map_err(Error::Git)?;
let mut walker = self.raw().revwalk().map_err(Error::Git)?;
walker.push(oid).map_err(Error::Git)?;
self.iterate_walker(walker)
}
pub fn head(&self) -> Result<Commit<'_>, Error> {
let head = self
.repo
.head()
.map_err(Error::Git)?
.peel_to_commit()
.map_err(Error::Git)?;
Ok(Commit::new(head, self))
}
pub fn single(&self, oid: &str) -> Result<Commit<'_>, Error> {
let git_commit = self
.repo
.find_commit(git2::Oid::from_str(oid).map_err(Error::Git)?)
.map_err(Error::Git)?;
Ok(Commit::new(git_commit, self))
}
fn iterate_walker(
&self,
walker: git2::Revwalk<'_>,
) -> Result<impl Iterator<Item = Result<Commit<'_>, Error>>, Error> {
Ok(walker.map(|result| {
result.map_err(Error::Git).and_then(|oid| {
self.raw()
.find_commit(oid)
.map_err(Error::Git)
.map(|git_commit| Commit::new(git_commit, self))
})
}))
}
}
impl Repository<Remote> {
pub fn new<P>(url: &str, dest: Option<P>) -> Result<Repository<Local>, Error>
where
P: AsRef<Path>,
{
let git_url = GitUrl::parse(url)?;
let scheme = git_url
.scheme()
.ok_or(Error::UrlScheme("None".to_string()))?;
match scheme {
"http" | "https" => Repository::from_https(url, dest),
"ssh" => Repository::from_ssh(url, dest),
_ => Err(Error::UrlScheme(scheme.to_string())),
}
}
pub fn from_https<P>(url: &str, dest: Option<P>) -> Result<Repository<Local>, Error>
where
P: AsRef<Path>,
{
let git_url = GitUrl::parse(url)?;
let dest = utils::resolve_destination(&git_url, dest);
let git_repo = git2::Repository::clone(url, dest).map_err(Error::Git)?;
Ok(Repository {
repo: git_repo,
location: PhantomData::<Local>,
})
}
pub fn from_ssh<P>(_url: &str, _dest: Option<P>) -> Result<Repository<Local>, Error>
where
P: AsRef<Path>,
{
todo!(
"SSH cloning is not yet supported, attempt cloning via a http/https URL or clone manually"
)
}
}
#[cfg(test)]
impl Repository<Local> {
pub fn from_repository(repo: git2::Repository) -> Self {
Self {
repo,
location: PhantomData::<Local>,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use tempfile::{NamedTempFile, TempDir};
#[test]
fn test_fail_on_bad_dir() {
let fp = NamedTempFile::new().expect("Failed to make tempfile");
assert!(Repository::<Local>::new(fp.path()).is_err())
}
#[test]
fn test_fail_on_bad_git_dir() {
let dir = TempDir::new().expect("Failed to make tempdir");
assert!(Repository::<Local>::new(dir.path()).is_err())
}
}