1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use std::process::Command;

#[cfg(test)]
use mockall::{automock, predicate::*};
#[cfg_attr(test, automock)]
pub trait CoauthorRepo {
    fn list(&self, show_keys: bool) -> Vec<String>;
    fn list_mob(&self) -> Vec<String>;
    fn get(&self, key: &str) -> Option<String>;
    fn remove(&self, key: &str);
    fn add(&self, key: &str, coauthor: &str);
    fn add_to_mob(&self, coauthor: &str);
    fn clear_mob(&self);
}

pub struct GitConfigCoauthorRepo {}
impl GitConfigCoauthorRepo {
    const COAUTHORS_SECTION: &str = "coauthors";
    const COAUTHORS_MOB_SECTION: &str = "coauthors-mob";
    const COAUTHOR_MOB_KEY: &str = "entry";
}
type Gccr = GitConfigCoauthorRepo;

impl CoauthorRepo for GitConfigCoauthorRepo {
    fn list(&self, show_keys: bool) -> Vec<String> {
        let section = Gccr::COAUTHORS_SECTION;
        let search_regex = format!("^{section}\\.");

        let output = Command::new("git")
            .args(["config", "--global", "--get-regexp", &search_regex])
            .output()
            .expect("failed to execute process");

        String::from_utf8(output.stdout)
            .expect("failed to convert stdout to string")
            .lines()
            .map(|x| {
                let delimeter = match show_keys {
                    true => format!("{section}."),
                    false => " ".to_owned(),
                };
                x.split_once(&delimeter)
                    .expect("failed to split string")
                    .1
                    .to_owned()
            })
            .collect()
    }

    fn list_mob(&self) -> Vec<String> {
        let full_key = format!("{}.{}", Gccr::COAUTHORS_MOB_SECTION, Gccr::COAUTHOR_MOB_KEY);

        let output = Command::new("git")
            .args(["config", "--global", "--get-all", &full_key])
            .output()
            .expect("failed to execute process");

        String::from_utf8(output.stdout)
            .expect("failed to convert stdout to string")
            .lines()
            .map(|x| x.to_owned())
            .collect()
    }

    fn get(&self, key: &str) -> Option<String> {
        let full_key = format!("{}.{key}", Gccr::COAUTHORS_SECTION);

        let output = Command::new("git")
            .args(["config", "--global", &full_key])
            .output()
            .expect("failed to execute process");

        match output.status.success() {
            true => Some(
                String::from_utf8(output.stdout)
                    .expect("failed to convert stdout to string")
                    .trim()
                    .to_owned(),
            ),
            false => None,
        }
    }

    fn remove(&self, key: &str) {
        let full_key = format!("{}.{key}", Gccr::COAUTHORS_SECTION);

        let status = Command::new("git")
            .args(["config", "--global", "--unset-all", &full_key])
            .status()
            .expect("failed to execute process");
        assert!(status.success());
    }

    fn add(&self, key: &str, coauthor: &str) {
        let full_key = format!("{}.{key}", Gccr::COAUTHORS_SECTION);

        let status = Command::new("git")
            .args(["config", "--global", &full_key, coauthor])
            .status()
            .expect("failed to execute process");
        assert!(status.success());
    }

    fn add_to_mob(&self, coauthor: &str) {
        let full_key = format!("{}.{}", Gccr::COAUTHORS_MOB_SECTION, Gccr::COAUTHOR_MOB_KEY);

        let status = Command::new("git")
            .args(["config", "--global", "--add", &full_key, coauthor])
            .status()
            .expect("failed to execute process");
        assert!(status.success());
    }

    fn clear_mob(&self) {
        let section = Gccr::COAUTHORS_MOB_SECTION.to_owned();

        Command::new("git")
            .args(["config", "--global", "--remove-section", &section])
            .output()
            .expect("failed to execute process");
    }
}