summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorToby Vincent <tobyv13@gmail.com>2022-11-26 16:43:14 -0600
committerToby Vincent <tobyv13@gmail.com>2022-11-26 16:43:14 -0600
commit46cee052d6d4b60b483ab6841af976d68d954705 (patch)
tree19bbe2170c5a2e6af3b306e14c942ccd1393228a /src
parent0f428974b2d7f4cf59490904564e52d134b9ef3a (diff)
feat: add project filtering
Diffstat (limited to 'src')
-rw-r--r--src/cli.rs18
-rw-r--r--src/config.rs20
-rw-r--r--src/error.rs3
-rw-r--r--src/lib.rs8
-rw-r--r--src/main.rs9
-rw-r--r--src/project.rs12
-rw-r--r--src/project/git.rs90
-rw-r--r--src/project/path.rs74
-rw-r--r--src/projects.rs117
-rw-r--r--src/projects/entry.rs76
-rw-r--r--src/search.rs129
-rw-r--r--src/search/entry.rs57
-rw-r--r--src/search/entry/config.rs90
-rw-r--r--src/sorter.rs62
14 files changed, 489 insertions, 276 deletions
diff --git a/src/cli.rs b/src/cli.rs
index a30bb7f..666a223 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -1,4 +1,4 @@
-use crate::{Config, SearchPath};
+use crate::{search, Config};
use clap::{Args, Parser};
use figment::{value, Metadata, Profile, Provider};
use serde::{Deserialize, Serialize};
@@ -34,8 +34,16 @@ pub struct Projects {
///
/// Traverse into hidden directories while searching. A directory is considered hidden
/// if its name starts with a `.` sign (dot). If `--max-depth` is 0, this has no effect.
- #[arg(long, default_value_t)]
+ #[arg(long)]
hidden: bool,
+
+ /// Match git repositories
+ #[arg(long, short)]
+ git: bool,
+
+ /// Match directories containing item named <PATTERN>
+ #[arg(long, short)]
+ pattern: Option<String>,
}
impl Provider for Projects {
@@ -54,10 +62,12 @@ impl From<Projects> for Config {
.paths
.iter()
.cloned()
- .map(|p| SearchPath {
- path: p,
+ .map(|path_buf| search::entry::Config {
+ path_buf,
hidden: value.hidden,
max_depth: value.max_depth,
+ git: value.git,
+ pattern: value.pattern.to_owned(),
})
.collect();
diff --git a/src/config.rs b/src/config.rs
index 677b202..126b939 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,10 +1,11 @@
-use super::SearchPath;
use figment::{providers::Serialized, value, Figment, Metadata, Profile, Provider};
use serde::{Deserialize, Serialize};
+use crate::search;
+
#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
pub struct Config {
- pub(crate) paths: Vec<SearchPath>,
+ pub(crate) paths: Vec<search::entry::Config>,
}
impl Config {
@@ -57,20 +58,23 @@ mod tests {
config,
Config {
paths: Vec::from([
- SearchPath {
- path: "/path/to/projects".into(),
+ search::entry::Config {
+ path_buf: "/path/to/projects".into(),
hidden: false,
max_depth: None,
+ ..Default::default()
},
- SearchPath {
- path: "/path/to/other_projects".into(),
+ search::entry::Config {
+ path_buf: "/path/to/other_projects".into(),
hidden: true,
max_depth: Some(1),
+ ..Default::default()
},
- SearchPath {
- path: "/path/to/another_project".into(),
+ search::entry::Config {
+ path_buf: "/path/to/another_project".into(),
hidden: false,
max_depth: Some(0),
+ ..Default::default()
},
]),
}
diff --git a/src/error.rs b/src/error.rs
index ae5b227..5c0f26b 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -9,5 +9,8 @@ pub enum Error {
Ignore(#[from] ignore::Error),
#[error(transparent)]
+ Git(#[from] git2::Error),
+
+ #[error(transparent)]
Other(#[from] anyhow::Error),
}
diff --git a/src/lib.rs b/src/lib.rs
index dffa16c..87cc348 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,11 +1,11 @@
pub use crate::cli::Cli;
pub use crate::config::Config;
pub use crate::error::{Error, Result};
-pub use crate::projects::{Projects, SearchPath};
-pub use crate::sorter::{GitSorter, Sorter};
+pub use crate::project::Project;
+pub use crate::search::{entry, Search};
mod cli;
mod config;
mod error;
-mod projects;
-mod sorter;
+mod project;
+mod search;
diff --git a/src/main.rs b/src/main.rs
index 2c2f980..ca08fa4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,8 +1,7 @@
use anyhow::{Context, Result};
use clap::Parser;
use figment::providers::{Env, Format, Toml};
-use projectr::{Cli, Config, GitSorter, Projects, Sorter};
-use std::path::PathBuf;
+use projectr::{Cli, Config, Project, Search};
#[tracing::instrument]
fn main() -> Result<()> {
@@ -26,14 +25,14 @@ fn main() -> Result<()> {
#[tracing::instrument]
pub fn run(config: &Config) -> Result<()> {
- let mut projects: Vec<PathBuf> = Projects::from_provider(config)
+ let mut projects: Vec<Box<dyn Project>> = Search::from_provider(config)
.context("Failed to extract paths config")?
.collect();
- GitSorter::sort(&mut projects);
+ projects.sort_unstable_by_key(|p| p.timestamp());
for project in projects {
- println!("{}", project.to_string_lossy())
+ println!("{}", project.to_path_buf().to_string_lossy())
}
Ok(())
diff --git a/src/project.rs b/src/project.rs
new file mode 100644
index 0000000..1188c88
--- /dev/null
+++ b/src/project.rs
@@ -0,0 +1,12 @@
+use std::{path::PathBuf, time::Duration};
+
+pub use git::GitProject;
+pub use path::PathProject;
+
+mod git;
+mod path;
+
+pub trait Project {
+ fn timestamp(&self) -> Option<Duration>;
+ fn to_path_buf(&self) -> PathBuf;
+}
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
+ }
+}
diff --git a/src/projects.rs b/src/projects.rs
deleted file mode 100644
index 8819628..0000000
--- a/src/projects.rs
+++ /dev/null
@@ -1,117 +0,0 @@
-use crate::{Config, Result};
-use figment::Provider;
-use ignore::Walk;
-use std::{path::PathBuf, vec::IntoIter};
-use tracing::{error, info};
-
-pub use entry::SearchPath;
-
-mod entry;
-
-pub struct Projects {
- search_path_iter: IntoIter<SearchPath>,
- walk: Option<Walk>,
-}
-
-impl std::fmt::Debug for Projects {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("Projects")
- .field("paths_iter", &self.search_path_iter)
- .finish_non_exhaustive()
- }
-}
-
-impl Projects {
- pub fn new() -> Result<Self> {
- Self::from_provider(Config::figment())
- }
-
- /// Extract `Config` from `provider` to construct new `Paths`
- pub fn from_provider<T: Provider>(provider: T) -> Result<Self> {
- Config::extract(&provider)
- .map_err(Into::into)
- .map(Into::into)
- }
-}
-
-impl From<Vec<SearchPath>> for Projects {
- fn from(value: Vec<SearchPath>) -> Self {
- Self {
- search_path_iter: value.into_iter(),
- walk: None,
- }
- }
-}
-
-impl From<Config> for Projects {
- fn from(value: Config) -> Self {
- value.paths.into()
- }
-}
-
-impl Iterator for Projects {
- type Item = PathBuf;
-
- #[tracing::instrument]
- fn next(&mut self) -> Option<Self::Item> {
- loop {
- match self.walk.as_mut().and_then(|iter| iter.next()) {
- Some(Ok(path)) if SearchPath::filter(&path) => return Some(path.into_path()),
- Some(Ok(path)) => info!(?path, "Ignoring filtered path"),
- Some(Err(err)) => error!(%err, "Ignoring errored path"),
- None => {
- self.walk = Some(self.search_path_iter.next()?.into());
- }
- };
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use pretty_assertions::assert_eq;
- use std::fs;
-
- #[test]
- fn test_iteration() {
- let temp_dir = tempfile::Builder::new()
- .tempdir()
- .unwrap()
- .path()
- .to_owned();
-
- let project_dir = temp_dir.join("project_dir");
- let project1 = temp_dir.join("project_dir/project1");
- let project2 = temp_dir.join("project_dir/project2");
- let project3 = temp_dir.join("project3");
- let project4 = temp_dir.join("subdir/project4");
-
- let paths = Projects::from(Vec::from([
- SearchPath {
- path: project_dir.to_owned(),
- hidden: false,
- max_depth: Some(1),
- },
- SearchPath {
- path: project3.to_owned(),
- hidden: false,
- max_depth: Some(0),
- },
- SearchPath {
- path: project4.to_owned(),
- hidden: false,
- max_depth: Some(0),
- },
- ]));
-
- let mut path_bufs = Vec::from([project_dir, project1, project2, project3, project4]);
- path_bufs.iter().try_for_each(fs::create_dir_all).unwrap();
- path_bufs.sort();
-
- let mut results = paths.into_iter().collect::<Vec<PathBuf>>();
- results.sort();
-
- assert_eq!(path_bufs, results);
- }
-}
diff --git a/src/projects/entry.rs b/src/projects/entry.rs
deleted file mode 100644
index 739ed83..0000000
--- a/src/projects/entry.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-use ignore::{DirEntry, Walk, WalkBuilder};
-use serde::{Deserialize, Deserializer, Serialize};
-use std::{convert::Infallible, path::PathBuf, str::FromStr};
-
-#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize)]
-#[serde(default)]
-pub struct SearchPath {
- pub path: PathBuf,
- pub hidden: bool,
- pub max_depth: Option<usize>,
-}
-
-impl SearchPath {
- pub fn filter(dir_entry: &DirEntry) -> bool {
- dir_entry.path().join(".git").exists()
- }
-}
-
-impl From<PathBuf> for SearchPath {
- fn from(path: PathBuf) -> Self {
- Self {
- path,
- ..Default::default()
- }
- }
-}
-
-impl From<SearchPath> for Walk {
- fn from(value: SearchPath) -> Self {
- WalkBuilder::new(value.path)
- .standard_filters(true)
- .max_depth(value.max_depth)
- .hidden(!value.hidden)
- .filter_entry(SearchPath::filter)
- .build()
- }
-}
-
-impl FromStr for SearchPath {
- type Err = Infallible;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- s.parse().map(PathBuf::into)
- }
-}
-
-impl<'de> Deserialize<'de> for SearchPath {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- #[derive(Deserialize)]
- #[serde(untagged)]
- enum Variants {
- String(String),
- Struct {
- path: PathBuf,
- hidden: bool,
- recurse: Option<usize>,
- },
- }
-
- match Variants::deserialize(deserializer)? {
- Variants::String(s) => s.parse().map_err(serde::de::Error::custom),
- Variants::Struct {
- path,
- hidden,
- recurse,
- } => Ok(Self {
- path,
- hidden,
- max_depth: recurse,
- }),
- }
- }
-}
diff --git a/src/search.rs b/src/search.rs
new file mode 100644
index 0000000..e39d67b
--- /dev/null
+++ b/src/search.rs
@@ -0,0 +1,129 @@
+use figment::Provider;
+use std::vec::IntoIter;
+
+use crate::{Config, Project, Result};
+
+pub use entry::Entry;
+
+pub mod entry;
+
+pub struct Search {
+ iter: IntoIter<entry::Config>,
+ curr: Option<Entry>,
+}
+
+impl std::fmt::Debug for Search {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("Projects")
+ .field("paths_iter", &self.iter)
+ .finish_non_exhaustive()
+ }
+}
+
+impl Search {
+ pub fn new() -> Result<Self> {
+ Self::from_provider(Config::figment())
+ }
+
+ /// Extract `Config` from `provider` to construct new `Paths`
+ pub fn from_provider<T: Provider>(provider: T) -> Result<Self> {
+ Config::extract(&provider)
+ .map_err(Into::into)
+ .map(|config| config.paths.into())
+ }
+}
+
+// impl<T> From<T> for Search
+// where
+// T: IntoIterator<Item = entry::Config, IntoIter = IntoIter<entry::Config>>,
+// {
+// fn from(value: T) -> Self {
+// Self {
+// iter: value.into_iter(),
+// curr: None,
+// }
+// }
+// }
+
+impl From<Vec<entry::Config>> for Search {
+ fn from(value: Vec<entry::Config>) -> Self {
+ Self {
+ iter: value.into_iter(),
+ curr: None,
+ }
+ }
+}
+
+impl From<Config> for Search {
+ fn from(value: Config) -> Self {
+ value.paths.into()
+ }
+}
+
+impl Iterator for Search {
+ type Item = Box<dyn Project>;
+
+ #[tracing::instrument]
+ fn next(&mut self) -> Option<Self::Item> {
+ match self.curr.as_mut().and_then(|c| c.next()) {
+ Some(proj) => Some(proj),
+ None => {
+ self.curr = Some(self.iter.next()?.into());
+ self.next()
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use pretty_assertions::assert_eq;
+ use std::{fs, path::PathBuf};
+
+ #[test]
+ fn test_iteration() {
+ let temp_dir = tempfile::Builder::new()
+ .tempdir()
+ .unwrap()
+ .path()
+ .to_owned();
+
+ let project_dir = temp_dir.join("project_dir");
+ let project1 = temp_dir.join("project_dir/project1");
+ let project2 = temp_dir.join("project_dir/project2");
+ let project3 = temp_dir.join("project3");
+ let project4 = temp_dir.join("subdir/project4");
+
+ let paths = Search::from(Vec::from([
+ entry::Config {
+ path_buf: project_dir.to_owned(),
+ max_depth: Some(1),
+ ..Default::default()
+ },
+ entry::Config {
+ path_buf: project3.to_owned(),
+ max_depth: Some(0),
+ ..Default::default()
+ },
+ entry::Config {
+ path_buf: project4.to_owned(),
+ max_depth: Some(0),
+ ..Default::default()
+ },
+ ]));
+
+ let mut path_bufs = Vec::from([project_dir, project1, project2, project3, project4]);
+ path_bufs.iter().try_for_each(fs::create_dir_all).unwrap();
+ path_bufs.sort();
+
+ let mut results = paths.into_iter().collect::<Vec<Box<dyn Project>>>();
+ results.sort_unstable_by_key(|p| p.timestamp());
+ let results = results
+ .into_iter()
+ .map(|p| p.to_path_buf())
+ .collect::<Vec<PathBuf>>();
+
+ assert_eq!(path_bufs, results);
+ }
+}
diff --git a/src/search/entry.rs b/src/search/entry.rs
new file mode 100644
index 0000000..6e94b35
--- /dev/null
+++ b/src/search/entry.rs
@@ -0,0 +1,57 @@
+use ignore::{DirEntry, Walk};
+use tracing::error;
+
+use crate::{
+ project::{GitProject, PathProject},
+ Project,
+};
+
+pub use config::Config;
+
+mod config;
+
+pub struct Entry {
+ config: Config,
+ iter: Walk,
+}
+
+impl std::fmt::Debug for Entry {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("SearchPath")
+ .field("config", &self.config)
+ .finish()
+ }
+}
+
+impl Entry {
+ pub fn match_project(&self, dir_entry: DirEntry) -> Option<Box<dyn Project>> {
+ if self.config.git {
+ if let Ok(git) = GitProject::try_from(dir_entry.to_owned()) {
+ return Some(Box::new(git));
+ };
+ };
+
+ if let Some(pattern) = &self.config.pattern {
+ if let Ok(proj) = PathProject::try_from((pattern, dir_entry)) {
+ return Some(Box::new(proj));
+ };
+ };
+
+ None
+ }
+}
+
+impl Iterator for Entry {
+ type Item = Box<dyn Project>;
+
+ #[tracing::instrument]
+ fn next(&mut self) -> Option<Self::Item> {
+ match self.iter.next()? {
+ Ok(dir_entry) => self.match_project(dir_entry),
+ Err(err) => {
+ error!(%err, "Ignoring errored path");
+ self.next()
+ }
+ }
+ }
+}
diff --git a/src/search/entry/config.rs b/src/search/entry/config.rs
new file mode 100644
index 0000000..d325b58
--- /dev/null
+++ b/src/search/entry/config.rs
@@ -0,0 +1,90 @@
+use ignore::WalkBuilder;
+use serde::{Deserialize, Deserializer, Serialize};
+use std::{convert::Infallible, path::PathBuf, str::FromStr};
+
+use super::Entry;
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize)]
+#[serde(default)]
+pub struct Config {
+ pub path_buf: PathBuf,
+ pub hidden: bool,
+ pub max_depth: Option<usize>,
+ pub git: bool,
+ pub pattern: Option<String>,
+}
+
+impl From<Config> for Entry {
+ fn from(config: Config) -> Self {
+ let iter = WalkBuilder::new(&config.path_buf)
+ .standard_filters(true)
+ .max_depth(config.max_depth)
+ .hidden(!config.hidden)
+ .build();
+ Self { iter, config }
+ }
+}
+
+impl From<PathBuf> for Config {
+ fn from(path_buf: PathBuf) -> Self {
+ Self {
+ path_buf,
+ ..Default::default()
+ }
+ }
+}
+
+impl Config {
+ pub fn new(path_buf: PathBuf) -> Self {
+ Self {
+ path_buf,
+ ..Default::default()
+ }
+ }
+}
+
+impl FromStr for Config {
+ type Err = Infallible;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ s.parse().map(PathBuf::into)
+ }
+}
+
+// Custom deserialize impl to accept either string or struct
+impl<'de> Deserialize<'de> for Config {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ #[derive(Deserialize)]
+ #[serde(untagged)]
+ enum Variants {
+ String(String),
+ Struct {
+ path_buf: PathBuf,
+ hidden: bool,
+ max_depth: Option<usize>,
+ git: bool,
+ pattern: Option<String>,
+ },
+ }
+
+ match Variants::deserialize(deserializer)? {
+ Variants::String(s) => s.parse().map_err(serde::de::Error::custom),
+ Variants::Struct {
+ path_buf,
+ hidden,
+ max_depth,
+ git,
+ pattern,
+ } => Ok(Self {
+ path_buf,
+ hidden,
+ max_depth,
+ git,
+ pattern,
+ }),
+ }
+ }
+}
diff --git a/src/sorter.rs b/src/sorter.rs
deleted file mode 100644
index 049b8d2..0000000
--- a/src/sorter.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-use std::{cmp::Ordering, path::PathBuf};
-
-use git2::{BranchType, Repository};
-use tracing::error;
-
-pub trait Sorter {
- #[allow(clippy::ptr_arg)]
- fn compare(a: &PathBuf, b: &PathBuf) -> Ordering;
-
- fn sort(vec: &mut Vec<PathBuf>) {
- vec.sort_unstable_by(Self::compare);
- }
-}
-
-pub struct GitSorter;
-
-impl GitSorter {
- fn get_commit(path: &PathBuf) -> Result<i64, git2::Error> {
- let repo = Repository::open(path)?;
- let mut branches = repo.branches(Some(BranchType::Local))?;
- 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"))?;
-
- repo.revparse_single(name)?
- .peel_to_commit()
- .map(|c| c.time().seconds().max(latest))
- })
- }
-}
-
-impl Sorter for GitSorter {
- fn compare(path_a: &PathBuf, path_b: &PathBuf) -> Ordering {
- let commit_a = Self::get_commit(path_a);
- let commit_b = Self::get_commit(path_b);
-
- match (commit_a, commit_b) {
- (Ok(a), Ok(b)) => a.cmp(&b),
- (Ok(_), Err(error_b)) => {
- error!(?path_b, ?error_b, "Error while comparing git repos");
- Ordering::Less
- }
- (Err(error_a), Ok(_)) => {
- error!(?path_a, ?error_a, "Error while comparing git repos");
- Ordering::Greater
- }
- (Err(error_a), Err(error_b)) => {
- error!(
- ?path_a,
- ?error_a,
- ?path_b,
- ?error_b,
- "Error while comparing git repos"
- );
- Ordering::Equal
- }
- }
- }
-}