vinxen-git 0.1.1

Git operations for Vinxen CLI
Documentation
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
    }
}