use crate::bounded_push::STOP_GRACE;
use crate::push_deadline_advice::limit_clause;
use crate::bounded_query::{QueryOutput, query_window, run_bounded_query};
use crate::js_text::trim_javascript;
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;
#[derive(Debug)]
pub enum SingleFlightError {
Start(std::io::Error),
Git {
arguments: String,
code: Option<i32>,
stderr: String,
},
Io(std::io::Error),
TimedOut {
arguments: String,
seconds: u64,
},
}
impl std::fmt::Display for SingleFlightError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return match self {
Self::Start(error) => write!(formatter, "git could not be started: {error}"),
Self::Git {
arguments,
code,
stderr,
} => write!(
formatter,
"git {arguments} exited with {code:?}: {}",
stderr.trim_end()
),
Self::Io(error) => write!(formatter, "{error}"),
Self::TimedOut { arguments, seconds } => write!(
formatter,
"git {arguments} showed no sign of work for {seconds} seconds and was stopped; {}",
limit_clause()
),
};
}
}
impl std::error::Error for SingleFlightError {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PushAttempt {
pub exit_code: i64,
pub output: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PushGit {
pub real_git: PathBuf,
pub cwd: PathBuf,
pub environment: Vec<(OsString, OsString)>,
pub lfs: bool,
}
pub fn git_command(git: &PushGit, arguments: &[OsString]) -> Command {
let mut command = Command::new(&git.real_git);
crate::hooks_off::add_arguments(&mut command, arguments);
let mut in_place = in_push_environment(command, git);
crate::started_as_git::mark_real_git_start(&mut in_place, crate::started_as_git::StartMode::Child);
return in_place;
}
fn in_push_environment(mut command: Command, git: &PushGit) -> Command {
command
.current_dir(&git.cwd)
.env_clear()
.envs(git.environment.iter().map(|pair| {
return (&pair.0, &pair.1);
}));
return command;
}
fn describe_arguments(arguments: &[OsString]) -> String {
return arguments
.iter()
.map(|argument| {
return argument.to_string_lossy().into_owned();
})
.collect::<Vec<String>>()
.join(" ");
}
pub fn git_output(git: &PushGit, arguments: &[OsString]) -> Result<String, SingleFlightError> {
let output = run_query(git, arguments)?;
if output.code != Some(0) {
return Err(SingleFlightError::Git {
arguments: describe_arguments(arguments),
code: output.code,
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
});
}
let text = String::from_utf8_lossy(&output.stdout);
return Ok(trim_javascript(&text).to_owned());
}
pub fn run_query(git: &PushGit, arguments: &[OsString]) -> Result<QueryOutput, SingleFlightError> {
if let Some((code, stdout, stderr)) = crate::repo_read_hook::try_complete(arguments, git.cwd.as_path(), crate::git_system_config::with_real_git(git.environment.as_slice(), git.real_git.as_path()).as_slice()) {
return Ok(QueryOutput { code, stdout, stderr });
}
return run_bounded_query(
git_command(git, arguments),
describe_arguments(arguments).as_str(),
query_window(&git.environment),
STOP_GRACE,
git.environment.as_slice(),
);
}
#[cfg(test)]
#[path = "auto_push_single_flight_tests.rs"]
mod tests;