use crate::package_repair_command::ToolLimits;
use crate::add_timing::work;
use crate::diagnostic_log::debug;
use crate::package_repair_cargo::repair_cargo;
use crate::package_repair_pnpm::repair_pnpm;
use crate::package_repair_report::{Finding, render};
use crate::package_repair_toolchain::repair_toolchain;
use crate::transaction_git::GitContext;
use std::ffi::{OsStr, OsString};
use std::io::Write;
use std::path::Path;
use std::time::Duration;
const TAG: &str = "packageRepair";
const VALUE_OPTIONS: [&str; 3] = ["-b", "-B", "--reason"];
const TOOL_QUIET_WINDOW: Duration = Duration::from_secs(600);
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct RepairSwitch {
pub quiet: bool,
pub no_checkout: bool,
}
impl RepairSwitch {
pub fn from_arguments(arguments: &[OsString]) -> RepairSwitch {
let mut found: RepairSwitch = RepairSwitch::default();
let Some(add_at) = arguments.windows(2).position(|pair: &[OsString]| { return pair[0] == "worktree" && pair[1] == "add"; }) else { return found };
let mut words = arguments[add_at + 2..].iter();
while let Some(word) = words.next() {
let bytes: &[u8] = word.as_encoded_bytes();
if bytes == b"--" {
break;
}
if VALUE_OPTIONS.iter().any(|option: &&str| { return bytes == option.as_bytes(); }) {
words.next();
} else if bytes == b"--quiet" {
found.quiet = true;
} else if bytes == b"--no-checkout" {
found.no_checkout = true;
} else if bytes == b"--checkout" {
found.no_checkout = false;
} else if bytes.len() > 1 && bytes[0] == b'-' && bytes[1] != b'-' {
let flags: &[u8] = &bytes[1..];
let end: usize = flags.iter().position(|flag: &u8| { return *flag == b'b' || *flag == b'B'; }).unwrap_or(flags.len());
found.quiet |= flags[..end].contains(&b'q');
if end + 1 == flags.len() {
words.next();
}
}
}
return found;
}
}
pub fn workers_for(available: usize) -> usize {
return available.max(1);
}
#[derive(Clone, Debug)]
pub struct RepairEnvironment {
pub search_path: Option<OsString>,
pub quiet_window: Duration,
pub ceiling: Duration,
pub workers: usize,
}
impl RepairEnvironment {
pub fn from_process() -> RepairEnvironment {
let available: usize = std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get);
return RepairEnvironment { search_path: std::env::var_os("PATH"), quiet_window: TOOL_QUIET_WINDOW, ceiling: crate::wait_ceilings::REPAIR_TOOL_CEILING, workers: workers_for(available) };
}
pub fn limits(&self) -> ToolLimits {
return ToolLimits { window: self.quiet_window, ceiling: self.ceiling };
}
pub fn path(&self) -> Option<&OsStr> {
return self.search_path.as_deref();
}
}
pub struct RepairRequest<'a> {
pub git: &'a GitContext,
pub source: &'a Path,
pub destination: &'a Path,
pub switch: RepairSwitch,
pub environment: &'a RepairEnvironment,
}
pub fn repair_worktree(request: &RepairRequest<'_>, notices: &mut dyn Write) -> Vec<Finding> {
if request.switch.no_checkout {
debug(TAG, format!("{}: --no-checkout, so there is nothing to repair", request.destination.display()).as_str());
return Vec::new();
}
let started: std::time::Instant = std::time::Instant::now();
let (pnpm, cargo, toolchain): (Option<Finding>, Vec<Finding>, Option<Finding>) = std::thread::scope(|scope| {
let pnpm = scope.spawn(|| { return work("repairPnpm", || { return repair_pnpm(request.destination, request.environment); }); });
let toolchain = scope.spawn(|| { return work("repairToolchain", || { return repair_toolchain(request.destination, request.environment); }); });
let cargo: Vec<Finding> = work("repairCargo", || { return repair_cargo(request); });
let settled = |joined: std::thread::Result<Option<Finding>>, tool: &str| -> Option<Finding> {
return joined.unwrap_or_else(|_panic| {
debug(TAG, format!("the {tool} repair stopped unexpectedly and reports nothing").as_str());
return None;
});
};
return (settled(pnpm.join(), "pnpm"), cargo, settled(toolchain.join(), "toolchain"));
});
let findings: Vec<Finding> = pnpm.into_iter().chain(cargo).chain(toolchain).collect();
debug(TAG, format!("{}: {} findings in {} ms", request.destination.display(), findings.len(), started.elapsed().as_millis()).as_str());
let text: String = render(request.destination, findings.as_slice(), request.switch.quiet);
let _written = notices.write_all(text.as_bytes());
return findings;
}
#[cfg(test)]
#[path = "package_repair_tests.rs"]
mod tests;