notes/
git.rs

1#[cfg(test)]
2use mockall::automock;
3
4use crate::console_output::ConsoleOutput;
5use crate::default_error::DefaultError;
6use crate::note::Note;
7use crate::shell::Shell;
8
9#[cfg_attr(test, automock)]
10pub trait Git {
11    fn init(&self) -> Result<ConsoleOutput, DefaultError>;
12    fn commit(&self, note: &Note, message: &str) -> Result<ConsoleOutput, DefaultError>;
13    fn has_changed(&self, note: &Note) -> bool;
14    fn push(&self) -> Result<ConsoleOutput, DefaultError>;
15    fn pull(&self) -> Result<ConsoleOutput, DefaultError>;
16}
17
18pub struct GitImpl<'a> {
19    shell: &'a dyn Shell,
20}
21
22impl<'a> GitImpl<'a> {
23    pub fn new(shell: &'a dyn Shell) -> GitImpl<'a> {
24        GitImpl { shell }
25    }
26}
27
28impl<'a> Git for GitImpl<'a> {
29    fn init(&self) -> Result<ConsoleOutput, DefaultError> {
30        match self.shell.execute_in_repo("git init") {
31            Ok(o) => Ok(o.into()),
32            Err(e) => Err(e),
33        }
34    }
35
36    fn commit(&self, note: &Note, message: &str) -> Result<ConsoleOutput, DefaultError> {
37        let path = &note.path.to_str().unwrap();
38        let mut out = ConsoleOutput::empty();
39        out.append_command_output(self.shell.execute_in_repo(format!("git add '{}'", path).as_str())?);
40        out.append_command_output(self.shell.execute_in_repo(format!("git commit -m '{}' '{}'", message, path).as_str())?);
41        Ok(out)
42    }
43
44    fn has_changed(&self, note: &Note) -> bool {
45        let path = note.path.to_str().unwrap();
46        self.shell
47            .execute_in_repo(format!("git add '{p}' && git diff --exit-code HEAD '{p}' > /dev/null", p = path).as_str())
48            .is_err()
49    }
50
51    fn push(&self) -> Result<ConsoleOutput, DefaultError> {
52        match self.shell.execute_interactive_in_repo("git push") {
53            Ok(o) => Ok(o.into()),
54            Err(e) => Err(e),
55        }
56    }
57
58    fn pull(&self) -> Result<ConsoleOutput, DefaultError> {
59        match self.shell.execute_interactive_in_repo("git pull") {
60            Ok(o) => Ok(o.into()),
61            Err(e) => Err(e),
62        }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use crate::shell::{CommandOutput, MockShell};
70    use std::path::PathBuf;
71
72    fn test_note() -> Note {
73        Note::from(0, PathBuf::from("/repository/test.md"), "# Title\nContent\n".to_string()).unwrap()
74    }
75
76    #[test]
77    fn init() {
78        let mut shell_mock = MockShell::new();
79        let exp_command = "git init";
80        shell_mock
81            .expect_execute_in_repo()
82            .times(1)
83            .withf(move |c| c == exp_command)
84            .returning(|_| Ok(CommandOutput::default()));
85
86        let git = GitImpl::new(&shell_mock);
87        git.init().unwrap();
88    }
89
90    #[test]
91    fn commit() {
92        let note = test_note();
93
94        let mut shell_mock = MockShell::new();
95        let exp_command = format!("git add '{}'", note.path.to_str().unwrap());
96        shell_mock
97            .expect_execute_in_repo()
98            .times(1)
99            .withf(move |c| c == exp_command)
100            .returning(|_| Ok(CommandOutput::default()));
101
102        let exp_command = format!("git commit -m 'message' '{}'", note.path.to_str().unwrap());
103        shell_mock
104            .expect_execute_in_repo()
105            .times(1)
106            .withf(move |c| c == exp_command)
107            .returning(|_| Ok(CommandOutput::default()));
108
109        let git = GitImpl::new(&shell_mock);
110        git.commit(&note, "message").unwrap();
111    }
112
113    #[test]
114    fn has_changed() {
115        let note = test_note();
116
117        let mut shell_mock = MockShell::new();
118        let exp_command = format!("git add '{p}' && git diff --exit-code HEAD '{p}' > /dev/null", p = note.path.to_str().unwrap());
119        shell_mock
120            .expect_execute_in_repo()
121            .times(1)
122            .withf(move |c| c == exp_command)
123            .returning(|_| Err(DefaultError::new("".to_string())));
124
125        let git = GitImpl::new(&shell_mock);
126        assert!(git.has_changed(&note));
127    }
128
129    #[test]
130    fn push() {
131        let mut shell_mock = MockShell::new();
132        let exp_command = "git push";
133        shell_mock
134            .expect_execute_interactive_in_repo()
135            .times(1)
136            .withf(move |c| c == exp_command)
137            .returning(|_| Ok(CommandOutput::default()));
138
139        let git = GitImpl::new(&shell_mock);
140        git.push().unwrap();
141    }
142
143    #[test]
144    fn pull() {
145        let mut shell_mock = MockShell::new();
146        let exp_command = "git pull";
147        shell_mock
148            .expect_execute_interactive_in_repo()
149            .times(1)
150            .withf(move |c| c == exp_command)
151            .returning(|_| Ok(CommandOutput::default()));
152
153        let git = GitImpl::new(&shell_mock);
154        git.pull().unwrap();
155    }
156}