use super::{MARK_REF_ROOT, repo_git};
use crate::template::GitRunner;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use thiserror::Error;
pub const CWD_REF_PREFIX: &str = "cwd/";
pub fn cwd_ref(agent_id: &str) -> String {
format!("{MARK_REF_ROOT}{CWD_REF_PREFIX}{agent_id}")
}
pub fn read(workspace: &Path, agent_id: &str, git: &dyn GitRunner) -> Option<PathBuf> {
let spec = cwd_ref(agent_id);
let out = git
.run_capture(&repo_git(workspace), &["cat-file", "blob", &spec])
.ok()?;
(!out.is_empty()).then(|| PathBuf::from(out))
}
pub fn write(workspace: &Path, agent_id: &str, dir: &Path, git: &dyn GitRunner) -> io::Result<()> {
storable(dir).map_err(io::Error::other)?;
let repo = repo_git(workspace);
let staged = repo.join(format!("cwd-mark.{}.tmp", std::process::id()));
std::fs::write(&staged, dir.as_os_str().as_bytes())?;
let staged_str = staged.to_string_lossy().into_owned();
let hashed = git.run_capture(&repo, &["hash-object", "-w", "--", &staged_str]);
std::fs::remove_file(&staged)?;
git.run(&repo, &["update-ref", &cwd_ref(agent_id), &hashed?])
}
#[derive(Debug, Error)]
pub enum ResolveError {
#[error("no such directory {path:?}: {source}")]
NoSuchDir {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("{path:?} is not a directory — a working directory is one (ARCH §3.3)")]
NotADir { path: PathBuf },
#[error(
"cannot store {path:?} as a working directory: the mark holds trimmed UTF-8 text, so \
a path that is not UTF-8 or that leads or trails with whitespace would read back as \
a different directory (ARCH §3.3)"
)]
NotStorable { path: PathBuf },
}
pub fn resolve(path: &Path) -> Result<PathBuf, ResolveError> {
let abs = std::fs::canonicalize(path).map_err(|source| ResolveError::NoSuchDir {
path: path.to_owned(),
source,
})?;
if !abs.is_dir() {
return Err(ResolveError::NotADir {
path: path.to_owned(),
});
}
storable(&abs)?;
Ok(abs)
}
fn storable(dir: &Path) -> Result<(), ResolveError> {
let text = dir.to_str().filter(|s| !s.is_empty() && s.trim() == *s);
match text {
Some(_) => Ok(()),
None => Err(ResolveError::NotStorable {
path: dir.to_owned(),
}),
}
}
#[cfg(test)]
mod tests;