use crate::action::{Action, ENGINE_FAILURE_EXIT_CODE, failure};
use crate::candidate_prediction::CandidateRequest;
use crate::child_environment::{child_environment_overlay, environment_value};
use crate::config_error::ConfigError;
use crate::config_file::LoadedConfig;
use crate::diagnostics::EngineFailureCode;
use crate::direct_fix::run_direct_fix;
use crate::invocation_config::{config_invalid_event, legacy_warning_events, load_identity_config};
use crate::management_arguments::{
MANAGEMENT_USAGE, ManagementRefusal,
};
use crate::management_selection::selection_error;
use crate::policy_checks::{ShippedChecks, shipped_checks};
use crate::policy_content::LifecycleContent;
use crate::policy_engine::{StageEnd, StageRequest, pass_exit_code};
use crate::policy_events::{PolicyEvent, render_policy_events};
use crate::policy_pass::{PassResult, run_policy_pass};
use crate::policy_registry::PolicyId;
use crate::policy_trigger::Trigger;
use crate::repository_facts::{GitFacts, RepositoryFacts, git_facts};
use crate::repository_location::RepositoryLocation;
use crate::scanner_selection::RULES_VARIABLE;
use crate::startup_gate::{Gate, recover_for_command};
use crate::unported::{unported_from_unavailable, unported_notice};
use crate::worktree_identity::worktree_root;
use crate::wrapper_controls::Controls;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
fn direct_name(fix: bool) -> &'static str {
if fix {
return "fix";
}
return "check";
}
fn direct_command(fix: bool) -> String {
return format!("monochromatic-git-policy-cli {}", direct_name(fix));
}
pub(crate) fn refusal(reason: ManagementRefusal) -> Action {
let stderr: String = match reason {
ManagementRefusal::Usage => String::from(MANAGEMENT_USAGE),
ManagementRefusal::PathspecsBeforeSeparator { fix } => format!(
"git monochromatic-git-policy-cli {} pathspecs must follow --.\n",
direct_name(fix)
),
ManagementRefusal::ScopeRequired { fix } => format!(
"git monochromatic-git-policy-cli {} requires exactly one scope: --all or non-empty pathspecs after --.\n",
direct_name(fix)
),
};
return Action::Exit {
code: ENGINE_FAILURE_EXIT_CODE,
stdout: String::new(),
stderr,
};
}
pub(crate) fn direct_config_failure(error: &ConfigError) -> Action {
return Action::Exit {
code: ENGINE_FAILURE_EXIT_CODE,
stdout: config_invalid_event(error),
stderr: String::new(),
};
}
fn run_direct_command(
fix: bool,
selected: Vec<PolicyId>,
controls: &Controls,
loaded: &LoadedConfig,
checks: &mut ShippedChecks<GitFacts>,
root: Option<&Path>,
) -> Action {
let mut stdout: String = if fix {
String::new()
} else {
legacy_warning_events(loaded)
};
let first_sequence: u64 = stdout.matches('\n').count() as u64;
checks.scanner_settings.options = loaded.config.policies.forbidden_strings.clone();
checks.markdown.options = loaded.config.policies.markdown_autofix.clone();
checks.discarded_limits = loaded.config.discarded;
let trigger: Trigger = if fix {
Trigger::DirectFix
} else {
Trigger::DirectCheck
};
if let Err(error) = checks
.content
.prepare(&checks.candidates, &mut checks.facts)
{
stdout.push_str(
render_policy_events(
first_sequence,
&[PolicyEvent::EngineFailure {
code: EngineFailureCode::TransactionFailed,
hint: crate::policy_events::EngineFailureHint::OfCode,
message: error.message,
trigger: Some(trigger),
policy: None,
path: None,
}],
)
.as_str(),
);
return Action::Exit {
code: ENGINE_FAILURE_EXIT_CODE,
stdout,
stderr: String::new(),
};
}
let request: StageRequest = StageRequest {
trigger,
config: loaded.config.policies.clone(),
controls: controls.clone(),
selected,
};
let pass: PassResult = if fix {
run_direct_fix(&request, checks, root)
} else {
run_policy_pass(&request, checks)
};
stdout.push_str(render_policy_events(first_sequence, pass.events.as_slice()).as_str());
let stderr: String = if let StageEnd::Unavailable(unavailable) = pass.end {
unported_notice(
&unported_from_unavailable(unavailable),
direct_command(fix).as_str(),
)
} else {
String::new()
};
return Action::Exit {
code: pass_exit_code(pass.events.as_slice(), pass.end),
stdout,
stderr,
};
}
pub(crate) fn plan_direct(
fix: bool,
selected: Vec<PolicyId>,
scope: Vec<OsString>,
controls: &Controls,
environment: &[(OsString, OsString)],
git_global_arguments: &[OsString],
real_git: PathBuf,
) -> Action {
let overlay: Vec<(OsString, OsString)> =
child_environment_overlay(environment, real_git.as_path());
let mut checks: ShippedChecks<GitFacts> = shipped_checks(
git_facts(real_git.as_path(), git_global_arguments, overlay.as_slice()),
git_global_arguments.to_vec(),
LifecycleContent::Requested(CandidateRequest::Direct(scope)),
Vec::<PathBuf>::new(),
);
checks.scanner_settings.rules_variable = environment_value(environment, RULES_VARIABLE);
let location: RepositoryLocation = match checks.facts.location() {
Ok(found) => found,
Err(message) => return failure(message.as_str()),
};
match recover_for_command(&checks.facts.transaction_context(), &location.identity) {
Gate::Clear => {}
Gate::Stop(stderr) => {
return Action::Exit {
code: ENGINE_FAILURE_EXIT_CODE,
stdout: String::new(),
stderr,
};
}
}
let action: Action = match load_identity_config(&location.identity) {
Ok(loaded) => match selection_error(
selected.as_slice(),
&loaded.config.policies,
if fix { Trigger::DirectFix } else { Trigger::DirectCheck },
) {
Some(error) => direct_config_failure(&error),
None => run_direct_command(
fix,
selected,
controls,
&loaded,
&mut checks,
worktree_root(&location.identity),
),
},
Err(error) => direct_config_failure(&error),
};
return action;
}