git-perf 0.20.0

Track, plot, and statistically validate simple measurements using git-notes for storage
Documentation
use anyhow::Result;
use clap::CommandFactory;
use clap::{error::ErrorKind::ArgumentConflict, Parser};
use env_logger::Env;
use log::Level;

use crate::audit;
use crate::basic_measure::measure;
use crate::config::bump_epoch;
use crate::config_cmd;
use crate::git::git_interop::check_git_version;
use crate::git::git_interop::{list_commits_with_measurements, prune, pull, push};
use crate::import::{handle_import, ImportOptions};
use crate::measurement_storage::{add_to_commit as add, remove_measurements_from_commits};
use crate::reporting::report;
use crate::reset;
use crate::size;
use crate::stats::ReductionFunc;
use crate::status;
use git_perf_cli_types::{Cli, Commands};

pub fn handle_calls() -> Result<()> {
    let cli = Cli::parse();
    let logger_level = match cli.verbose {
        0 => Level::Warn,
        1 => Level::Info,
        2 => Level::Debug,
        _ => Level::Trace,
    };
    env_logger::Builder::from_env(Env::default().default_filter_or(logger_level.as_str())).init();

    check_git_version()?;

    match cli.command {
        Commands::Measure {
            repetitions,
            measurement,
            commit,
            command,
        } => {
            let commit = commit.as_deref().unwrap_or("HEAD");
            measure(
                commit,
                &measurement.name,
                repetitions,
                &command,
                &measurement.key_value,
            )
        }
        Commands::Add {
            value,
            measurement,
            commit,
        } => {
            let commit = commit.as_deref().unwrap_or("HEAD");
            add(commit, &measurement.name, value, &measurement.key_value)
        }
        Commands::Import {
            format,
            file,
            commit,
            prefix,
            metadata,
            filter,
            dry_run,
            verbose,
        } => {
            let commit = commit.as_deref().unwrap_or("HEAD").to_string();
            handle_import(ImportOptions {
                commit,
                format,
                file,
                prefix,
                metadata,
                filter,
                dry_run,
                verbose,
            })
        }
        Commands::Push { remote } => push(None, remote.as_deref()),
        Commands::Pull {} => pull(None),
        Commands::Report {
            commit,
            output,
            separate_by,
            report_history,
            measurement,
            key_value,
            aggregate_by,
            filter,
            template,
            custom_css,
            title,
            show_epochs,
            show_changes,
        } => {
            let commit = commit.as_deref().unwrap_or("HEAD");

            // Combine measurements (as exact matches) and filter patterns into unified regex patterns
            let combined_patterns =
                crate::filter::combine_measurements_and_filters(&measurement, &filter);

            let template_config = crate::reporting::ReportTemplateConfig {
                template_path: template,
                custom_css_path: custom_css,
                title,
            };

            report(
                commit,
                output,
                separate_by,
                report_history.max_count,
                &key_value,
                aggregate_by.map(ReductionFunc::from),
                &combined_patterns,
                template_config,
                show_epochs,
                show_changes,
            )
        }
        Commands::Audit {
            commit,
            measurement,
            report_history,
            selectors,
            separate_by,
            min_measurements,
            aggregate_by,
            sigma,
            dispersion_method,
            filter,
            no_change_point_warning,
        } => {
            let commit = commit.as_deref().unwrap_or("HEAD");
            // Validate that at least one of measurement or filter is provided
            // (clap's required_unless_present should handle this, but double-check for safety)
            if measurement.is_empty() && filter.is_empty() {
                Cli::command()
                    .error(
                        clap::error::ErrorKind::MissingRequiredArgument,
                        "At least one of --measurement or --filter must be provided",
                    )
                    .exit()
            }

            // Validate max_count vs min_measurements if min_measurements is specified via CLI
            if let Some(min_count) = min_measurements {
                if report_history.max_count < min_count.into() {
                    Cli::command().error(ArgumentConflict, format!("The minimal number of measurements ({}) cannot be more than the maximum number of measurements ({})", min_count, report_history.max_count)).exit()
                }
            }

            // Combine measurements (as exact matches) and filter patterns into unified regex patterns
            let combined_patterns =
                crate::filter::combine_measurements_and_filters(&measurement, &filter);

            audit::audit_multiple(
                commit,
                report_history.max_count,
                min_measurements,
                &selectors,
                aggregate_by.map(ReductionFunc::from),
                sigma,
                dispersion_method.map(crate::stats::DispersionMethod::from),
                &combined_patterns,
                &separate_by,
                no_change_point_warning,
            )
        }
        Commands::BumpEpoch { measurements } => {
            for measurement in measurements {
                bump_epoch(&measurement)?;
            }
            Ok(())
        }
        Commands::Prune {} => prune(),
        Commands::Status { detailed } => status::show_status(detailed),
        Commands::Reset { dry_run, force } => reset::reset_measurements(dry_run, force),
        Commands::Remove {
            older_than,
            no_prune,
            dry_run,
        } => remove_measurements_from_commits(older_than, !no_prune, dry_run),
        Commands::ListCommits {} => {
            let commits = list_commits_with_measurements()?;
            for commit in commits {
                println!("{}", commit);
            }
            Ok(())
        }
        Commands::Size {
            detailed,
            format,
            disk_size,
            include_objects,
        } => size::calculate_measurement_size(detailed, format, disk_size, include_objects),
        Commands::Config {
            list,
            detailed,
            format,
            validate,
            measurement,
        } => {
            if list {
                config_cmd::list_config(detailed, format, validate, measurement)
            } else {
                // For now, --list is required. In the future, this could support
                // other config operations like --get, --set, etc.
                anyhow::bail!("config command requires --list flag (try: git perf config --list)");
            }
        }
    }
}