Skip to content

Commit 468784c

Browse files
committed
Fixed: Support Windows #30
* replaced `touch` in windows context with `copy nul file.txt` * replaced `touch` in with `regex::Regex` * replaced `xargs` by giving multiple branch names to `git -d/-D' * tested on Windows and WSL Ubuntu
1 parent aa62f04 commit 468784c

File tree

8 files changed

+170
-138
lines changed

8 files changed

+170
-138
lines changed

Cargo.lock

+33
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ autotests = false
1111

1212
[dependencies]
1313
clap = "2.33.1"
14+
regex = "1.6"
1415

1516
[dev-dependencies]
1617
tempdir = "0.3"

src/branches.rs

+38-58
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use commands::*;
22
use error::Error;
33
use options::*;
4-
use std::io::{stdin, stdout, Read, Write};
4+
use regex::Regex;
5+
use std::io::{stdin, stdout, Write};
56

67
pub const COLUMN_SPACER_LENGTH: usize = 30;
78

@@ -42,84 +43,63 @@ impl Branches {
4243
println!("Updating remote {}", options.remote);
4344
run_command_with_no_output(&["git", "remote", "update", &options.remote, "--prune"]);
4445

45-
let merged_branches_regex =
46-
format!("\\*{branch}|\\s{branch}", branch = &options.base_branch);
47-
let merged_branches_filter = spawn_piped(&["grep", "-vE", &merged_branches_regex]);
46+
let merged_branches_regex = format!("^\\*?\\s*{}$", options.base_branch);
47+
let merged_branches_filter = Regex::new(&merged_branches_regex).unwrap();
4848
let merged_branches_cmd = run_command(&["git", "branch", "--merged"]);
49+
let merged_branches_output = std::str::from_utf8(&merged_branches_cmd.stdout).unwrap();
4950

50-
{
51-
merged_branches_filter
52-
.stdin
53-
.unwrap()
54-
.write_all(&merged_branches_cmd.stdout)
55-
.unwrap();
56-
}
51+
let merged_branches = merged_branches_output
52+
.lines()
53+
.fold(Vec::<String>::new(), |mut acc, line| {
54+
if !merged_branches_filter.is_match(line) {
55+
acc.push(line.trim().to_string());
56+
}
57+
acc
58+
});
5759

58-
let mut merged_branches_output = String::new();
59-
merged_branches_filter
60-
.stdout
61-
.unwrap()
62-
.read_to_string(&mut merged_branches_output)
63-
.unwrap();
64-
let mut merged_branches = merged_branches_output.split('\n').map(|b| b.trim().into());
65-
66-
let local_branches_regex =
67-
format!("\\*{branch}|\\s{branch}", branch = &options.base_branch);
68-
let local_branches_filter = spawn_piped(&["grep", "-vE", &local_branches_regex]);
60+
let local_branches_regex = format!("^\\*?\\s*{}$", options.base_branch);
61+
let local_branches_filter = Regex::new(&local_branches_regex).unwrap();
6962
let local_branches_cmd = run_command(&["git", "branch"]);
70-
71-
{
72-
local_branches_filter
73-
.stdin
74-
.unwrap()
75-
.write_all(&local_branches_cmd.stdout)
76-
.unwrap();
77-
}
78-
79-
let mut local_branches_output = String::new();
80-
local_branches_filter
81-
.stdout
82-
.unwrap()
83-
.read_to_string(&mut local_branches_output)
84-
.unwrap();
63+
let local_branches_output = std::str::from_utf8(&local_branches_cmd.stdout).unwrap();
8564

8665
let local_branches = local_branches_output
87-
.split('\n')
88-
.map(|b| b.trim().into())
66+
.lines()
67+
.fold(Vec::<String>::new(), |mut acc, line| {
68+
if !local_branches_filter.is_match(line) {
69+
acc.push(line.trim().to_string());
70+
}
71+
acc
72+
})
73+
.iter()
8974
.filter(|branch| !options.ignored_branches.contains(branch))
75+
.cloned()
9076
.collect::<Vec<String>>();
9177

92-
let remote_branches_regex = format!("(HEAD|{})", &options.base_branch);
93-
let remote_branches_filter = spawn_piped(&["grep", "-vE", &remote_branches_regex]);
78+
let remote_branches_regex = format!("\\b(HEAD|{})\\b", &options.base_branch);
79+
let remote_branches_filter = Regex::new(&remote_branches_regex).unwrap();
9480
let remote_branches_cmd = run_command(&["git", "branch", "-r"]);
81+
let remote_branches_output = std::str::from_utf8(&remote_branches_cmd.stdout).unwrap();
9582

96-
{
97-
remote_branches_filter
98-
.stdin
99-
.unwrap()
100-
.write_all(&remote_branches_cmd.stdout)
101-
.unwrap();
102-
}
103-
104-
let mut remote_branches_output = String::new();
105-
remote_branches_filter
106-
.stdout
107-
.unwrap()
108-
.read_to_string(&mut remote_branches_output)
109-
.unwrap();
110-
let mut remote_branches = remote_branches_output.split('\n').map(|b| b.trim().into());
83+
let remote_branches = remote_branches_output
84+
.lines()
85+
.fold(Vec::<String>::new(), |mut acc, line| {
86+
if !remote_branches_filter.is_match(line) {
87+
acc.push(line.trim().to_string());
88+
}
89+
acc
90+
});
11191

11292
for branch in local_branches {
11393
// First check if the local branch doesn't exist in the remote, it's the cheapest and easiest
11494
// way to determine if we want to suggest to delete it.
115-
if !remote_branches.any(|b: String| b == format!("{}/{}", &options.remote, branch)) {
95+
if !remote_branches.iter().any(|b: &String| *b == format!("{}/{}", &options.remote, branch)) {
11696
branches.push(branch.to_owned());
11797
continue;
11898
}
11999

120100
// If it does exist in the remote, check to see if it's listed in git branches --merged. If
121101
// it is, that means it wasn't merged using Github squashes, and we can suggest it.
122-
if merged_branches.any(|b: String| b == branch) {
102+
if merged_branches.iter().any(|b: &String| *b == branch) {
123103
branches.push(branch.to_owned());
124104
continue;
125105
}

src/commands.rs

+37-54
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
use std::collections::BTreeSet;
2-
use std::io::{Error as IOError, Read, Write};
3-
use std::process::{Child, Command, ExitStatus, Output, Stdio};
2+
use std::io::Error as IOError;
3+
use std::process::{Command, ExitStatus, Output, Stdio};
44

55
use branches::Branches;
66
use error::Error;
77
use options::Options;
88

9-
pub fn spawn_piped(args: &[&str]) -> Child {
10-
Command::new(&args[0])
11-
.args(&args[1..])
12-
.stdin(Stdio::piped())
13-
.stdout(Stdio::piped())
14-
.stderr(Stdio::piped())
15-
.spawn()
16-
.unwrap_or_else(|e| panic!("Error with child process: {}", e))
17-
}
18-
199
pub fn run_command_with_no_output(args: &[&str]) {
2010
Command::new(&args[0])
2111
.args(&args[1..])
@@ -56,27 +46,25 @@ pub fn validate_git_installation() -> Result<(), Error> {
5646
}
5747

5848
pub fn delete_local_branches(branches: &Branches) -> String {
59-
let xargs = spawn_piped(&["xargs", "git", "branch", "-D"]);
60-
61-
{
62-
xargs
63-
.stdin
64-
.unwrap()
65-
.write_all(branches.string.as_bytes())
66-
.unwrap()
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.vec.iter().fold(vec!["git", "branch", "-D"], |mut acc, b| {
59+
acc.push(b);
60+
acc
61+
});
62+
let delete_branches_cmd = run_command(&delete_branches_args);
63+
String::from_utf8(delete_branches_cmd.stdout).unwrap()
6764
}
68-
69-
let mut branches_delete_result = String::new();
70-
xargs
71-
.stdout
72-
.unwrap()
73-
.read_to_string(&mut branches_delete_result)
74-
.unwrap();
75-
branches_delete_result
7665
}
7766

7867
pub fn delete_remote_branches(branches: &Branches, options: &Options) -> String {
79-
let xargs = spawn_piped(&["xargs", "git", "push", &options.remote, "--delete"]);
8068

8169
let remote_branches_cmd = run_command(&["git", "branch", "-r"]);
8270

@@ -99,16 +87,17 @@ pub fn delete_remote_branches(branches: &Branches, options: &Options) -> String
9987
.cloned()
10088
.collect();
10189

102-
{
103-
xargs
104-
.stdin
105-
.unwrap()
106-
.write_all(intersection.join("\n").as_bytes())
107-
.unwrap()
108-
}
109-
110-
let mut stderr = String::new();
111-
xargs.stderr.unwrap().read_to_string(&mut stderr).unwrap();
90+
let stderr = if intersection.is_empty() {
91+
String::default()
92+
} else {
93+
let delete_branches_args =
94+
intersection.iter().fold(vec!["git", "push", &options.remote, "--delete"], |mut acc, b| {
95+
acc.push(b);
96+
acc
97+
});
98+
let delete_remote_branches_cmd = run_command(&delete_branches_args);
99+
String::from_utf8(delete_remote_branches_cmd.stderr).unwrap()
100+
};
112101

113102
// Everything is written to stderr, so we need to process that
114103
let split = stderr.split('\n');
@@ -131,24 +120,18 @@ pub fn delete_remote_branches(branches: &Branches, options: &Options) -> String
131120

132121
#[cfg(test)]
133122
mod test {
134-
use super::spawn_piped;
135123

136-
use std::io::{Read, Write};
124+
use regex::Regex;
137125

126+
// `spawn_piped` was removed so this test is somewhat outdated.
127+
// It now tests the match operation for which `grep` was used before.
138128
#[test]
139129
fn test_spawn_piped() {
140-
let echo = spawn_piped(&["grep", "foo"]);
141-
142-
{
143-
echo.stdin
144-
.unwrap()
145-
.write_all("foo\nbar\nbaz".as_bytes())
146-
.unwrap()
147-
}
148-
149-
let mut stdout = String::new();
150-
echo.stdout.unwrap().read_to_string(&mut stdout).unwrap();
151-
152-
assert_eq!(stdout, "foo\n");
130+
let echo = Regex::new("foo\n").unwrap();
131+
assert_eq!(echo.captures_iter("foo\nbar\nbaz")
132+
.fold(String::new(), |mut acc, e| {
133+
acc.push_str(&e[0]);
134+
acc
135+
}), "foo\n");
153136
}
154137
}

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
extern crate clap;
44

5+
extern crate regex;
6+
57
pub mod cli;
68

79
use clap::ArgMatches;

src/options.rs

+11-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clap::ArgMatches;
2-
use commands::{output, run_command, spawn_piped};
2+
use commands::{output, run_command};
33
use error::Error;
4-
use std::io::{Read, Write};
4+
use regex::Regex;
55

66
const DEFAULT_REMOTE: &str = "origin";
77
const DEFAULT_BRANCH: &str = "main";
@@ -77,18 +77,16 @@ impl Options {
7777
}
7878

7979
fn validate_remote(&self) -> Result<(), Error> {
80-
let grep = spawn_piped(&["grep", &self.remote]);
80+
let remote_rx = Regex::new(&self.remote).unwrap();
8181
let remotes = run_command(&["git", "remote"]);
82-
83-
{
84-
grep.stdin.unwrap().write_all(&remotes.stdout).unwrap();
85-
}
86-
87-
let mut remote_result = String::new();
88-
grep.stdout
89-
.unwrap()
90-
.read_to_string(&mut remote_result)
91-
.unwrap();
82+
let remotes_output = std::str::from_utf8(&remotes.stdout).unwrap();
83+
84+
let remote_result = remote_rx
85+
.captures_iter(remotes_output)
86+
.fold(String::new(), |mut acc, e| {
87+
acc.push_str(&e[0]);
88+
acc
89+
});
9290

9391
if remote_result.is_empty() {
9492
return Err(Error::InvalidRemote);

0 commit comments

Comments
 (0)