summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Gerace <nickagerace@gmail.com>2022-12-21 18:03:37 -0500
committerNick Gerace <nickagerace@gmail.com>2022-12-21 18:22:35 -0500
commit5c297ada857562b30de3801b09fdbf697182eb76 (patch)
tree12f3fe6d641aea64cbe04fe88e8ef71f14f331aa
parent687b3f50d359c4225a5b4e4085c43d693e8a12d4 (diff)
downloadgfold-5c297ada857562b30de3801b09fdbf697182eb76.zip
Add submodule support in json mode
Primary: - Add submodule collection and support in json display mode Doc: - Ensure build from source instructions work with cargo workspaces - Add "no deps" flag to clippy usages Misc: - Add result type aliases - Change NOTE, FIXME, and TODO comments to have the author's nickname Signed-off-by: Nick Gerace <nickagerace@gmail.com>
-rw-r--r--CHANGELOG.md6
-rw-r--r--crates/bench-loosely/src/main.rs4
-rw-r--r--crates/gfold/src/cli.rs4
-rw-r--r--crates/gfold/src/config.rs10
-rw-r--r--crates/gfold/src/display.rs13
-rw-r--r--crates/gfold/src/display/color.rs18
-rw-r--r--crates/gfold/src/error.rs12
-rw-r--r--crates/gfold/src/main.rs57
-rw-r--r--crates/gfold/src/report.rs125
-rw-r--r--crates/gfold/src/report/target.rs8
-rw-r--r--crates/gfold/src/run.rs3
-rw-r--r--docs/DEVELOPING.md4
-rw-r--r--docs/MANUAL_INSTALL.md12
-rw-r--r--docs/RELEASE.md4
14 files changed, 188 insertions, 92 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8d686a3..792bbc3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,11 @@ For new changes prior to version 4.0.0, please see [CHANGELOG_PRE_V4](./docs/CHA
## Unreleased
-The latest version contains all changes.
+<!-- The latest version contains all changes. -->
+
+### Added
+
+- Submodule information in the `json` display mode
## 4.2.0 - 2022-12-21
diff --git a/crates/bench-loosely/src/main.rs b/crates/bench-loosely/src/main.rs
index 8ad46a3..52aa498 100644
--- a/crates/bench-loosely/src/main.rs
+++ b/crates/bench-loosely/src/main.rs
@@ -4,8 +4,8 @@
//! gfold is via CLI and not a library. Thus, this loose bench executes gfold as a CLI similarly
//! to real world use. This benchmark is not precise and is designed to give a high level overview.
-// TODO: add consistency deviation. We should see how far each result strays from the average. We
-// want gfold to perform consistently as well as quickly.
+// TODO(nick): add consistency deviation. We should see how far each result strays from the average.
+// We want gfold to perform consistently as well as quickly.
use std::path::Path;
use std::process::Command;
diff --git a/crates/gfold/src/cli.rs b/crates/gfold/src/cli.rs
index 09b58f1..d9bb593 100644
--- a/crates/gfold/src/cli.rs
+++ b/crates/gfold/src/cli.rs
@@ -6,7 +6,7 @@ use log::debug;
use std::env;
use crate::config::{ColorMode, Config, DisplayMode};
-use crate::error::Error;
+use crate::error::{AnyhowResult, Error};
use crate::run;
const HELP: &str = "\
@@ -59,7 +59,7 @@ struct Cli {
/// Parse CLI arguments, initialize the logger, merge configurations as needed, and call
/// [`run::run()`] with the resulting [`Config`].
-pub fn parse_and_run() -> anyhow::Result<()> {
+pub fn parse_and_run() -> AnyhowResult<()> {
// First and foremost, get logging up and running. We want logs as quickly as possible for
// debugging by setting "RUST_LOG".
let cli = Cli::parse();
diff --git a/crates/gfold/src/config.rs b/crates/gfold/src/config.rs
index 7b8d472..4eba4e4 100644
--- a/crates/gfold/src/config.rs
+++ b/crates/gfold/src/config.rs
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::{env, fs, io};
-use crate::error::Error;
+use crate::error::{AnyhowResult, Error, IoResult, TomlResult};
/// This struct is the actual config type consumed through the codebase. It is boostrapped via its
/// public methods and uses [`EntryConfig`], a private struct, under the hood in order to
@@ -67,7 +67,7 @@ impl Config {
/// This method tries to deserialize the config file (empty, non-existent, partial or complete)
/// and uses [`EntryConfig`] as an intermediary struct. This is the primary method used when
/// creating a config.
- pub fn try_config() -> anyhow::Result<Self> {
+ pub fn try_config() -> AnyhowResult<Self> {
// Within this method, we check if the config file is empty before deserializing it. Users
// should be able to proceed with empty config files. If empty or not found, then we fall
// back to the "EntryConfig" default before conversion.
@@ -88,17 +88,17 @@ impl Config {
/// This method does not look for the config file and uses [`EntryConfig`]'s defaults instead.
/// Use this method when the user wishes to skip config file lookup.
- pub fn try_config_default() -> io::Result<Self> {
+ pub fn try_config_default() -> IoResult<Self> {
Self::from_entry_config(&EntryConfig::default())
}
/// This method prints the full config (merged with config file, as needed) as valid, pretty TOML.
- pub fn print(self) -> Result<(), toml::ser::Error> {
+ pub fn print(self) -> TomlResult<()> {
print!("{}", toml::to_string_pretty(&self)?);
Ok(())
}
- fn from_entry_config(entry_config: &EntryConfig) -> io::Result<Self> {
+ fn from_entry_config(entry_config: &EntryConfig) -> IoResult<Self> {
Ok(Config {
path: match &entry_config.path {
Some(s) => s.clone(),
diff --git a/crates/gfold/src/display.rs b/crates/gfold/src/display.rs
index 40f088e..a613c3a 100644
--- a/crates/gfold/src/display.rs
+++ b/crates/gfold/src/display.rs
@@ -2,12 +2,11 @@
use log::debug;
use log::warn;
-use std::io;
use std::path::Path;
use crate::config::{ColorMode, DisplayMode};
use crate::display::color::ColorHarness;
-use crate::error::Error;
+use crate::error::{AnyhowResult, Error, IoResult, SerdeJsonResult};
use crate::report::LabeledReports;
mod color;
@@ -20,7 +19,7 @@ pub fn display(
display_mode: &DisplayMode,
reports: &LabeledReports,
color_mode: &ColorMode,
-) -> anyhow::Result<()> {
+) -> AnyhowResult<()> {
match display_mode {
DisplayMode::Standard => standard(reports, color_mode)?,
DisplayMode::Json => json(reports)?,
@@ -30,7 +29,7 @@ pub fn display(
}
/// Display [`LabeledReports`] to `stdout` in the standard (default) format.
-fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> anyhow::Result<()> {
+fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> AnyhowResult<()> {
debug!("detected standard display mode");
let mut all_reports = Vec::new();
for grouped_report in reports {
@@ -74,7 +73,7 @@ fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> anyhow::Result<
}
/// Display [`LabeledReports`] to `stdout` in JSON format.
-fn json(reports: &LabeledReports) -> serde_json::error::Result<()> {
+fn json(reports: &LabeledReports) -> SerdeJsonResult<()> {
debug!("detected json display mode");
let mut all_reports = Vec::new();
for grouped_report in reports {
@@ -87,14 +86,14 @@ fn json(reports: &LabeledReports) -> serde_json::error::Result<()> {
}
/// Display [`LabeledReports`] to `stdout` in the classic format.
-fn classic(reports: &LabeledReports, color_mode: &ColorMode) -> io::Result<()> {
+fn classic(reports: &LabeledReports, color_mode: &ColorMode) -> IoResult<()> {
debug!("detected classic display mode");
let color_harness = ColorHarness::new(color_mode);
let length = reports.keys().len();
let mut first = true;
for (title, group) in reports {
- // FIXME: make group title matching less cumbersome.
+ // FIXME(nick): make group title matching less cumbersome.
if length > 1 {
match first {
true => {
diff --git a/crates/gfold/src/display/color.rs b/crates/gfold/src/display/color.rs
index 6072963..153b8ab 100644
--- a/crates/gfold/src/display/color.rs
+++ b/crates/gfold/src/display/color.rs
@@ -1,9 +1,10 @@
//! This module provides a harness for non-trivial displays of information to `stdout`.
-use std::io::{self, Write};
+use std::io::Write;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use crate::config::ColorMode;
+use crate::error::IoResult;
use crate::status::Status;
/// This harness provides methods to write to `stdout`. It maps the internal [`ColorMode`] type to
@@ -24,7 +25,7 @@ impl ColorHarness {
}
/// Writes the [`Status`] of the Git repository to `stdout`.
- pub fn write_status(&self, status: &Status, status_width: usize) -> io::Result<()> {
+ pub fn write_status(&self, status: &Status, status_width: usize) -> IoResult<()> {
let mut stdout = StandardStream::stdout(self.color_choice);
stdout.set_color(ColorSpec::new().set_fg(Some(match status {
Status::Bare | Status::Unknown => Color::Red,
@@ -42,13 +43,13 @@ impl ColorHarness {
}
/// Writes the input [`&str`] to `stdout` in bold.
- pub fn write_bold(&self, input: &str, newline: bool) -> io::Result<()> {
+ pub fn write_bold(&self, input: &str, newline: bool) -> IoResult<()> {
self.write_color(input, newline, ColorSpec::new().set_bold(true))
}
/// Writes the input [`&str`] to `stdout` in gray (or cyan if in compatibility mode).
- pub fn write_gray(&self, input: &str, newline: bool) -> io::Result<()> {
- // FIXME: check why Color::Rg(128, 128, 128) breaks in tmux on macOS Terminal.app.
+ pub fn write_gray(&self, input: &str, newline: bool) -> IoResult<()> {
+ // FIXME(nick): check why Color::Rg(128, 128, 128) breaks in tmux on macOS Terminal.app.
self.write_color(
input,
newline,
@@ -59,12 +60,7 @@ impl ColorHarness {
)
}
- fn write_color(
- &self,
- input: &str,
- newline: bool,
- color_spec: &mut ColorSpec,
- ) -> io::Result<()> {
+ fn write_color(&self, input: &str, newline: bool, color_spec: &mut ColorSpec) -> IoResult<()> {
let mut stdout = StandardStream::stdout(self.color_choice);
stdout.set_color(color_spec)?;
match newline {
diff --git a/crates/gfold/src/error.rs b/crates/gfold/src/error.rs
index 6eeda29..dd2ec3a 100644
--- a/crates/gfold/src/error.rs
+++ b/crates/gfold/src/error.rs
@@ -3,6 +3,16 @@
use std::path::PathBuf;
use thiserror::Error;
+// Type aliases for external results.
+pub type AnyhowResult<T> = anyhow::Result<T>;
+pub type IoResult<T> = std::io::Result<T>;
+pub type LibGitResult<T> = std::result::Result<T, git2::Error>;
+pub type SerdeJsonResult<T> = serde_json::error::Result<T>;
+pub type TomlResult<T> = std::result::Result<T, toml::ser::Error>;
+
+// Type alias for internal errors.
+pub type Result<T> = std::result::Result<T, Error>;
+
#[derive(Error, Debug)]
pub enum Error {
#[error("received None (Option<&OsStr>) for file name: {0}")]
@@ -19,6 +29,8 @@ pub enum Error {
#[error("full shorthand for Git reference is invalid UTF-8")]
GitReferenceShorthandInvalid,
+ #[error("submodule name is invalid UTF-8")]
+ SubmoduleNameInvalid,
#[error("could not find home directory")]
HomeDirNotFound,
}
diff --git a/crates/gfold/src/main.rs b/crates/gfold/src/main.rs
index 6b7bdb6..074d357 100644
--- a/crates/gfold/src/main.rs
+++ b/crates/gfold/src/main.rs
@@ -7,6 +7,8 @@ use log::debug;
use log::LevelFilter;
use std::env;
+use crate::error::AnyhowResult;
+
mod cli;
mod config;
mod display;
@@ -17,7 +19,7 @@ mod status;
/// Initializes the logger based on the debug flag and `RUST_LOG` environment variable and calls
/// [`cli::parse_and_run()`] to generate a [`config::Config`] and eventually call [`run::run()`].
-fn main() -> anyhow::Result<()> {
+fn main() -> AnyhowResult<()> {
match env::var("RUST_LOG").is_err() {
true => Builder::new().filter_level(LevelFilter::Off).init(),
false => env_logger::init(),
@@ -36,13 +38,11 @@ mod tests {
use crate::report::{LabeledReports, Report};
use crate::status::Status;
- use anyhow::{anyhow, Result};
-
+ use anyhow::anyhow;
use git2::ErrorCode;
use git2::Oid;
use git2::Repository;
use git2::Signature;
-
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
use std::fs::File;
@@ -54,7 +54,7 @@ mod tests {
/// [`tempfile`](tempfile) crate to create some repositories with varying states and levels
/// of nesting.
#[test]
- fn integration() -> Result<()> {
+ fn integration() -> AnyhowResult<()> {
env_logger::builder()
.is_test(true)
.filter_level(LevelFilter::Info)
@@ -138,9 +138,30 @@ mod tests {
.ok_or_else(|| anyhow!("could not convert PathBuf to &str"))?
.to_string();
let mut expected_reports_raw = vec![
- Report::new(&repo_one, "HEAD", &Status::Unclean, None, None)?,
- Report::new(&repo_two, "HEAD", &Status::Clean, None, None)?,
- Report::new(&repo_three, "HEAD", &Status::Clean, None, None)?,
+ Report::new(
+ &repo_one,
+ "HEAD",
+ &Status::Unclean,
+ None,
+ None,
+ Vec::with_capacity(0),
+ )?,
+ Report::new(
+ &repo_two,
+ "HEAD",
+ &Status::Clean,
+ None,
+ None,
+ Vec::with_capacity(0),
+ )?,
+ Report::new(
+ &repo_three,
+ "HEAD",
+ &Status::Clean,
+ None,
+ None,
+ Vec::with_capacity(0),
+ )?,
];
expected_reports_raw.sort_by(|a, b| a.name.cmp(&b.name));
expected_reports.insert(Some(expected_reports_key), expected_reports_raw);
@@ -157,14 +178,23 @@ mod tests {
&Status::Clean,
Some("https://github.com/nickgerace/gfold".to_string()),
None,
+ Vec::with_capacity(0),
+ )?,
+ Report::new(
+ &repo_five,
+ "HEAD",
+ &Status::Unclean,
+ None,
+ None,
+ Vec::with_capacity(0),
)?,
- Report::new(&repo_five, "HEAD", &Status::Unclean, None, None)?,
Report::new(
&repo_six,
"master",
&Status::Unpushed,
Some("https://github.com/nickgerace/gfold".to_string()),
None,
+ Vec::with_capacity(0),
)?,
Report::new(
&repo_seven,
@@ -172,6 +202,7 @@ mod tests {
&Status::Unpushed,
Some("https://github.com/nickgerace/gfold".to_string()),
None,
+ Vec::with_capacity(0),
)?,
];
nested_expected_reports_raw.sort_by(|a, b| a.name.cmp(&b.name));
@@ -197,7 +228,7 @@ mod tests {
Ok(())
}
- fn create_directory<P: AsRef<Path>>(parent: P, name: &str) -> Result<PathBuf> {
+ fn create_directory<P: AsRef<Path>>(parent: P, name: &str) -> AnyhowResult<PathBuf> {
let parent = parent.as_ref();
let new_directory = parent.join(name);
@@ -213,13 +244,13 @@ mod tests {
Ok(new_directory)
}
- fn create_file<P: AsRef<Path>>(parent: P) -> Result<()> {
+ fn create_file<P: AsRef<Path>>(parent: P) -> AnyhowResult<()> {
let parent = parent.as_ref();
File::create(parent.join("file"))?;
Ok(())
}
- fn commit_head_and_create_branch(repository: &Repository, name: &str) -> Result<()> {
+ fn commit_head_and_create_branch(repository: &Repository, name: &str) -> AnyhowResult<()> {
// We need to commit at least once before branching.
let commit_oid = commit(repository, "HEAD")?;
let commit = repository.find_commit(commit_oid)?;
@@ -228,7 +259,7 @@ mod tests {
}
// Source: https://github.com/rust-lang/git2-rs/pull/885
- fn commit(repository: &Repository, update_ref: &str) -> Result<Oid> {
+ fn commit(repository: &Repository, update_ref: &str) -> AnyhowResult<Oid> {
// We will commit the contents of the index.
let mut index = repository.index()?;
let tree_oid = index.write_tree()?;
diff --git a/crates/gfold/src/report.rs b/crates/gfold/src/report.rs
index df8f523..e72a995 100644
--- a/crates/gfold/src/report.rs
+++ b/crates/gfold/src/report.rs
@@ -8,7 +8,7 @@ use std::collections::BTreeMap;
use std::path::Path;
use crate::config::DisplayMode;
-use crate::error::Error;
+use crate::error::{AnyhowResult, Error, LibGitResult, Result};
use crate::status::Status;
mod target;
@@ -18,7 +18,7 @@ const HEAD: &str = "HEAD";
/// This type represents a [`BTreeMap`] using an optional [`String`] for keys, which represents the
/// parent directory for a group of reports ([`Vec<Report>`]). The values corresponding to those keys
/// are the actual groups of reports.
-// NOTE: We use a BTreeMap over a HashMap for sorted keys.
+// NOTE(nick): We use a BTreeMap over a HashMap for sorted keys.
pub type LabeledReports = BTreeMap<Option<String>, Vec<Report>>;
/// A collection of results for a Git repository at a given path.
@@ -36,8 +36,17 @@ pub struct Report {
/// The remote origin URL. The value will be `None` if the URL cannot be found.
pub url: Option<String>,
- /// The "user.email" of a Git config that's only collected when using [`DisplayMode::Standard`].
+ /// The "user.email" of a Git config that's only collected when using [`DisplayMode::Standard`]
+ /// and [`DisplayMode::Json`].
pub email: Option<String>,
+ /// The submodules of a repository that are only collected when using [`DisplayMode::Json`].
+ pub submodules: Vec<SubmoduleReport>,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
+pub struct SubmoduleReport {
+ pub name: String,
+ pub status: Status,
}
impl Report {
@@ -47,7 +56,8 @@ impl Report {
status: &Status,
url: Option<String>,
email: Option<String>,
- ) -> Result<Self, crate::error::Error> {
+ submodules: Vec<SubmoduleReport>,
+ ) -> Result<Self> {
Ok(Self {
name: match path.file_name() {
Some(s) => match s.to_str() {
@@ -67,22 +77,24 @@ impl Report {
},
url,
email,
+ submodules,
})
}
}
/// Generate [`LabeledReports`] for a given path and its children. The [`DisplayMode`] is required
/// because any two display modes can require differing amounts of data to be collected.
-pub fn generate_reports(path: &Path, display_mode: &DisplayMode) -> anyhow::Result<LabeledReports> {
- let include_email = match display_mode {
- DisplayMode::Standard | DisplayMode::Json => true,
- DisplayMode::Classic => false,
+pub fn generate_reports(path: &Path, display_mode: &DisplayMode) -> AnyhowResult<LabeledReports> {
+ let (include_email, include_submodules) = match display_mode {
+ DisplayMode::Classic => (false, false),
+ DisplayMode::Json => (true, true),
+ DisplayMode::Standard => (true, false),
};
let unprocessed = target::generate_targets(path.to_path_buf())?
.par_iter()
- .map(|path| generate_report(path, include_email))
- .collect::<Vec<anyhow::Result<Report>>>();
+ .map(|path| generate_report(path, include_email, include_submodules))
+ .collect::<Vec<AnyhowResult<Report>>>();
let mut processed = LabeledReports::new();
for wrapped_report in unprocessed {
@@ -102,7 +114,11 @@ pub fn generate_reports(path: &Path, display_mode: &DisplayMode) -> anyhow::Resu
}
/// Generates a report with a given path.
-fn generate_report(repo_path: &Path, include_email: bool) -> anyhow::Result<Report> {
+fn generate_report(
+ repo_path: &Path,
+ include_email: bool,
+ include_submodules: bool,
+) -> AnyhowResult<Report> {
debug!(
"attempting to generate report for repository at path: {:?}",
repo_path
@@ -112,18 +128,36 @@ fn generate_report(repo_path: &Path, include_email: bool) -> anyhow::Result<Repo
Ok(repo) => repo,
Err(e) if e.message() == "unsupported extension name extensions.worktreeconfig" => {
error!("skipping error ({e}) until upstream libgit2 issue is resolved: https://github.com/libgit2/libgit2/issues/6044");
- let unknown_report = Report::new(repo_path, "unknown", &Status::Unknown, None, None)?;
+ let unknown_report = Report::new(
+ repo_path,
+ "unknown",
+ &Status::Unknown,
+ None,
+ None,
+ Vec::with_capacity(0),
+ )?;
return Ok(unknown_report);
}
Err(e) => return Err(e.into()),
};
+ let (status, head, remote) = find_status(&repo)?;
- let head = match repo.head() {
- Ok(head) => Some(head),
- Err(ref e) if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound => {
- None
+ let submodules = if include_submodules {
+ let mut submodules = Vec::new();
+ for submodule in repo.submodules()? {
+ if let Ok(subrepo) = submodule.open() {
+ let (status, _, _) = find_status(&subrepo)?;
+ let name = submodule.name().ok_or(Error::SubmoduleNameInvalid)?;
+
+ submodules.push(SubmoduleReport {
+ name: name.to_string(),
+ status,
+ });
+ }
}
- Err(e) => return Err(e.into()),
+ submodules
+ } else {
+ Vec::with_capacity(0)
};
let branch = match &head {
@@ -133,16 +167,42 @@ fn generate_report(repo_path: &Path, include_email: bool) -> anyhow::Result<Repo
None => HEAD,
};
+ let url = match remote {
+ Some(remote) => remote.url().map(|s| s.to_string()),
+ None => None,
+ };
+
+ let email = match include_email {
+ true => get_email(&repo),
+ false => None,
+ };
+
+ debug!(
+ "finalized report collection for repository at path: {:?}",
+ repo_path
+ );
+ Ok(Report::new(
+ repo_path, branch, &status, url, email, submodules,
+ )?)
+}
+
+/// Find the [`Status`] for a given [`Repository`](git2::Repository). The
+/// [`head`](Option<git2::Reference>) and [`remote`](Option<git2::Remote>) are also returned.
+fn find_status(repo: &Repository) -> AnyhowResult<(Status, Option<Reference>, Option<Remote>)> {
+ let head = match repo.head() {
+ Ok(head) => Some(head),
+ Err(ref e) if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound => {
+ None
+ }
+ Err(e) => return Err(e.into()),
+ };
+
// Greedily chooses a remote if "origin" is not found.
let (remote, remote_name) = match repo.find_remote("origin") {
Ok(origin) => (Some(origin), Some("origin".to_string())),
- Err(e) if e.code() == ErrorCode::NotFound => choose_remote_greedily(&repo)?,
+ Err(e) if e.code() == ErrorCode::NotFound => choose_remote_greedily(repo)?,
Err(e) => return Err(e.into()),
};
- let url = match remote {
- Some(remote) => remote.url().map(|s| s.to_string()),
- None => None,
- };
// We'll include all untracked files and directories in the status options.
let mut opts = StatusOptions::new();
@@ -153,7 +213,7 @@ fn generate_report(repo_path: &Path, include_email: bool) -> anyhow::Result<Repo
let status = match repo.statuses(Some(&mut opts)) {
Ok(v) if v.is_empty() => match &head {
Some(head) => match remote_name {
- Some(remote_name) => match is_unpushed(&repo, head, &remote_name)? {
+ Some(remote_name) => match is_unpushed(repo, head, &remote_name)? {
true => Status::Unpushed,
false => Status::Clean,
},
@@ -166,24 +226,11 @@ fn generate_report(repo_path: &Path, include_email: bool) -> anyhow::Result<Repo
Err(e) => return Err(e.into()),
};
- let email = match include_email {
- true => get_email(&repo),
- false => None,
- };
-
- debug!(
- "finalized report collection for repository at path: {:?}",
- repo_path
- );
- Ok(Report::new(repo_path, branch, &status, url, email)?)
+ Ok((status, head, remote))
}
/// Checks if local commit(s) on the current branch have not yet been pushed to the remote.
-fn is_unpushed(
- repo: &Repository,
- head: &Reference,
- remote_name: &str,
-) -> Result<bool, git2::Error> {
+fn is_unpushed(repo: &Repository, head: &Reference, remote_name: &str) -> LibGitResult<bool> {
let local_head = head.peel_to_commit()?;
let remote = format!(
"{}/{}",
@@ -248,7 +295,7 @@ fn get_email(repository: &Repository) -> Option<String> {
fn choose_remote_greedily(
repository: &Repository,
-) -> Result<(Option<Remote>, Option<String>), git2::Error> {
+) -> LibGitResult<(Option<Remote>, Option<String>)> {
let remotes = repository.remotes()?;
Ok(match remotes.get(0) {
Some(remote_name) => (
diff --git a/crates/gfold/src/report/target.rs b/crates/gfold/src/report/target.rs
index abc836a..f9475ce 100644
--- a/crates/gfold/src/report/target.rs
+++ b/crates/gfold/src/report/target.rs
@@ -6,6 +6,8 @@ use std::fs::DirEntry;
use std::path::PathBuf;
use std::{fs, io};
+use crate::error::IoResult;
+
enum TargetOption {
Multiple(Vec<PathBuf>),
Single(PathBuf),
@@ -15,7 +17,7 @@ enum TargetOption {
/// Ensure the entry is a directory and is not hidden. Then, check if a Git sub directory exists,
/// which will indicate if the entry is a repository. Finally, generate targets based on that
/// repository.
-fn process_entry(entry: &DirEntry) -> io::Result<TargetOption> {
+fn process_entry(entry: &DirEntry) -> IoResult<TargetOption> {
match entry.file_type()?.is_dir()
&& !entry
.file_name()
@@ -40,7 +42,7 @@ fn process_entry(entry: &DirEntry) -> io::Result<TargetOption> {
/// Generate targets from a given [`PathBuf`] based on its children (recursively).
/// We use recursion paired with [`rayon`] since we prioritize speed over memory use.
-pub fn generate_targets(path: PathBuf) -> io::Result<Vec<PathBuf>> {
+pub fn generate_targets(path: PathBuf) -> IoResult<Vec<PathBuf>> {
let entries: Vec<DirEntry> = match fs::read_dir(&path) {
Ok(o) => o.filter_map(|r| r.ok()).collect(),
Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {
@@ -56,7 +58,7 @@ pub fn generate_targets(path: PathBuf) -> io::Result<Vec<PathBuf>> {
let processed = entries
.par_iter()
.map(process_entry)
- .collect::<Vec<io::Result<TargetOption>>>();
+ .collect::<Vec<IoResult<TargetOption>>>();
let mut results = Vec::new();
for entry in processed {
diff --git a/crates/gfold/src/run.rs b/crates/gfold/src/run.rs
index a67b9cf..442c5e4 100644
--- a/crates/gfold/src/run.rs
+++ b/crates/gfold/src/run.rs
@@ -1,12 +1,13 @@
//! This module contains the execution logic for generating reports and displaying them to `stdout`.
use crate::config::Config;
+use crate::error::AnyhowResult;
use crate::{display, report};
/// This function is the primary entrypoint for the crate. It takes a given config and performs
/// the end-to-end workflow using it. At this point, all CLI and config file options should be
/// set, merged, ignored, etc.
-pub fn run(config: &Config) -> anyhow::Result<()> {
+pub fn run(config: &Config) -> AnyhowResult<()> {
let reports = report::generate_reports(&config.path, &config.display_mode)?;
display::display(&config.display_mode, &reports, &config.color_mode)
}
diff --git a/docs/DEVELOPING.md b/docs/DEVELOPING.md
index d4ed78e..8402799 100644
--- a/docs/DEVELOPING.md
+++ b/docs/DEVELOPING.md
@@ -10,7 +10,7 @@ First, update dependencies and tidy your changes.
cargo fmt
cargo update
cargo fix --edition-idioms --allow-dirty --allow-staged
-cargo clippy --all-features --all-targets
+cargo clippy --all-features --all-targets --no-deps
```
Now, ensure that lints, tests, and builds succeed.
@@ -18,7 +18,7 @@ Now, ensure that lints, tests, and builds succeed.
```shell
cargo fmt --all -- --check
cargo clippy -- -D warnings
-RUSTDOCFLAGS="-Dwarnings" cargo doc --all
+RUSTDOCFLAGS="-Dwarnings" cargo doc --all --no-deps
cargo test -- --nocapture
cargo build --all-targets
```
diff --git a/docs/MANUAL_INSTALL.md b/docs/MANUAL_INSTALL.md
index 02e7473..d89a074 100644
--- a/docs/MANUAL_INSTALL.md
+++ b/docs/MANUAL_INSTALL.md
@@ -78,12 +78,16 @@ rm /usr/local/bin/gfold
rm $HOME/.config/gfold.toml
```
-## Build From Source Locally with Cargo
+## Build From Source Locally On All Platforms
If you want to install from source locally, and not from [crates.io](https://crates.io/crates/gfold), you can clone the repository and build `gfold`.
This should work on all major platforms.
-```shell
+```bash
git clone https://github.com/nickgerace/gfold.git
-cargo install --path gfold
-``` \ No newline at end of file
+cd gfold; cargo install --path crates/gfold
+```
+
+The commands above were tested on macOS.
+Slight modification may be required for your platform, but the flow should be the same: clone, change directory and run
+`cargo install`. \ No newline at end of file
diff --git a/docs/RELEASE.md b/docs/RELEASE.md
index b46ea97..4b87ee0 100644
--- a/docs/RELEASE.md
+++ b/docs/RELEASE.md
@@ -16,8 +16,8 @@ Steps should be executed in sequential order.
cargo fmt --all -- --check
cargo clippy -- -D warnings
cargo test
-RUSTDOCFLAGS="-Dwarnings" cargo doc --all
-cargo build
+RUSTDOCFLAGS="-Dwarnings" cargo doc --all --no-deps
+cargo build --all-targets
```
- [ ] Create and _do not merge_ a commit with the following message: `Update to <tag>`