use crate::side_by_side_threads::run_all_side_by_side;
use crate::worktree_copy_compare::{entry_matches, lstat_or_absent};
use crate::worktree_copy_error::{WorktreeCopyError, failure_with_cause};
use crate::parallel_budget::{HostResources, sliced_workers};
use crate::worktree_copy_stage_leaf_copying::PARALLEL_COPY_MAX_WORKERS;
use crate::worktree_copy_install_entry::{
InstalledPath, capture_installed_path, create_selected_entry, ensure_parents_remembering,
};
use crate::worktree_copy_manifest::{EntryKind, WorktreeCopyEntry, apply_entry_modes};
use crate::worktree_copy_rollback::rollback_created;
use crate::worktree_copy_selection::filesystem_path;
use crate::json_record::quote;
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
pub const INSTALL_BATCH_ENTRIES: usize = 4096;
pub trait InstallRecorder {
fn record_intents(&mut self, relative_paths: &[String]) -> Result<(), WorktreeCopyError>;
fn record_creations(&mut self, entries: &[InstalledPath]) -> Result<(), WorktreeCopyError>;
}
#[derive(Debug, Default)]
pub struct NoRecord;
impl InstallRecorder for NoRecord {
fn record_intents(&mut self, _relative_paths: &[String]) -> Result<(), WorktreeCopyError> {
return Ok(());
}
fn record_creations(&mut self, _entries: &[InstalledPath]) -> Result<(), WorktreeCopyError> {
return Ok(());
}
}
pub fn preflight_destination(
stage_root: &Path,
destination_root: &Path,
entries: &[WorktreeCopyEntry],
claimed: &BTreeSet<String>,
) -> Result<BTreeSet<String>, WorktreeCopyError> {
let mut absent: BTreeSet<String> = BTreeSet::new();
for entry in entries {
let destination_path: PathBuf =
filesystem_path(destination_root, entry.relative_path.as_str())?;
match lstat_or_absent(destination_path.as_path())? {
None => {
absent.insert(entry.relative_path.clone());
}
Some(metadata) => {
let claimed_directory: bool = entry.kind == EntryKind::Directory
&& metadata.is_dir()
&& claimed.contains(&entry.relative_path);
if claimed_directory {
continue;
}
if !entry_matches(stage_root, destination_root, entry)? {
let message: String = format!(
"monochromatic-git-policy-cli: ignored-state copy would overwrite differing destination entry {} in {}.",
quote(entry.relative_path.as_str()),
quote(destination_root.to_string_lossy().as_ref())
);
return Err(failure_with_cause(message.as_str(), None));
}
}
}
}
return Ok(absent);
}
fn install_batch(
stage_root: &Path,
destination_root: &Path,
batch: &[WorktreeCopyEntry],
absent: &BTreeSet<String>,
created: &mut Vec<InstalledPath>,
recorder: &mut dyn InstallRecorder,
known: &mut std::collections::HashSet<String>,
) -> Result<(), WorktreeCopyError> {
let mut claims: Vec<String> = Vec::new();
for entry in batch {
if absent.contains(&entry.relative_path) {
claims.push(entry.relative_path.clone());
}
}
recorder.record_intents(claims.as_slice())?;
let batch_start: usize = created.len();
let mut leaves: Vec<&WorktreeCopyEntry> = Vec::new();
for entry in batch {
ensure_parents_remembering(destination_root, entry, created, known)?;
if entry.kind != EntryKind::Directory {
leaves.push(entry);
continue;
}
let destination_path: PathBuf =
filesystem_path(destination_root, entry.relative_path.as_str())?;
if lstat_or_absent(destination_path.as_path())?.is_none() {
create_selected_entry(stage_root, destination_root, entry)?;
created.push(capture_installed_path(
destination_path.as_path(),
entry.relative_path.as_str(),
true,
)?);
}
known.insert(entry.relative_path.clone());
}
install_leaves(stage_root, destination_root, leaves.as_slice(), created)?;
recorder.record_creations(&created[batch_start..])?;
return Ok(());
}
const PARALLEL_INSTALL_MIN_LEAVES: usize = crate::parallel_budget::PARALLEL_BREAK_EVEN_ENTRIES;
fn install_one(stage_root: &Path, destination_root: &Path, entry: &WorktreeCopyEntry) -> Result<Option<InstalledPath>, WorktreeCopyError> {
let destination_path: PathBuf = filesystem_path(destination_root, entry.relative_path.as_str())?;
if lstat_or_absent(destination_path.as_path())?.is_some() {
return Ok(None);
}
create_selected_entry(stage_root, destination_root, entry)?;
return capture_installed_path(destination_path.as_path(), entry.relative_path.as_str(), true).map(Some);
}
fn install_slice(
(stage_root, destination_root, slice): (&Path, &Path, &[&WorktreeCopyEntry]),
) -> (Vec<InstalledPath>, Option<WorktreeCopyError>) {
let mut made: Vec<InstalledPath> = Vec::new();
for entry in slice {
let outcome: Result<Option<InstalledPath>, WorktreeCopyError> = install_one(stage_root, destination_root, entry);
match outcome {
Ok(Some(installed)) => made.push(installed),
Ok(None) => {}
Err(error) => return (made, Some(error)),
}
}
return (made, None);
}
fn install_leaves(
stage_root: &Path,
destination_root: &Path,
leaves: &[&WorktreeCopyEntry],
created: &mut Vec<InstalledPath>,
) -> Result<(), WorktreeCopyError> {
let workers: usize = if leaves.len() < PARALLEL_INSTALL_MIN_LEAVES {
1
} else {
sliced_workers(leaves.len(), PARALLEL_COPY_MAX_WORKERS, &HostResources::read())
};
let slice_length: usize = leaves.len().div_ceil(workers).max(1);
let mut slices: Vec<(&Path, &Path, &[&WorktreeCopyEntry])> = Vec::new();
for slice in leaves.chunks(slice_length) {
slices.push((stage_root, destination_root, slice));
}
let mut results: Vec<(Vec<InstalledPath>, Option<WorktreeCopyError>)> = Vec::new();
for joined in run_all_side_by_side(slices, install_slice) {
results.push(match joined {
Ok(done) => done,
Err(_panic) => (Vec::new(), Some(failure_with_cause("monochromatic-git-policy-cli: an install worker panicked.", None))),
});
}
let mut failure: Option<WorktreeCopyError> = None;
for (made, stopped) in results {
created.extend(made);
failure = failure.or(stopped);
}
return match failure {
Some(error) => Err(error),
None => Ok(()),
};
}
pub fn install_snapshot(
stage_root: &Path,
destination_root: &Path,
entries: &[WorktreeCopyEntry],
claimed: &BTreeSet<String>,
prior: &[InstalledPath],
recorder: &mut dyn InstallRecorder,
) -> Result<usize, WorktreeCopyError> {
let mut created: Vec<InstalledPath> = prior.to_vec();
match install_all(
stage_root,
destination_root,
entries,
claimed,
&mut created,
recorder,
) {
Ok(copied) => return Ok(copied),
Err(error) => {
let retained: Vec<String> =
rollback_created(stage_root, destination_root, entries, created.as_slice())?;
let suffix: String = if retained.is_empty() {
String::new()
} else {
let names: Vec<String> = retained.iter().map(String::as_str).map(quote).collect();
format!(" Rollback retained: {}.", names.join(", "))
};
let message: String = format!(
"monochromatic-git-policy-cli: ignored-state installation failed for {}.{suffix}",
quote(destination_root.to_string_lossy().as_ref())
);
return Err(failure_with_cause(message.as_str(), Some(&error.0)));
}
}
}
fn install_all(
stage_root: &Path,
destination_root: &Path,
entries: &[WorktreeCopyEntry],
claimed: &BTreeSet<String>,
created: &mut Vec<InstalledPath>,
recorder: &mut dyn InstallRecorder,
) -> Result<usize, WorktreeCopyError> {
let started: std::time::Instant = std::time::Instant::now();
let absent: BTreeSet<String> =
preflight_destination(stage_root, destination_root, entries, claimed)?;
let preflighted: std::time::Duration = started.elapsed();
let mut known: std::collections::HashSet<String> = std::collections::HashSet::new();
for batch in entries.chunks(INSTALL_BATCH_ENTRIES) {
install_batch(
stage_root,
destination_root,
batch,
&absent,
created,
recorder,
&mut known,
)?;
}
let created_after: std::time::Duration = started.elapsed();
apply_entry_modes(destination_root, entries)?;
crate::diagnostic_log::debug(
"worktreeCopyInstall",
format!(
"installed {} entries: preflight {} ms, create and record {} ms, modes {} ms",
entries.len(),
preflighted.as_millis(),
(created_after - preflighted).as_millis(),
(started.elapsed() - created_after).as_millis()
)
.as_str(),
);
let mut selected: usize = 0;
for installed in created.iter() {
if installed.selected {
selected += 1;
}
}
return Ok(selected);
}
#[cfg(test)]
#[path = "worktree_copy_install_tests.rs"]
mod tests;