use crate::diagnostics::EngineFailureCode;
use crate::direct_fix::convergence_pass;
use crate::direct_fix_install::InstallChange;
use crate::policy_checks::ShippedChecks;
use crate::policy_content::Correction;
use crate::policy_convergence::{Convergence, FIX_PASS_LIMIT_MESSAGE, FixPasses, PassResult as ConvergencePass, converge};
use crate::policy_engine::{StageEnd, StageRequest};
use crate::policy_events::PolicyEvent;
use crate::policy_pass::{PassResult, run_policy_pass};
use crate::path_bytes::path_message_text;
use crate::policy_trigger::Trigger;
use crate::private_index_stage::{PrivateStage, stage_correction};
use crate::repository_facts::RepositoryFacts;
use std::collections::BTreeMap;
use std::ffi::OsString;
pub const COMMIT_CYCLE_MESSAGE: &str = "Policy patches repeated an exact prior candidate state.";
pub struct CommitConvergenceInput<'a, F: RepositoryFacts> {
pub request: &'a StageRequest,
pub checks: &'a mut ShippedChecks<F>,
pub stage: PrivateStage<'a>,
}
pub struct CommitConvergence {
pub convergence: Convergence,
pub pass: PassResult,
pub changed_paths: Vec<Vec<u8>>,
}
struct CommitPasses<'a, F: RepositoryFacts> {
request: &'a StageRequest,
checks: &'a mut ShippedChecks<F>,
stage: PrivateStage<'a>,
last: Option<PassResult>,
blocked: Option<PassResult>,
states: Vec<BTreeMap<Vec<u8>, InstallChange>>,
changed_paths: Vec<Vec<u8>>,
}
impl<'a, F: RepositoryFacts> CommitPasses<'a, F> {
fn new(input: CommitConvergenceInput<'a, F>) -> Self {
return CommitPasses {
request: input.request,
checks: input.checks,
stage: input.stage,
last: None,
blocked: None,
states: vec![BTreeMap::new()],
changed_paths: Vec::new(),
};
}
}
impl<F: RepositoryFacts> FixPasses for CommitPasses<'_, F> {
fn run_pass(&mut self, _candidate_version: usize) -> ConvergencePass {
let _ = self.checks.content.take_proposals();
let pass: PassResult = run_policy_pass(self.request, self.checks);
let result: ConvergencePass = convergence_pass(pass.end);
self.last = Some(pass);
return result;
}
fn apply(&mut self, _snapshot: usize) -> bool {
let proposals: Vec<Correction> = self.checks.content.take_proposals();
let arguments: Vec<OsString> = match &self.last {
Some(pass) => pass.arguments.clone(),
None => unreachable!("apply follows a pass that proposed corrections"),
};
for correction in &proposals {
if let Err(message) = stage_correction(&self.stage, correction) {
self.blocked = Some(patch_conflict_pass(arguments.as_slice(), correction.path.as_slice(), message));
return false;
}
record_path(&mut self.changed_paths, correction);
}
self.checks.content.apply(proposals);
self.states.push(self.checks.content.corrected_state());
return true;
}
fn snapshots_equal(&mut self, left: usize, right: usize) -> bool {
return self.states[left] == self.states[right];
}
}
fn record_path(changed: &mut Vec<Vec<u8>>, correction: &Correction) {
if !changed.contains(&correction.path) {
changed.push(correction.path.clone());
}
}
fn patch_conflict_pass(arguments: &[OsString], path: &[u8], message: String) -> PassResult {
let shown: String = path_message_text(path);
return PassResult {
arguments: arguments.to_vec(),
events: vec![PolicyEvent::EngineFailure {
code: EngineFailureCode::PatchConflict,
hint: crate::policy_events::EngineFailureHint::OfCode,
message: format!("Policy patch for {shown} does not apply to the private index: {message}"),
trigger: Some(Trigger::PreForward),
policy: None,
path: Some(crate::event_path::EventPath::from_git_bytes(path)),
}],
end: StageEnd::Failed,
};
}
fn engine_failure_pass(arguments: &[OsString], code: EngineFailureCode, message: &str) -> PassResult {
return PassResult {
arguments: arguments.to_vec(),
events: vec![PolicyEvent::EngineFailure {
code,
hint: crate::policy_events::EngineFailureHint::OfCode,
message: String::from(message),
trigger: Some(Trigger::PreForward),
policy: None,
path: None,
}],
end: StageEnd::Failed,
};
}
pub fn converge_commit_policies<F: RepositoryFacts>(input: CommitConvergenceInput<'_, F>) -> CommitConvergence {
input.checks.can_apply_patches = true;
let mut passes: CommitPasses<'_, F> = CommitPasses::new(input);
let convergence: Convergence = converge(&mut passes);
let Some(last) = passes.last.take() else {
unreachable!("the bounded loop runs at least one pass");
};
let pass: PassResult = finish(convergence, last, passes.blocked.take());
return CommitConvergence {
convergence,
pass,
changed_paths: passes.changed_paths,
};
}
fn finish(convergence: Convergence, last: PassResult, blocked: Option<PassResult>) -> PassResult {
match convergence {
Convergence::Settled { .. } => return last,
Convergence::Blocked => match blocked {
Some(found) => return found,
None => return last,
},
Convergence::FixCycle => {
return engine_failure_pass(last.arguments.as_slice(), EngineFailureCode::FixCycle, COMMIT_CYCLE_MESSAGE);
}
Convergence::FixPassLimit => {
return engine_failure_pass(last.arguments.as_slice(), EngineFailureCode::FixPassLimit, FIX_PASS_LIMIT_MESSAGE);
}
}
}
#[cfg(test)]
#[path = "commit_convergence_tests.rs"]
mod tests;