fp-dotfiles-manager 0.2.5

Minimal, zero-dependency Chezmoi-based dotfiles manager
use std::io::{self, Write};
use std::time::Duration;

fn read_line_with_timeout(prompt: &str, timeout: Duration) -> io::Result<Option<String>> {
    print!("{}", prompt);
    io::stdout().flush()?;

    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let mut input = String::new();
        if io::stdin().read_line(&mut input).is_ok() {
            let _ = tx.send(input.trim().to_string());
        }
    });

    match rx.recv_timeout(timeout) {
        Ok(val) => Ok(Some(val)),
        Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
            println!("\nPrompt timed out. Operation cancelled.");
            Ok(None)
        }
        Err(_) => Err(io::Error::new(io::ErrorKind::Other, "Channel closed")),
    }
}

pub fn run_clean_history(repo_path: &str) -> io::Result<()> {
    println!("Warning: This action will permanently delete all previous commit history.");

    let confirm1 = read_line_with_timeout(
        "Are you sure you want to proceed? [y/N] ",
        Duration::from_secs(15),
    )?;
    if confirm1.is_none() {
        return Ok(());
    }
    let confirm1 = confirm1.unwrap();
    if !confirm1.eq_ignore_ascii_case("y") {
        println!("Operation cancelled.");
        return Ok(());
    }

    println!("\nWarning: The force push will fail if your remote branch is protected.");
    println!("Please verify branch protection is disabled here:");
    println!(
        "https://gitlab.com/{}/-/settings/repository#js-protected-branches-settings",
        repo_path
    );

    let confirm2 = read_line_with_timeout(
        "Have you disabled protection for your current branch? [y/N] ",
        Duration::from_secs(15),
    )?;
    if confirm2.is_none() {
        return Ok(());
    }
    let confirm2 = confirm2.unwrap();
    if !confirm2.eq_ignore_ascii_case("y") {
        println!("Operation cancelled. Please disable branch protection and try again.");
        return Ok(());
    }

    println!("Resetting repository history...");

    let branch_output = std::process::Command::new("chezmoi")
        .args(&["git", "--", "branch", "--show-current"])
        .output()?;
    let mut branch = String::from_utf8_lossy(&branch_output.stdout)
        .trim()
        .to_string();
    if branch.is_empty() {
        branch = "main".to_string();
    }

    std::process::Command::new("chezmoi")
        .args(&["git", "--", "checkout", "--orphan", "temp_branch"])
        .status()?;

    std::process::Command::new("chezmoi")
        .args(&["git", "--", "add", "-A"])
        .status()?;

    std::process::Command::new("chezmoi")
        .args(&[
            "git",
            "--",
            "commit",
            "-m",
            "chore: reset repository history",
        ])
        .status()?;

    std::process::Command::new("chezmoi")
        .args(&["git", "--", "branch", "-D", &branch])
        .status()?;

    std::process::Command::new("chezmoi")
        .args(&["git", "--", "branch", "-m", &branch])
        .status()?;

    println!("Force pushing to remote...");
    std::process::Command::new("chezmoi")
        .args(&["git", "--", "push", "-f", "-u", "origin", &branch])
        .status()?;

    println!("Cleaning local garbage...");
    std::process::Command::new("chezmoi")
        .args(&["git", "--", "reflog", "expire", "--expire=now", "--all"])
        .status()?;

    std::process::Command::new("chezmoi")
        .args(&["git", "--", "gc", "--prune=now", "--aggressive"])
        .status()?;

    println!("History cleaned successfully.");
    Ok(())
}