use git2::{BranchType, Repository}; use ignore::DirEntry; use onefetch::ui::printer::Printer; use std::io; use std::{cmp::Ordering, path::PathBuf, time::Duration}; use crate::project::Error; use crate::project::Preview; 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 repo = Repository::open(&path_buf)?; Ok(Self { path_buf, latest_commit: Self::latest_commit(&repo).ok(), }) } 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(Duration::from_secs) .map_err(Into::into) } } impl Timestamp for GitProject { type Error = Error; fn timestamp(&self) -> Result { self.latest_commit.ok_or(Error::Git(git2::Error::from_str( "Failed to get latest commit", ))) } } impl Project for GitProject { fn to_path_buf(&self) -> &PathBuf { &self.path_buf } } impl Preview for GitProject { type Error = Error; fn preview(&self) -> Result<(), Self::Error> { // onefetch --include-hidden --show-logo=auto let config = onefetch::cli::Config { input: self.path_buf.to_owned(), ..Default::default() }; let info = onefetch::info::Info::new(&config)?; let mut printer = Printer::new(io::BufWriter::new(io::stdout()), info, config)?; printer.print().map_err(Into::into) } } 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 } }