use super::diagnostic_log::warn;
use super::stale_ref_lock_found::{Found, Outcome};
use super::stale_ref_lock_judge::{Examined, Identity, Judge, Verdict, identity_of};
use super::stale_ref_lock_walk::{Candidate, companions_of};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
const TAG: &str = "cli-git";
const ASIDE_PREFIX: &str = "cli-git-stale-lock";
static ASIDE_COUNT: AtomicU64 = AtomicU64::new(0);
fn aside_name(root: &Path) -> PathBuf {
let count: u64 = ASIDE_COUNT.fetch_add(1, Ordering::Relaxed);
let nanos: u128 = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_nanos();
return root.join(format!("{ASIDE_PREFIX}-{}-{count}-{nanos:x}", std::process::id()));
}
pub fn remove_if_unchanged(root: &Path, lock: &Path, judged: &Identity, outcome: &mut Outcome) -> Result<bool, String> {
let aside: PathBuf = aside_name(root);
match std::fs::rename(lock, aside.as_path()) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(error) => return Err(format!("it could not be set aside: {error}")),
}
if identity_of(aside.as_path()).as_ref() == Some(judged) {
return match std::fs::remove_file(aside.as_path()) {
Ok(()) => Ok(true),
Err(error) => Err(format!("the file that was set aside could not be removed ({error}); it is at {}", aside.display())),
};
}
match std::fs::hard_link(aside.as_path(), lock) {
Ok(()) => {
let _removed = std::fs::remove_file(aside.as_path());
warn(TAG, &format!("{} was taken again after it was judged; the new lock was put back", lock.display()));
}
Err(error) => {
warn(TAG, &format!("{} was taken again after it was judged and could not be put back ({error}); it is left at {}", lock.display(), aside.display()));
outcome.displaced.push(aside);
}
}
return Ok(false);
}
pub fn judge_and_clear(candidate: &Candidate, judge: &Judge, outcome: &mut Outcome) {
let maybe: Option<Examined> = if candidate.orphan_staging { judge.examine_staging(candidate.lock.as_path()) } else { judge.examine(candidate.lock.as_path()) };
let Some(Examined { verdict, pid_file, identity, born }) = maybe else {
return;
};
let mut found: Found = Found {
lock: candidate.lock.clone(),
verdict: verdict.clone(),
size: identity.size,
born,
pid_file,
companions: if candidate.orphan_staging { Vec::new() } else { companions_of(candidate.lock.as_path()) },
};
match verdict {
Verdict::TooNew => {}
Verdict::Held(_) | Verdict::Unproven(_) => outcome.left.push(found),
Verdict::Stale(_) if candidate.orphan_staging && candidate.lock.with_file_name("packed-refs.lock").symlink_metadata().is_ok() => {}
Verdict::Stale(_) => match remove_if_unchanged(candidate.root.as_path(), candidate.lock.as_path(), &identity, outcome) {
Ok(true) => {
for companion in &found.companions {
let _removed = std::fs::remove_file(companion);
}
outcome.cleared.push(found);
}
Ok(false) => {}
Err(reason) => {
found.verdict = Verdict::Unproven(reason);
outcome.left.push(found);
}
},
}
}