ralph_api/
loop_support.rs1use std::path::Path;
2use std::process::Command;
3
4use chrono::{SecondsFormat, Utc};
5use ralph_core::{MergeQueueError, WorktreeError};
6
7use crate::errors::ApiError;
8
9pub fn loop_not_found_error(loop_id: &str) -> ApiError {
10 ApiError::loop_not_found(format!("Loop '{loop_id}' not found"))
11 .with_details(serde_json::json!({ "loopId": loop_id }))
12}
13
14pub fn map_merge_error(error: MergeQueueError) -> ApiError {
15 match error {
16 MergeQueueError::NotFound(loop_id) => loop_not_found_error(&loop_id),
17 MergeQueueError::InvalidTransition(loop_id, from, to) => ApiError::precondition_failed(
18 format!("Invalid loop transition for '{loop_id}': {from:?} -> {to:?}"),
19 )
20 .with_details(serde_json::json!({
21 "loopId": loop_id,
22 "from": format!("{from:?}"),
23 "to": format!("{to:?}")
24 })),
25 other => ApiError::internal(format!("merge queue operation failed: {other}")),
26 }
27}
28
29pub fn map_worktree_error(loop_id: &str, error: WorktreeError) -> ApiError {
30 ApiError::internal(format!(
31 "worktree cleanup failed for loop '{loop_id}': {error}"
32 ))
33 .with_details(serde_json::json!({ "loopId": loop_id }))
34}
35
36pub fn current_commit(workspace_root: &Path) -> String {
37 let output = Command::new("git")
38 .args(["rev-parse", "--short", "HEAD"])
39 .current_dir(workspace_root)
40 .output();
41
42 match output {
43 Ok(output) if output.status.success() => {
44 let sha = String::from_utf8_lossy(&output.stdout).trim().to_string();
45 if sha.is_empty() {
46 "manual".to_string()
47 } else {
48 sha
49 }
50 }
51 _ => "manual".to_string(),
52 }
53}
54
55pub fn is_pid_alive(pid: u32) -> bool {
56 Command::new("kill")
57 .args(["-0", &pid.to_string()])
58 .status()
59 .is_ok_and(|status| status.success())
60}
61
62pub(crate) fn now_ts() -> String {
63 Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true)
64}