use std::io::{BufRead, BufReader, Read};
use std::process::{Child, Command, Stdio};
use std::thread;
use std::time::Duration;
use anyhow::{Context, Result, anyhow};
use crate::args;
use crate::base_fix::{self, BaseFixAsk};
use crate::failed_run;
use crate::interrupt;
use crate::issue::IssueUrl;
use crate::progress;
const POLL: Duration = Duration::from_millis(100);
#[derive(Debug, PartialEq, Eq)]
pub enum Kind {
Ticket { spec_branch: String },
BaseFix { base: String },
}
impl Kind {
pub fn base(&self) -> &str {
match self {
Kind::Ticket { spec_branch } => spec_branch,
Kind::BaseFix { base } => base,
}
}
fn hidden_argument(&self) -> &'static str {
match self {
Kind::Ticket { .. } => args::SPEC_BRANCH,
Kind::BaseFix { .. } => args::BASE_FIX_INTO,
}
}
}
pub enum Ended {
Reached {
pr_url: Option<String>,
base_fix: Option<String>,
},
Failed { cause: String, log: Option<String> },
Interrupted,
}
pub fn start(issue: &IssueUrl, kind: &Kind, base_fix: BaseFixAsk) -> Result<Child> {
Command::new(std::env::current_exe().context("no thirdshift executable")?)
.args([kind.hidden_argument(), kind.base()])
.args((base_fix == BaseFixAsk::Allow).then_some(args::BASE_FIX))
.arg(&issue.url)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.with_context(|| format!("could not start the Run for #{}", issue.number))
}
pub fn wait(number: u64, mut child: Child) -> Result<Ended> {
let stderr = child.stderr.take().context("no stderr from the Run")?;
let relay = thread::spawn(move || -> std::io::Result<_> {
let mut last_lines: [Option<String>; 2] = [None, None];
let mut base_fix = None;
for line in BufReader::new(stderr).lines() {
let line = line?;
let message = progress::relay(format_args!("#{number}"), &line).to_string();
if let Some(report) = message.strip_prefix(base_fix::REPORT) {
base_fix = Some(report.to_string());
}
last_lines = [last_lines[1].take(), Some(message)];
}
Ok((last_lines, base_fix))
});
let mut passed_on = false;
let status = loop {
if !passed_on && interrupt::requested() {
unsafe { libc::kill(child.id() as libc::pid_t, libc::SIGTERM) };
passed_on = true;
}
if let Some(status) = child
.try_wait()
.with_context(|| format!("could not wait for the Run for #{number}"))?
{
break status;
}
thread::sleep(POLL);
};
let (last_lines, base_fix) = relay
.join()
.map_err(|_| anyhow!("the relay of the Run for #{number} panicked"))?
.with_context(|| format!("could not read the Run for #{number}"))?;
let mut stdout = String::new();
child
.stdout
.take()
.context("no stdout from the Run")?
.read_to_string(&mut stdout)
.with_context(|| format!("could not read the Run for #{number}"))?;
if status.success() {
return Ok(Ended::Reached {
pr_url: stdout.lines().last().map(String::from),
base_fix,
});
}
if interrupt::requested() {
return Ok(Ended::Interrupted);
}
let [before, last] = last_lines;
let (cause, log) = match last {
Some(last) => match last.strip_prefix(failed_run::SESSION_LOG) {
Some(log) => (before, Some(log.to_string())),
None => (Some(last), None),
},
None => (None, None),
};
Ok(Ended::Failed {
cause: cause.unwrap_or_else(|| format!("the Run {status}")),
log,
})
}