use git2::{BranchType, Repository}; use ignore::DirEntry; use std::{cmp::Ordering, path::PathBuf, time::Duration}; use crate::project::Error; use crate::project::Timestamp; use crate::Project; #[derive(Debug, Clone)] pub struct GitProject { path_buf: PathBuf, latest_commit: Option, } impl GitProject { fn new(path_buf: PathBuf) -> Result { let latest_commit = Self::latest_commit(&path_buf).ok(); Ok(Self { path_buf, latest_commit, }) } fn latest_commit(path_buf: &PathBuf) -> Result { let repository = Repository::open(path_buf)?; let mut branches = repository.branches(Some(BranchType::Local))?; branches .try_fold(0, |latest, branch| { let (branch, _) = branch?; let name = branch .name()? .ok_or_else(|| git2::Error::from_str("Failed to find branch"))?; repository .revparse_single(name)? .peel_to_commit() .map(|c| (c.time().seconds() as u64).max(latest)) }) .map(Duration::from_secs) .map_err(Into::into) } } impl Timestamp for GitProject { type Error = Error; fn timestamp(&self) -> Result { match self.latest_commit { Some(t) => Ok(t), None => Self::latest_commit(&self.path_buf), } } } impl Project for GitProject { fn to_path_buf(&self) -> &PathBuf { &self.path_buf } } impl PartialEq for GitProject { fn eq(&self, other: &Self) -> bool { match (self.latest_commit, other.latest_commit) { (Some(time), Some(other_time)) => time.eq(&other_time), _ => false, } } } impl PartialOrd for GitProject { fn partial_cmp(&self, other: &Self) -> Option { match (self.latest_commit, other.latest_commit) { (Some(time), Some(other_time)) => time.partial_cmp(&other_time), _ => None, } } } impl TryFrom for GitProject { type Error = Error; fn try_from(value: PathBuf) -> Result { Self::new(value) } } impl TryFrom for GitProject { type Error = Error; fn try_from(value: DirEntry) -> Result { Self::new(value.into_path()) } } impl From for PathBuf { fn from(value: GitProject) -> Self { value.path_buf } }