jj-cz 1.1.0

Conventional commits for Jujutsu
Documentation
mod cli;

use clap::Parser as _;
use jj_cz::{CommitWorkflow, Error, JjLib};
use std::process;

/// Exit codes used by jj-cz
const EXIT_SUCCESS: i32 = 0;
const EXIT_CANCELLED: i32 = 130; // Same as SIGINT (Ctrl+C)
const EXIT_ERROR: i32 = 1;

/// Map application errors to appropriate exit codes
fn error_to_exit_code(error: Error) -> i32 {
    match error {
        Error::Cancelled => EXIT_CANCELLED,
        Error::NotARepository => EXIT_ERROR,
        Error::RepositoryLocked => EXIT_ERROR,
        Error::JjOperation { .. } => EXIT_ERROR,
        Error::InvalidScope(_) => EXIT_ERROR,
        Error::InvalidDescription(_) => EXIT_ERROR,
        Error::InvalidCommitMessage(_) => EXIT_ERROR,
        Error::NonInteractive => EXIT_ERROR,
        Error::FailedGettingCurrentDir => EXIT_ERROR,
        Error::FailedReadingConfig { .. } => EXIT_ERROR,
        Error::RevsetResolutionError { .. } => EXIT_ERROR,
        Error::MultipleRevisions { .. } => EXIT_ERROR,
        Error::NewFlagWithMultipleRevisions => EXIT_ERROR,
    }
}

/// Check if we're running in an interactive terminal
fn is_interactive_terminal() -> bool {
    use std::io::IsTerminal;
    std::io::stdin().is_terminal() && std::io::stdout().is_terminal()
}

#[tokio::main]
async fn main() -> Result<(), ()> {
    let cli = cli::Cli::parse();

    cli.validate().map_err(exit_on_error)?;

    if !is_interactive_terminal() {
        eprintln!("❌ Error: jj-cz requires an interactive terminal (TTY)");
        eprintln!("   This tool cannot be used in non-interactive mode or when piping input.");
        eprintln!("   Use --help for usage information.");
        process::exit(EXIT_ERROR);
    }
    let executor = JjLib::new().await.map_err(exit_on_error)?;
    let workflow = CommitWorkflow::new(executor);
    for revset in cli.revsets() {
        let result = workflow.run_for_revset(revset).await;
        handle_result(result);
        if cli.create_new() {
            println!("Creating a new revision after {revset}");
            workflow.new_revision(revset).await.map_err(exit_on_error)?;
        }
    }

    fn handle_result(result: Result<(), Error>) {
        match result {
            Ok(()) => println!("✅ Commit message applied successfully!"),
            Err(Error::Cancelled) => {
                println!("🟡 Operation cancelled by user.");
                process::exit(EXIT_CANCELLED);
            }
            Err(e) => exit_on_error(e),
        }
    }
    process::exit(EXIT_SUCCESS);
}

fn exit_on_error(e: Error) {
    eprintln!("❌ Error: {}", e);
    process::exit(error_to_exit_code(e));
}