use git2::{BranchType, Repository}; use ignore::DirEntry; use std::{path::PathBuf, time::Duration}; use tracing::{debug, warn}; use crate::{Error, Result}; use super::{ProjectParser, Timestamp}; #[derive(Debug, Clone)] pub struct GitMatcher; impl ProjectParser for GitMatcher { #[tracing::instrument] fn parse(&self, path_buf: PathBuf) -> Option { match GitProject::new(path_buf) { Ok(g) => Some(Box::new(g)), Err(err) => { debug!(%err, "Failed to create git project"); None } } } } #[derive(Debug, Clone)] pub struct GitProject { path_buf: PathBuf, latest_commit: Duration, } impl GitProject { fn new(path_buf: PathBuf) -> Result { let repo = Repository::open(&path_buf)?; let latest_commit = Self::get_timestamp(&repo); Ok(Self { path_buf, latest_commit, }) } fn get_timestamp(repo: &Repository) -> Duration { match Self::latest_commit(repo) { Ok(s) => Duration::from_secs(s), Err(err) => { warn!(%err, "Failed to get latest commit from repository"); Duration::default() } } } fn latest_commit(repository: &Repository) -> Result { 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_err(Into::into) }) } } impl Timestamp for GitProject { fn timestamp(&self) -> &Duration { &self.latest_commit } } impl AsRef for GitProject { fn as_ref(&self) -> &PathBuf { &self.path_buf } } 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 } }