1use std::path::PathBuf;
2use std::process::Command;
3
4use crate::context;
5use crate::{
6 ActionPlan, Config, EnvironmentInput, Error, ExecuteOptions, Executor, InitScriptDiscovery,
7 OutputEvent, Reporter, Result, RuntimePolicy, Worktree, WorktreeOptions,
8};
9
10#[derive(Debug, Clone, Default, PartialEq, Eq)]
12pub struct RunOptions {
13 pub cwd: Option<PathBuf>,
15 pub root: Option<PathBuf>,
17 pub environment: EnvironmentInput,
19 pub config: Option<PathBuf>,
21 pub no_init_script: bool,
23 pub strict: bool,
25 pub force: bool,
27 pub dry_run: bool,
29 pub verbose: bool,
31 pub skip_commands: bool,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum RunAction {
38 MissingConfig,
40 RootWorktreeSkipped,
42 WouldRunInitScript {
44 path: PathBuf,
46 },
47 RanInitScript {
49 path: PathBuf,
51 },
52 ConfigDetected {
54 path: PathBuf,
56 },
57 ConfigApplied {
59 path: PathBuf,
61 },
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct RunReport {
67 pub context: Worktree,
69 pub action: RunAction,
71}
72
73pub fn run(options: RunOptions, reporter: &mut dyn Reporter) -> Result<RunReport> {
85 let runtime_policy = RuntimePolicy::from_environment(&options.environment, options.strict)?;
86 let pre_config_strict = runtime_policy.pre_config_strict();
87 let context = context::resolve(&WorktreeOptions {
88 cwd: options.cwd.clone(),
89 root: options.root.clone(),
90 environment: options.environment.clone(),
91 })?;
92
93 if context.root_path == context.worktree_path {
94 report(reporter, OutputEvent::RootWorktreeDetected)?;
95
96 if pre_config_strict {
97 return Err(Error::RootWorktreeStrict);
98 }
99
100 return Ok(RunReport {
101 context,
102 action: RunAction::RootWorktreeSkipped,
103 });
104 }
105
106 if options.config.is_none() && !options.no_init_script {
107 let scripts = InitScriptDiscovery::discover(&context);
108
109 for ignored in scripts.ignored {
110 report(
111 reporter,
112 OutputEvent::IgnoredInitScript { path: ignored.path },
113 )?;
114 }
115
116 if let Some(path) = scripts.executable {
117 return run_init_script(path, context, &options, reporter);
118 }
119 }
120
121 match Config::discover_path(&context, options.config.as_deref())? {
122 Some(path) => {
123 report(reporter, OutputEvent::ConfigDetected { path: path.clone() })?;
124 let config = Config::load(&path, &context)?;
125 let plan_options = runtime_policy.resolve(&config.options);
126 let strict = plan_options.strict();
127 let plan = ActionPlan::from_manifest(
128 &path,
129 &config,
130 &context,
131 plan_options.into_action_plan_options(),
132 )?;
133 Executor::new(ExecuteOptions {
134 strict,
135 force: options.force,
136 dry_run: options.dry_run,
137 verbose: options.verbose,
138 skip_commands: options.skip_commands,
139 })
140 .execute(&plan, reporter)?;
141
142 Ok(RunReport {
143 context,
144 action: RunAction::ConfigApplied { path },
145 })
146 }
147 None => {
148 report(reporter, OutputEvent::NoConfigDetected)?;
149
150 if pre_config_strict {
151 Err(Error::NoConfigDetectedStrict)
152 } else {
153 Ok(RunReport {
154 context,
155 action: RunAction::MissingConfig,
156 })
157 }
158 }
159 }
160}
161
162fn run_init_script(
163 path: PathBuf,
164 context: Worktree,
165 options: &RunOptions,
166 reporter: &mut dyn Reporter,
167) -> Result<RunReport> {
168 if options.dry_run {
169 report(
170 reporter,
171 OutputEvent::WouldRunInitScript {
172 path: path.clone(),
173 root_path: context.root_path.clone(),
174 },
175 )?;
176
177 return Ok(RunReport {
178 context,
179 action: RunAction::WouldRunInitScript { path },
180 });
181 }
182
183 report(reporter, OutputEvent::RunInitScript { path: path.clone() })?;
184
185 let status = Command::new(&path)
186 .arg(&context.root_path)
187 .current_dir(&context.worktree_path)
188 .envs(&context.environment)
189 .status()
190 .map_err(|source| Error::ScriptIo {
191 path: path.clone(),
192 source,
193 })?;
194
195 if !status.success() {
196 return Err(Error::ScriptFailed { path, status });
197 }
198
199 Ok(RunReport {
200 context,
201 action: RunAction::RanInitScript { path },
202 })
203}
204
205fn report(reporter: &mut dyn Reporter, event: OutputEvent) -> Result<()> {
206 reporter
207 .report(event)
208 .map_err(|source| Error::Output { source })
209}