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