ralph_api/
loop_side_effects.rs1use 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}