use crate::index_lock_evidence::{
Evidence, Verdict, blocker_words, ceiling_line, classify_index_lock, dead_owner_diagnostic, gather_with, proven_holder_line, proven_holder_progress_line, unproven_wait_line,
};
use crate::index_lock_holders::scan_platform_holders_bounded;
use crate::process_start::resolve_process_start;
use crate::service_index_lock::{Watch, lock_path_of};
use crate::wait_ceilings::LOCK_CEILING;
use crate::service_index_watch::POLL_PAUSE;
use crate::wait_progress::{Cadence, Ticker};
use std::io::Write;
use std::path::Path;
use std::time::{Duration, Instant};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IndexWait {
NotHeld,
Released(
Duration,
),
OwnerGone(
Duration,
),
GaveUp(
Duration,
),
}
enum Proof {
Alive(Evidence, i64, bool, Option<String>),
Dead(i64),
Nothing(Option<Evidence>),
}
fn look_at(real_index: &Path, gather: &dyn Fn(&Path) -> std::io::Result<Option<Evidence>>) -> Proof {
let Ok(Some(evidence)) = gather(real_index) else {
return Proof::Nothing(None);
};
return match classify_index_lock(&evidence) {
Verdict::ProvenAlive { pid, by_descriptor, command } => Proof::Alive(evidence, pid, by_descriptor, command),
Verdict::Dead(pid) => Proof::Dead(pid),
Verdict::EvidenceFree => Proof::Nothing(Some(evidence)),
};
}
fn gather_here(real_index: &Path) -> std::io::Result<Option<Evidence>> {
return gather_with(real_index, resolve_process_start, scan_platform_holders_bounded);
}
fn notice(command: &str, real_index: &Path, waited: Duration, proof: &Proof, first: bool) -> String {
let tail: String = match proof {
Proof::Alive(evidence, pid, by_descriptor, holder_command) => {
let line: String = if first {
proven_holder_line(*pid, *by_descriptor, holder_command.as_deref(), evidence.lock_path.as_path())
} else {
proven_holder_progress_line(*pid, *by_descriptor, holder_command.as_deref(), evidence.lock_path.as_path(), waited.as_secs())
};
line
}
Proof::Nothing(found) => unproven_wait_line(lock_path_of(real_index).as_path(), found.as_ref()),
Proof::Dead(_) => unproven_wait_line(lock_path_of(real_index).as_path(), None),
};
return format!(
"cli-git: git {command} has changed nothing yet and waits for the index lock of this repository; {}",
tail.strip_prefix("cli-git: ").unwrap_or(tail.as_str())
);
}
pub fn wait_for_index_release(real_index: &Path, command: &str, cadence: Cadence, out: &mut dyn Write) -> IndexWait {
return wait_for_index_release_within(real_index, command, LOCK_CEILING, cadence, out);
}
pub fn wait_for_index_release_within(real_index: &Path, command: &str, ceiling: Duration, cadence: Cadence, out: &mut dyn Write) -> IndexWait {
return wait_with_watches(real_index, command, ceiling, cadence, out, &Watch::on);
}
fn wait_with_watches(real_index: &Path, command: &str, ceiling: Duration, cadence: Cadence, out: &mut dyn Write, set_watch: &dyn Fn(&Path) -> Option<Watch>) -> IndexWait {
let lock = lock_path_of(real_index);
let mut watch: Option<Watch> = lock.parent().and_then(set_watch);
let lock_name: std::ffi::OsString = lock.file_name().map(std::ffi::OsStr::to_os_string).unwrap_or_default();
let consequence: String = format!("git {command} stops waiting and reports what Git says");
let began: Instant = Instant::now();
let mut ticker: Ticker = Ticker::announced_at_start(cadence);
let mut held: bool = false;
let mut announced: bool = false;
let mut proof: Proof = Proof::Nothing(None);
loop {
if std::fs::symlink_metadata(&lock).is_err() {
return if held { IndexWait::Released(began.elapsed()) } else { IndexWait::NotHeld };
}
held = true;
let elapsed: Duration = began.elapsed();
let due: bool = !announced || ticker.due(elapsed);
if due || elapsed >= ceiling {
proof = look_at(real_index, &gather_here);
}
if let Proof::Dead(pid) = &proof {
let _noted = out.write_all(dead_owner_diagnostic(lock.as_path(), *pid, consequence.as_str()).as_bytes()).and_then(|()| { return out.flush(); });
return IndexWait::OwnerGone(elapsed);
}
if due {
let _noted = out.write_all(notice(command, real_index, elapsed, &proof, !announced).as_bytes()).and_then(|()| { return out.flush(); });
announced = true;
}
if elapsed >= ceiling {
let blocker: String = match &proof {
Proof::Alive(evidence, ..) => blocker_words(evidence),
Proof::Nothing(Some(evidence)) => blocker_words(evidence),
Proof::Nothing(None) | Proof::Dead(_) => String::from("an owner whose evidence could not be read"),
};
let line: String = ceiling_line(lock.as_path(), blocker.as_str(), elapsed.as_secs(), consequence.as_str());
let _noted = out.write_all(line.as_bytes()).and_then(|()| { return out.flush(); });
return IndexWait::GaveUp(elapsed);
}
let pause: Duration = ticker.next_in(elapsed).min(ceiling.saturating_sub(elapsed)).max(Duration::from_millis(1));
match watch.as_ref() {
Some(active) => {
if !active.wait(pause, lock_name.as_os_str()) {
drop(watch.take());
watch = lock.parent().and_then(set_watch);
}
}
None => std::thread::sleep(pause.min(POLL_PAUSE)),
}
}
}
pub fn wait_for_index_release_here(real_index: &Path, command: &str) -> IndexWait {
return wait_for_index_release(real_index, command, Cadence::from_environment(), &mut std::io::stderr());
}
#[cfg(test)]
#[path = "index_holder_wait_tests.rs"]
mod tests;