use std::path::PathBuf;
use std::process::Command;
use crate::config::RuntimeOptionOverrides;
use crate::context;
use crate::{
ActionPlan, Config, Error, ExecuteOptions, Executor, InitScriptDiscovery, OutputEvent,
Reporter, Result, Worktree, WorktreeOptions,
};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RunOptions {
pub cwd: Option<PathBuf>,
pub root: Option<PathBuf>,
pub config: Option<PathBuf>,
pub no_init_script: bool,
pub strict: bool,
pub force: bool,
pub dry_run: bool,
pub skip_commands: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RunAction {
MissingConfig,
RootWorktreeSkipped,
WouldRunInitScript {
path: PathBuf,
},
RanInitScript {
path: PathBuf,
},
ConfigDetected {
path: PathBuf,
},
ConfigApplied {
path: PathBuf,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RunReport {
pub context: Worktree,
pub action: RunAction,
}
pub fn run(options: RunOptions, reporter: &mut dyn Reporter) -> Result<RunReport> {
let env_options = RuntimeOptionOverrides::from_env()?;
let pre_config_strict = env_options.pre_config_strict(options.strict);
let context = context::resolve(&WorktreeOptions {
cwd: options.cwd.clone(),
root: options.root.clone(),
})?;
if context.root_path == context.worktree_path {
report(reporter, OutputEvent::RootWorktreeDetected)?;
if pre_config_strict {
return Err(Error::RootWorktreeStrict);
}
return Ok(RunReport {
context,
action: RunAction::RootWorktreeSkipped,
});
}
if options.config.is_none() && !options.no_init_script {
let scripts = InitScriptDiscovery::discover(&context);
for path in scripts.ignored {
report(reporter, OutputEvent::IgnoredInitScript { path })?;
}
if let Some(path) = scripts.executable {
return run_init_script(path, context, &options, reporter);
}
}
match Config::discover_path(&context, options.config.as_deref())? {
Some(path) => {
report(reporter, OutputEvent::ConfigDetected { path: path.clone() })?;
let config = Config::load(&path, &context)?;
let plan_options = env_options.resolve(&config.options, options.strict);
let plan = ActionPlan::from_manifest(&path, &config, &context, plan_options.into())?;
Executor::new(ExecuteOptions {
strict: plan_options.strict,
force: options.force,
dry_run: options.dry_run,
skip_commands: options.skip_commands,
})
.execute(&plan, reporter)?;
Ok(RunReport {
context,
action: RunAction::ConfigApplied { path },
})
}
None => {
report(reporter, OutputEvent::NoConfigDetected)?;
if pre_config_strict {
Err(Error::NoConfigDetectedStrict)
} else {
Ok(RunReport {
context,
action: RunAction::MissingConfig,
})
}
}
}
}
fn run_init_script(
path: PathBuf,
context: Worktree,
options: &RunOptions,
reporter: &mut dyn Reporter,
) -> Result<RunReport> {
if options.dry_run {
report(
reporter,
OutputEvent::WouldRunInitScript {
path: path.clone(),
root_path: context.root_path.clone(),
},
)?;
return Ok(RunReport {
context,
action: RunAction::WouldRunInitScript { path },
});
}
report(reporter, OutputEvent::RunInitScript { path: path.clone() })?;
let status = Command::new(&path)
.arg(&context.root_path)
.current_dir(&context.worktree_path)
.envs(&context.environment)
.status()
.map_err(|source| Error::ScriptIo {
path: path.clone(),
source,
})?;
if !status.success() {
return Err(Error::ScriptFailed { path, status });
}
Ok(RunReport {
context,
action: RunAction::RanInitScript { path },
})
}
fn report(reporter: &mut dyn Reporter, event: OutputEvent) -> Result<()> {
reporter
.report(event)
.map_err(|source| Error::Output { source })
}