use std::fmt;
use std::fs::{self, File, TryLockError};
use anyhow::{Context, Result};
use crate::config;
use crate::git::Git;
use crate::issue::Repo;
use crate::preflight;
pub struct Launch {
pub git: Git,
pub origin: String,
pub repo: Repo,
pub checked_out: Option<String>,
pub base: String,
}
pub enum Start {
Clear(Launch),
AlreadyRunning(AlreadyRunning),
}
#[derive(Debug)]
pub struct AlreadyRunning(pub Repo);
impl fmt::Display for AlreadyRunning {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"an Architect run or a Pickup run is already running on {}",
self.0.slug()
)
}
}
pub fn start(base: Option<&str>) -> Result<Start> {
let (git, origin, repo) = directory()?;
preflight::check_identity(&git)?;
let checked_out = git
.run(&["symbolic-ref", "--quiet", "--short", "HEAD"])
.ok();
let base = base
.or(checked_out.as_deref())
.context(
"HEAD is detached; check out the branch the work should be based on, \
or name it with base <branch>",
)?
.to_string();
preflight::check_base_branch(&git, &base)?;
let Some(lock) = try_run_lock(&repo)? else {
return Ok(Start::AlreadyRunning(AlreadyRunning(repo)));
};
std::mem::forget(lock);
Ok(Start::Clear(Launch {
git,
origin,
repo,
checked_out,
base,
}))
}
fn directory() -> Result<(Git, String, Repo)> {
let git = Git::new(std::env::current_dir().context("no current directory")?);
let origin = git.run(&["config", "remote.origin.url"])?;
let repo = Repo::of_origin(&origin)
.with_context(|| format!("origin {origin} is not a GitHub repository"))?;
Ok((git, origin, repo))
}
pub fn repo() -> Result<Repo> {
directory().map(|(_, _, repo)| repo)
}
fn try_run_lock(repo: &Repo) -> Result<Option<File>> {
let dir = config::home()?
.join(".thirdshift/architect-locks")
.join(repo.owner.to_ascii_lowercase());
fs::create_dir_all(&dir).with_context(|| format!("can't create {}", dir.display()))?;
let path = dir.join(format!("{}.lock", repo.name.to_ascii_lowercase()));
let file = File::create(&path).with_context(|| format!("can't open {}", path.display()))?;
match file.try_lock() {
Ok(()) => Ok(Some(file)),
Err(TryLockError::WouldBlock) => Ok(None),
Err(TryLockError::Error(error)) => {
Err(error).with_context(|| format!("can't lock {}", path.display()))
}
}
}