use crate::stopped_git_locks::{LOCK_SUFFIX, PID_LOCK_SUFFIX};
use std::path::{Path, PathBuf};
const PACKED_STAGING: &str = "packed-refs.new";
const PACKED_LOCK: &str = "packed-refs.lock";
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Candidate {
pub root: PathBuf,
pub lock: PathBuf,
pub orphan_staging: bool,
}
fn is_regular_file(path: &Path) -> bool {
return match std::fs::symlink_metadata(path) {
Ok(metadata) => metadata.is_file(),
Err(_) => false,
};
}
fn is_missing(path: &Path) -> bool {
return match std::fs::symlink_metadata(path) {
Ok(_) => false,
Err(error) => error.kind() == std::io::ErrorKind::NotFound,
};
}
fn by_lock(left: &Candidate, right: &Candidate) -> std::cmp::Ordering {
return left.lock.cmp(&right.lock);
}
fn same_lock(left: &mut Candidate, right: &mut Candidate) -> bool {
return left.lock == right.lock;
}
fn is_reference_lock(path: &Path, is_file: bool) -> bool {
let Some(name) = path.file_name().and_then(std::ffi::OsStr::to_str) else {
return false;
};
return is_file && name.ends_with(LOCK_SUFFIX) && !name.ends_with(PID_LOCK_SUFFIX);
}
pub fn ref_lock_candidates(roots: &[PathBuf]) -> Vec<Candidate> {
let mut found: Vec<Candidate> = Vec::new();
for root in roots {
for name in [PACKED_LOCK, "HEAD.lock"] {
let path: PathBuf = root.join(name);
if is_regular_file(path.as_path()) {
found.push(Candidate { root: root.clone(), lock: path, orphan_staging: false });
}
}
let staging: PathBuf = root.join(PACKED_STAGING);
if is_regular_file(staging.as_path()) && is_missing(root.join(PACKED_LOCK).as_path()) {
found.push(Candidate { root: root.clone(), lock: staging, orphan_staging: true });
}
let mut pending: Vec<PathBuf> = vec![root.join("refs")];
while let Some(directory) = pending.pop() {
let Ok(entries) = std::fs::read_dir(directory.as_path()) else {
continue;
};
for entry in entries.flatten() {
let Ok(kind) = entry.file_type() else {
continue;
};
if kind.is_dir() {
pending.push(entry.path());
} else if is_reference_lock(entry.path().as_path(), kind.is_file()) {
found.push(Candidate { root: root.clone(), lock: entry.path(), orphan_staging: false });
}
}
}
}
found.sort_by(by_lock);
found.dedup_by(same_lock);
return found;
}
pub fn pid_file_beside(lock: &Path) -> PathBuf {
let name: String = match lock.file_name() {
Some(file_name) => file_name.to_string_lossy().into_owned(),
None => String::new(),
};
let stem: &str = name.strip_suffix(LOCK_SUFFIX).unwrap_or(name.as_str());
return lock.with_file_name(format!("{stem}{PID_LOCK_SUFFIX}"));
}
pub fn companions_of(lock: &Path) -> Vec<PathBuf> {
let mut companions: Vec<PathBuf> = vec![pid_file_beside(lock)];
if lock.file_name() == Some(std::ffi::OsStr::new(PACKED_LOCK)) {
companions.push(lock.with_file_name(PACKED_STAGING));
}
let mut existing: Vec<PathBuf> = Vec::new();
for path in companions {
if is_regular_file(path.as_path()) {
existing.push(path);
}
}
return existing;
}