1use std::path::PathBuf;
2use std::process::Command;
3
4use crate::config::RuntimeOptionOverrides;
5use crate::context;
6use crate::{
7 ActionPlan, Config, Error, ExecuteOptions, Executor, InitScriptDiscovery, OutputEvent,
8 Reporter, Result, Worktree, WorktreeOptions,
9};
10
11#[derive(Debug, Clone, Default, PartialEq, Eq)]
13pub struct RunOptions {
14 pub cwd: Option<PathBuf>,
16 pub root: Option<PathBuf>,
18 pub config: Option<PathBuf>,
20 pub no_init_script: bool,
22 pub strict: bool,
24 pub force: bool,
26 pub dry_run: bool,
28 pub verbose: bool,
30 pub skip_commands: bool,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum RunAction {
37 MissingConfig,
39 RootWorktreeSkipped,
41 WouldRunInitScript {
43 path: PathBuf,
45 },
46 RanInitScript {
48 path: PathBuf,
50 },
51 ConfigDetected {
53 path: PathBuf,
55 },
56 ConfigApplied {
58 path: PathBuf,
60 },
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct RunReport {
66 pub context: Worktree,
68 pub action: RunAction,
70}
71
72pub fn run(options: RunOptions, reporter: &mut dyn Reporter) -> Result<RunReport> {
84 let env_options = RuntimeOptionOverrides::from_env()?;
85 let pre_config_strict = env_options.pre_config_strict(options.strict);
86 let context = context::resolve(&WorktreeOptions {
87 cwd: options.cwd.clone(),
88 root: options.root.clone(),
89 })?;
90
91 if context.root_path == context.worktree_path {
92 report(reporter, OutputEvent::RootWorktreeDetected)?;
93
94 if pre_config_strict {
95 return Err(Error::RootWorktreeStrict);
96 }
97
98 return Ok(RunReport {
99 context,
100 action: RunAction::RootWorktreeSkipped,
101 });
102 }
103
104 if options.config.is_none() && !options.no_init_script {
105 let scripts = InitScriptDiscovery::discover(&context);
106
107 for ignored in scripts.ignored {
108 report(
109 reporter,
110 OutputEvent::IgnoredInitScript { path: ignored.path },
111 )?;
112 }
113
114 if let Some(path) = scripts.executable {
115 return run_init_script(path, context, &options, reporter);
116 }
117 }
118
119 match Config::discover_path(&context, options.config.as_deref())? {
120 Some(path) => {
121 report(reporter, OutputEvent::ConfigDetected { path: path.clone() })?;
122 let config = Config::load(&path, &context)?;
123 let plan_options = env_options.resolve(&config.options, options.strict);
124 let plan = ActionPlan::from_manifest(&path, &config, &context, plan_options.into())?;
125 Executor::new(ExecuteOptions {
126 strict: plan_options.strict,
127 force: options.force,
128 dry_run: options.dry_run,
129 verbose: options.verbose,
130 skip_commands: options.skip_commands,
131 })
132 .execute(&plan, reporter)?;
133
134 Ok(RunReport {
135 context,
136 action: RunAction::ConfigApplied { path },
137 })
138 }
139 None => {
140 report(reporter, OutputEvent::NoConfigDetected)?;
141
142 if pre_config_strict {
143 Err(Error::NoConfigDetectedStrict)
144 } else {
145 Ok(RunReport {
146 context,
147 action: RunAction::MissingConfig,
148 })
149 }
150 }
151 }
152}
153
154fn run_init_script(
155 path: PathBuf,
156 context: Worktree,
157 options: &RunOptions,
158 reporter: &mut dyn Reporter,
159) -> Result<RunReport> {
160 if options.dry_run {
161 report(
162 reporter,
163 OutputEvent::WouldRunInitScript {
164 path: path.clone(),
165 root_path: context.root_path.clone(),
166 },
167 )?;
168
169 return Ok(RunReport {
170 context,
171 action: RunAction::WouldRunInitScript { path },
172 });
173 }
174
175 report(reporter, OutputEvent::RunInitScript { path: path.clone() })?;
176
177 let status = Command::new(&path)
178 .arg(&context.root_path)
179 .current_dir(&context.worktree_path)
180 .envs(&context.environment)
181 .status()
182 .map_err(|source| Error::ScriptIo {
183 path: path.clone(),
184 source,
185 })?;
186
187 if !status.success() {
188 return Err(Error::ScriptFailed { path, status });
189 }
190
191 Ok(RunReport {
192 context,
193 action: RunAction::RanInitScript { path },
194 })
195}
196
197fn report(reporter: &mut dyn Reporter, event: OutputEvent) -> Result<()> {
198 reporter
199 .report(event)
200 .map_err(|source| Error::Output { source })
201}