g-cli 0.1.0

Git that talks back. A human-friendly CLI wrapper for Git.
mod cmd_clean;
mod cmd_explain;
mod cmd_log;
mod cmd_map;
mod cmd_push;
mod cmd_save;
mod cmd_stash;
mod cmd_status;
mod cmd_switch;
mod cmd_sync;
mod cmd_undo;
mod cmd_whoops;
mod git;

use clap::{Parser, Subcommand};
use colored::Colorize;

#[derive(Parser)]
#[command(name = "g", about = "Git that talks back.", version)]
struct Cli {
    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    /// Show a friendly, actionable status of your repo
    Status,
    /// Stage everything and commit in one step
    Save {
        /// Commit message describing your changes
        message: String,
    },
    /// Undo your last action safely
    Undo {
        /// Show interactive undo timeline
        #[arg(long)]
        list: bool,
    },
    /// Explain your recent actions in plain English
    Explain,
    /// Show a human-readable commit log
    Log {
        /// Tell the project story in plain English
        #[arg(long)]
        story: bool,
    },
    /// Switch to a branch (creates it if needed)
    Switch {
        /// Branch name to switch to
        branch: String,
    },
    /// Fix the last commit message
    Whoops {
        /// The corrected commit message
        message: String,
    },
    /// Push with a preview of what's being sent
    Push,
    /// Clean up stale and merged branches
    Clean,
    /// Pull latest changes with guided conflict resolution
    Sync,
    /// Stash your changes with a name
    Stash {
        #[command(subcommand)]
        action: StashAction,
    },
    /// Export repo history as a Graphviz .dot file
    Map {
        /// Output file path (default: repo-map.dot)
        #[arg(short, long)]
        output: Option<String>,
        /// Max commits to include
        #[arg(long, default_value = "50")]
        max_commits: usize,
    },
}

#[derive(Subcommand)]
enum StashAction {
    /// Save current changes to a named stash
    Save {
        /// Description of what you're stashing
        message: String,
    },
    /// Restore a stash interactively
    Pop,
    /// List all stashes
    List,
}

fn main() {
    let cli = Cli::parse();

    if !git::is_git_repo() {
        eprintln!(
            "\n  {} Not a git repository. Run {} to get started.\n",
            "".red(),
            "git init".cyan()
        );
        std::process::exit(1);
    }

    match cli.command {
        Some(Commands::Status) => cmd_status::run(),
        Some(Commands::Save { message }) => cmd_save::run(&message),
        Some(Commands::Undo { list }) => cmd_undo::run(list),
        Some(Commands::Explain) => cmd_explain::run(),
        Some(Commands::Log { story }) => cmd_log::run(story),
        Some(Commands::Switch { branch }) => cmd_switch::run(&branch),
        Some(Commands::Whoops { message }) => cmd_whoops::run(&message),
        Some(Commands::Push) => cmd_push::run(),
        Some(Commands::Clean) => cmd_clean::run(),
        Some(Commands::Sync) => cmd_sync::run(),
        Some(Commands::Stash { action }) => match action {
            StashAction::Save { message } => cmd_stash::run_save(&message),
            StashAction::Pop => cmd_stash::run_pop(),
            StashAction::List => cmd_stash::run_list(),
        },
        Some(Commands::Map { output, max_commits }) => cmd_map::run(output, max_commits),
        None => {
            // Default to status when no command is given
            cmd_status::run();
        }
    }
}