summaryrefslogtreecommitdiffstats
path: root/src/project
diff options
context:
space:
mode:
Diffstat (limited to 'src/project')
-rw-r--r--src/project/git.rs90
-rw-r--r--src/project/path.rs74
2 files changed, 164 insertions, 0 deletions
diff --git a/src/project/git.rs b/src/project/git.rs
new file mode 100644
index 0000000..f1c4d5a
--- /dev/null
+++ b/src/project/git.rs
@@ -0,0 +1,90 @@
+use crate::{Error, Project};
+use git2::{BranchType, Repository};
+use ignore::DirEntry;
+use std::{cmp::Ordering, path::PathBuf, time::Duration};
+
+#[derive(Debug, Clone)]
+pub struct GitProject {
+ path_buf: PathBuf,
+ latest_commit: Option<Duration>,
+}
+
+impl GitProject {
+ fn new(path_buf: PathBuf) -> Result<Self, Error> {
+ let repository = Repository::open(&path_buf)?;
+ let latest_commit = Self::latest_commit(&repository);
+ Ok(Self {
+ path_buf,
+ latest_commit,
+ })
+ }
+
+ fn latest_commit(repository: &Repository) -> Option<Duration> {
+ let mut branches = repository.branches(Some(BranchType::Local)).ok()?;
+ branches
+ .try_fold(0, |latest, branch| {
+ let branch = branch?.0;
+
+ 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)
+ .ok()
+ }
+}
+
+impl Project for GitProject {
+ fn timestamp(&self) -> Option<Duration> {
+ self.latest_commit
+ }
+
+ fn to_path_buf(&self) -> PathBuf {
+ self.path_buf.to_owned()
+ }
+}
+
+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<Ordering> {
+ match (self.latest_commit, other.latest_commit) {
+ (Some(time), Some(other_time)) => time.partial_cmp(&other_time),
+ _ => None,
+ }
+ }
+}
+
+impl TryFrom<PathBuf> for GitProject {
+ type Error = Error;
+
+ fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
+ Self::new(value)
+ }
+}
+
+impl TryFrom<DirEntry> for GitProject {
+ type Error = Error;
+
+ fn try_from(value: DirEntry) -> Result<Self, Self::Error> {
+ Self::new(value.into_path())
+ }
+}
+
+impl From<GitProject> for PathBuf {
+ fn from(value: GitProject) -> Self {
+ value.path_buf
+ }
+}
diff --git a/src/project/path.rs b/src/project/path.rs
new file mode 100644
index 0000000..24dad9d
--- /dev/null
+++ b/src/project/path.rs
@@ -0,0 +1,74 @@
+use ignore::DirEntry;
+use std::{
+ path::PathBuf,
+ time::{Duration, SystemTime},
+};
+
+use crate::Project;
+
+#[derive(Debug, PartialEq, Eq, Clone, Default)]
+pub struct PathProject {
+ path_buf: PathBuf,
+ timestamp: Option<Duration>,
+}
+
+impl PathProject {
+ fn new(path_buf: PathBuf) -> Self {
+ let timestamp = path_buf
+ .metadata()
+ .ok()
+ .and_then(|m| m.modified().ok())
+ .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok());
+
+ Self {
+ timestamp,
+ path_buf,
+ }
+ }
+}
+
+impl Project for PathProject {
+ fn timestamp(&self) -> Option<Duration> {
+ self.timestamp
+ }
+
+ fn to_path_buf(&self) -> PathBuf {
+ self.path_buf.to_owned()
+ }
+}
+
+impl TryFrom<(&String, DirEntry)> for PathProject {
+ type Error = String;
+
+ fn try_from((pattern, dir_entry): (&String, DirEntry)) -> Result<Self, Self::Error> {
+ dir_entry
+ .path()
+ .join(pattern)
+ .exists()
+ .then(|| Self::from(dir_entry.to_owned()))
+ .ok_or_else(|| {
+ format!(
+ "Pattern `{:?}` not found in path: `{:?}`",
+ pattern, dir_entry
+ )
+ })
+ }
+}
+
+impl From<PathBuf> for PathProject {
+ fn from(value: PathBuf) -> Self {
+ Self::new(value)
+ }
+}
+
+impl From<DirEntry> for PathProject {
+ fn from(value: DirEntry) -> Self {
+ Self::new(value.into_path())
+ }
+}
+
+impl From<PathProject> for PathBuf {
+ fn from(value: PathProject) -> Self {
+ value.path_buf
+ }
+}