use crate::Result;
use notify::{recommended_watcher, Event, RecommendedWatcher, RecursiveMode, Watcher};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, Receiver};
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum GitEvent {
IndexChanged,
HeadChanged,
RefsChanged,
ConfigChanged,
WorktreeChanged,
}
pub struct GitWatcher {
_watcher: RecommendedWatcher,
receiver: Receiver<notify::Result<Event>>,
git_dir: PathBuf,
}
impl GitWatcher {
pub fn new(git_dir: impl AsRef<Path>) -> Result<Self> {
let git_dir = git_dir.as_ref().to_path_buf();
let (tx, rx) = channel();
let mut watcher = recommended_watcher(tx)?;
watcher.watch(&git_dir, RecursiveMode::Recursive)?;
Ok(Self {
_watcher: watcher,
receiver: rx,
git_dir,
})
}
pub fn try_recv(&self) -> Option<GitEvent> {
self.receiver
.try_recv()
.ok()
.and_then(|r| r.ok())
.and_then(|e| self.classify_event(&e))
}
pub fn recv_timeout(&self, timeout: Duration) -> Option<GitEvent> {
self.receiver
.recv_timeout(timeout)
.ok()
.and_then(|r| r.ok())
.and_then(|e| self.classify_event(&e))
}
fn classify_event(&self, event: &Event) -> Option<GitEvent> {
for path in &event.paths {
if let Ok(relative) = path.strip_prefix(&self.git_dir) {
let rel_str = relative.to_string_lossy();
if rel_str == "index" || rel_str.starts_with("index.") {
return Some(GitEvent::IndexChanged);
}
if rel_str == "HEAD" {
return Some(GitEvent::HeadChanged);
}
if rel_str.starts_with("refs/") {
return Some(GitEvent::RefsChanged);
}
if rel_str == "config" {
return Some(GitEvent::ConfigChanged);
}
}
}
if event.paths.iter().any(|p| !p.starts_with(&self.git_dir)) {
return Some(GitEvent::WorktreeChanged);
}
None
}
}