git_perf/
cli.rs

1use anyhow::Result;
2use clap::CommandFactory;
3use clap::{error::ErrorKind::ArgumentConflict, Parser};
4use env_logger::Env;
5use log::Level;
6
7use crate::audit;
8use crate::basic_measure::measure;
9use crate::config::bump_epoch;
10use crate::config_cmd;
11use crate::git::git_interop::check_git_version;
12use crate::git::git_interop::{list_commits_with_measurements, prune, pull, push};
13use crate::import::handle_import;
14use crate::measurement_storage::{add, remove_measurements_from_commits};
15use crate::reporting::report;
16use crate::size;
17use crate::stats::ReductionFunc;
18use git_perf_cli_types::{Cli, Commands};
19
20pub fn handle_calls() -> Result<()> {
21    let cli = Cli::parse();
22    let logger_level = match cli.verbose {
23        0 => Level::Warn,
24        1 => Level::Info,
25        2 => Level::Debug,
26        _ => Level::Trace,
27    };
28    env_logger::Builder::from_env(Env::default().default_filter_or(logger_level.as_str())).init();
29
30    check_git_version()?;
31
32    match cli.command {
33        Commands::Measure {
34            repetitions,
35            command,
36            measurement,
37        } => measure(
38            &measurement.name,
39            repetitions,
40            &command,
41            &measurement.key_value,
42        ),
43        Commands::Add { value, measurement } => {
44            add(&measurement.name, value, &measurement.key_value)
45        }
46        Commands::Import {
47            format,
48            file,
49            prefix,
50            metadata,
51            filter,
52            dry_run,
53            verbose,
54        } => handle_import(format, file, prefix, metadata, filter, dry_run, verbose),
55        Commands::Push { remote } => push(None, remote.as_deref()),
56        Commands::Pull {} => pull(None),
57        Commands::Report {
58            output,
59            separate_by,
60            report_history,
61            measurement,
62            key_value,
63            aggregate_by,
64            filter,
65        } => {
66            // Combine measurements (as exact matches) and filter patterns into unified regex patterns
67            let combined_patterns =
68                crate::filter::combine_measurements_and_filters(&measurement, &filter);
69
70            report(
71                output,
72                separate_by,
73                report_history.max_count,
74                &key_value,
75                aggregate_by.map(ReductionFunc::from),
76                &combined_patterns,
77            )
78        }
79        Commands::Audit {
80            measurement,
81            report_history,
82            selectors,
83            min_measurements,
84            aggregate_by,
85            sigma,
86            dispersion_method,
87            filter,
88        } => {
89            // Validate that at least one of measurement or filter is provided
90            // (clap's required_unless_present should handle this, but double-check for safety)
91            if measurement.is_empty() && filter.is_empty() {
92                Cli::command()
93                    .error(
94                        clap::error::ErrorKind::MissingRequiredArgument,
95                        "At least one of --measurement or --filter must be provided",
96                    )
97                    .exit()
98            }
99
100            // Validate max_count vs min_measurements if min_measurements is specified via CLI
101            if let Some(min_count) = min_measurements {
102                if report_history.max_count < min_count.into() {
103                    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()
104                }
105            }
106
107            // Combine measurements (as exact matches) and filter patterns into unified regex patterns
108            let combined_patterns =
109                crate::filter::combine_measurements_and_filters(&measurement, &filter);
110
111            audit::audit_multiple(
112                report_history.max_count,
113                min_measurements,
114                &selectors,
115                aggregate_by.map(ReductionFunc::from),
116                sigma,
117                dispersion_method.map(crate::stats::DispersionMethod::from),
118                &combined_patterns,
119            )
120        }
121        Commands::BumpEpoch { measurements } => {
122            for measurement in measurements {
123                bump_epoch(&measurement)?;
124            }
125            Ok(())
126        }
127        Commands::Prune {} => prune(),
128        Commands::Remove {
129            older_than,
130            no_prune,
131        } => remove_measurements_from_commits(older_than, !no_prune),
132        Commands::ListCommits {} => {
133            let commits = list_commits_with_measurements()?;
134            for commit in commits {
135                println!("{}", commit);
136            }
137            Ok(())
138        }
139        Commands::Size {
140            detailed,
141            format,
142            disk_size,
143            include_objects,
144        } => size::calculate_measurement_size(detailed, format, disk_size, include_objects),
145        Commands::Config {
146            list,
147            detailed,
148            format,
149            validate,
150            measurement,
151        } => {
152            if list {
153                config_cmd::list_config(detailed, format, validate, measurement)
154            } else {
155                // For now, --list is required. In the future, this could support
156                // other config operations like --get, --set, etc.
157                anyhow::bail!("config command requires --list flag (try: git perf config --list)");
158            }
159        }
160    }
161}