git_policy_cli/
service_route.rs1use super::repo_read_layout::read_layout_in;
18use super::service_instance::{SOCKET_DIRECTORY, instance_of, socket_for};
19use super::service_provision::{ProvisionHost, RETIREMENT_WAIT, SERVICE_PROGRAM};
20use super::service_server::Bind;
21use super::systemd_dbus::session_bus_path;
22use super::transaction_git::{GitContext, GitRequest, run_git};
23use std::path::{Path, PathBuf};
24
25static OWN_EXECUTABLE: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
26
27pub fn remember_own_executable(path: &Path) {
31 let _first = OWN_EXECUTABLE.set(path.to_path_buf());
32}
33
34pub fn remembered_own_executable() -> Option<&'static Path> {
36 return OWN_EXECUTABLE.get().map(PathBuf::as_path);
37}
38
39#[derive(Clone, Copy, Debug)]
41pub struct RouteInputs<'a> {
42 pub runtime_dir: Option<&'a Path>,
44 pub bus_address: Option<&'a str>,
46 pub config_home: Option<&'a Path>,
48 pub home: Option<&'a Path>,
50 pub own_executable: Option<&'a Path>,
52 pub real_git: &'a Path,
54 pub git_dir: &'a Path,
56}
57
58#[derive(Clone, Debug, Eq, PartialEq)]
60pub enum Route {
61 Earlier,
63 Service {
65 address: Bind,
67 provision: Option<ProvisionHost>,
70 },
71}
72
73pub fn user_unit_dir(config_home: Option<&Path>, home: Option<&Path>) -> Option<PathBuf> {
75 return config_home
76 .map(|directory| { return directory.join("systemd").join("user"); })
77 .or_else(|| { return home.map(|directory| { return directory.join(".config").join("systemd").join("user"); }); });
78}
79
80pub fn choose_route(inputs: &RouteInputs<'_>) -> Route {
94 let Some(runtime_dir) = inputs.runtime_dir else { return Route::Earlier };
95 let socket: PathBuf = socket_for(runtime_dir, inputs.git_dir);
96 let socket_exists: bool = socket.exists();
97 let Some(bus) = session_bus_path(inputs.bus_address, Some(runtime_dir)).filter(|path| { return path.exists(); }) else {
98 return if socket_exists { Route::Service { address: Bind::Path(socket), provision: None } } else { Route::Earlier };
99 };
100 let Some(service_binary) = inputs
101 .own_executable
102 .and_then(Path::parent)
103 .map(|directory| { return directory.join(SERVICE_PROGRAM); })
104 .filter(|path| { return path.is_file(); })
105 else {
106 return if socket_exists { Route::Service { address: Bind::Path(socket), provision: None } } else { Route::Earlier };
107 };
108 let host: ProvisionHost = ProvisionHost {
109 runtime_dir: runtime_dir.to_path_buf(),
110 user_unit_dir: user_unit_dir(inputs.config_home, inputs.home),
111 bus,
112 service_binary,
113 git: inputs.real_git.to_path_buf(),
114 instance: instance_of(inputs.git_dir),
115 retirement_wait: RETIREMENT_WAIT,
116 };
117 return Route::Service { address: Bind::Path(socket), provision: Some(host) };
118}
119
120#[derive(Clone, Debug, Eq, PartialEq)]
122pub struct StatusReport {
123 pub code: i32,
125 pub stdout: String,
127 pub stderr: String,
129}
130
131pub fn worktree_git_dir(context: &GitContext, cwd: &Path) -> Option<PathBuf> {
143 if let Some(layout) = read_layout_in(cwd, context.overlay.as_slice()) {
144 return Some(layout.git_dir);
145 }
146 let output = run_git(context, &GitRequest::new(cwd, &["rev-parse", "--path-format=absolute", "--absolute-git-dir"])).ok()?;
147 if !output.succeeded() {
148 return None;
149 }
150 let text: String = String::from_utf8_lossy(output.stdout.as_slice()).trim().to_string();
151 return if text.is_empty() { None } else { Some(PathBuf::from(text)) };
152}
153
154pub fn service_status(runtime_dir: Option<&Path>, git_dir: Option<&Path>) -> StatusReport {
166 let answers = |socket: &Path| -> bool { return std::os::unix::net::UnixStream::connect(socket).is_ok(); };
167 let (answered, what): (bool, &str) = match (runtime_dir, git_dir) {
168 (Some(runtime), Some(git)) => (answers(socket_for(runtime, git).as_path()), "The commit service of this worktree"),
169 (Some(runtime), None) => {
170 let any: bool = std::fs::read_dir(runtime.join(SOCKET_DIRECTORY)).is_ok_and(|entries| {
171 return entries.flatten().any(|entry| { return entry.path().extension().is_some_and(|extension| { return extension == "sock"; }) && answers(entry.path().as_path()); });
172 });
173 (any, "A commit service")
174 }
175 (None, _) => (false, "The commit service"),
176 };
177 return if answered {
178 StatusReport { code: 0, stdout: format!("{what} is running and answers.\n"), stderr: String::new() }
179 } else {
180 StatusReport {
181 code: 1,
182 stdout: String::new(),
183 stderr: format!(
184 "{what} does not answer here. It sets itself up the first time a commit needs it, so there is nothing to \
185 install; if a commit fails, its message says what to do.\n"
186 ),
187 }
188 };
189}
190
191#[cfg(test)]
193#[path = "service_route_tests.rs"]
194mod tests;