use std::path::{Path, PathBuf};
use anyhow::{Context as _, Result};
use openmoat_hosts::{HookRequest, Host, SessionDir};
use crate::context::{absolute, path_string};
use crate::home::{Home, user_home};
use crate::install::{HostConfig, read_or_empty};
use crate::project;
pub(super) fn placed(
home: &Home,
host: Host,
request: HookRequest,
drifted: &[crate::integrity::Drift],
) -> Result<HookRequest> {
let mut request = in_session(host, request, &std::env::current_dir)?;
if request.searches_workspace() {
request = in_workspace(home, host, request)?;
}
request.sandboxed =
request.sandboxed && crate::protection::os_sandboxed_by_lock(home, host, drifted);
Ok(request)
}
fn in_session(
host: Host,
request: HookRequest,
session: &dyn Fn() -> std::io::Result<PathBuf>,
) -> Result<HookRequest> {
if host.spec().session_dir != SessionDir::HookProcess {
return Ok(request);
}
let Some(tool) = request.cwd.as_deref().filter(|c| !c.is_empty()) else {
return Ok(request);
};
let session = session().context("determining the hook's working directory")?;
let home = absolute(&user_home()?)?;
let tool = absolute(Path::new(tool))?;
let keep = match project::root_of(&session, &home) {
Some(root) => inside(&root, &tool),
None => git_root(&tool, &home).is_some(),
};
Ok(if keep {
request
} else {
request.from_session_dir(&path_string(&session))
})
}
fn inside(root: &Path, tool: &Path) -> bool {
let real = |p: &Path| std::fs::canonicalize(p).ok();
project::contains(root, tool)
|| matches!((real(root), real(tool)), (Some(r), Some(t)) if project::contains(&r, &t))
}
fn git_root(start: &Path, home: &Path) -> Option<PathBuf> {
start
.ancestors()
.find(|dir| dir.join(".git").exists())
.filter(|root| project::trusted(root, home))
.map(Path::to_path_buf)
}
fn in_workspace(home: &Home, host: Host, request: HookRequest) -> Result<HookRequest> {
let Some(read) = host.spec().workspace_dirs else {
return Ok(request);
};
let mut files = vec![HostConfig::for_host(home, host)?.settings_path];
if let (Some(cwd), Some(settings)) = (request.cwd.as_deref(), &host.spec().project_settings) {
let cwd = absolute(Path::new(cwd))?;
let user_home = absolute(&user_home()?)?;
let mut dirs = vec![cwd.clone()];
dirs.extend(project::root_of(&cwd, &user_home).filter(|root| *root != cwd));
for dir in dirs {
let file = dir.join(settings.dir).join(settings.shared);
if !files.contains(&file) {
files.push(file);
}
}
}
let mut extra = Vec::new();
for file in files {
match read_or_empty(&file) {
Ok(settings) => extra.extend(read(&settings)),
Err(_) => return Ok(request),
}
}
Ok(request.with_workspace_dirs(&extra))
}
#[cfg(test)]
mod tests;