vinxen_git/
watcher.rs

1use crate::Result;
2use notify::{recommended_watcher, Event, RecommendedWatcher, RecursiveMode, Watcher};
3use serde::{Deserialize, Serialize};
4use std::path::{Path, PathBuf};
5use std::sync::mpsc::{channel, Receiver};
6use std::time::Duration;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub enum GitEvent {
10    IndexChanged,
11    HeadChanged,
12    RefsChanged,
13    ConfigChanged,
14    WorktreeChanged,
15}
16
17pub struct GitWatcher {
18    _watcher: RecommendedWatcher,
19    receiver: Receiver<notify::Result<Event>>,
20    git_dir: PathBuf,
21}
22
23impl GitWatcher {
24    pub fn new(git_dir: impl AsRef<Path>) -> Result<Self> {
25        let git_dir = git_dir.as_ref().to_path_buf();
26        let (tx, rx) = channel();
27
28        let mut watcher = recommended_watcher(tx)?;
29        watcher.watch(&git_dir, RecursiveMode::Recursive)?;
30
31        Ok(Self {
32            _watcher: watcher,
33            receiver: rx,
34            git_dir,
35        })
36    }
37
38    pub fn try_recv(&self) -> Option<GitEvent> {
39        self.receiver
40            .try_recv()
41            .ok()
42            .and_then(|r| r.ok())
43            .and_then(|e| self.classify_event(&e))
44    }
45
46    pub fn recv_timeout(&self, timeout: Duration) -> Option<GitEvent> {
47        self.receiver
48            .recv_timeout(timeout)
49            .ok()
50            .and_then(|r| r.ok())
51            .and_then(|e| self.classify_event(&e))
52    }
53
54    fn classify_event(&self, event: &Event) -> Option<GitEvent> {
55        for path in &event.paths {
56            if let Ok(relative) = path.strip_prefix(&self.git_dir) {
57                let rel_str = relative.to_string_lossy();
58
59                if rel_str == "index" || rel_str.starts_with("index.") {
60                    return Some(GitEvent::IndexChanged);
61                }
62                if rel_str == "HEAD" {
63                    return Some(GitEvent::HeadChanged);
64                }
65                if rel_str.starts_with("refs/") {
66                    return Some(GitEvent::RefsChanged);
67                }
68                if rel_str == "config" {
69                    return Some(GitEvent::ConfigChanged);
70                }
71            }
72        }
73
74        if event.paths.iter().any(|p| !p.starts_with(&self.git_dir)) {
75            return Some(GitEvent::WorktreeChanged);
76        }
77
78        None
79    }
80}