use std::fs;
use std::io::{ErrorKind, Write};
use std::path::PathBuf;
use anyhow::{Context, Result, bail};
use crate::git;
const LOCK_FILE: &str = "stk-lock";
pub struct Lock {
path: Option<PathBuf>,
}
impl Lock {
pub fn acquire(command: &str) -> Result<Self> {
let Ok(path) = git::git_common_path(LOCK_FILE) else {
return Ok(Self { path: None });
};
let path = PathBuf::from(path);
for reclaim in [true, false] {
match fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&path)
{
Ok(mut file) => {
let _ = writeln!(file, "{} {command}", std::process::id());
return Ok(Self { path: Some(path) });
}
Err(error) if error.kind() == ErrorKind::AlreadyExists => {
let holder = fs::read_to_string(&path).unwrap_or_default();
let holder = holder.trim().to_owned();
if reclaim && holder_is_stale(&holder) {
anstream::eprintln!(
"{}",
crate::style::dim(&format!(
"reclaiming a stale git-stk lock; its holder ({holder}) is gone"
))
);
let _ = fs::remove_file(&path);
continue;
}
let by = if holder.is_empty() {
String::new()
} else {
format!(" ({holder})")
};
bail!(
"another git stk operation is in progress{by}; wait for it to \
finish, or remove {} if it is stale",
path.display()
);
}
Err(error) => {
return Err(error)
.with_context(|| format!("failed to take the lock at {}", path.display()));
}
}
}
unreachable!("the second pass always returns or bails");
}
}
fn holder_is_stale(holder: &str) -> bool {
holder
.split_whitespace()
.next()
.and_then(|token| token.parse::<i32>().ok())
.is_some_and(process_is_dead)
}
#[cfg(unix)]
fn process_is_dead(pid: i32) -> bool {
if pid <= 0 {
return false;
}
if unsafe { libc::kill(pid, 0) } == 0 {
return false;
}
std::io::Error::last_os_error().raw_os_error() == Some(libc::ESRCH)
}
#[cfg(not(unix))]
fn process_is_dead(_pid: i32) -> bool {
false
}
impl Drop for Lock {
fn drop(&mut self) {
if let Some(path) = &self.path {
let _ = fs::remove_file(path);
}
}
}