Skip to content
Clone
0079d8d
128 lines 3.4 KB Rust
use crate::command::cmd::Cmd;
use crate::command::context::GitPipeContext;
use std::fmt;
use std::path::PathBuf;
#[derive(Clone)]
pub struct Pipe {
pub name: String,
pub cmds: Vec<Cmd>,
pub dir: PathBuf,
pub timeout: Option<u64>,
pub cancelled: bool,
}
impl Pipe {
pub fn new(name: impl Into<String>, dir: PathBuf) -> Self {
Self {
name: name.into(),
cmds: Vec::new(),
dir,
timeout: None,
cancelled: false,
}
}
/// Whether new subprocesses may still be spawned on this pipe.
/// Once `cancel_pipe()` has been called this returns `false` until `reset()`.
pub fn spawn_allowed(&self) -> bool {
!self.cancelled
}
/// Re-arm a cancelled pipe so new commands may be scheduled again.
/// Pre-existing children that were killed during `cancel_pipe` are kept in
/// `cmds` for audit but must not be reused.
pub fn reset(&mut self) {
self.cancelled = false;
}
pub fn push(mut self, cmd: Cmd) -> Self {
self.cmds.push(cmd);
self
}
pub fn extend<I: IntoIterator<Item = Cmd>>(mut self, cmds: I) -> Self {
self.cmds.extend(cmds);
self
}
/// Set the per-command timeout in **seconds** (matching `Cmd::timeout`,
/// which is interpreted via `Duration::from_secs`).
pub fn with_timeout(mut self, timeout_secs: u64) -> Self {
self.timeout = Some(timeout_secs);
self
}
pub fn with_dir(mut self, dir: PathBuf) -> Self {
self.dir = dir;
self
}
pub fn len(&self) -> usize {
self.cmds.len()
}
pub fn is_empty(&self) -> bool {
self.cmds.is_empty()
}
pub fn is_cancelled(&self) -> bool {
self.cancelled
}
pub fn first(&self) -> Option<&Cmd> {
self.cmds.first()
}
pub fn last(&self) -> Option<&Cmd> {
self.cmds.last()
}
}
impl fmt::Debug for Pipe {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Pipe")
.field("name", &self.name)
.field("cmds", &self.cmds.len())
.field("dir", &self.dir)
.field("timeout", &self.timeout)
.field("cancelled", &self.cancelled)
.finish()
}
}
impl fmt::Display for Pipe {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.cmds.is_empty() {
return write!(f, "{}:<empty>", self.name);
}
let parts: Vec<String> = self.cmds.iter().map(|c| format!("{c}")).collect();
write!(f, "{}: {}", self.name, parts.join(" | "))
}
}
impl GitPipeContext for Pipe {
fn cancel_pipe(&mut self) {
self.cancelled = true;
for cmd in &mut self.cmds {
if let Some(mut child) = cmd.cmd.take() {
// The child handle is handed to the reaper task below; mark the
// command as finished so it can never be spawned again, even
// after a later `reset()` re-arms the pipe.
cmd.finished = true;
let _ = child.start_kill();
tokio::spawn(async move {
let _ = child.wait().await;
});
}
}
}
fn current_index(&self) -> Option<usize> {
if self.cancelled || self.cmds.is_empty() {
None
} else {
Some(self.cmds.len().saturating_sub(1))
}
}
}