use crate::diagnostics::EngineFailureCode;
use crate::direct_fix_install::{InstallChange, install_corrections};
use crate::event_path::EventPath;
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, pass_exit_code};
use crate::policy_events::PolicyEvent;
use crate::policy_pass::{PassResult, run_policy_pass};
use crate::policy_trigger::Trigger;
use crate::repository_facts::RepositoryFacts;
use std::collections::BTreeMap;
use std::path::Path;
pub const DIRECT_FIX_CYCLE_MESSAGE: &str =
"Policy patches entered a repeated candidate-state cycle.";
pub const NO_WORKTREE_MESSAGE: &str =
"cli-git fix could not install its corrections: the repository has no worktree.";
pub struct DirectFixPasses<'a, F: RepositoryFacts> {
request: &'a StageRequest,
checks: &'a mut ShippedChecks<F>,
last: Option<PassResult>,
states: Vec<BTreeMap<Vec<u8>, InstallChange>>,
}
pub fn convergence_pass(end: StageEnd) -> ConvergencePass {
match end {
StageEnd::Proposed => return ConvergencePass::Proposed,
StageEnd::Failed | StageEnd::Unavailable(_) => return ConvergencePass::Failed,
StageEnd::Completed | StageEnd::Stopped => return ConvergencePass::Stable,
}
}
impl<'a, F: RepositoryFacts> DirectFixPasses<'a, F> {
pub fn new(request: &'a StageRequest, checks: &'a mut ShippedChecks<F>) -> Self {
return DirectFixPasses {
request,
checks,
last: None,
states: vec![BTreeMap::new()],
};
}
}
impl<F: RepositoryFacts> FixPasses for DirectFixPasses<'_, 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();
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];
}
}
pub fn fix_failure(
arguments: &[std::ffi::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::DirectFix),
policy: None,
path: None,
}],
end: StageEnd::Failed,
};
}
pub fn settled_fix<F: RepositoryFacts>(
pass: PassResult,
changed_passes: usize,
checks: &ShippedChecks<F>,
root: Option<&Path>,
) -> PassResult {
if pass_exit_code(pass.events.as_slice(), pass.end) != 0 {
return pass;
}
let changes: Vec<InstallChange> = checks.content.install_changes();
if changes.is_empty() {
return pass;
}
let (Some(top_level), Some(real_index)) = (root, checks.content.real_index()) else {
return fix_failure(
pass.arguments.as_slice(),
EngineFailureCode::TransactionFailed,
NO_WORKTREE_MESSAGE,
);
};
if let Err(message) = install_corrections(top_level, real_index, changes.as_slice()) {
return fix_failure(
pass.arguments.as_slice(),
EngineFailureCode::TransactionFailed,
message.as_str(),
);
}
let mut changed_paths: Vec<EventPath> = Vec::new();
for change in &changes {
changed_paths.push(EventPath::from_git_bytes(change.path.as_slice()));
}
let mut events: Vec<PolicyEvent> = pass.events;
events.push(PolicyEvent::FixSummary {
trigger: Trigger::DirectFix,
passes: changed_passes as u64,
changed_paths,
});
return PassResult {
arguments: pass.arguments,
events,
end: pass.end,
};
}
pub fn finish_fix<F: RepositoryFacts>(
convergence: Convergence,
pass: PassResult,
checks: &ShippedChecks<F>,
root: Option<&Path>,
) -> PassResult {
match convergence {
Convergence::Settled { changed_passes } => {
return settled_fix(pass, changed_passes, checks, root);
}
Convergence::Blocked => return pass,
Convergence::FixCycle => {
return fix_failure(
pass.arguments.as_slice(),
EngineFailureCode::FixCycle,
DIRECT_FIX_CYCLE_MESSAGE,
);
}
Convergence::FixPassLimit => {
return fix_failure(
pass.arguments.as_slice(),
EngineFailureCode::FixPassLimit,
FIX_PASS_LIMIT_MESSAGE,
);
}
}
}
pub fn run_direct_fix<F: RepositoryFacts>(
request: &StageRequest,
checks: &mut ShippedChecks<F>,
root: Option<&Path>,
) -> PassResult {
let mut passes: DirectFixPasses<'_, F> = DirectFixPasses::new(request, checks);
let convergence: Convergence = converge(&mut passes);
let Some(pass) = passes.last else {
unreachable!("the bounded loop runs at least one pass");
};
return finish_fix(convergence, pass, checks, root);
}
#[cfg(test)]
#[path = "direct_fix_tests.rs"]
mod tests;