git_clean/
commands.rs

1use std::collections::BTreeSet;
2use std::io::Error as IOError;
3use std::process::{Command, ExitStatus, Output, Stdio};
4
5use branches::Branches;
6use error::Error;
7use options::Options;
8
9pub fn run_command_with_no_output(args: &[&str]) {
10    Command::new(args[0])
11        .args(&args[1..])
12        .stdin(Stdio::null())
13        .stdout(Stdio::null())
14        .stderr(Stdio::null())
15        .output()
16        .unwrap_or_else(|e| panic!("Error with command: {}", e));
17}
18
19pub fn output(args: &[&str]) -> String {
20    let result = run_command(args);
21    String::from_utf8(result.stdout).unwrap().trim().to_owned()
22}
23
24pub fn run_command(args: &[&str]) -> Output {
25    run_command_with_result(args).unwrap_or_else(|e| panic!("Error with command: {}", e))
26}
27
28pub fn run_command_with_result(args: &[&str]) -> Result<Output, IOError> {
29    Command::new(args[0]).args(&args[1..]).output()
30}
31
32pub fn run_command_with_status(args: &[&str]) -> Result<ExitStatus, IOError> {
33    Command::new(args[0])
34        .args(&args[1..])
35        .stdin(Stdio::null())
36        .stdout(Stdio::null())
37        .stderr(Stdio::null())
38        .status()
39}
40
41pub fn validate_git_installation() -> Result<(), Error> {
42    match Command::new("git").output() {
43        Ok(_) => Ok(()),
44        Err(_) => Err(Error::GitInstallation),
45    }
46}
47
48pub fn delete_local_branches(branches: &Branches) -> String {
49    // https://git-scm.com/docs/git-branch
50    // With a -d or -D option, <branchname> will be deleted. You may specify more than one branch
51    // for deletion.
52    //
53    // So we can work without xargs.
54    if branches.vec.is_empty() {
55        String::default()
56    } else {
57        let delete_branches_args =
58            branches
59                .vec
60                .iter()
61                .fold(vec!["git", "branch", "-D"], |mut acc, b| {
62                    acc.push(b);
63                    acc
64                });
65        let delete_branches_cmd = run_command(&delete_branches_args);
66        String::from_utf8(delete_branches_cmd.stdout).unwrap()
67    }
68}
69
70pub fn delete_remote_branches(branches: &Branches, options: &Options) -> String {
71    let remote_branches_cmd = run_command(&["git", "branch", "-r"]);
72
73    let s = String::from_utf8(remote_branches_cmd.stdout).unwrap();
74    let all_remote_branches = s.split('\n').collect::<Vec<&str>>();
75    let origin_for_trim = &format!("{}/", &options.remote)[..];
76    let b_tree_remotes = all_remote_branches
77        .iter()
78        .map(|b| b.trim().trim_start_matches(origin_for_trim).to_owned())
79        .collect::<BTreeSet<String>>();
80
81    let mut b_tree_branches = BTreeSet::new();
82
83    for branch in branches.vec.clone() {
84        b_tree_branches.insert(branch);
85    }
86
87    let intersection: Vec<_> = b_tree_remotes
88        .intersection(&b_tree_branches)
89        .cloned()
90        .collect();
91
92    let stderr = if intersection.is_empty() {
93        String::default()
94    } else {
95        let delete_branches_args = intersection.iter().fold(
96            vec!["git", "push", &options.remote, "--delete"],
97            |mut acc, b| {
98                acc.push(b);
99                acc
100            },
101        );
102        let delete_remote_branches_cmd = run_command(&delete_branches_args);
103        String::from_utf8(delete_remote_branches_cmd.stderr).unwrap()
104    };
105
106    // Everything is written to stderr, so we need to process that
107    let split = stderr.split('\n');
108    let vec: Vec<&str> = split.collect();
109    let mut output = vec![];
110    for s in vec {
111        if s.contains("error: unable to delete '") {
112            let branch = s
113                .trim_start_matches("error: unable to delete '")
114                .trim_end_matches("': remote ref does not exist");
115
116            output.push(branch.to_owned() + " was already deleted in the remote.");
117        } else if s.contains(" - [deleted]") {
118            output.push(s.to_owned());
119        }
120    }
121
122    output.join("\n")
123}
124
125#[cfg(test)]
126mod test {
127
128    use regex::Regex;
129
130    // `spawn_piped` was removed so this test is somewhat outdated.
131    // It now tests the match operation for which `grep` was used before.
132    #[test]
133    fn test_spawn_piped() {
134        let echo = Regex::new("foo\n").unwrap();
135        assert_eq!(
136            echo.captures_iter("foo\nbar\nbaz")
137                .fold(String::new(), |mut acc, e| {
138                    acc.push_str(&e[0]);
139                    acc
140                }),
141            "foo\n"
142        );
143    }
144}