gitwatch_rs/
app.rs

1use crate::{app_config::AppConfig, filter::PathFilter};
2use std::{path::PathBuf, sync::mpsc::Receiver};
3
4use anyhow::{Context, Result};
5use log::{debug, warn};
6
7use crate::{repo::GitwatchRepo, watcher::FileWatcher};
8
9pub struct App {
10    commit_on_start: bool,
11    path_filter: PathFilter,
12    repo: GitwatchRepo,
13    repo_path: PathBuf,
14    watch: bool,
15    watcher: FileWatcher,
16}
17
18impl App {
19    pub fn new(config: AppConfig) -> Result<Self> {
20        let repo_path = &config.repository;
21        let repo = GitwatchRepo::new(
22            repo_path,
23            config.commit_message,
24            config.commit_message_script,
25            config.ignore_regex.clone(),
26            config.dry_run,
27            config.remote,
28        )?;
29        let watcher = FileWatcher::new(config.debounce_seconds, config.retries);
30        let path_filter = PathFilter::new(repo_path, config.ignore_regex)?;
31
32        Ok(Self {
33            commit_on_start: config.commit_on_start,
34            path_filter,
35            repo,
36            repo_path: config.repository,
37            watch: config.watch,
38            watcher,
39        })
40    }
41
42    pub fn run(&self, shutdown_rx: Option<Receiver<()>>) -> Result<()> {
43        if self.commit_on_start {
44            self.repo.process_changes().context(format!(
45                "Failed to create initial commit in repo '{}'",
46                self.repo
47            ))?;
48        }
49
50        if !self.watch {
51            warn!("Watch is disabled");
52            return Ok(());
53        }
54
55        self.watcher.watch(
56            &self.repo_path,
57            |paths| {
58                self.log_changed_paths(paths);
59                self.repo.process_changes()
60            },
61            |path| self.path_filter.is_path_ignored(path),
62            shutdown_rx,
63        )
64    }
65
66    fn log_changed_paths(&self, paths: &[PathBuf]) {
67        let formatted_paths = paths
68            .iter()
69            .map(|p| p.strip_prefix(&self.repo_path).unwrap_or(p))
70            .map(|p| format!("  {}", p.display()))
71            .collect::<Vec<_>>()
72            .join("\n");
73
74        debug!("Detected changes:\n{}", formatted_paths);
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use std::fs;
81
82    use git2::Repository;
83
84    use super::*;
85
86    #[test]
87    fn test_watch_false() -> Result<()> {
88        let temp_dir = tempfile::tempdir()?;
89        let repository = temp_dir.path().to_path_buf();
90        let git_repo = Repository::init(&repository)?;
91
92        // create unstaged change
93        fs::write(repository.join("foo.txt"), "bar")?;
94
95        let config = AppConfig {
96            repository,
97            commit_message: Some("test message".to_string()),
98            commit_on_start: false,
99            watch: false,
100            ..AppConfig::default()
101        };
102        let app = App::new(config)?;
103        app.run(None)?;
104
105        assert!(!git_repo.statuses(None)?.is_empty());
106        Ok(())
107    }
108
109    #[test]
110    fn test_commit_on_start() -> Result<()> {
111        let temp_dir = tempfile::tempdir()?;
112        let repository = temp_dir.path().to_path_buf();
113        let git_repo = Repository::init(&repository)?;
114
115        // create unstaged change
116        fs::write(repository.join("foo.txt"), "bar")?;
117
118        let config = AppConfig {
119            repository,
120            commit_message: Some("test message".to_string()),
121            commit_on_start: true,
122            watch: false,
123            ..AppConfig::default()
124        };
125        let app = App::new(config)?;
126        let result = app.run(None);
127        assert!(result.is_err());
128        assert!(result.unwrap_err().to_string().contains(
129            format!(
130                "Failed to create initial commit in repo '{}'",
131                temp_dir.path().display()
132            )
133            .as_str()
134        ));
135
136        assert!(!git_repo.statuses(None)?.is_empty());
137        Ok(())
138    }
139}