watch-and-commit 1.0.0

A CLI tool that automatically stages and commits file changes using a filesystem watcher
// src/event_handler.rs

use notify::event::{AccessKind, CreateKind, Event, EventKind, ModifyKind, RemoveKind, RenameMode};
use std::process::Command;

/// A helper function to neatly print details about a batch of file system events
/// and then execute the git commit action.
pub fn handle_events(events: &[Event]) {
    if events.is_empty() {
        return;
    }

    println!("\n=======================================================");
    println!("✅ DEBOUNCED ACTION! Processing {} events...", events.len());
    println!("=======================================================");

    for event in events {
        handle_single_event(event);
    }
    println!("-------------------------------------------------------");

    println!("🚀 Executing git auto-commit...");
    run_git_commit();
    println!("-------------------------------------------------------\n");
}

/// Executes `git add .` and `git commit -m "Update"`.
fn run_git_commit() {
    // --- Step 1: git add . ---
    println!("-> Running: git add .");
    let add_output = Command::new("git").arg("add").arg(".").output();

    match add_output {
        Ok(output) => {
            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                eprintln!("[ERROR] `git add .` failed:\n{}", stderr);
                return;
            }
            println!("[SUCCESS] Staged changes.");
        }
        Err(e) => {
            eprintln!("[ERROR] Failed to execute `git add .`: {}", e);
            return;
        }
    }

    // --- Step 2: Check if there are actually changes to commit ---
    let has_changes = Command::new("git")
        .args(["diff", "--cached", "--quiet"])
        .status()
        .map(|status| !status.success())
        .unwrap_or(false);

    if !has_changes {
        println!("[INFO] No changes detected in index. Skipping commit.");
        return;
    }

    // --- Step 3: git commit -m "Update" ---
    println!("-> Running: git commit -m \"Update\"");
    let commit_output = Command::new("git")
        .arg("commit")
        .arg("-m")
        .arg("Update")
        .output();

    match commit_output {
        Ok(output) => {
            if output.status.success() {
                let stdout = String::from_utf8_lossy(&output.stdout);
                println!("[SUCCESS] Committed changes:\n{}", stdout);

                // --- Step 4: Check if remote exists before pushing ---
                let has_remote = Command::new("git")
                    .args(["remote"])
                    .output()
                    .map(|output| {
                        output.status.success() && !output.stdout.is_empty()
                    })
                    .unwrap_or(false);

                if !has_remote {
                    println!("[INFO] No remote configured. Skipping push.");
                    return;
                }

                // --- Step 5: git push (only if remote exists) ---
                println!("-> Running: git push");
                let push_output = Command::new("git").arg("push").output();

                match push_output {
                    Ok(p_out) => {
                        if p_out.status.success() {
                            println!("[SUCCESS] Pushed changes.");
                        } else {
                            eprintln!(
                                "[ERROR] `git push` failed:\n{}",
                                String::from_utf8_lossy(&p_out.stderr)
                            );
                        }
                    }
                    Err(e) => eprintln!("[ERROR] Failed to execute `git push`: {}", e),
                }
            } else {
                let stderr = String::from_utf8_lossy(&output.stderr);
                eprintln!("[ERROR] `git commit` failed:\n{}", stderr);
            }
        }
        Err(e) => {
            eprintln!("[ERROR] Failed to execute `git commit`: {}", e);
        }
    }
}

/// Processes and prints a single file system event for logging purposes.
fn handle_single_event(event: &Event) {
    let path = event
        .paths
        .first()
        .map_or("N/A", |p| p.to_str().unwrap_or("Invalid UTF-8"));

    match &event.kind {
        EventKind::Create(CreateKind::File) => println!("[CREATE] File created: {}", path),
        EventKind::Create(CreateKind::Folder) => println!("[CREATE] Folder created: {}", path),
        EventKind::Modify(ModifyKind::Data(_)) => {
            println!("[MODIFY] File content changed: {}", path)
        }
        EventKind::Modify(ModifyKind::Name(RenameMode::To)) => {
            println!("[RENAME] Renamed/Moved to: {}", path)
        }
        EventKind::Modify(ModifyKind::Name(RenameMode::From)) => {
            println!("[RENAME] Renamed/Moved from: {}", path)
        }
        EventKind::Remove(RemoveKind::File) => println!("[REMOVE] File removed: {}", path),
        EventKind::Remove(RemoveKind::Folder) => println!("[REMOVE] Folder removed: {}", path),
        EventKind::Access(AccessKind::Close(_)) => println!("[ACCESS] File closed: {}", path),
        _ => println!("[OTHER] Event: {:?} on {}", event.kind, path),
    }
}