use crate::config_snapshot::ConfigSnapshot;
use crate::maintenance_decision::{
MAINTENANCE_CONFIG_PATTERN, MaintenanceDecision, MaintenanceError, auto_maintenance_arguments,
parse_config_records,
};
use crate::diagnostic_log;
use crate::inner_git_words::record_words;
use crate::transaction_git::{GitContext, GitOutput, GitRequest, run_git};
use std::collections::BTreeMap;
use std::path::Path;
const TAG: &str = "runAutoMaintenance";
const USAGE_EXIT_CODE: i32 = 129;
const CONFIG_UNSET_EXIT_CODE: i32 = 1;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AutoMaintenanceOutcome {
Disabled,
Exited(i32),
}
fn exit_status(code: Option<i32>) -> i32 {
return code.unwrap_or(1);
}
fn start_failure(command: &str, error: std::io::Error) -> MaintenanceError {
return MaintenanceError::Start(format!("{command} could not start: {error}"));
}
enum MaintenanceConfig {
Records(BTreeMap<String, String>),
Exited(i32),
}
fn read_maintenance_config(context: &GitContext, cwd: &Path) -> Result<MaintenanceConfig, MaintenanceError> {
let lookup: GitOutput = run_git(
context,
&GitRequest::new(
cwd,
&[
"config",
"--type=bool-or-int",
"--null",
"--get-regexp",
MAINTENANCE_CONFIG_PATTERN,
],
),
)
.map_err(|error| return start_failure("git config", error))?;
let lookup_code: i32 = exit_status(lookup.code);
if lookup_code != 0 && lookup_code != CONFIG_UNSET_EXIT_CODE {
diagnostic_log::warn(
TAG,
format!(
"skipping automatic maintenance: git config exited {lookup_code}: {}",
String::from_utf8_lossy(&lookup.stderr).trim()
)
.as_str(),
);
return Ok(MaintenanceConfig::Exited(lookup_code));
}
let config_bytes: &[u8] = if lookup_code == 0 {
lookup.stdout.as_slice()
} else {
&[]
};
return Ok(MaintenanceConfig::Records(parse_config_records(config_bytes)?));
}
pub fn run_auto_maintenance(
context: &GitContext,
cwd: &Path,
) -> Result<AutoMaintenanceOutcome, MaintenanceError> {
return run_auto_maintenance_with(context, cwd, None);
}
pub fn run_auto_maintenance_with(
context: &GitContext,
cwd: &Path,
snapshot: Option<&ConfigSnapshot>,
) -> Result<AutoMaintenanceOutcome, MaintenanceError> {
let from_snapshot: Option<BTreeMap<String, String>> = snapshot
.filter(|candidate| { return candidate.without_global_options(); })
.and_then(ConfigSnapshot::maintenance_records);
let config: BTreeMap<String, String> = match from_snapshot {
Some(records) => records,
None => match read_maintenance_config(context, cwd)? {
MaintenanceConfig::Records(records) => records,
MaintenanceConfig::Exited(code) => return Ok(AutoMaintenanceOutcome::Exited(code)),
},
};
let arguments: Vec<&'static str> = match auto_maintenance_arguments(&config) {
MaintenanceDecision::Disabled => {
diagnostic_log::debug(
TAG,
"automatic maintenance disabled by configuration",
);
return Ok(AutoMaintenanceOutcome::Disabled);
}
MaintenanceDecision::Run(arguments) => arguments,
};
diagnostic_log::debug(TAG, format!("running git {}", arguments.join(" ")).as_str());
let result: GitOutput = run_git(
context,
&GitRequest::new(cwd, arguments.as_slice()),
)
.map_err(|error| return start_failure("git maintenance run", error))?;
let result_code: i32 = exit_status(result.code);
if result_code != USAGE_EXIT_CODE {
record_words(arguments.as_slice(), &result);
diagnostic_log::debug(
TAG,
format!("automatic maintenance exited {result_code}").as_str(),
);
return Ok(AutoMaintenanceOutcome::Exited(result_code));
}
let flag: &str = arguments.last().copied().unwrap_or("");
diagnostic_log::debug(
TAG,
format!(
"git rejected {flag}; retrying in the pre-2.47 form: {}",
String::from_utf8_lossy(&result.stderr).trim()
)
.as_str(),
);
let fallback_arguments: &[&str] = &arguments[..arguments.len() - 1];
let fallback: GitOutput = run_git(
context,
&GitRequest::new(cwd, fallback_arguments),
)
.map_err(|error| return start_failure("git maintenance run", error))?;
let fallback_code: i32 = exit_status(fallback.code);
record_words(fallback_arguments, &fallback);
diagnostic_log::debug(
TAG,
format!("automatic maintenance exited {fallback_code}").as_str(),
);
return Ok(AutoMaintenanceOutcome::Exited(fallback_code));
}
#[cfg(test)]
#[path = "maintenance_run_tests.rs"]
mod tests;