harvest/
git_stash.rs

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}