1use std::path::PathBuf;
2
3use serde::Serialize;
4
5use crate::config::RuntimeOptionOverrides;
6use crate::context;
7use crate::{ActionPlan, Config, Error, InitScriptDiscovery, Result, Worktree, WorktreeOptions};
8
9#[derive(Debug, Clone, Default, PartialEq, Eq)]
11pub struct CheckOptions {
12 pub cwd: Option<PathBuf>,
14 pub root: Option<PathBuf>,
16 pub config: Option<PathBuf>,
18 pub no_init_script: bool,
20 pub strict: bool,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
26#[serde(tag = "kind", rename_all = "snake_case")]
27pub enum CheckAction {
28 MissingConfig,
30 RootWorktreeSkipped,
32 InitScript {
34 path: PathBuf,
36 },
37 Config {
39 path: PathBuf,
41 },
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
46pub struct CheckReport {
47 pub context: WorktreeSnapshot,
49 pub action: CheckAction,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
55pub struct WorktreeSnapshot {
56 pub root_path: PathBuf,
58 pub worktree_path: PathBuf,
60 pub default_branch: String,
62}
63
64impl From<&Worktree> for WorktreeSnapshot {
65 fn from(context: &Worktree) -> Self {
66 Self {
67 root_path: context.root_path.clone(),
68 worktree_path: context.worktree_path.clone(),
69 default_branch: context.default_branch.clone(),
70 }
71 }
72}
73
74pub fn check(options: CheckOptions) -> Result<CheckReport> {
82 let env_options = RuntimeOptionOverrides::from_env()?;
83 let pre_config_strict = env_options.pre_config_strict(options.strict);
84 let context = context::resolve(&WorktreeOptions {
85 cwd: options.cwd.clone(),
86 root: options.root.clone(),
87 })?;
88
89 if context.root_path == context.worktree_path {
90 if pre_config_strict {
91 return Err(Error::RootWorktreeStrict);
92 }
93
94 return Ok(CheckReport {
95 context: WorktreeSnapshot::from(&context),
96 action: CheckAction::RootWorktreeSkipped,
97 });
98 }
99
100 if options.config.is_none() && !options.no_init_script {
101 let scripts = InitScriptDiscovery::discover(&context);
102
103 if let Some(path) = scripts.executable {
104 return Ok(CheckReport {
105 context: WorktreeSnapshot::from(&context),
106 action: CheckAction::InitScript { path },
107 });
108 }
109 }
110
111 match Config::discover_path(&context, options.config.as_deref())? {
112 Some(path) => {
113 let config = Config::load(&path, &context)?;
114 let plan_options = env_options.resolve(&config.options, options.strict);
115 ActionPlan::from_manifest(&path, &config, &context, plan_options.into())?;
116
117 Ok(CheckReport {
118 context: WorktreeSnapshot::from(&context),
119 action: CheckAction::Config { path },
120 })
121 }
122 None => {
123 if pre_config_strict {
124 Err(Error::NoConfigDetectedStrict)
125 } else {
126 Ok(CheckReport {
127 context: WorktreeSnapshot::from(&context),
128 action: CheckAction::MissingConfig,
129 })
130 }
131 }
132 }
133}