Skip to content

Commit 5c62cee

Browse files
committed
split main file to mods
1 parent 34dec52 commit 5c62cee

File tree

5 files changed

+586
-521
lines changed

5 files changed

+586
-521
lines changed

wum/src/gitlab.rs

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
use crate::types::{AppEvent, CiJobResponse, CiPipelineResponse, MrViewResponse, TodoItem};
2+
use anyhow::{Context, Result};
3+
use std::time::Duration;
4+
use tokio::process::Command;
5+
use tokio::sync::mpsc;
6+
7+
pub fn spawn_mr_processor(todo: TodoItem, tx: mpsc::UnboundedSender<AppEvent>) {
8+
tokio::spawn(async move {
9+
loop {
10+
match process_mr_once(&todo, &tx).await {
11+
Ok(true) => {
12+
let _ = tx.send(AppEvent::MrStateUpdate {
13+
repo: todo.repo.clone(),
14+
iid: todo.iid.clone(),
15+
title: None,
16+
url: None,
17+
status_text: Some("MERGED!".to_string()),
18+
done: Some(true),
19+
});
20+
let _ = tx.send(AppEvent::MarkDoneInFile {
21+
repo: todo.repo.clone(),
22+
iid: todo.iid.clone(),
23+
});
24+
break;
25+
}
26+
Ok(false) => {
27+
tokio::time::sleep(Duration::from_secs(10)).await;
28+
}
29+
Err(e) => {
30+
let _ = tx.send(AppEvent::MrStateUpdate {
31+
repo: todo.repo.clone(),
32+
iid: todo.iid.clone(),
33+
title: None,
34+
url: None,
35+
status_text: Some(format!("Error: {e}")),
36+
done: None,
37+
});
38+
tokio::time::sleep(Duration::from_secs(15)).await;
39+
}
40+
}
41+
}
42+
});
43+
}
44+
45+
pub async fn process_mr_once(
46+
todo: &TodoItem,
47+
tx: &mpsc::UnboundedSender<AppEvent>,
48+
) -> Result<bool> {
49+
let output = Command::new("glab")
50+
.args(["mr", "view", &todo.iid, "-F", "json", "-R", &todo.repo])
51+
.output()
52+
.await?;
53+
54+
if !output.status.success() {
55+
let stderr = String::from_utf8_lossy(&output.stderr);
56+
anyhow::bail!("glab error: {}", stderr.trim());
57+
}
58+
59+
let mr_info: MrViewResponse =
60+
serde_json::from_slice(&output.stdout).context("Failed to parse glab mr view JSON")?;
61+
62+
let detailed_status = mr_info.detailed_merge_status.as_deref().unwrap_or("");
63+
let url = mr_info.web_url.clone().unwrap_or_default();
64+
let source_branch = mr_info.source_branch.clone().unwrap_or_default();
65+
let title = mr_info.title.clone().unwrap_or_default();
66+
67+
let _ = tx.send(AppEvent::MrStateUpdate {
68+
repo: todo.repo.clone(),
69+
iid: todo.iid.clone(),
70+
title: Some(title),
71+
url: Some(url.clone()),
72+
status_text: Some(format!("Status: {detailed_status}")),
73+
done: None,
74+
});
75+
76+
if detailed_status == "need_rebase" {
77+
return handle_need_rebase(todo, tx, &url).await;
78+
}
79+
80+
if detailed_status == "mergeable" {
81+
return handle_mergeable(todo, tx, &url).await;
82+
}
83+
84+
handle_ci_status(todo, tx, &source_branch, &url, detailed_status).await?;
85+
86+
Ok(false)
87+
}
88+
89+
async fn handle_need_rebase(
90+
todo: &TodoItem,
91+
tx: &mpsc::UnboundedSender<AppEvent>,
92+
url: &str,
93+
) -> Result<bool> {
94+
let _ = tx.send(AppEvent::MrStateUpdate {
95+
repo: todo.repo.clone(),
96+
iid: todo.iid.clone(),
97+
title: None,
98+
url: None,
99+
status_text: Some("Rebasing...".to_string()),
100+
done: None,
101+
});
102+
103+
let rebase_output = Command::new("glab")
104+
.args(["mr", "rebase", &todo.iid, "-R", &todo.repo])
105+
.output()
106+
.await?;
107+
108+
if !rebase_output.status.success() {
109+
let _ = tx.send(AppEvent::LogIssueInFile {
110+
repo: todo.repo.clone(),
111+
iid: todo.iid.clone(),
112+
message: format!("❌ Rebase failed for MR {url} - needs manual fix"),
113+
});
114+
anyhow::bail!("Rebase failed");
115+
}
116+
Ok(false)
117+
}
118+
119+
async fn handle_mergeable(
120+
todo: &TodoItem,
121+
tx: &mpsc::UnboundedSender<AppEvent>,
122+
url: &str,
123+
) -> Result<bool> {
124+
let _ = tx.send(AppEvent::MrStateUpdate {
125+
repo: todo.repo.clone(),
126+
iid: todo.iid.clone(),
127+
title: None,
128+
url: None,
129+
status_text: Some("Merging...".to_string()),
130+
done: None,
131+
});
132+
133+
let merge_output = Command::new("glab")
134+
.args([
135+
"mr",
136+
"merge",
137+
&todo.iid,
138+
"-R",
139+
&todo.repo,
140+
"--remove-source-branch",
141+
"--squash",
142+
"--auto-merge",
143+
"--yes",
144+
])
145+
.output()
146+
.await?;
147+
148+
if merge_output.status.success() {
149+
Ok(true)
150+
} else {
151+
let _ = tx.send(AppEvent::LogIssueInFile {
152+
repo: todo.repo.clone(),
153+
iid: todo.iid.clone(),
154+
message: format!("❌ Merge failed for MR {url}"),
155+
});
156+
anyhow::bail!("Merge failed");
157+
}
158+
}
159+
160+
async fn handle_ci_status(
161+
todo: &TodoItem,
162+
tx: &mpsc::UnboundedSender<AppEvent>,
163+
source_branch: &str,
164+
url: &str,
165+
detailed_status: &str,
166+
) -> Result<()> {
167+
let ci_get_output = Command::new("glab")
168+
.args([
169+
"ci",
170+
"get",
171+
"--branch",
172+
source_branch,
173+
"-F",
174+
"json",
175+
"-R",
176+
&todo.repo,
177+
])
178+
.output()
179+
.await?;
180+
181+
if let Ok(ci_info) = serde_json::from_slice::<CiPipelineResponse>(&ci_get_output.stdout) {
182+
let pipeline_status = ci_info.status.as_deref().unwrap_or("");
183+
184+
if pipeline_status == "manual" {
185+
let _ = tx.send(AppEvent::MrStateUpdate {
186+
repo: todo.repo.clone(),
187+
iid: todo.iid.clone(),
188+
title: None,
189+
url: None,
190+
status_text: Some("Triggering manual pipeline...".to_string()),
191+
done: None,
192+
});
193+
194+
let ci_jobs = Command::new("glab")
195+
.args([
196+
"ci",
197+
"get",
198+
"--branch",
199+
source_branch,
200+
"-F",
201+
"json",
202+
"-R",
203+
&todo.repo,
204+
])
205+
.output()
206+
.await?;
207+
208+
let mut triggered = false;
209+
if let Ok(jobs_info) = serde_json::from_slice::<CiJobResponse>(&ci_jobs.stdout)
210+
&& let Some(jobs) = jobs_info.jobs
211+
{
212+
for job in jobs {
213+
if job.stage.as_deref() == Some("build")
214+
&& job.status.as_deref() == Some("manual")
215+
&& let Some(jid) = job.id
216+
{
217+
let trigger = Command::new("glab")
218+
.args(["ci", "trigger", &jid.to_string(), "-R", &todo.repo])
219+
.output()
220+
.await?;
221+
if trigger.status.success() {
222+
triggered = true;
223+
break;
224+
}
225+
}
226+
}
227+
}
228+
229+
if !triggered {
230+
let _ = tx.send(AppEvent::LogIssueInFile {
231+
repo: todo.repo.clone(),
232+
iid: todo.iid.clone(),
233+
message: format!("❌ Check what pipeline needs manual trigger for MR {url}"),
234+
});
235+
anyhow::bail!("Manual action required");
236+
}
237+
} else if pipeline_status == "running" {
238+
let _ = tx.send(AppEvent::MrStateUpdate {
239+
repo: todo.repo.clone(),
240+
iid: todo.iid.clone(),
241+
title: None,
242+
url: None,
243+
status_text: Some("Pipeline running...".to_string()),
244+
done: None,
245+
});
246+
} else if pipeline_status == "failed" {
247+
let _ = tx.send(AppEvent::LogIssueInFile {
248+
repo: todo.repo.clone(),
249+
iid: todo.iid.clone(),
250+
message: format!("❌ Pipeline failed for MR {url} - needs manual fix"),
251+
});
252+
anyhow::bail!("Pipeline failed");
253+
} else {
254+
let msg = match detailed_status {
255+
"not_approved" => "Waiting for approval",
256+
"ci_must_pass" | "ci_still_running" => "Waiting for CI",
257+
other => other,
258+
};
259+
let _ = tx.send(AppEvent::MrStateUpdate {
260+
repo: todo.repo.clone(),
261+
iid: todo.iid.clone(),
262+
title: None,
263+
url: None,
264+
status_text: Some(msg.to_string()),
265+
done: None,
266+
});
267+
}
268+
}
269+
Ok(())
270+
}

0 commit comments

Comments
 (0)