use crate::repo_read_layout::read_layout_in;
use crate::service_instance::{SOCKET_DIRECTORY, instance_of, socket_for};
use crate::service_provision::{ProvisionHost, RETIREMENT_WAIT, SERVICE_PROGRAM};
use crate::service_server::Bind;
use crate::systemd_dbus::session_bus_path;
use crate::transaction_git::{GitContext, GitRequest, run_git};
use std::path::{Path, PathBuf};
static OWN_EXECUTABLE: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
pub fn remember_own_executable(path: &Path) {
let _first = OWN_EXECUTABLE.set(path.to_path_buf());
}
pub fn remembered_own_executable() -> Option<&'static Path> {
return OWN_EXECUTABLE.get().map(PathBuf::as_path);
}
#[derive(Clone, Copy, Debug)]
pub struct RouteInputs<'a> {
pub runtime_dir: Option<&'a Path>,
pub bus_address: Option<&'a str>,
pub config_home: Option<&'a Path>,
pub home: Option<&'a Path>,
pub own_executable: Option<&'a Path>,
pub real_git: &'a Path,
pub git_dir: &'a Path,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Route {
pub address: Bind,
pub provision: Option<ProvisionHost>,
}
pub fn user_unit_dir(config_home: Option<&Path>, home: Option<&Path>) -> Option<PathBuf> {
return config_home
.map(|directory| { return directory.join("systemd").join("user"); })
.or_else(|| { return home.map(|directory| { return directory.join(".config").join("systemd").join("user"); }); });
}
pub fn choose_route(inputs: &RouteInputs<'_>) -> Option<Route> {
let runtime_dir = inputs.runtime_dir?;
let socket: PathBuf = socket_for(runtime_dir, inputs.git_dir);
let socket_exists: bool = socket.exists();
let Some(bus) = session_bus_path(inputs.bus_address, Some(runtime_dir)).filter(|path| { return path.exists(); }) else {
return if socket_exists { Some(Route { address: Bind::Path(socket), provision: None }) } else { None };
};
let Some(service_binary) = inputs
.own_executable
.and_then(Path::parent)
.map(|directory| { return directory.join(SERVICE_PROGRAM); })
.filter(|path| { return path.is_file(); })
else {
return if socket_exists { Some(Route { address: Bind::Path(socket), provision: None }) } else { None };
};
let host: ProvisionHost = ProvisionHost {
runtime_dir: runtime_dir.to_path_buf(),
user_unit_dir: user_unit_dir(inputs.config_home, inputs.home),
bus,
service_binary,
git: inputs.real_git.to_path_buf(),
instance: instance_of(inputs.git_dir),
retirement_wait: RETIREMENT_WAIT,
};
return Some(Route { address: Bind::Path(socket), provision: Some(host) });
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StatusReport {
pub code: i32,
pub stdout: String,
pub stderr: String,
}
pub fn worktree_git_dir(context: &GitContext, cwd: &Path) -> Option<PathBuf> {
if let Some(layout) = read_layout_in(cwd, context.overlay.as_slice()) {
return Some(layout.git_dir);
}
let output = run_git(context, &GitRequest::new(cwd, &["rev-parse", "--path-format=absolute", "--absolute-git-dir"])).ok()?;
if !output.succeeded() {
return None;
}
let text: String = String::from_utf8_lossy(output.stdout.as_slice()).trim().to_string();
return if text.is_empty() { None } else { Some(PathBuf::from(text)) };
}
pub fn route_from_environment(real_git: &Path, git_dir: &Path) -> Option<Route> {
let path_variable = |name: &str| -> Option<PathBuf> { return std::env::var_os(name).filter(|value| { return !value.is_empty(); }).map(PathBuf::from); };
let runtime: Option<PathBuf> = path_variable("XDG_RUNTIME_DIR");
let config_home: Option<PathBuf> = path_variable("XDG_CONFIG_HOME");
let home: Option<PathBuf> = path_variable("HOME");
let bus_address: Option<String> = std::env::var("DBUS_SESSION_BUS_ADDRESS").ok();
return choose_route(&RouteInputs {
runtime_dir: runtime.as_deref(),
bus_address: bus_address.as_deref(),
config_home: config_home.as_deref(),
home: home.as_deref(),
own_executable: remembered_own_executable(),
real_git,
git_dir,
});
}
pub fn service_status(runtime_dir: Option<&Path>, git_dir: Option<&Path>) -> StatusReport {
let answers = |socket: &Path| -> bool { return std::os::unix::net::UnixStream::connect(socket).is_ok(); };
let (answered, what): (bool, &str) = match (runtime_dir, git_dir) {
(Some(runtime), Some(git)) => (answers(socket_for(runtime, git).as_path()), "The commit service of this worktree"),
(Some(runtime), None) => {
let any: bool = std::fs::read_dir(runtime.join(SOCKET_DIRECTORY)).is_ok_and(|entries| {
return entries.flatten().any(|entry| { return entry.path().extension().is_some_and(|extension| { return extension == "sock"; }) && answers(entry.path().as_path()); });
});
(any, "A commit service")
}
(None, _) => (false, "The commit service"),
};
return if answered {
StatusReport { code: 0, stdout: format!("{what} is running and answers.\n"), stderr: String::new() }
} else {
StatusReport {
code: 1,
stdout: String::new(),
stderr: format!(
"{what} does not answer here. It sets itself up the first time a commit needs it, so there is nothing to \
install; if a commit fails, its message says what to do.\n"
),
}
};
}
#[cfg(test)]
#[path = "service_route_tests.rs"]
mod tests;