use crate::index_holder_wait::wait_for_index_release_here;
use crate::service_index_wait::wait_for_service_hold_here;
use crate::service_hold_retry::{may_run_again, run_again_while_service_holds};
use crate::worktree_copy_forward::{CopyFacts, ForwardedRun, run_forwarded_git};
use crate::forwarded_command::ResolvedCommand;
use crate::index_writer_commands::is_index_writer;
use crate::landing_location::{LandingLocation, resolve_landing_location};
use crate::diagnostics::{EngineFailureCode, render_engine_failure};
use crate::git_version::check_git_version;
use crate::worktree_identity::WorktreeIdentity;
use crate::merge_isolation::{IsolatedMerge, IsolatedMergeInput, run_isolated_merge};
use crate::pull_isolation::{PullStep, pull_step};
use crate::merge_second_pass_worktree::{Watch, resolve_after, watch_before};
use crate::transaction_git::GitContext;
use std::ffi::OsString;
use std::io::Write;
use std::path::PathBuf;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CoordinatedForward {
pub real_git: PathBuf,
pub arguments: Vec<OsString>,
pub overlay: Vec<(OsString, OsString)>,
pub identity: WorktreeIdentity,
pub command: ResolvedCommand,
pub environment: Vec<(OsString, OsString)>,
pub copy: Option<CopyFacts>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CoordinatedRun {
NotCoordinated,
Ran(i32),
Failed {
stderr: String,
code: i32,
},
}
fn written_real_index(context: &GitContext, command: &ResolvedCommand) -> Result<Option<LandingLocation>, String> {
if !is_index_writer(command) {
return Ok(None);
}
let global: &[OsString] = &command.arguments[..command.subcommand_index];
let Some(location) = resolve_landing_location(context, global)? else {
return Ok(None);
};
if location.index_path != location.real_index_path {
return Ok(None);
}
return Ok(Some(location));
}
pub fn run_coordinated_forward(typed: &CoordinatedForward) -> CoordinatedRun {
let context: GitContext = GitContext {
real_git: typed.real_git.clone(),
overlay: typed.overlay.clone(),
global_prefix: Vec::new(),
common_dir: None,
};
let location: LandingLocation = match written_real_index(&context, &typed.command) {
Ok(Some(found)) => found,
Ok(None) => {
let Some(facts) = typed.copy.as_ref() else {
return CoordinatedRun::NotCoordinated;
};
return from_forwarded(run_forwarded_git(
typed.real_git.as_path(),
typed.arguments.as_slice(),
typed.overlay.as_slice(),
Some(facts),
));
}
Err(reason) => return failed_with(EngineFailureCode::ContentUnavailable, reason.as_str()),
};
if let Err(reason) = check_git_version(&context) {
return failed_with(EngineFailureCode::ContentUnavailable, reason.as_str());
}
let merge_of_pull: CoordinatedForward;
let mut isolate: bool = true;
let forward: &CoordinatedForward = if typed.command.word() == b"pull" {
match pull_step(typed, location.real_index_path.as_path()) {
PullStep::Native => typed,
PullStep::Ended(run) => return run,
PullStep::Merge { forward: merge, makes_commit } => {
merge_of_pull = *merge;
isolate = makes_commit;
&merge_of_pull
}
}
} else {
typed
};
let name: String = String::from_utf8_lossy(forward.command.word()).into_owned();
wait_for_service_hold_here(location.real_index_path.as_path(), name.as_str());
if forward.command.word() == b"merge" && !isolate {
wait_for_index_release_here(location.real_index_path.as_path(), name.as_str());
} else if forward.command.word() == b"merge" {
let merge_input: IsolatedMergeInput<'_> = IsolatedMergeInput {
real_git: forward.real_git.as_path(),
overlay: forward.overlay.as_slice(),
identity: &forward.identity,
command: &forward.command,
environment: forward.environment.as_slice(),
};
match run_isolated_merge(&merge_input) {
IsolatedMerge::Forward { notice } => {
if let Some(text) = notice {
let _ = std::io::stderr().write_all(text.as_bytes());
}
wait_for_index_release_here(location.real_index_path.as_path(), name.as_str());
}
IsolatedMerge::Finished { code, stdout, stderr } => {
let _ = std::io::stdout().write_all(stdout.as_bytes());
let _ = std::io::stderr().write_all(stderr.as_bytes());
return CoordinatedRun::Ran(code);
}
}
}
let second_pass_watch: Option<Watch> = watch_before(forward);
let outcome: ForwardedRun = if forward.copy.is_none() && may_run_again(forward.command.word(), forward.command.region()) {
run_again_while_service_holds(
forward.real_git.as_path(),
forward.arguments.as_slice(),
forward.overlay.as_slice(),
forward.environment.as_slice(),
location.real_index_path.as_path(),
name.as_str(),
&mut std::io::stderr(),
)
} else {
run_forwarded_git(
forward.real_git.as_path(),
forward.arguments.as_slice(),
forward.overlay.as_slice(),
forward.copy.as_ref(),
)
};
let finished: CoordinatedRun = from_forwarded(outcome);
if let CoordinatedRun::Ran(code) = &finished
&& let Some(ended) = resolve_after(second_pass_watch.as_ref(), forward, *code, &mut std::io::stderr())
{
return CoordinatedRun::Ran(ended);
}
return finished;
}
fn from_forwarded(run: ForwardedRun) -> CoordinatedRun {
match run {
ForwardedRun::Ran(code) => return CoordinatedRun::Ran(code),
ForwardedRun::Failed { stderr, code } => return CoordinatedRun::Failed { stderr, code },
}
}
fn failed_with(code: EngineFailureCode, message: &str) -> CoordinatedRun {
return CoordinatedRun::Failed {
stderr: render_engine_failure(0, code, message),
code: crate::action::ENGINE_FAILURE_EXIT_CODE,
};
}
#[cfg(test)]
#[path = "index_writer_forward_tests.rs"]
mod tests;