Skip to main content

ralph_api/
loop_side_effects.rs

1use std::path::{Path, PathBuf};
2use std::process::Command;
3
4use ralph_core::{LoopRegistry, MergeQueue, list_ralph_worktrees};
5
6use crate::errors::ApiError;
7use crate::loop_support::{loop_not_found_error, map_merge_error};
8
9pub struct ResolvedLoop {
10    pub id: String,
11    pub worktree_path: Option<PathBuf>,
12}
13
14pub fn spawn_retry_merge_flow(
15    workspace_root: &Path,
16    ralph_command: &str,
17    loop_id: &str,
18) -> Result<(), ApiError> {
19    let status = Command::new(ralph_command)
20        .args(["loops", "retry", loop_id])
21        .current_dir(workspace_root)
22        .status()
23        .map_err(|error| {
24            ApiError::internal(format!(
25                "failed invoking '{ralph_command}' for loop retry '{loop_id}': {error}"
26            ))
27        })?;
28
29    if !status.success() {
30        return Err(ApiError::internal(format!(
31            "loop retry command exited with status {status} for loop '{loop_id}'"
32        )));
33    }
34
35    Ok(())
36}
37
38pub fn resolve_discard_target(
39    workspace_root: &Path,
40    loop_id: &str,
41) -> Result<ResolvedLoop, ApiError> {
42    let registry = LoopRegistry::new(workspace_root);
43    match registry.get(loop_id) {
44        Ok(Some(entry)) => {
45            return Ok(ResolvedLoop {
46                id: entry.id,
47                worktree_path: entry.worktree_path.map(PathBuf::from),
48            });
49        }
50        Ok(None) => {}
51        Err(error) => {
52            return Err(ApiError::internal(format!(
53                "failed resolving loop '{loop_id}' from registry: {error}"
54            )));
55        }
56    }
57
58    let queue = MergeQueue::new(workspace_root);
59    if let Some(entry) = queue.get_entry(loop_id).map_err(map_merge_error)? {
60        return Ok(ResolvedLoop {
61            id: entry.loop_id.clone(),
62            worktree_path: find_worktree_path(workspace_root, &entry.loop_id)?,
63        });
64    }
65
66    if let Some(worktree_path) = find_worktree_path(workspace_root, loop_id)? {
67        return Ok(ResolvedLoop {
68            id: loop_id.to_string(),
69            worktree_path: Some(worktree_path),
70        });
71    }
72
73    Err(loop_not_found_error(loop_id))
74}
75
76pub fn resolve_loop_root(workspace_root: &Path, loop_id: &str) -> Result<PathBuf, ApiError> {
77    if loop_id == "(primary)" || loop_id == "primary" {
78        return Ok(workspace_root.to_path_buf());
79    }
80
81    let registry = LoopRegistry::new(workspace_root);
82    match registry.get(loop_id) {
83        Ok(Some(entry)) => Ok(entry
84            .worktree_path
85            .map(PathBuf::from)
86            .unwrap_or_else(|| workspace_root.to_path_buf())),
87        Ok(None) => Err(loop_not_found_error(loop_id)),
88        Err(error) => Err(ApiError::internal(format!(
89            "failed resolving loop '{loop_id}': {error}"
90        ))),
91    }
92}
93
94fn find_worktree_path(workspace_root: &Path, loop_id: &str) -> Result<Option<PathBuf>, ApiError> {
95    let worktrees = list_ralph_worktrees(workspace_root)
96        .map_err(|error| ApiError::internal(format!("failed listing worktrees: {error}")))?;
97
98    Ok(worktrees.into_iter().find_map(|worktree| {
99        worktree
100            .branch
101            .strip_prefix("ralph/")
102            .is_some_and(|branch_loop_id| branch_loop_id == loop_id)
103            .then_some(worktree.path)
104    }))
105}