use crate::auto_push_single_flight::SingleFlightError;
use crate::bounded_push::{MAX_PUSH_OUTPUT, Patience, StopCause, push_deadline, stop_push, wait_until_done};
use crate::quiet_ledger;
use crate::stopped_git_locks::LockLedger;
use crate::scratch_location::scratch_file;
use crate::start_retry::RetryStart;
use std::ffi::OsString;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::process::{Command, ExitStatus, Stdio};
use std::time::Duration;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct QueryOutput {
pub code: Option<i32>,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
}
pub fn query_window(environment: &[(OsString, OsString)]) -> Duration {
return push_deadline(environment);
}
fn read_back(file: &mut File) -> Result<Vec<u8>, SingleFlightError> {
file.seek(SeekFrom::Start(0))
.map_err(SingleFlightError::Start)?;
let mut bytes: Vec<u8> = Vec::new();
file.take(MAX_PUSH_OUTPUT)
.read_to_end(&mut bytes)
.map_err(SingleFlightError::Start)?;
return Ok(bytes);
}
pub fn run_bounded_query(
mut command: Command,
arguments: &str,
window: Duration,
grace: Duration,
environment: &[(OsString, OsString)],
) -> Result<QueryOutput, SingleFlightError> {
let mut stdout: File = scratch_file(environment).map_err(SingleFlightError::Start)?;
let mut stderr: File = scratch_file(environment).map_err(SingleFlightError::Start)?;
let for_stdout: File = stdout.try_clone().map_err(SingleFlightError::Start)?;
let for_stderr: File = stderr.try_clone().map_err(SingleFlightError::Start)?;
command
.stdin(Stdio::null())
.stdout(Stdio::from(for_stdout))
.stderr(Stdio::from(for_stderr));
let locks = LockLedger::before_start(&command);
let mut child = command.spawn_retrying().map_err(SingleFlightError::Start)?;
drop(command);
let mut quiet: Option<Duration> = None;
let ended: Result<ExitStatus, StopCause> =
wait_until_done(&mut child, &[&stdout, &stderr], Patience::Progress { stall: window }, &mut quiet)?;
let step: String = format!("git {}", arguments.split_whitespace().next().unwrap_or_default());
if let Some(gap) = quiet {
quiet_ledger::note(step.as_str(), gap, ended.is_err());
}
return match ended {
Ok(status) => Ok(QueryOutput {
code: status.code(),
stdout: read_back(&mut stdout)?,
stderr: read_back(&mut stderr)?,
}),
Err(cause) => {
stop_push(child, grace, locks);
Err(match cause {
StopCause::Deadline | StopCause::Stalled(_) => SingleFlightError::TimedOut {
arguments: arguments.to_owned(),
seconds: window.as_secs(),
},
StopCause::Flood => SingleFlightError::Git {
arguments: arguments.to_owned(),
code: None,
stderr: format!(
"the query wrote more than {MAX_PUSH_OUTPUT} bytes and was stopped"
),
},
})
}
};
}
#[cfg(test)]
#[path = "bounded_query_tests.rs"]
mod tests;