diff options
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | Cargo.lock | 11 | ||||
-rw-r--r-- | src/driver.rs | 91 | ||||
-rw-r--r-- | src/lib.rs | 192 | ||||
-rw-r--r-- | src/util.rs | 117 |
5 files changed, 217 insertions, 196 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c6042a..caa095a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Debugging calls for general usage and the new unpushed commit code - Derive debug to the `Config` struct - Lightweight logging stack with `env_logger` and `log` +- Two files: `driver.rs` and `util.rs` - Unpushed commit status functionality and output ### Changed - Bare repository detection to use upstream function +- Library contents into `driver.rs` and `util.rs` through a major refactor ## [0.7.1] - 2020-11-18 @@ -125,12 +125,6 @@ dependencies = [ ] [[package]] -name = "const_fn" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" - -[[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -138,13 +132,12 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "crossbeam-utils" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" dependencies = [ "autocfg", "cfg-if 1.0.0", - "const_fn", "lazy_static", ] diff --git a/src/driver.rs b/src/driver.rs new file mode 100644 index 0000000..56438e1 --- /dev/null +++ b/src/driver.rs @@ -0,0 +1,91 @@ +/* + * gfold + * https://github.com/nickgerace/gfold + * Author: Nick Gerace + * License: Apache 2.0 + */ + +use crate::util; + +use std::cmp::Ordering; +use std::fs; +use std::path::Path; + +use eyre::Result; + +#[derive(Debug)] +pub struct Config { + pub no_color: bool, + pub recursive: bool, + pub skip_sort: bool, +} + +pub struct TableWrapper { + pub path_string: String, + pub table: prettytable::Table, +} + +pub struct Results(Vec<TableWrapper>); + +impl Results { + pub fn new(path: &Path, config: &Config) -> Result<Results> { + let mut results = Results(Vec::new()); + results.execute_in_directory(&config, path)?; + if !&config.skip_sort { + results.sort_results(); + } + Ok(results) + } + + pub fn print_results(self) { + match self.0.len().cmp(&1) { + Ordering::Greater => { + for table_wrapper in self.0 { + println!("\n{}", table_wrapper.path_string); + table_wrapper.table.printstd(); + } + } + Ordering::Equal => { + self.0[0].table.printstd(); + } + Ordering::Less => { + println!("There are no results to display."); + } + }; + } + + fn execute_in_directory(&mut self, config: &Config, dir: &Path) -> Result<()> { + // FIXME: find ways to add concurrent programming (tokio, async, etc.) to this section. + let path_entries = fs::read_dir(dir)?; + let mut repos = Vec::new(); + + for entry in path_entries { + let subpath = &entry?.path(); + if subpath.is_dir() { + if git2::Repository::open(subpath).is_ok() { + repos.push(subpath.to_owned()); + } else if config.recursive { + self.execute_in_directory(&config, &subpath)?; + } + } + } + if !repos.is_empty() { + if !&config.skip_sort { + repos.sort(); + } + if let Some(table_wrapper) = + util::create_table_from_paths(repos, &dir, &config.no_color) + { + self.0.push(table_wrapper); + } + } + Ok(()) + } + + fn sort_results(&mut self) { + if self.0.len() >= 2 { + // FIXME: find a way to do this without "clone()". + self.0.sort_by_key(|table| table.path_string.clone()); + } + } +} @@ -7,199 +7,17 @@ //! This is a CLI tool to help keep track of your Git repositories. +mod driver; +mod util; + #[macro_use] extern crate prettytable; -use std::cmp::Ordering; -use std::fs; use std::path::Path; -use std::path::PathBuf; use eyre::Result; use log::debug; -#[derive(Debug)] -struct Config { - no_color: bool, - recursive: bool, - skip_sort: bool, -} - -struct TableWrapper { - path_string: String, - table: prettytable::Table, -} - -struct Results(Vec<TableWrapper>); - -impl Results { - fn new(path: &Path, config: &Config) -> Result<Results> { - let mut results = Results(Vec::new()); - results.execute_in_directory(&config, path)?; - if !&config.skip_sort { - results.sort_results(); - } - Ok(results) - } - - fn execute_in_directory(&mut self, config: &Config, dir: &Path) -> Result<()> { - // FIXME: find ways to add concurrent programming (tokio, async, etc.) to this section. - let path_entries = fs::read_dir(dir)?; - let mut repos = Vec::new(); - - for entry in path_entries { - let subpath = &entry?.path(); - if subpath.is_dir() { - if git2::Repository::open(subpath).is_ok() { - repos.push(subpath.to_owned()); - } else if config.recursive { - self.execute_in_directory(&config, &subpath)?; - } - } - } - if !repos.is_empty() { - if !&config.skip_sort { - repos.sort(); - } - if let Some(table_wrapper) = create_table_from_paths(repos, &dir, &config.no_color) { - self.0.push(table_wrapper); - } - } - Ok(()) - } - - fn sort_results(&mut self) { - if self.0.len() >= 2 { - // FIXME: find a way to do this without "clone()". - self.0.sort_by_key(|table| table.path_string.clone()); - } - } - - fn print_results(self) { - match self.0.len().cmp(&1) { - Ordering::Greater => { - for table_wrapper in self.0 { - println!("\n{}", table_wrapper.path_string); - table_wrapper.table.printstd(); - } - } - Ordering::Equal => { - self.0[0].table.printstd(); - } - Ordering::Less => { - println!("There are no results to display."); - } - }; - } -} - -fn create_table_from_paths( - repos: Vec<PathBuf>, - path: &Path, - no_color: &bool, -) -> Option<TableWrapper> { - let mut table = prettytable::Table::new(); - table.set_format( - prettytable::format::FormatBuilder::new() - .column_separator(' ') - .padding(0, 1) - .build(), - ); - - // FIXME: maximize error recovery in this loop. - for repo in repos { - debug!("Creating row from path: {:#?}", repo); - let repo_obj = git2::Repository::open(&repo).ok()?; - - // FIXME: in case deeper recoverable errors are desired, use the match arm... - // Err(error) if error.class() == git2::ErrorClass::Config => continue, - let origin = match repo_obj.find_remote("origin") { - Ok(origin) => origin, - Err(_) => continue, - }; - let url = match origin.url() { - Some(url) => url, - None => "none", - }; - - let head = repo_obj.head().ok()?; - let branch = match head.shorthand() { - Some(branch) => branch, - None => "none", - }; - - let str_name = match Path::new(&repo).strip_prefix(path).ok()?.to_str() { - Some(x) => x, - None => "none", - }; - - if repo_obj.is_bare() { - if *no_color { - table.add_row(row![Fl->str_name, Fl->"bare", Fl->branch, Fl->url]); - } else { - table.add_row(row![Flb->str_name, Frl->"bare", Fl->branch, Fl->url]); - } - } else { - let mut opts = git2::StatusOptions::new(); - match repo_obj.statuses(Some(&mut opts)) { - Ok(statuses) if statuses.is_empty() => { - if is_unpushed(&repo_obj, &head).ok()? { - if *no_color { - table.add_row(row![Fl->str_name, Fl->"unpushed", Fl->branch, Fl->url]) - } else { - table.add_row(row![Flb->str_name, Fcl->"unpushed", Fl->branch, Fl->url]) - } - } else { - if *no_color { - table.add_row(row![Fl->str_name, Fl->"clean", Fl->branch, Fl->url]) - } else { - table.add_row(row![Flb->str_name, Fgl->"clean", Fl->branch, Fl->url]) - } - } - } - Ok(_) => { - if *no_color { - table.add_row(row![Fl->str_name, Fl->"unclean", Fl->branch, Fl->url]) - } else { - table.add_row(row![Flb->str_name, Fyl->"unclean", Fl->branch, Fl->url]) - } - } - Err(_) => { - if *no_color { - table.add_row(row![Fl->str_name, Fl->"error", Fl->branch, Fl->url]) - } else { - table.add_row(row![Flb->str_name, Frl->"error", Fl->branch, Fl->url]) - } - } - }; - } - } - - match table.is_empty() { - true => None, - false => Some(TableWrapper { - path_string: path.to_str()?.to_string(), - table, - }), - } -} - -// FIXME: add an -o/--offline flag if this function is changed to connect to the remote. -fn is_unpushed(repo: &git2::Repository, head: &git2::Reference) -> Result<bool> { - let local = head.peel_to_commit()?; - debug!("Local commit: {:#?}", local.id()); - - let upstream = repo - .resolve_reference_from_short_name("origin")? - .peel_to_commit()?; - debug!("Origin commit: {:#?}", upstream.id()); - - match repo.graph_ahead_behind(local.id(), upstream.id())? { - ahead if ahead.0 > 0 => Ok(true), - _ => Ok(false), - } -} - /// This function is the primary, backend driver for `gfold`. /// /// - `path`: the target path to find and parse Git repositories @@ -209,7 +27,7 @@ fn is_unpushed(repo: &git2::Repository, head: &git2::Reference) -> Result<bool> /// /// When executed, results will be printed to STDOUT. pub fn run(path: &Path, no_color: bool, recursive: bool, skip_sort: bool) -> Result<()> { - let config = Config { + let config = driver::Config { no_color, recursive, skip_sort, @@ -217,7 +35,7 @@ pub fn run(path: &Path, no_color: bool, recursive: bool, skip_sort: bool) -> Res debug!("Running with path: {:#?}", path); debug!("Running with config: {:#?}", &config); - let results = Results::new(path, &config)?; + let results = driver::Results::new(path, &config)?; results.print_results(); Ok(()) } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..b93fc26 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,117 @@ +/* + * gfold + * https://github.com/nickgerace/gfold + * Author: Nick Gerace + * License: Apache 2.0 + */ + +use crate::driver; + +use std::path::{Path, PathBuf}; + +use eyre::Result; +use log::debug; + +pub fn create_table_from_paths( + repos: Vec<PathBuf>, + path: &Path, + no_color: &bool, +) -> Option<driver::TableWrapper> { + let mut table = prettytable::Table::new(); + table.set_format( + prettytable::format::FormatBuilder::new() + .column_separator(' ') + .padding(0, 1) + .build(), + ); + + // FIXME: maximize error recovery in this loop. + for repo in repos { + debug!("Creating row from path: {:#?}", repo); + let repo_obj = git2::Repository::open(&repo).ok()?; + + // FIXME: in case deeper recoverable errors are desired, use the match arm... + // Err(error) if error.class() == git2::ErrorClass::Config => continue, + let origin = match repo_obj.find_remote("origin") { + Ok(origin) => origin, + Err(_) => continue, + }; + let url = match origin.url() { + Some(url) => url, + None => "none", + }; + + let head = repo_obj.head().ok()?; + let branch = match head.shorthand() { + Some(branch) => branch, + None => "none", + }; + + let str_name = match Path::new(&repo).strip_prefix(path).ok()?.to_str() { + Some(x) => x, + None => "none", + }; + + if repo_obj.is_bare() { + if *no_color { + table.add_row(row![Fl->str_name, Fl->"bare", Fl->branch, Fl->url]); + } else { + table.add_row(row![Flb->str_name, Frl->"bare", Fl->branch, Fl->url]); + } + } else { + let mut opts = git2::StatusOptions::new(); + match repo_obj.statuses(Some(&mut opts)) { + Ok(statuses) if statuses.is_empty() => { + if is_unpushed(&repo_obj, &head).ok()? { + if *no_color { + table.add_row(row![Fl->str_name, Fl->"unpushed", Fl->branch, Fl->url]) + } else { + table.add_row(row![Flb->str_name, Fcl->"unpushed", Fl->branch, Fl->url]) + } + } else if *no_color { + table.add_row(row![Fl->str_name, Fl->"clean", Fl->branch, Fl->url]) + } else { + table.add_row(row![Flb->str_name, Fgl->"clean", Fl->branch, Fl->url]) + } + } + Ok(_) => { + if *no_color { + table.add_row(row![Fl->str_name, Fl->"unclean", Fl->branch, Fl->url]) + } else { + table.add_row(row![Flb->str_name, Fyl->"unclean", Fl->branch, Fl->url]) + } + } + Err(_) => { + if *no_color { + table.add_row(row![Fl->str_name, Fl->"error", Fl->branch, Fl->url]) + } else { + table.add_row(row![Flb->str_name, Frl->"error", Fl->branch, Fl->url]) + } + } + }; + } + } + + match table.is_empty() { + true => None, + false => Some(driver::TableWrapper { + path_string: path.to_str()?.to_string(), + table, + }), + } +} + +fn is_unpushed(repo: &git2::Repository, head: &git2::Reference) -> Result<bool> { + let local = head.peel_to_commit()?; + debug!("Local commit: {:#?}", local.id()); + + let upstream = repo + .resolve_reference_from_short_name("origin")? + .peel_to_commit()?; + debug!("Origin commit: {:#?}", upstream.id()); + + match repo.graph_ahead_behind(local.id(), upstream.id())? { + ahead if ahead.0 > 0 => Ok(true), + _ => Ok(false), + } +} |