1use std::error::Error;
2use std::fmt;
3use std::process::Command;
4use std::str;
5
6pub trait GitStashExecutable {
7 fn execute_git_stash_command(&self, command: &str) -> Result<Vec<u8>, GitError>;
8 fn stash_show(&self, n: usize) -> Result<Vec<u8>, GitError>;
9 fn stash_patch_show(&self, n: usize) -> Result<Vec<u8>, GitError>;
10}
11
12pub struct GitStashExecutor;
13
14impl GitStashExecutor {
15 pub fn new() -> Self {
16 GitStashExecutor
17 }
18}
19
20impl GitStashExecutable for GitStashExecutor {
21 fn execute_git_stash_command(&self, command: &str) -> Result<Vec<u8>, GitError> {
22 let git_command = format!("git stash {}", command);
23 let result = Command::new("sh")
24 .arg("-c")
25 .arg(git_command)
26 .output()
27 .unwrap();
28 if result.status.success() {
29 Ok(result.stdout)
30 } else {
31 Err(GitError {
32 msg: String::from_utf8(result.stderr).unwrap(),
33 })
34 }
35 }
36
37 fn stash_show(&self, n: usize) -> Result<Vec<u8>, GitError> {
38 let show_command = format!("show stash@{{{}}}", n);
39 self.execute_git_stash_command(&show_command)
40 }
41
42 fn stash_patch_show(&self, n: usize) -> Result<Vec<u8>, GitError> {
43 let patch_show_command = format!("show -p stash@{{{}}}", n);
44 self.execute_git_stash_command(&patch_show_command)
45 }
46}
47
48#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
49pub struct GitStash {
50 pub number: usize,
51 pub list_output: String,
52 pub show_output: String,
53 pub patch_show_output: String,
54}
55
56impl GitStash {
57 fn new(
58 executor: &impl GitStashExecutable,
59 number: usize,
60 list_output: String,
61 ) -> Result<GitStash, GitError> {
62 let show_output = String::from_utf8(executor.stash_show(number)?).unwrap();
63 let patch_show_output = String::from_utf8(executor.stash_patch_show(number)?).unwrap();
64 Ok(GitStash {
65 number,
66 list_output,
67 show_output,
68 patch_show_output,
69 })
70 }
71}
72
73impl fmt::Display for GitStash {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 write!(f, "{}\n{}", self.list_output, self.show_output)
76 }
77}
78
79#[derive(Debug)]
80pub struct GitError {
81 msg: String,
82}
83
84impl fmt::Display for GitError {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 write!(f, "{}", self.msg)
87 }
88}
89
90impl Error for GitError {
91 fn source(&self) -> Option<&(dyn Error + 'static)> {
92 Some(self)
93 }
94}
95
96pub fn get_stashes(executor: impl GitStashExecutable) -> Result<Vec<GitStash>, Box<dyn Error>> {
97 let stash_lists = String::from_utf8(executor.execute_git_stash_command("list")?).unwrap();
98
99 let stashes = stash_lists
100 .lines()
101 .enumerate()
102 .map(|(number, list_output)| {
103 GitStash::new(&executor, number, String::from(list_output)).unwrap()
104 })
105 .collect();
106 Ok(stashes)
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 struct MockStashExecutor;
114 impl MockStashExecutor {
115 fn new() -> MockStashExecutor {
116 MockStashExecutor {}
117 }
118 }
119 impl GitStashExecutable for MockStashExecutor {
120 fn execute_git_stash_command(&self, _command: &str) -> Result<Vec<u8>, GitError> {
121 let sample =
122 b"stash@{0}: WIP on refactor: cddaae4 refactor create git_stash module".to_vec();
123 Ok(sample)
124 }
125 fn stash_show(&self, _n: usize) -> Result<Vec<u8>, GitError> {
126 let sample = br#" Cargo.lock | 21 +++++++++++++++++++++
127 Cargo.toml | 1 +
128 src/git_stash.rs | 8 ++++++++
129 3 files changed, 30 insertions(+)
130 "#
131 .to_vec();
132 Ok(sample)
133 }
134 fn stash_patch_show(&self, _n: usize) -> Result<Vec<u8>, GitError> {
135 let sample = br#"diff --git a/Cargo.lock b/Cargo.lock
136 index 295fdbb..6d9592b 100644
137 --- a/Cargo.lock
138 +++ b/Cargo.lock
139 @@ -87,6 +87,7 @@ name = "harvest"
140 version = "0.1.2"
141 dependencies = [
142 "clap",
143 + "mocktopus",
144 "pager",
145 ]
146 "#
147 .to_vec();
148
149 Ok(sample)
150 }
151 }
152
153 #[test]
154 fn test_git_stash_new() {
155 let mock_executor = MockStashExecutor::new();
156 let list_output =
157 String::from_utf8(mock_executor.execute_git_stash_command("list").unwrap()).unwrap();
158 let git_stash = GitStash::new(&mock_executor, 0, list_output).unwrap();
159
160 assert_eq!(
161 GitStash {
162 number: 0,
163 list_output: "stash@{0}: WIP on refactor: cddaae4 refactor create git_stash module"
164 .to_string(),
165 show_output: String::from_utf8(mock_executor.stash_show(0).unwrap()).unwrap(),
166 patch_show_output: String::from_utf8(mock_executor.stash_patch_show(0).unwrap())
167 .unwrap(),
168 },
169 git_stash
170 )
171 }
172
173 #[test]
174 fn test_get_stashes() {
175 let mock_executor = MockStashExecutor::new();
176 let v = vec![GitStash {
177 number: 0,
178 list_output: "stash@{0}: WIP on refactor: cddaae4 refactor create git_stash module"
179 .to_string(),
180 show_output: String::from_utf8(mock_executor.stash_show(0).unwrap()).unwrap(),
181 patch_show_output: String::from_utf8(mock_executor.stash_patch_show(0).unwrap())
182 .unwrap(),
183 }];
184
185 assert_eq!(v, get_stashes(mock_executor).unwrap())
186 }
187}