Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ name = "git-mob"
path = "src/main.rs"

[dependencies]
anyhow = "1.0"
clap = { version = "4.5.41", features = ["derive"] }
inquire = "0.7.5"
path-clean = "1.0.1"
Expand Down
7 changes: 3 additions & 4 deletions src/commands/mob.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::Result;
use crate::repositories::{MobSessionRepo, TeamMemberRepo};
use anyhow::bail;
use clap::{Parser, arg};
use inquire::MultiSelect;
use std::io::Write;
Expand Down Expand Up @@ -72,9 +73,7 @@ impl Mob {
Some([]) => {
let team_members = team_member_repo.list(false)?;
if team_members.is_empty() {
return Err(
"No team member(s) found. At least one team member must be added".into(),
);
bail!("No team member(s) found. At least one team member must be added");
}

let result = MultiSelect::new("Select active co-author(s):", team_members)
Expand All @@ -100,7 +99,7 @@ impl Mob {
mob_repo.add_coauthor(&team_member)?;
coauthors.push(team_member);
}
None => return Err(format!("No team member found with key: {key}").into()),
None => bail!("No team member found with key: {key}"),
}
}

Expand Down
10 changes: 6 additions & 4 deletions src/commands/setup.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::Result;
use anyhow::{anyhow, bail};
use clap::Parser;
use path_clean::PathClean;
use std::{
Expand Down Expand Up @@ -41,7 +42,7 @@ impl Setup {
Some(hooks_dir) => hooks_dir,
None => {
let new_hooks_dir = env::home_dir()
.ok_or("Failed to get home directory")?
.ok_or_else(|| anyhow!("Failed to get home directory"))?
.join(".git")
.join("hooks")
.clean();
Expand Down Expand Up @@ -73,7 +74,7 @@ impl Setup {
fn handle_local(&self, out: &mut impl Write) -> Result<()> {
let hooks_dir = match Self::get_hooks_dir("--local")? {
Some(hooks_dir) => hooks_dir,
None => return Err("Local githooks directory is not set".into()),
None => bail!("Local githooks directory is not set"),
};

let prepare_commit_msg_path = hooks_dir.join("prepare-commit-msg").clean();
Expand Down Expand Up @@ -108,7 +109,8 @@ impl Setup {
return Ok(Some(hooks_dir));
}

let mut expanded_hooks_dir = env::home_dir().ok_or("Failed to get home directory")?;
let mut expanded_hooks_dir =
env::home_dir().ok_or_else(|| anyhow!("Failed to get home directory"))?;
expanded_hooks_dir.extend(hooks_dir.components().skip(1));
Ok(Some(expanded_hooks_dir.clean()))
}
Expand All @@ -120,7 +122,7 @@ impl Setup {
.status()?;

if !status.success() {
return Err(format!("Failed to set global githooks directory to {path_str}").into());
bail!("Failed to set global githooks directory to {path_str}");
}

writeln!(out, "Set global githooks directory: {path_str}")?;
Expand Down
3 changes: 2 additions & 1 deletion src/commands/team_member.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::Result;
use crate::repositories::TeamMemberRepo;
use anyhow::bail;
use clap::{Parser, arg};
use std::io::Write;

Expand Down Expand Up @@ -32,7 +33,7 @@ impl TeamMember {
if let Some(key) = self.delete.as_deref() {
match team_member_repo.get(key)? {
Some(_) => team_member_repo.remove(key)?,
None => return Err(format!("No team member found with key: {key}").into()),
None => bail!("No team member found with key: {key}"),
}
}
if self.list {
Expand Down
5 changes: 4 additions & 1 deletion src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::Result;
use anyhow::Context;
use std::process::Command;

pub struct CmdOutput {
Expand All @@ -19,7 +20,9 @@ pub struct StdCommandRunner;

impl CommandRunner for StdCommandRunner {
fn execute(&self, program: &str, args: &[&str]) -> Result<CmdOutput> {
let output = Command::new(program).args(args).output()?;
let output = Command::new(program).args(args).output().with_context(|| {
format!("Failed to execute command: {} {}", program, args.join(" "))
})?;

Ok(CmdOutput {
stdout: output.stdout,
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub type Result<T> = anyhow::Result<T>;

pub mod cli;
mod commands;
Expand Down
17 changes: 11 additions & 6 deletions src/repositories/mob_session_repo.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::Result;
use crate::helpers::{CmdOutput, CommandRunner};
use anyhow::{Context, bail};

#[cfg(test)]
use mockall::{automock, predicate::*};
Expand All @@ -23,8 +24,8 @@ impl<Cmd: CommandRunner> GitConfigMobRepo<Cmd> {

fn git_config_error<T>(output: &CmdOutput) -> Result<T> {
match output.status_code {
Some(code) => Err(format!("Git config command exited with status code: {code}").into()),
None => Err("Git config command terminated by signal".into()),
Some(code) => bail!("Git config command exited with status code: {code}"),
None => bail!("Git config command terminated by signal"),
}
}
}
Expand All @@ -35,10 +36,12 @@ impl<Cmd: CommandRunner> MobSessionRepo for GitConfigMobRepo<Cmd> {

let output = self
.command_runner
.execute("git", &["config", "--global", "--get-all", &full_key])?;
.execute("git", &["config", "--global", "--get-all", &full_key])
.context("Failed to list coauthors from git config")?;

match output.status_code {
Some(Self::EXIT_CODE_SUCCESS) => Ok(String::from_utf8(output.stdout)?
Some(Self::EXIT_CODE_SUCCESS) => Ok(String::from_utf8(output.stdout)
.context("Failed to parse git config output as UTF-8")?
.lines()
.map(|x| x.into())
.collect()),
Expand All @@ -52,7 +55,8 @@ impl<Cmd: CommandRunner> MobSessionRepo for GitConfigMobRepo<Cmd> {

let output = self
.command_runner
.execute("git", &["config", "--global", "--add", &full_key, coauthor])?;
.execute("git", &["config", "--global", "--add", &full_key, coauthor])
.with_context(|| format!("Failed to add coauthor '{coauthor}' to git config"))?;

match output.status_code {
Some(Self::EXIT_CODE_SUCCESS) => Ok(()),
Expand All @@ -68,7 +72,8 @@ impl<Cmd: CommandRunner> MobSessionRepo for GitConfigMobRepo<Cmd> {

let output = self
.command_runner
.execute("git", &["config", "--global", "--remove-section", &section])?;
.execute("git", &["config", "--global", "--remove-section", &section])
.context("Failed to clear mob session from git config")?;

match output.status_code {
Some(Self::EXIT_CODE_SUCCESS) => Ok(()),
Expand Down
40 changes: 26 additions & 14 deletions src/repositories/team_member_repo.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::Result;
use crate::helpers::{CmdOutput, CommandRunner};
use anyhow::{Context, anyhow, bail};

#[cfg(test)]
use mockall::{automock, predicate::*};
Expand All @@ -24,8 +25,8 @@ impl<Cmd: CommandRunner> GitConfigTeamMemberRepo<Cmd> {

fn git_config_error<T>(output: &CmdOutput) -> Result<T> {
match output.status_code {
Some(code) => Err(format!("Git config command exited with status code: {code}").into()),
None => Err("Git config command terminated by signal".into()),
Some(code) => bail!("Git config command exited with status code: {code}"),
None => bail!("Git config command terminated by signal"),
}
}
}
Expand All @@ -35,10 +36,13 @@ impl<Cmd: CommandRunner> TeamMemberRepo for GitConfigTeamMemberRepo<Cmd> {
let section = Self::COAUTHORS_SECTION;
let search_regex = format!("^{section}\\.");

let output = self.command_runner.execute(
"git",
&["config", "--global", "--get-regexp", &search_regex],
)?;
let output = self
.command_runner
.execute(
"git",
&["config", "--global", "--get-regexp", &search_regex],
)
.context("Failed to list team members from git config")?;

match output.status_code {
Some(Self::EXIT_CODE_SUCCESS) => String::from_utf8(output.stdout)?
Expand All @@ -50,7 +54,7 @@ impl<Cmd: CommandRunner> TeamMemberRepo for GitConfigTeamMemberRepo<Cmd> {
" ".to_owned()
};
x.split_once(&delimiter)
.ok_or(format!("Failed to split string: '{x}'").into())
.ok_or_else(|| anyhow!("Failed to split string: '{x}'"))
.map(|(_, team_member)| team_member.to_owned())
})
.collect(),
Expand All @@ -65,12 +69,16 @@ impl<Cmd: CommandRunner> TeamMemberRepo for GitConfigTeamMemberRepo<Cmd> {

let output = self
.command_runner
.execute("git", &["config", "--global", &full_key])?;
.execute("git", &["config", "--global", &full_key])
.with_context(|| format!("Failed to get team member '{key}' from git config"))?;

match output.status_code {
Some(Self::EXIT_CODE_SUCCESS) => {
Ok(Some(String::from_utf8(output.stdout)?.trim().into()))
}
Some(Self::EXIT_CODE_SUCCESS) => Ok(Some(
String::from_utf8(output.stdout)
.context("Failed to parse git config output as UTF-8")?
.trim()
.into(),
)),
Some(Self::EXIT_CODE_CONFIG_INVALID_KEY) => Ok(None),
_ => Self::git_config_error(&output),
}
Expand All @@ -81,7 +89,8 @@ impl<Cmd: CommandRunner> TeamMemberRepo for GitConfigTeamMemberRepo<Cmd> {

let output = self
.command_runner
.execute("git", &["config", "--global", "--unset-all", &full_key])?;
.execute("git", &["config", "--global", "--unset-all", &full_key])
.with_context(|| format!("Failed to remove team member '{key}' from git config"))?;

match output.status_code {
Some(Self::EXIT_CODE_SUCCESS) => Ok(()),
Expand All @@ -94,11 +103,14 @@ impl<Cmd: CommandRunner> TeamMemberRepo for GitConfigTeamMemberRepo<Cmd> {

let output = self
.command_runner
.execute("git", &["config", "--global", &full_key, team_member])?;
.execute("git", &["config", "--global", &full_key, team_member])
.with_context(|| format!("Failed to add team member '{key}' to git config"))?;

match output.status_code {
Some(Self::EXIT_CODE_SUCCESS) => Ok(()),
Some(Self::EXIT_CODE_CONFIG_INVALID_KEY) => Err(format!("Invalid key: {key}").into()),
Some(Self::EXIT_CODE_CONFIG_INVALID_KEY) => {
bail!("Invalid key: {key}")
}
_ => Self::git_config_error(&output),
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/mob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ fn test_mob_with_by_key_when_team_member_not_found(
.assert()
.failure()
.stderr(predicate::str::diff(
"Error: \"No team member found with key: jk\"\n",
"Error: No team member found with key: jk\n",
));

Ok(())
Expand All @@ -61,7 +61,7 @@ fn test_mob_with_multiselect_given_no_team_members_added(
.assert()
.failure()
.stderr(predicate::str::diff(
"Error: \"No team member(s) found. At least one team member must be added\"\n",
"Error: No team member(s) found. At least one team member must be added\n",
));

Ok(())
Expand Down
4 changes: 2 additions & 2 deletions tests/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ fn test_setup_given_invalid_git_config_global_path(
.assert()
.failure()
.stderr(predicate::str::contains(
"Error: \"Failed to set global githooks directory to",
"Error: Failed to set global githooks directory to",
));

Ok(())
Expand All @@ -203,7 +203,7 @@ fn test_setup_local_given_hooks_dir_not_set(ctx: TestContextRepo) -> Result<(),
.args(["mob", "setup", "--local"])
.assert()
.failure()
.stderr("Error: \"Local githooks directory is not set\"\n");
.stderr("Error: Local githooks directory is not set\n");

Ok(())
}
Expand Down
4 changes: 2 additions & 2 deletions tests/team_member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ fn test_add_member_with_invalid_key(ctx: TestContextCli) -> Result<(), Box<dyn E
.assert()
.failure()
.stderr(predicate::str::diff(
"Error: \"Invalid key: invalid_key_with_underscore\"\n",
"Error: Invalid key: invalid_key_with_underscore\n",
));

Ok(())
Expand Down Expand Up @@ -139,7 +139,7 @@ fn test_delete_member_when_member_not_found(ctx: TestContextCli) -> Result<(), B
.assert()
.failure()
.stderr(predicate::str::diff(
"Error: \"No team member found with key: lm\"\n",
"Error: No team member found with key: lm\n",
));

Ok(())
Expand Down
Loading