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 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 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}