git_mob_tool/
cli.rs

1use crate::coauthor_repo::CoauthorRepo;
2use crate::commands::{mob::Mob, setup::Setup, team_member::TeamMember};
3use clap::{Parser, Subcommand};
4use std::error::Error;
5use std::io::Write;
6use std::str;
7
8#[derive(Parser)]
9#[command(
10    author,
11    version,
12    about,
13    long_about,
14    bin_name = "git mob",
15    override_usage = "git mob [COMMAND] [OPTIONS]"
16)]
17#[command(propagate_version = true)]
18/// A CLI tool which can help users automatically add co-author(s) to git commits
19/// for pair/mob programming.
20///
21/// A user can attribute a git commit to more than one author by adding one or more
22/// Co-authored-by trailers to the commit's message.
23/// Co-authored commits are visible on GitHub.
24/// This CLI tool will helper users do the this automatically and also help them
25/// store and manage co-authors for pair/mob programming sessions.
26///
27/// Usage example:
28///
29/// git mob setup
30///
31/// git mob team-member --add lm "Leo Messi" leo.messi@example.com
32///
33/// git mob --with lm
34struct Cli {
35    #[command(subcommand)]
36    command: Option<Commands>,
37    #[command(flatten)]
38    mob: Mob,
39}
40
41#[derive(Subcommand)]
42enum Commands {
43    /// Create global prepare-commit-msg githook which appends Co-authored-by trailers to commit message
44    Setup(Setup),
45    /// Add/delete/list team member(s) from team member repository
46    ///
47    /// User must store team member(s) to team member repository by using keys
48    /// before starting pair/mob programming session(s).
49    #[clap(alias = "coauthor")] // alias for backward compatibility
50    TeamMember(TeamMember),
51}
52
53pub fn run(coauthor_repo: &impl CoauthorRepo, out: &mut impl Write) -> Result<(), Box<dyn Error>> {
54    let cli = Cli::parse();
55    run_inner(&cli, coauthor_repo, out)
56}
57
58fn run_inner(
59    cli: &Cli,
60    coauthor_repo: &impl CoauthorRepo,
61    out: &mut impl Write,
62) -> Result<(), Box<dyn Error>> {
63    match &cli.command {
64        None => cli.mob.handle(coauthor_repo, out)?,
65        Some(Commands::Setup(setup)) => setup.handle(out)?,
66        Some(Commands::TeamMember(team_member)) => team_member.handle(coauthor_repo, out)?,
67    }
68    Ok(())
69}
70
71#[cfg(test)]
72mod tests {
73    use std::error::Error;
74
75    use super::*;
76    use crate::coauthor_repo::MockCoauthorRepo;
77    use mockall::predicate;
78
79    #[test]
80    fn test_clear_mob_session() -> Result<(), Box<dyn Error>> {
81        let mut mock_coauthor_repo = MockCoauthorRepo::new();
82        mock_coauthor_repo
83            .expect_clear_mob()
84            .once()
85            .returning(|| Ok(()));
86
87        let cli = Cli {
88            command: None,
89            mob: Mob {
90                with: None,
91                clear: true,
92                list: false,
93                trailers: false,
94                add: None,
95            },
96        };
97
98        let mut out = Vec::new();
99        run_inner(&cli, &mock_coauthor_repo, &mut out)?;
100
101        Ok(())
102    }
103
104    #[test]
105    fn test_delete_team_member() -> Result<(), Box<dyn Error>> {
106        let key = "lm";
107        let mut mock_coauthor_repo = MockCoauthorRepo::new();
108        mock_coauthor_repo
109            .expect_get()
110            .with(predicate::eq(key))
111            .once()
112            .returning(|_| Ok(Some("Leo Messi <leo.messi@example.com>".to_owned())));
113        mock_coauthor_repo
114            .expect_remove()
115            .with(predicate::eq(key))
116            .once()
117            .returning(|_| Ok(()));
118
119        let cli = Cli {
120            command: Some(Commands::TeamMember(TeamMember {
121                delete: Some(key.to_owned()),
122                add: None,
123                list: false,
124            })),
125            mob: Mob {
126                with: None,
127                clear: false,
128                list: false,
129                trailers: false,
130                add: None,
131            },
132        };
133
134        let mut out = Vec::new();
135        run_inner(&cli, &mock_coauthor_repo, &mut out)?;
136
137        Ok(())
138    }
139}