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
45                .process_changes()
46                .context("Failed to commit changes")?;
47        }
48
49        if !self.watch {
50            warn!("Watch is disabled");
51            return Ok(());
52        }
53
54        self.watcher.watch(
55            &self.repo_path,
56            |paths| {
57                self.log_changed_paths(paths);
58                self.repo.process_changes()
59            },
60            |path| self.path_filter.is_path_ignored(path),
61            shutdown_rx,
62        )
63    }
64
65    fn log_changed_paths(&self, paths: &[PathBuf]) {
66        let formatted_paths = paths
67            .iter()
68            .map(|p| p.strip_prefix(&self.repo_path).unwrap_or(p))
69            .map(|p| format!("  {}", p.display()))
70            .collect::<Vec<_>>()
71            .join("\n");
72
73        debug!("Detected changes:\n{formatted_paths}");
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use std::fs;
80
81    use git2::Repository;
82
83    use super::*;
84
85    #[test]
86    fn test_watch_false() -> Result<()> {
87        let temp_dir = tempfile::tempdir()?;
88        let repository = temp_dir.path().to_path_buf();
89        let git_repo = Repository::init(&repository)?;
90
91        // create unstaged change
92        fs::write(repository.join("foo.txt"), "bar")?;
93
94        let config = AppConfig {
95            repository,
96            commit_message: Some("test message".to_string()),
97            commit_on_start: false,
98            watch: false,
99            ..AppConfig::default()
100        };
101        let app = App::new(config)?;
102        app.run(None)?;
103
104        assert!(!git_repo.statuses(None)?.is_empty());
105        Ok(())
106    }
107
108    #[test]
109    fn test_commit_on_start() -> Result<()> {
110        let temp_dir = tempfile::tempdir()?;
111        let repository = temp_dir.path().to_path_buf();
112        let git_repo = Repository::init(&repository)?;
113
114        // create unstaged change
115        fs::write(repository.join("foo.txt"), "bar")?;
116
117        let config = AppConfig {
118            repository,
119            commit_message: Some("test message".to_string()),
120            commit_on_start: true,
121            watch: false,
122            ..AppConfig::default()
123        };
124        let app = App::new(config)?;
125        let result = app.run(None);
126        assert!(result.is_err());
127        assert!(result
128            .unwrap_err()
129            .to_string()
130            .eq("Failed to commit changes"));
131
132        assert!(!git_repo.statuses(None)?.is_empty());
133        Ok(())
134    }
135}