summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Gerace <nickagerace@gmail.com>2020-11-26 11:28:41 -0500
committerNick Gerace <nickagerace@gmail.com>2020-11-26 11:36:47 -0500
commitb483f55b60f647893989289eef5b1cb90525ca6b (patch)
treef4dfe480285674f25f333fb7299029f5610c9075
parent84962f609c1ea0e3255a778683e38f83b58f833c (diff)
downloadgfold-b483f55b60f647893989289eef5b1cb90525ca6b.zip
Refactor library into driver and util files
Refactor library into driver and util files. This library layout helps organize the gfold API.
-rw-r--r--CHANGELOG.md2
-rw-r--r--Cargo.lock11
-rw-r--r--src/driver.rs91
-rw-r--r--src/lib.rs192
-rw-r--r--src/util.rs117
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
diff --git a/Cargo.lock b/Cargo.lock
index f816e74..4c73b42 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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());
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 5a704b8..b72d557 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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),
+ }
+}