use crate::forwarding::{exit_code, git_command, outcome_of, run_real_git};
use crate::scratch_location::scratch_file;
use crate::service_index_wait::{foreign_holds, wait_for_service_hold_within};
use crate::wait_ceilings::SERVICE_HOLD_CEILING;
use crate::wait_progress::Cadence;
use crate::start_retry::RetryStart;
use crate::worktree_copy_forward::ForwardedRun;
use std::ffi::OsString;
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;
use std::process::Stdio;
pub const ATTEMPTS: usize = 50;
const LOCK_REFUSAL: &str = "index.lock";
const DIE_STATUS: i32 = 128;
const ADD_OPTIONS: [&str; 20] = [
"-v", "-n", "-f", "-A", "-u", "-N", "-q", "--verbose", "--dry-run", "--force", "--all", "--update", "--intent-to-add",
"--ignore-errors", "--refresh", "--renormalize", "--no-all", "--ignore-removal", "--sparse", "--no-warn-embedded-repo",
];
const REMOVE_OPTIONS: [&str; 10] = ["-f", "-n", "-q", "-r", "--cached", "--force", "--dry-run", "--quiet", "--ignore-unmatch", "--sparse"];
const MOVE_OPTIONS: [&str; 8] = ["-f", "-k", "-n", "-v", "--force", "--dry-run", "--verbose", "--sparse"];
pub fn may_run_again(word: &[u8], after_word: &[OsString]) -> bool {
let known: &[&str] = match word {
b"add" => &ADD_OPTIONS,
b"rm" => &REMOVE_OPTIONS,
b"mv" => &MOVE_OPTIONS,
_ => return false,
};
for argument in after_word {
let Some(text) = argument.to_str() else { return false };
if text == "--" {
break;
}
if text.starts_with('-') && text != "-" && !known.contains(&text) {
return false;
}
}
return true;
}
fn run_once(real_git: &Path, arguments: &[OsString], overlay: &[(OsString, OsString)], captured: std::fs::File) -> std::io::Result<i32> {
let mut command = git_command(real_git, arguments, overlay);
command.stderr(Stdio::from(captured));
let status = command.status_retrying()?;
return Ok(exit_code(outcome_of(status)));
}
fn run_plainly(real_git: &Path, arguments: &[OsString], overlay: &[(OsString, OsString)]) -> ForwardedRun {
return match run_real_git(real_git, arguments, overlay) {
Ok(outcome) => ForwardedRun::Ran(exit_code(outcome)),
Err(error) => ForwardedRun::Failed { stderr: format!("cli-git: cannot start {}: {error}\n", real_git.display()), code: crate::action::ENGINE_FAILURE_EXIT_CODE },
};
}
pub fn run_again_while_service_holds(
real_git: &Path,
arguments: &[OsString],
overlay: &[(OsString, OsString)],
environment: &[(OsString, OsString)],
real_index: &Path,
name: &str,
shown: &mut dyn Write,
) -> ForwardedRun {
return run_again_within(real_git, arguments, overlay, environment, real_index, name, SERVICE_HOLD_CEILING, shown);
}
pub fn run_again_within(
real_git: &Path,
arguments: &[OsString],
overlay: &[(OsString, OsString)],
environment: &[(OsString, OsString)],
real_index: &Path,
name: &str,
ceiling: std::time::Duration,
shown: &mut dyn Write,
) -> ForwardedRun {
let began: std::time::Instant = std::time::Instant::now();
for attempt in 1..=ATTEMPTS {
let Ok(mut file) = scratch_file(environment) else { return run_plainly(real_git, arguments, overlay) };
let Ok(for_child) = file.try_clone() else { return run_plainly(real_git, arguments, overlay) };
let code: i32 = match run_once(real_git, arguments, overlay, for_child) {
Ok(code) => code,
Err(error) => {
return ForwardedRun::Failed { stderr: format!("cli-git: cannot start {}: {error}\n", real_git.display()), code: crate::action::ENGINE_FAILURE_EXIT_CODE };
}
};
let mut said: Vec<u8> = Vec::new();
let read_back = file.seek(SeekFrom::Start(0)).and_then(|_| { return file.read_to_end(&mut said); });
let lost_to_service: bool = code == DIE_STATUS
&& read_back.is_ok()
&& String::from_utf8_lossy(said.as_slice()).contains(LOCK_REFUSAL)
&& !foreign_holds(real_index);
if lost_to_service && attempt < ATTEMPTS && began.elapsed() < ceiling {
wait_for_service_hold_within(real_index, name, ceiling.saturating_sub(began.elapsed()), Cadence::from_environment(), &mut std::io::stderr());
continue;
}
let _shown = shown.write_all(said.as_slice()).and_then(|()| { return shown.flush(); });
return ForwardedRun::Ran(code);
}
return ForwardedRun::Ran(DIE_STATUS);
}
#[cfg(test)]
#[path = "service_hold_retry_tests.rs"]
mod tests;