summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2022-04-16 22:34:08 +0000
committerGitHub <noreply@github.com>2022-04-16 22:34:08 +0000
commit4516907acec09ace341927c89d93d1f23c662e92 (patch)
tree448b3b7710e6959bbd822ec0b9904c0c01eabfb3
parentdf24f0d80db4ef3b4757d6a3fea6be344eae45fc (diff)
parent468e71e137833d589baa0a651599feb4258f1e84 (diff)
downloadgfold-4516907acec09ace341927c89d93d1f23c662e92.zip
Merge #191
191: Fix origin remote and config file lookup errors r=nickgerace a=nickgerace - Fix error when remote is not "origin" and fallback to alternate remotes if "origin" is not found - Add failure scenario to core integration test - Fix error where "gfold.toml" not existing would result in execution failure - Add config file lookup behavior to README - Remove debug flag in favor of RUST_LOG - This also results in the logging module being removed since setting the logging should be configured via RUST_LOG - Add RUST_LOG and RUST_BACKTRACE to README and CLI help troubleshooting sections - Update display flags in README to use `-d` - Update flag description for color to use "never" instead of "off" as one of its options - Add sequencing debug logs (e.g. "foo has completed") - Switch back to anyhow for its backtrace feature - Update to 4.0.0-rc.2 Closes #189 Closes #190 Closes #192 Co-authored-by: Nick Gerace <nickagerace@gmail.com>
-rw-r--r--CHANGELOG.md8
-rw-r--r--Cargo.lock147
-rw-r--r--Cargo.toml6
-rw-r--r--README.md64
-rw-r--r--docs/DEVELOPING.md4
-rw-r--r--docs/RELEASE.md2
-rw-r--r--scripts/bench-loosely/src/main.rs15
-rw-r--r--src/cli.rs47
-rw-r--r--src/cli/logging.rs17
-rw-r--r--src/config.rs27
-rw-r--r--src/display.rs8
-rw-r--r--src/error.rs11
-rw-r--r--src/main.rs126
-rw-r--r--src/report.rs66
-rw-r--r--src/report/target.rs7
-rw-r--r--src/result.rs6
-rw-r--r--src/run.rs2
17 files changed, 326 insertions, 237 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce4a116..4b279a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,14 +17,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- [git2-rs](https://github.com/rust-lang/git2-rs), which replaces `git` subcommand usage
- Even though `git` subcommands were used over **git2-rs** to reduce binary size, significant speed increases could only be achieved by using the latter.
- JSON output flag for both version and results printing
+- Troubleshooting section to CLI help
+- Troubleshooting section to README for using `RUST_LOG` and `RUST_BACKTRACE`
### Changed
- Config file location from `<prefix>/gfold/gfold.json` to `<prefix>/gfold.toml`
- Config file type from JSON to TOML
+- CLI help sections to be divided by headers
- Major performance improvements due to moving from sequential target generation to nested, parallel iterators for target generation
- Module layout
- - `cli`, `display`, and `report` modules now contain their children: `logging`, `color`, and `target` respectively.
+ - `display` now contains its child, `color`
+ - `report` now contains its child, `target`
- `target` is a new submodule of `display` since they are within the same bounded context, but the former is a subdomain of the latter.
- `color` now uses a harness rather than individual functions.
- Grey color default to avoid a bug where the `stdout` color is not refreshed within `tmux` when using macOS `Terminal.app`
@@ -33,7 +37,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Removed
-- `anyhow` dependency (reduced binary size) in favor of internal `Result` type
+- Debug flag in favor of using `RUST_LOG`
- Display of `none` fields for the standard (default) display of result (i.e. now, if an optional field was not found, it is not shown)
- Git path option for CLI and config file
- `git` subcommand usage
diff --git a/Cargo.lock b/Cargo.lock
index 386095e..baa57f5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,12 +3,27 @@
version = 3
[[package]]
-name = "ansi_term"
-version = "0.12.1"
+name = "addr2line"
+version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
dependencies = [
- "winapi",
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "anyhow"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
+dependencies = [
+ "backtrace",
]
[[package]]
@@ -58,6 +73,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
+name = "backtrace"
+version = "0.3.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -124,22 +154,6 @@ dependencies = [
]
[[package]]
-name = "ctor"
-version = "0.1.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
-dependencies = [
- "quote",
- "syn",
-]
-
-[[package]]
-name = "diff"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
-
-[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -199,14 +213,14 @@ dependencies = [
[[package]]
name = "gfold"
-version = "4.0.0-rc.1"
+version = "4.0.0-rc.2"
dependencies = [
+ "anyhow",
"argh",
"dirs",
"env_logger",
"git2",
"log",
- "pretty_assertions",
"rayon",
"serde",
"serde_json",
@@ -216,6 +230,12 @@ dependencies = [
]
[[package]]
+name = "gimli"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
+
+[[package]]
name = "git2"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -286,9 +306,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.121"
+version = "0.2.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
+checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd"
[[package]]
name = "libgit2-sys"
@@ -330,6 +350,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -339,6 +365,16 @@ dependencies = [
]
[[package]]
+name = "miniz_oxide"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
+dependencies = [
+ "adler",
+ "autocfg",
+]
+
+[[package]]
name = "num_cpus"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -349,12 +385,12 @@ dependencies = [
]
[[package]]
-name = "output_vt100"
-version = "0.1.3"
+name = "object"
+version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
+checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9"
dependencies = [
- "winapi",
+ "memchr",
]
[[package]]
@@ -365,45 +401,33 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pkg-config"
-version = "0.3.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
-
-[[package]]
-name = "pretty_assertions"
-version = "1.2.0"
+version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57c038cb5319b9c704bf9c227c261d275bfec0ad438118a2787ce47944fb228b"
-dependencies = [
- "ansi_term",
- "ctor",
- "diff",
- "output_vt100",
-]
+checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "proc-macro2"
-version = "1.0.36"
+version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
+checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
-version = "1.0.17"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58"
+checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
-version = "1.5.1"
+version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
+checksum = "fd249e82c21598a9a426a4e00dd7adc1d640b22445ec8545feef801d1a74c221"
dependencies = [
"autocfg",
"crossbeam-deque",
@@ -413,31 +437,30 @@ dependencies = [
[[package]]
name = "rayon-core"
-version = "1.9.1"
+version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
+checksum = "9f51245e1e62e1f1629cbfec37b5793bbabcaeb90f30e94d2ba03564687353e4"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
- "lazy_static",
"num_cpus",
]
[[package]]
name = "redox_syscall"
-version = "0.2.12"
+version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0"
+checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7776223e2696f1aa4c6b0170e83212f47296a00424305117d013dfe86fb0fe55"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
@@ -445,6 +468,12 @@ dependencies = [
]
[[package]]
+name = "rustc-demangle"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
+
+[[package]]
name = "ryu"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -489,9 +518,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "1.0.90"
+version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f"
+checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
dependencies = [
"proc-macro2",
"quote",
@@ -544,9 +573,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "toml"
-version = "0.5.8"
+version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
+checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
diff --git a/Cargo.toml b/Cargo.toml
index 1229fd9..4db83ac 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,9 +10,10 @@ readme = "README.md"
repository = "https://github.com/nickgerace/gfold/"
edition = "2021"
-version = "4.0.0-rc.1"
+version = "4.0.0-rc.2"
[dependencies]
+anyhow = { version = "1", features = ["backtrace"] }
argh = "0"
dirs = "4"
git2 = { version = "0", default_features = false }
@@ -28,9 +29,6 @@ toml = "0"
# Removed features: ["regex", "termcolor"]
env_logger = { version = "0", features = ["atty", "humantime"], default_features = false }
-[dev-dependencies]
-pretty_assertions = "1"
-
[profile.release]
codegen-units = 1
diff --git a/README.md b/README.md
index 2fb27c7..f0ae162 100644
--- a/README.md
+++ b/README.md
@@ -34,10 +34,11 @@ pam ~ /home/neloth/src/pam
neloth@solstheimcommunityserver.org
```
-The classic display mode can be toggled on with `--classic`.
+Want the classic display mode?
+Use `-d classic`.
```
-% gfold --classic
+% gfold -d classic
astrid unclean main git@github.com:db/astrid.git
fev bare main none
gb unpushed dev https://github.com/hrothgar/gb.git
@@ -69,7 +70,7 @@ Analysis is performed by leveraging the [git2-rs](https://github.com/rust-lang/g
Pass in `--help` flag to see all the options for using this application.
-```bash
+```shell
gfold
gfold ..
gfold $HOME
@@ -86,27 +87,35 @@ Upon execution, `gfold` will look for a config file at the following path on mac
$HOME/.config/gfold.toml
```
-On Windows, the config file is located at the following path:
+On Windows, the lookup path will be in a similar location.
```powershell
{FOLDERID_Profile}\.config\gfold.toml
```
-Creating and using the config file is entirely optional, and you can ignore your config file at any time using the `-i` flag.
+Creating and using the config file is entirely optional.
-Here is an example creation workflow for a config file:
+For config file creation, you can use the `--dry-run` flag to print valid TOML.
+Here is an example config file creation workflow on macOS, Linux and similar platforms:
```shell
-gfold --classic ~/ --print > $HOME/.config/gfold.toml
+gfold -d classic -c never ~/ --dry-run > $HOME/.config/gfold.toml
```
-This config file will default to the classic display mode and set the default path to `$HOME`, rather than the current working directory.
-
Here are the contents of the resulting config file:
```toml
path = '/home/neloth'
display_mode = 'Classic'
+color_mode = 'Never'
+```
+
+Let's say you created a config file, but wish to execute `gfold` with entirely different settings _and_ you want to ensure that
+you do not accidentally inherit options from the config file.
+In that scenario you can ignore your config file by using the `-i` flag.
+
+```shell
+gfold -i
```
You can back up a config file and track its history with `git`.
@@ -191,6 +200,28 @@ The uninstall script can also be used for cleanup in the event of a failed insta
**Preferred package manager not listed:** please [file an issue](https://github.com/nickgerace/gfold/issues/new/choose)!
+## Compatibility
+
+`gfold` is intended to be ran on *any* tier one Rust 🦀 target.
+Please [file an issue](https://github.com/nickgerace/gfold/issues) if your platform is unsupported.
+
+## Troubleshooting
+
+If you encounter unexpected behavior or a bug, please [file an issue](https://github.com/nickgerace/gfold/issues) and debug
+locally with `RUST_BACKTRACE=1 RUST_LOG=debug` prepended when executing `gfold`.
+You can also adjust each variable, as needed, to aid investigation.
+Please attach relevant logs from execution with sensitive bits redacted in order to help resolve your issue.
+
+### Coreutils Collision on macOS
+
+If `fold` from [GNU Coreutils](https://www.gnu.org/software/coreutils/) is installed on macOS via `brew`, it will be named `gfold`.
+You can avoid this collision with shell aliases, shell functions, and/or `PATH` changes.
+Here is an example with the `o` dropped from `gfold`:
+
+```shell
+alias gfld=$HOME/.cargo/bin/gfold
+```
+
## Community
For more information and thanks to contributors, users, and the "community" at large, please refer to the **[THANKS](./docs/THANKS.md)** file.
@@ -209,18 +240,3 @@ For more information and thanks to contributors, users, and the "community" at l
- [nixpkgs](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/version-management/git-and-tools/gfold/default.nix) for the `gfold` package
- [AUR](https://github.com/orhun/PKGBUILDs) for the `gfold-git` (VCS/development) package
- In the past, this included the `gfold` and `gfold-bin` packages as well, they those have been deprecated in favor of the official community repository package above
-
-## Compatibility
-
-`gfold` is intended to be ran on *any* tier one Rust 🦀 target.
-Please [file an issue](https://github.com/nickgerace/gfold/issues) if your platform is unsupported.
-
-## Troubleshooting
-
-If `fold` from [GNU Coreutils](https://www.gnu.org/software/coreutils/) is installed on macOS via `brew`, it will be named `gfold`.
-You can avoid this collision with shell aliases, shell functions, and/or `PATH` changes.
-Here is an example with the `o` dropped from `gfold`:
-
-```shell
-alias gfld=$HOME/.cargo/bin/gfold
-```
diff --git a/docs/DEVELOPING.md b/docs/DEVELOPING.md
index d990684..23b6607 100644
--- a/docs/DEVELOPING.md
+++ b/docs/DEVELOPING.md
@@ -18,9 +18,9 @@ Now, ensure that lints, tests, and builds succeed.
```shell
cargo fmt --all -- --check
cargo clippy -- -D warnings
-cargo doc
+cargo doc --all
cargo test
-cargo build
+cargo build --all-targets
```
> Alternatively, you can replace `cargo test` above with [cargo nextest](https://github.com/nextest-rs/nextest).
diff --git a/docs/RELEASE.md b/docs/RELEASE.md
index 3c0f43e..7d177c5 100644
--- a/docs/RELEASE.md
+++ b/docs/RELEASE.md
@@ -23,7 +23,7 @@ cargo build
- [ ] Create and _do not merge_ a commit with the following message: `Update to <tag>`
- [ ] Test and verify the publishing workflow:
-```bash
+```shell
cargo publish --dry-run
```
diff --git a/scripts/bench-loosely/src/main.rs b/scripts/bench-loosely/src/main.rs
index 68d58f6..37da22d 100644
--- a/scripts/bench-loosely/src/main.rs
+++ b/scripts/bench-loosely/src/main.rs
@@ -1,7 +1,4 @@
-use std::fs;
-use std::fs::Metadata;
-use std::io;
-use std::path::{Path, PathBuf};
+use std::path::Path;
use std::process::Command;
use std::time::{Duration, Instant};
@@ -55,12 +52,10 @@ fn main() {
fn loose_bench(new: &Path, old: &Path, target: &Path) {
let new_duration = execute(new, target);
let old_duration = execute(old, target);
- let (new_text, old_text) = if new_duration > old_duration {
- ("LOST", "WON ")
- } else if new_duration < old_duration {
- ("WON ", "LOST")
- } else {
- ("TIE ", "TIE ")
+ let (new_text, old_text) = match new_duration {
+ new_duration if new_duration > old_duration => ("LOST", "WON "),
+ new_duration if new_duration < old_duration => ("WON ", "LOST"),
+ _ => ("TIE ", "TIE "),
};
println!(
diff --git a/src/cli.rs b/src/cli.rs
index 6ae5114..ed6c8fc 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -3,26 +3,32 @@
use crate::config::{ColorMode, Config, DisplayMode};
use crate::error::Error;
-use crate::result::Result;
use crate::run;
+use anyhow::Result;
use argh::FromArgs;
+use log::debug;
use std::env;
-mod logging;
-
#[derive(FromArgs)]
#[argh(description = "More information: https://github.com/nickgerace/gfold
-This application helps you keep track of multiple Git repositories via CLI.
-By default, it displays relevant information for all repos in the current
-working directory.
+Description:
+ This application helps you keep track of multiple Git repositories via CLI.
+ By default, it displays relevant information for all repos in the current
+ working directory.
+
+Config File Usage:
+ While CLI options are prioritized, default options will fallback to the
+ config file if it exists. Here is the config file lookup locations for some
+ common platforms:
-While CLI options are prioritized, default options will fallback to the config
-file if it exists. Here is the config file lookup locations for some common
-operating systems:
+ macOS, Linux, etc. $HOME/.config/gfold.toml
+ Windows {{FOLDERID_Profile}}\\.config\\gfold.toml
- macOS/Linux $HOME/.config/gfold.toml
- Windows {{FOLDERID_Profile}}\\.config\\gfold.toml")]
+Troubleshooting:
+ Investigate unexpected behavior by prepending execution with
+ \"RUST_BACKTRACE=1\"and \"RUST_LOG=debug\". You can adjust those variable's
+ values to aid investigation.")]
struct Args {
#[argh(
positional,
@@ -33,23 +39,18 @@ struct Args {
#[argh(
option,
short = 'c',
- description = "specify color mode [options: \"always\", \"compatibility\", \"off\"]"
+ description = "specify color mode [options: \"always\", \"compatibility\", \"never\"]"
)]
color_mode: Option<String>,
#[argh(
- switch,
- description = "enable debug logging (sets \"RUST_LOG\" to \"debug\")"
- )]
- debug: bool,
- #[argh(
option,
short = 'd',
- description = "specify display format [options: \"standard\"/\"default\", \"json\", \"classic\"]"
+ description = "specify display format [options: \"standard\" or \"default\", \"json\", \"classic\"]"
)]
display_mode: Option<String>,
#[argh(
switch,
- description = "display config options chosen, including those from the config file if they exist"
+ description = "display finalized config options and exit (merged options from an optional config file and command line arguments)"
)]
dry_run: bool,
#[argh(switch, short = 'i', description = "ignore config file settings")]
@@ -68,19 +69,20 @@ pub fn parse_and_run() -> Result<()> {
// First and foremost, get logging up and running. We want logs as quickly as possible for
// debugging by setting "RUST_LOG".
let args: Args = argh::from_env();
- logging::init(args.debug);
+ debug!("collected args");
let mut config = match args.ignore_config_file {
true => Config::new()?,
false => Config::try_config()?,
};
+ debug!("loaded initial config");
if let Some(found_display_mode) = &args.display_mode {
config.display_mode = match found_display_mode.to_lowercase().as_str() {
"classic" => DisplayMode::Classic,
"json" => DisplayMode::Json,
"standard" | "default" => DisplayMode::Standard,
- _ => return Err(Error::InvalidDisplayMode(found_display_mode.to_string())),
+ _ => return Err(Error::InvalidDisplayMode(found_display_mode.to_string()).into()),
}
}
@@ -99,7 +101,7 @@ pub fn parse_and_run() -> Result<()> {
"always" => ColorMode::Always,
"compatibility" => ColorMode::Compatibility,
"never" => ColorMode::Never,
- _ => return Err(Error::InvalidColorMode(found_color_mode.to_string())),
+ _ => return Err(Error::InvalidColorMode(found_color_mode.to_string()).into()),
}
}
@@ -107,6 +109,7 @@ pub fn parse_and_run() -> Result<()> {
config.path = env::current_dir()?.join(found_path).canonicalize()?;
}
+ debug!("finalized config options");
match &args.dry_run {
true => config.print(),
false => run::run(&config),
diff --git a/src/cli/logging.rs b/src/cli/logging.rs
deleted file mode 100644
index f0f1ac6..0000000
--- a/src/cli/logging.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-//! This module contains the logger initialization logic.
-
-use env_logger::Builder;
-use log::LevelFilter;
-use std::env;
-
-/// Initialize the logger based on the debug flag and `RUST_LOG` environment variable. The flag
-/// takes precedence over the environment variable.
-pub fn init(debug: bool) {
- match debug {
- true => Builder::new().filter_level(LevelFilter::Debug).init(),
- false => match env::var("RUST_LOG").is_err() {
- true => Builder::new().filter_level(LevelFilter::Off).init(),
- false => env_logger::init(),
- },
- }
-}
diff --git a/src/config.rs b/src/config.rs
index f898d55..db3537a 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,10 +1,10 @@
//! This module contains the config specification and functionality for creating a config.
use crate::error::Error;
-use crate::result::Result;
+use anyhow::Result;
use serde::{Deserialize, Serialize};
-use std::env;
use std::path::PathBuf;
+use std::{env, fs, io};
/// 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
@@ -65,29 +65,34 @@ pub enum ColorMode {
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
+ /// and uses [`EntryConfig`] as an intermediary struct. This is the primary method used when
/// creating a config.
pub fn try_config() -> Result<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, then we fall back to the
- // "EntryConfig" default before conversion.
+ // should be able to proceed with empty config files. If empty or not found, then we fall
+ // back to the "EntryConfig" default before conversion.
let home = dirs::home_dir().ok_or(Error::HomeDirNotFound)?;
let path = home.join(".config").join("gfold.toml");
- let contents = std::fs::read_to_string(path)?;
- let entry_config = match contents.is_empty() {
- true => EntryConfig::default(),
- false => toml::from_str(&contents)?,
+ let entry_config = match fs::read_to_string(path) {
+ Ok(contents) => match contents.is_empty() {
+ true => EntryConfig::default(),
+ false => toml::from_str(&contents)?,
+ },
+ Err(e) => match e.kind() {
+ io::ErrorKind::NotFound => EntryConfig::default(),
+ _ => return Err(e.into()),
+ },
};
Self::from_entry_config(&entry_config)
}
- /// This method does not look for the config file and uses "EntryConfig"'s defaults instead.
+ /// This method does not look for the config file and uses [`EntryConfig`]'s defaults instead.
/// It is best for testing use and when the user wishes to skip config file lookup.
pub fn new() -> Result<Self> {
Self::from_entry_config(&EntryConfig::default())
}
- // This method prints the full config (merged with config file, as needed) as valid, pretty TOML.
+ /// This method prints the full config (merged with config file, as needed) as valid, pretty TOML.
pub fn print(self) -> Result<()> {
print!("{}", toml::to_string_pretty(&self)?);
Ok(())
diff --git a/src/display.rs b/src/display.rs
index fc1a339..6440021 100644
--- a/src/display.rs
+++ b/src/display.rs
@@ -4,7 +4,8 @@ use crate::config::{ColorMode, DisplayMode};
use crate::display::color::ColorHarness;
use crate::error::Error;
use crate::report::LabeledReports;
-use crate::result::Result;
+use anyhow::Result;
+use log::debug;
use log::warn;
use std::path::Path;
@@ -21,13 +22,14 @@ pub fn display(
) -> Result<()> {
match display_mode {
DisplayMode::Standard => standard(reports, color_mode),
- DisplayMode::Json => Ok(json(reports)?),
+ DisplayMode::Json => json(reports),
DisplayMode::Classic => classic(reports, color_mode),
}
}
/// Display [`LabeledReports`] to `stdout` in the standard (default) format.
fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> Result<()> {
+ debug!("detected standard display mode");
let mut all_reports = Vec::new();
for grouped_report in reports {
all_reports.append(&mut grouped_report.1.clone());
@@ -71,6 +73,7 @@ fn standard(reports: &LabeledReports, color_mode: &ColorMode) -> Result<()> {
/// Display [`LabeledReports`] to `stdout` in JSON format.
fn json(reports: &LabeledReports) -> Result<()> {
+ debug!("detected json display mode");
let mut all_reports = Vec::new();
for grouped_report in reports {
all_reports.append(&mut grouped_report.1.clone());
@@ -83,6 +86,7 @@ fn json(reports: &LabeledReports) -> Result<()> {
/// Display [`LabeledReports`] to `stdout` in the classic format.
fn classic(reports: &LabeledReports, color_mode: &ColorMode) -> Result<()> {
+ debug!("detected classic display mode");
let color_harness = ColorHarness::new(color_mode);
let length = reports.keys().len();
diff --git a/src/error.rs b/src/error.rs
index 09fc0d2..6eeda29 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -21,15 +21,4 @@ pub enum Error {
GitReferenceShorthandInvalid,
#[error("could not find home directory")]
HomeDirNotFound,
-
- #[error("git2::Error")]
- Git2Rs(#[from] git2::Error),
- #[error("serde_json::Error")]
- SerdeJson(#[from] serde_json::Error),
- #[error("std::io::Error")]
- StdIo(#[from] std::io::Error),
- #[error("toml::de::Error")]
- TomlDe(#[from] toml::de::Error),
- #[error("toml::ser::Error")]
- TomlSe(#[from] toml::ser::Error),
}
diff --git a/src/main.rs b/src/main.rs
index ea39e87..f1c7c72 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,61 +2,48 @@
//! track of multiple Git repositories. The source code uses private modules rather than leveraging
//! a library via `lib.rs`.
-use crate::result::Result;
+use anyhow::Result;
+use env_logger::Builder;
+use log::debug;
+use log::LevelFilter;
+use std::env;
mod cli;
mod config;
mod display;
mod error;
mod report;
-mod result;
mod run;
mod status;
-/// Calls [`cli::parse_and_run()`] to generate a [`config::Config`] and eventually call [`run::run()`];
+/// 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() -> Result<()> {
- cli::parse_and_run()
+ match env::var("RUST_LOG").is_err() {
+ true => Builder::new().filter_level(LevelFilter::Off).init(),
+ false => env_logger::init(),
+ }
+ debug!("initialized logger");
+
+ cli::parse_and_run()?;
+ Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
- use crate::config::{Config, DisplayMode};
+ use crate::config::{ColorMode, Config, DisplayMode};
use crate::report::{LabeledReports, Report};
use crate::status::Status;
+ use git2::ErrorCode;
use git2::Repository;
- use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
- use std::path::Path;
+ use std::path::{Path, PathBuf};
use std::{env, fs, io};
#[test]
fn integration() {
- fn create_dir_or_die(path: &Path) {
- if let Err(e) = fs::create_dir(path) {
- if e.kind() != io::ErrorKind::AlreadyExists {
- panic!(
- "could not create directory ({:?}) due to error kind: {:?}",
- path,
- e.kind()
- );
- }
- }
- }
-
- fn create_file_or_die(path: &Path) {
- if let Err(e) = fs::File::create(path) {
- if e.kind() != io::ErrorKind::AlreadyExists {
- panic!(
- "could not create file ({:?}) due to error kind: {:?}",
- path,
- e.kind()
- );
- }
- }
- }
-
// Test directory structure within "target":
// └── test
// ├── bar
@@ -69,35 +56,64 @@ mod tests {
// ├── three
// └── two
- let cwd = env::current_dir().expect("failed to get current working directory");
- let target = cwd.join("target");
- create_dir_or_die(&target);
+ let manifest_directory = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ let target = manifest_directory.join("target");
+ create_directory(&target);
+
+ // Warning: setting up test directory by removing it and its contents recursively.
let test = target.join("test");
- create_dir_or_die(&test);
+ if let Err(e) = fs::remove_dir_all(&test) {
+ if e.kind() != io::ErrorKind::NotFound {
+ panic!(
+ "could not remove directory and its contents ({:?}) due to error kind: {:?}",
+ &test,
+ e.kind()
+ );
+ }
+ }
+ create_directory(&test);
+
for name in ["foo", "bar", "baz"] {
let current = test.join(name);
- create_dir_or_die(&current);
+ create_directory(&current);
Repository::init(&current).expect("could not initialize repository");
if name == "foo" {
- create_file_or_die(&current.join("newfile"));
+ create_file(&current.join("newfile"));
}
}
let nested = test.join("nested");
- create_dir_or_die(&nested);
+ create_directory(&nested);
for name in ["one", "two", "three"] {
let current = nested.join(name);
- create_dir_or_die(&current);
- Repository::init(&current).expect("could not initialize repository");
+ create_directory(&current);
+ let repository = Repository::init(&current).expect("could not initialize repository");
if name == "one" {
- create_file_or_die(&current.join("newfile"));
+ create_file(&current.join("newfile"));
+ }
+
+ if name == "two" {
+ if let Err(e) = repository.remote("origin", "https://github.com/nickgerace/gfold") {
+ if e.code() != ErrorCode::Exists {
+ panic!("{}", e);
+ }
+ }
+ }
+
+ if name == "three" {
+ if let Err(e) = repository.remote("fork", "https://github.com/nickgerace/gfold") {
+ if e.code() != ErrorCode::Exists {
+ panic!("{}", e);
+ }
+ }
}
}
let mut config = Config::new().expect("could not create new config");
config.path = test;
+ config.color_mode = ColorMode::Never;
assert!(run::run(&config).is_ok());
// Now, let's ensure our reports are what we expect.
@@ -139,7 +155,7 @@ mod tests {
&nested_test_dir.join("two"),
"HEAD",
&Status::Clean,
- None,
+ Some("https://github.com/nickgerace/gfold".to_string()),
None,
)
.expect("could not create report"),
@@ -147,7 +163,7 @@ mod tests {
&nested_test_dir.join("three"),
"HEAD",
&Status::Clean,
- None,
+ Some("https://github.com/nickgerace/gfold".to_string()),
None,
)
.expect("could not create report"),
@@ -168,4 +184,28 @@ mod tests {
assert_eq!(found_labeled_reports_sorted, expected_reports);
}
+
+ fn create_directory(path: &Path) {
+ if let Err(e) = fs::create_dir(path) {
+ if e.kind() != io::ErrorKind::AlreadyExists {
+ panic!(
+ "could not create directory ({:?}) due to error kind: {:?}",
+ path,
+ e.kind()
+ );
+ }
+ }
+ }
+
+ fn create_file(path: &Path) {
+ if let Err(e) = fs::File::create(path) {
+ if e.kind() != io::ErrorKind::AlreadyExists {
+ panic!(
+ "could not create file ({:?}) due to error kind: {:?}",
+ path,
+ e.kind()
+ );
+ }
+ }
+ }
}
diff --git a/src/report.rs b/src/report.rs
index bcf0dbb..70a2799 100644
--- a/src/report.rs
+++ b/src/report.rs
@@ -2,9 +2,9 @@
use crate::config::DisplayMode;
use crate::error::Error;
-use crate::result::Result;
use crate::status::Status;
-use git2::{ErrorCode, Reference, Repository, StatusOptions};
+use anyhow::Result;
+use git2::{ErrorCode, Reference, Remote, Repository, StatusOptions};
use log::{debug, trace};
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
@@ -52,16 +52,18 @@ impl Report {
name: match path.file_name() {
Some(s) => match s.to_str() {
Some(s) => s.to_string(),
- None => return Err(Error::FileNameToStrConversionFailure(path.to_path_buf())),
+ None => {
+ return Err(Error::FileNameToStrConversionFailure(path.to_path_buf()).into())
+ }
},
- None => return Err(Error::FileNameNotFound(path.to_path_buf())),
+ None => return Err(Error::FileNameNotFound(path.to_path_buf()).into()),
},
branch: (*branch).into(),
status: *status,
parent: match path.parent() {
Some(s) => match s.to_str() {
Some(s) => Some(s.to_string()),
- None => return Err(Error::PathToStrConversionFailure(s.to_path_buf())),
+ None => return Err(Error::PathToStrConversionFailure(s.to_path_buf()).into()),
},
None => None,
},
@@ -101,14 +103,17 @@ pub fn generate_reports(path: &Path, display_mode: &DisplayMode) -> Result<Label
/// Generates a report with a given path.
fn generate_report(repo_path: &Path, include_email: bool) -> Result<Report> {
- debug!("attemping to generate report for path: {:?}", repo_path);
+ debug!(
+ "attemping to generate report for repository at path: {:?}",
+ repo_path
+ );
let repo = Repository::open(repo_path)?;
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(Error::Git2Rs(e)),
+ Err(e) => return Err(e.into()),
};
let branch = match &head {
Some(head) => head
@@ -117,6 +122,17 @@ fn generate_report(repo_path: &Path, include_email: bool) -> Result<Report> {
None => HEAD,
};
+ // 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) => 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();
opts.include_untracked(true).recurse_untracked_dirs(true);
@@ -125,9 +141,12 @@ fn generate_report(repo_path: &Path, include_email: bool) -> Result<Report> {
// are no commits to push.
let status = match repo.statuses(Some(&mut opts)) {
Ok(v) if v.is_empty() => match &head {
- Some(head) => match is_unpushed(&repo, head)? {
- true => Status::Unpushed,
- false => Status::Clean,
+ Some(head) => match remote_name {
+ Some(remote_name) => match is_unpushed(&repo, head, &remote_name)? {
+ true => Status::Unpushed,
+ false => Status::Clean,
+ },
+ None => Status::Clean,
},
None => Status::Clean,
},
@@ -136,28 +155,24 @@ fn generate_report(repo_path: &Path, include_email: bool) -> Result<Report> {
Err(e) => return Err(e.into()),
};
- let url = match repo.find_remote("origin") {
- Ok(origin) => origin.url().map(|s| s.to_string()),
- Err(e) if e.code() == ErrorCode::NotFound => None,
- Err(e) => return Err(Error::Git2Rs(e)),
- };
let email = match include_email {
true => get_email(&repo),
false => None,
};
+
debug!(
- "generating report for repository at {:?} on branch {} with status {:?}, url {:?}, and email {:?}",
- &repo_path, &branch, &status, &url, &email
+ "finalized report collection for repository at path: {:?}",
+ repo_path
);
-
Report::new(repo_path, branch, &status, url, email)
}
/// Checks if local commit(s) on the current branch have not yet been pushed to the remote.
-fn is_unpushed(repo: &Repository, head: &Reference) -> Result<bool> {
+fn is_unpushed(repo: &Repository, head: &Reference, remote_name: &str) -> Result<bool> {
let local_head = head.peel_to_commit()?;
let remote = format!(
- "origin/{}",
+ "{}/{}",
+ remote_name,
match head.shorthand() {
Some(v) => v,
None => {
@@ -211,3 +226,14 @@ fn get_email(repository: &Repository) -> Option<String> {
}
None
}
+
+fn choose_remote_greedily(repository: &Repository) -> Result<(Option<Remote>, Option<String>)> {
+ let remotes = repository.remotes()?;
+ Ok(match remotes.get(0) {
+ Some(remote_name) => (
+ Some(repository.find_remote(remote_name)?),
+ Some(remote_name.to_string()),
+ ),
+ None => (None, None),
+ })
+}
diff --git a/src/report/target.rs b/src/report/target.rs
index 3d5383c..3401101 100644
--- a/src/report/target.rs
+++ b/src/report/target.rs
@@ -1,6 +1,6 @@
//! This module contains target generation logic for eventually generating reports.
-use log::{error, warn};
+use log::{debug, error, warn};
use rayon::prelude::*;
use std::fs::DirEntry;
use std::path::{Path, PathBuf};
@@ -24,7 +24,10 @@ fn process_entry(entry: &DirEntry) -> Targets {
let path = entry.path();
let git_sub_directory = path.join(".git");
match git_sub_directory.exists() && git_sub_directory.is_dir() {
- true => Ok(Some(vec![path])),
+ true => {
+ debug!("found target: {:?}", &path);
+ Ok(Some(vec![path]))
+ }
false => Ok(Some(recursive_target_gen(&path)?)),
}
}
diff --git a/src/result.rs b/src/result.rs
deleted file mode 100644
index 8f1d80a..0000000
--- a/src/result.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-//! This module contains the [`crate::result::Result`] type.
-
-use crate::error::Error;
-
-/// Generic [`std::result::Result`] wrapper around [`Error`].
-pub type Result<T> = std::result::Result<T, Error>;
diff --git a/src/run.rs b/src/run.rs
index 8c25e09..48f0a4d 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -1,8 +1,8 @@
//! This module contains the execution logic for generating reports and displaying them to `stdout`.
use crate::config::Config;
-use crate::result::Result;
use crate::{display, report};
+use anyhow::Result;
/// 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