Skip to main content

garden/
model.rs

1use std::cell::{Cell, UnsafeCell};
2use std::str::FromStr;
3
4use better_default::Default;
5use indextree::{Arena, NodeId};
6use is_terminal::IsTerminal;
7use strum::VariantNames;
8use strum_macros;
9use which::which;
10
11use crate::cli::GardenOptions;
12use crate::{cli, collections, config, constants, errors, eval, path, syntax};
13
14pub(crate) type IndexMap<K, V> = indexmap::IndexMap<K, V>;
15pub(crate) type IndexSet<V> = indexmap::IndexSet<V>;
16pub type StringSet = indexmap::IndexSet<String>;
17
18/// TreeName keys into config.trees
19pub type TreeName = String;
20
21/// GroupName keys into config.groups
22pub type GroupName = String;
23
24/// GardenName keys into config.gardens
25pub type GardenName = String;
26
27/// GraftName keys into config.grafts
28pub type GraftName = String;
29
30/// Configuration Node IDs
31pub type ConfigId = NodeId;
32
33/// Environment variables are set when running commands.
34pub(crate) type Environment = Vec<(String, String)>;
35
36/// Config files can define a sequence of variables that are
37/// iteratively calculated.  Variables can reference other
38/// variables in their Tree, Garden, and Configuration scopes.
39///
40/// The config values can contain either plain values,
41/// string ${expressions} that resolve against other Variables,
42/// or exec expressions that evaluate to a command whose stdout is
43/// captured and placed into the value of the variable.
44///
45/// An exec expression can use shell-like ${variable} references as which
46/// are substituted when evaluating the command, just like a regular
47/// string expression.  An exec expression is denoted by using a "$ "
48/// (dollar-sign followed by space) before the value.  For example,
49/// using "$ echo foo" will place the value "foo" in the variable.
50#[derive(Debug, Default)]
51pub struct Variable {
52    name: String,
53    expr: String,
54    value: UnsafeCell<Option<String>>,
55    evaluating: Cell<bool>,
56    required: bool,
57}
58
59impl_display_brief!(Variable);
60
61/// A custom thread-safe clone implementation. RefCell::clone() is not thread-safe
62/// because it mutably borrows data under the hood. UnsafeCell avoids this property.
63impl Clone for Variable {
64    fn clone(&self) -> Self {
65        Self {
66            name: self.name.clone(),
67            expr: self.expr.clone(),
68            value: UnsafeCell::new(self.get_value().cloned()),
69            evaluating: Cell::new(false),
70            required: self.required,
71        }
72    }
73}
74
75impl Variable {
76    /// Create an un-evaluated variable from an expression string.
77    pub(crate) fn from_expr(name: String, expr: String) -> Self {
78        Variable {
79            name,
80            expr,
81            value: UnsafeCell::new(None),
82            evaluating: Cell::new(false),
83            required: false,
84        }
85    }
86
87    /// Create a resolved variable from a string that is both the expression and value.
88    pub(crate) fn from_resolved_expr(name: String, expr: String) -> Self {
89        Variable {
90            name,
91            expr: expr.clone(),
92            value: UnsafeCell::new(Some(expr)),
93            evaluating: Cell::new(false),
94            required: false,
95        }
96    }
97
98    /// Create an un-evaluated required variable from individual fields.
99    pub(crate) fn new(name: String, expr: String, required: bool) -> Self {
100        Variable {
101            name,
102            expr,
103            required,
104            value: UnsafeCell::new(None),
105            evaluating: Cell::new(false),
106        }
107    }
108
109    /// Does this variable have a value?
110    pub(crate) fn is_empty(&self) -> bool {
111        self.expr.is_empty()
112    }
113
114    /// Is this variable currently being evaluated?
115    /// This is a guard variable to avoid infinite loops when evaluating.
116    pub(crate) fn is_evaluating(&self) -> bool {
117        self.evaluating.get()
118    }
119
120    /// Is this variable a required variable?
121    pub(crate) fn is_required(&self) -> bool {
122        self.required
123    }
124
125    /// Indicate that we have started evaluating this variable.
126    pub(crate) fn set_evaluation_started(&self) {
127        self.set_evaluating(true);
128    }
129
130    /// Set the evaluation state.
131    fn set_evaluating(&self, value: bool) {
132        self.evaluating.set(value);
133    }
134
135    /// Return the name of this variable as defined in the configuration.
136    pub(crate) fn get_name(&self) -> &String {
137        &self.name
138    }
139
140    /// Return the raw expression for this variable.
141    pub fn get_expr(&self) -> &String {
142        &self.expr
143    }
144
145    /// Return a mutable reference to the underlying raw expression.
146    pub(crate) fn get_expr_mut(&mut self) -> &mut String {
147        &mut self.expr
148    }
149
150    /// Set the expression for this variable.
151    pub(crate) fn set_expr(&mut self, expr: String) {
152        self.expr = expr;
153    }
154
155    /// Transform the `RefCell<Option<String>>` value into `Option<&String>`.
156    pub fn get_value(&self) -> Option<&String> {
157        unsafe { (*self.value.get()).as_ref() }
158    }
159
160    /// Store the cached result of evaluating the expression.
161    pub(crate) fn set_value(&self, value: String) {
162        // Report an error and exit when a required variable has no value.
163        if !self.required || !value.is_empty() {
164            self.set_evaluating(false);
165            unsafe {
166                *self.value.get() = Some(value);
167            }
168        } else if self.required {
169            eprintln!("error: required variable '{}' is empty", self.name);
170            std::process::exit(errors::EX_DATAERR);
171        }
172    }
173
174    /// Reset the variable.
175    pub(crate) fn reset(&self) {
176        unsafe {
177            *self.value.get() = None;
178        }
179    }
180}
181
182/// An unordered mapping of names to a vector of Variables.
183pub(crate) type MultiVariableMap = IndexMap<String, Vec<Variable>>;
184
185/// Reset the variables held inside a MultiVariableMap.
186fn reset_map_variables(vec_variables: &MultiVariableMap) {
187    for variables in vec_variables.values() {
188        for variable in variables {
189            variable.reset();
190        }
191    }
192}
193
194/// An unordered mapping of name to Variable.
195pub(crate) type VariableMap = IndexMap<String, Variable>;
196
197// Named variables with multiple values
198#[derive(Clone, Debug)]
199pub struct MultiVariable {
200    name: String,
201    variables: Vec<Variable>,
202}
203
204impl MultiVariable {
205    pub(crate) fn new(name: String, variables: Vec<Variable>) -> Self {
206        MultiVariable { name, variables }
207    }
208
209    pub fn get(&self, idx: usize) -> &Variable {
210        &self.variables[idx]
211    }
212
213    pub fn get_name(&self) -> &String {
214        &self.name
215    }
216
217    pub fn len(&self) -> usize {
218        self.variables.len()
219    }
220
221    pub fn is_empty(&self) -> bool {
222        self.variables.is_empty()
223    }
224
225    pub fn reset(&self) {
226        for var in &self.variables {
227            var.reset();
228        }
229    }
230
231    pub fn iter(&self) -> std::slice::Iter<'_, Variable> {
232        self.variables.iter()
233    }
234}
235
236/// Trees represent a single worktree
237#[derive(Clone, Debug, Default)]
238pub struct Tree {
239    pub commands: MultiVariableMap,
240    pub environment: Vec<MultiVariable>,
241    pub gitconfig: MultiVariableMap,
242    pub remotes: VariableMap,
243    pub(crate) symlink: Variable,
244    pub templates: StringSet,
245    pub variables: VariableMap,
246    pub branch: Variable,
247    pub(crate) branches: VariableMap,
248    pub worktree: Variable,
249    #[default(string!("origin"))]
250    pub(crate) default_remote: String,
251    pub(crate) clone_depth: i64,
252    pub(crate) is_single_branch: bool,
253    pub is_symlink: bool,
254    pub is_bare_repository: bool,
255    pub is_worktree: bool,
256    pub(crate) description: String,
257    pub(crate) links: Vec<Variable>,
258
259    name: String,
260    path: Variable,
261}
262
263impl Tree {
264    pub fn get_name(&self) -> &String {
265        &self.name
266    }
267
268    /// Set the tree name.
269    pub fn set_name(&mut self, name: String) {
270        self.name = name;
271    }
272
273    pub(crate) fn get_name_mut(&mut self) -> &mut String {
274        &mut self.name
275    }
276
277    pub fn get_path(&self) -> &Variable {
278        &self.path
279    }
280
281    pub(crate) fn get_path_mut(&mut self) -> &mut Variable {
282        &mut self.path
283    }
284
285    pub(crate) fn path_is_valid(&self) -> bool {
286        self.path.get_value().is_some()
287    }
288
289    /// Build a canonicalized pathbuf for the current tree.
290    pub(crate) fn canonical_pathbuf(&self) -> Option<std::path::PathBuf> {
291        if let Some(pathbuf) = self.pathbuf() {
292            if let Ok(canon_path) = path::canonicalize(pathbuf) {
293                return Some(canon_path);
294            }
295        }
296
297        None
298    }
299
300    /// Build a pathbuf for the current tree.
301    pub(crate) fn pathbuf(&self) -> Option<std::path::PathBuf> {
302        if !self.path_is_valid() {
303            return None;
304        }
305        self.path.get_value().map(std::path::PathBuf::from)
306    }
307
308    pub fn path_as_ref(&self) -> Result<&String, errors::GardenError> {
309        match self.path.get_value() {
310            Some(value) => Ok(value),
311            None => Err(errors::GardenError::ConfigurationError(format!(
312                "unset tree path for {}",
313                self.name
314            ))),
315        }
316    }
317
318    pub(crate) fn symlink_as_ref(&self) -> Result<&String, errors::GardenError> {
319        match self.symlink.get_value() {
320            Some(value) => Ok(value),
321            None => Err(errors::GardenError::ConfigurationError(format!(
322                "unset tree path for {}",
323                self.name
324            ))),
325        }
326    }
327
328    /// Add the builtin TREE_NAME and TREE_PATH variables.
329    pub(crate) fn add_builtin_variables(&mut self) {
330        self.variables.insert(
331            constants::TREE_NAME.to_string(),
332            Variable::from_expr(constants::TREE_NAME.to_string(), self.get_name().clone()),
333        );
334
335        // Register the ${TREE_PATH} variable.
336        self.variables.insert(
337            constants::TREE_PATH.to_string(),
338            Variable::from_expr(
339                constants::TREE_PATH.to_string(),
340                self.get_path().get_expr().clone(),
341            ),
342        );
343    }
344
345    pub(crate) fn reset_variables(&self) {
346        // self.path is a variable but it is not reset because
347        // the tree path is evaluated once when the configuration
348        // is first read, and never again.
349        for var in self.variables.values() {
350            var.reset();
351        }
352        for env in &self.environment {
353            env.reset();
354        }
355
356        reset_map_variables(&self.gitconfig);
357        reset_map_variables(&self.commands);
358    }
359
360    /// Copy the guts of another tree into the current tree.
361    pub(crate) fn clone_from_tree(&mut self, tree: &Tree) {
362        collections::append_map(&mut self.commands, &tree.commands);
363        collections::append_map(&mut self.gitconfig, &tree.gitconfig);
364        collections::append_map(&mut self.variables, &tree.variables);
365        collections::append_map(&mut self.remotes, &tree.remotes);
366        collections::append_set(&mut self.templates, &tree.templates);
367
368        // "environment" follow last-set-wins semantics.
369        self.environment.append(&mut tree.environment.clone());
370        // The last value set is the one that wins.
371        if tree.clone_depth > 0 {
372            self.clone_depth = tree.clone_depth;
373        }
374        if tree.is_bare_repository {
375            self.is_bare_repository = tree.is_bare_repository;
376        }
377        if tree.is_single_branch {
378            self.is_single_branch = tree.is_single_branch;
379        }
380        if tree.is_worktree {
381            self.is_worktree = tree.is_worktree;
382        }
383        if tree.is_symlink {
384            self.is_symlink = tree.is_symlink;
385        }
386        if !tree.branch.is_empty() {
387            self.branch = tree.branch.clone();
388        }
389        if !tree.symlink.is_empty() {
390            self.symlink = tree.symlink.clone();
391        }
392        if !tree.worktree.is_empty() {
393            self.worktree = tree.worktree.clone();
394        }
395        self.default_remote = tree.default_remote.to_string();
396        self.description = tree.description.to_string();
397        self.links.clone_from(&tree.links);
398
399        self.update_flags();
400    }
401
402    /// Update internal flags in response to newly read data.
403    pub(crate) fn update_flags(&mut self) {
404        if !self.symlink.is_empty() {
405            self.is_symlink = true;
406        }
407        if !self.worktree.is_empty() {
408            self.is_worktree = true;
409        }
410        if self.is_worktree {
411            self.is_bare_repository = false;
412        }
413    }
414
415    /// Return the resolved "branch" field.
416    pub(crate) fn eval_branch(&self, eval_context: &EvalContext) -> String {
417        self.get_branch(
418            eval_context.app_context,
419            eval_context.config,
420            eval_context.graft_config,
421            eval_context.tree_context,
422        )
423    }
424
425    /// Return the resolved "branch" field.
426    pub(crate) fn get_branch(
427        &self,
428        app_context: &ApplicationContext,
429        config: &Configuration,
430        graft_config: Option<&Configuration>,
431        tree_context: &TreeContext,
432    ) -> String {
433        eval::tree_variable(
434            app_context,
435            config,
436            graft_config,
437            &tree_context.tree,
438            tree_context.garden.as_ref(),
439            &self.branch,
440        )
441    }
442
443    // Return the resolved "url" field for the default remote.
444    pub(crate) fn eval_url(&self, eval_context: &EvalContext) -> Option<String> {
445        self.get_url(
446            eval_context.app_context,
447            eval_context.config,
448            eval_context.graft_config,
449            eval_context.tree_context,
450        )
451    }
452
453    // Return the resolved "url" field for the default remote.
454    pub(crate) fn get_url(
455        &self,
456        app_context: &ApplicationContext,
457        config: &Configuration,
458        graft_config: Option<&Configuration>,
459        context: &TreeContext,
460    ) -> Option<String> {
461        self.remotes.get(&self.default_remote).map(|remote| {
462            eval::tree_variable(
463                app_context,
464                config,
465                graft_config,
466                &context.tree,
467                context.garden.as_ref(),
468                remote,
469            )
470        })
471    }
472
473    /// Return the resolved "worktree" field.
474    pub(crate) fn eval_worktree(&self, eval_context: &EvalContext) -> String {
475        self.get_worktree(
476            eval_context.app_context,
477            eval_context.config,
478            eval_context.graft_config,
479            eval_context.tree_context,
480        )
481    }
482
483    /// Return the resolved "worktree" field.
484    pub(crate) fn get_worktree(
485        &self,
486        app_context: &ApplicationContext,
487        config: &Configuration,
488        graft_config: Option<&Configuration>,
489        tree_context: &TreeContext,
490    ) -> String {
491        eval::tree_variable(
492            app_context,
493            config,
494            graft_config,
495            &tree_context.tree,
496            tree_context.garden.as_ref(),
497            &self.worktree,
498        )
499    }
500
501    /// Return the remote associated with a branch to checkout.
502    pub(crate) fn get_remote_for_branch(
503        &self,
504        eval_context: &EvalContext,
505        branch: &str,
506    ) -> Option<String> {
507        let remote_branch = self.get_upstream_branch(eval_context, branch)?;
508        let remote = remote_branch.split_once('/')?.0;
509        if self.remotes.contains_key(remote) {
510            Some(remote.to_string())
511        } else {
512            None
513        }
514    }
515
516    /// Return the remote branch associated with a local branch.
517    pub(crate) fn get_upstream_branch(
518        &self,
519        eval_context: &EvalContext,
520        branch: &str,
521    ) -> Option<String> {
522        if branch.is_empty() {
523            return None;
524        }
525        self.branches
526            .get(branch)
527            .map(|remote_branch_var| eval_context.tree_variable(remote_branch_var))
528    }
529}
530
531#[derive(Clone, Debug, Default)]
532pub struct Group {
533    name: String,
534    pub members: StringSet,
535}
536
537impl Group {
538    pub fn get_name(&self) -> &String {
539        &self.name
540    }
541
542    /// Return an owned copy of the name field.
543    pub(crate) fn get_name_owned(&self) -> String {
544        self.get_name().to_owned()
545    }
546
547    pub(crate) fn get_name_mut(&mut self) -> &mut String {
548        &mut self.name
549    }
550}
551
552/// Groups are stored in a GroupMap inside Configuration.
553pub type GroupMap = IndexMap<GroupName, Group>;
554
555/// Templates can be used to create trees.
556/// They contain a (path-less) tree object which can be used for creating
557/// materialized trees.
558#[derive(Clone, Debug, Default)]
559pub struct Template {
560    pub tree: Tree,
561    pub extend: StringSet,
562    name: String,
563}
564
565impl Template {
566    pub fn get_name(&self) -> &String {
567        &self.name
568    }
569
570    pub(crate) fn get_name_mut(&mut self) -> &mut String {
571        &mut self.name
572    }
573
574    /// Apply this template onto the specified tree.
575    pub(crate) fn apply(&self, tree: &mut Tree) {
576        tree.clone_from_tree(&self.tree);
577    }
578}
579
580// Gardens aggregate trees
581#[derive(Clone, Debug, Default)]
582pub struct Garden {
583    pub commands: MultiVariableMap,
584    pub environment: Vec<MultiVariable>,
585    pub gitconfig: MultiVariableMap,
586    pub groups: StringSet,
587    pub trees: StringSet,
588    pub variables: VariableMap,
589    name: GardenName,
590}
591
592impl Garden {
593    pub fn get_name(&self) -> &GardenName {
594        &self.name
595    }
596
597    pub(crate) fn get_name_mut(&mut self) -> &mut String {
598        &mut self.name
599    }
600}
601
602/// Gardens are stored in a GardenMap inside Configuration.
603pub type GardenMap = IndexMap<GardenName, Garden>;
604
605/// Return the default shell to use for custom commands and "garden shell".
606fn get_default_shell() -> String {
607    if which(constants::SHELL_ZSH).is_ok() {
608        constants::SHELL_ZSH
609    } else if which(constants::SHELL_BASH).is_ok() {
610        constants::SHELL_BASH
611    } else if which(constants::SHELL_DASH).is_ok() {
612        constants::SHELL_DASH
613    } else {
614        constants::SHELL_SH
615    }
616    .to_string()
617}
618
619/// Configuration represents an instantiated garden configuration
620#[derive(Clone, Debug, Default)]
621pub struct Configuration {
622    pub commands: MultiVariableMap,
623    pub debug: IndexMap<String, u8>,
624    pub environment: Vec<MultiVariable>,
625    pub gardens: GardenMap,
626    pub grafts: IndexMap<GraftName, Graft>,
627    pub groups: GroupMap,
628    pub path: Option<std::path::PathBuf>,
629    pub dirname: Option<std::path::PathBuf>,
630    pub root: Variable,
631    pub root_is_dynamic: bool,
632    pub root_path: std::path::PathBuf,
633    pub shell: String,
634    pub interactive_shell: String,
635    pub templates: IndexMap<String, Template>,
636    pub tree_search_path: Vec<std::path::PathBuf>,
637    pub trees: IndexMap<TreeName, Tree>,
638    pub variables: VariableMap,
639    /// Variables defined on the command-line using "-D name=value" have the
640    /// highest precedence and override variables defined by any configuration or tree.
641    pub override_variables: VariableMap,
642    pub config_verbose: u8,
643    pub quiet: bool,
644    pub verbose: u8,
645    pub(crate) shell_exit_on_error: bool,
646    pub(crate) shell_word_split: bool,
647    pub(crate) tree_branches: bool,
648    pub(crate) parent_id: Option<ConfigId>,
649    id: Option<ConfigId>,
650}
651
652impl_display!(Configuration);
653
654impl Configuration {
655    /// Create a default Configuration
656    pub fn new() -> Self {
657        Configuration {
658            id: None,
659            parent_id: None,
660            shell: get_default_shell(),
661            shell_exit_on_error: true,
662            shell_word_split: true,
663            tree_branches: true,
664            ..std::default::Default::default()
665        }
666    }
667
668    pub(crate) fn initialize(&mut self, app_context: &ApplicationContext) {
669        // Evaluate garden.root
670        let expr = self.root.get_expr().to_string();
671        let mut value = eval::value(app_context, self, &expr);
672        if expr.is_empty() {
673            if self.root_is_dynamic {
674                // Default to the current directory when garden.root is configured to
675                // the empty string.
676                let current_dir = path::current_dir_string();
677                self.root.set_expr(current_dir.clone());
678                self.root.set_value(current_dir);
679                self.root_path = path::current_dir();
680            } else {
681                // Default garden.root to ${GARDEN_CONFIG_DIR} by default.
682                self.root
683                    .set_expr(string!(constants::GARDEN_CONFIG_DIR_EXPR));
684                if let Some(ref dirname) = self.dirname {
685                    self.root.set_value(dirname.to_string_lossy().to_string());
686                    self.root_path = dirname.to_path_buf();
687                }
688            }
689        } else {
690            // Store the resolved, canonicalized garden.root
691            self.root_path = std::path::PathBuf::from(&value);
692            if let Ok(root_path_canon) = path::canonicalize(&self.root_path) {
693                if root_path_canon != self.root_path {
694                    value = root_path_canon
695                        .to_str()
696                        .unwrap_or(value.as_str())
697                        .to_string();
698                    self.root_path = root_path_canon;
699                }
700            }
701            self.root.set_value(value);
702        }
703        self.update_tree_paths(app_context); // Resolve tree paths
704        self.synthesize_default_tree(); // Synthesize a tree if no trees exist.
705                                        // Reset variables
706        self.reset();
707    }
708
709    /// Return Some(&NodeId) when the configuration is a graft and None otherwise.
710    pub(crate) fn graft_id(&self) -> Option<NodeId> {
711        self.parent_id.and(self.get_id())
712    }
713
714    pub(crate) fn update(
715        &mut self,
716        app_context: &ApplicationContext,
717        config: Option<&std::path::Path>,
718        root: Option<&std::path::Path>,
719        config_verbose: u8,
720        parent: Option<ConfigId>,
721    ) -> Result<(), errors::GardenError> {
722        if let Some(parent_id) = parent {
723            self.set_parent(parent_id);
724        }
725        self.config_verbose = config_verbose;
726        self.quiet = app_context.options.quiet;
727        self.verbose = app_context.options.verbose;
728
729        // Override the configured garden root
730        let root_pathbuf_option = root.map(path::abspath);
731        if let Some(root_path) = root_pathbuf_option {
732            self.root.set_expr(root_path.to_string_lossy().to_string());
733        }
734
735        let mut basename = string!(constants::GARDEN_CONFIG);
736
737        // Find garden.yaml in the search path
738        let mut found = false;
739        if let Some(config_path) = config {
740            if config_path.is_file() || config_path.is_absolute() {
741                // If an absolute path was specified, or if the file exists,
742                // short-circuit the search; the config file might be missing but
743                // we shouldn't silently use a different config file.
744                self.set_path(&config_path);
745                found = true;
746            } else {
747                // The specified path is a basename or relative path to be found
748                // in the config search path.
749                basename = config_path.to_string_lossy().into();
750            }
751        }
752
753        if !found {
754            for entry in config::search_path() {
755                let mut candidate = entry.to_path_buf();
756                candidate.push(basename.clone());
757                if candidate.exists() {
758                    self.set_path(&candidate);
759                    found = true;
760                    break;
761                }
762            }
763        }
764        if config_verbose > 0 {
765            debug!(
766                "config: path: {:?}, root: {:?}, found: {}",
767                self.path, self.root, found
768            );
769        }
770
771        if found {
772            // Read file contents.
773            let config_path = self.get_path()?;
774            if let Ok(config_string) = std::fs::read_to_string(config_path) {
775                config::parse(app_context, &config_string, config_verbose, self)?;
776            }
777        }
778
779        Ok(())
780    }
781
782    /// Apply MainOptions to a Configuration.
783    pub(crate) fn update_options(
784        &mut self,
785        options: &cli::MainOptions,
786    ) -> Result<(), errors::GardenError> {
787        let config_verbose = options.debug_level(constants::DEBUG_LEVEL_CONFIG);
788        if self.path.is_none() {
789            error!("unable to find a configuration file -- use --config <path>");
790        }
791        if config_verbose > 1 {
792            eprintln!("config: {:?}", self.get_path()?);
793        }
794        if config_verbose > 2 {
795            debug!("{}", self);
796        }
797        for key in &options.debug {
798            let current = *self.debug.get(key).unwrap_or(&0);
799            self.debug.insert(key.into(), current + 1);
800        }
801        self.apply_defines(&options.define);
802
803        Ok(())
804    }
805
806    // Apply --define name=value options.
807    pub(crate) fn apply_defines(&mut self, defines: &Vec<String>) {
808        for k_eq_v in defines {
809            let name: String;
810            let expr: String;
811            let values: Vec<&str> = k_eq_v.splitn(2, '=').collect();
812            if values.len() == 1 {
813                name = values[0].to_string();
814                expr = string!("");
815            } else if values.len() == 2 {
816                name = values[0].to_string();
817                expr = values[1].to_string();
818            } else {
819                error!("unable to split '{}'", k_eq_v);
820            }
821            // Allow overridding garden.<value> using "garden -D garden.<value>=false".
822            match name.as_str() {
823                constants::GARDEN_INTERACTIVE_SHELL => {
824                    self.interactive_shell = expr;
825                }
826                constants::GARDEN_SHELL => {
827                    self.shell = expr;
828                }
829                constants::GARDEN_SHELL_ERREXIT => {
830                    set_bool(name.as_str(), &expr, &mut self.shell_exit_on_error);
831                }
832                constants::GARDEN_SHELL_WORDSPLIT => {
833                    set_bool(name.as_str(), &expr, &mut self.shell_word_split);
834                }
835                constants::GARDEN_TREE_BRANCHES => {
836                    set_bool(name.as_str(), &expr, &mut self.tree_branches);
837                }
838                _ => {
839                    self.override_variables
840                        .insert(name.to_string(), Variable::from_expr(name, expr));
841                }
842            }
843        }
844    }
845
846    /// Apply the quiet and verbose options to resolve GARDEN_CMD_VERBOSE and GARDEN_CMD_QUIET.
847    pub(crate) fn update_quiet_and_verbose_variables(&mut self, quiet: bool, verbose: u8) {
848        // Provide GARDEN_CMD_QUIET and GARDEN_CMD_VERBOSE.
849        let quiet_string = if self.quiet || quiet { "--quiet" } else { "" }.to_string();
850        self.variables.insert(
851            constants::GARDEN_CMD_QUIET.to_string(),
852            Variable::from_resolved_expr(constants::GARDEN_CMD_QUIET.to_string(), quiet_string),
853        );
854        let verbose = self.verbose + verbose;
855        let verbose_string = if verbose > 0 {
856            format!("-{}", "v".repeat(verbose.into()))
857        } else {
858            string!("")
859        };
860        self.variables.insert(
861            constants::GARDEN_CMD_VERBOSE.to_string(),
862            Variable::from_resolved_expr(constants::GARDEN_CMD_VERBOSE.to_string(), verbose_string),
863        );
864    }
865
866    pub(crate) fn reset(&mut self) {
867        // Reset variables to allow for tree-scope evaluation
868        self.reset_variables();
869        // Add custom variables
870        self.reset_builtin_variables()
871    }
872
873    fn reset_builtin_variables(&mut self) {
874        // Update GARDEN_ROOT.
875        if let Some(var) = self.variables.get_mut(constants::GARDEN_ROOT) {
876            if let Some(value) = self.root.get_value() {
877                var.set_expr(value.into());
878                var.set_value(value.into());
879            }
880        }
881
882        for tree in self.trees.values_mut() {
883            // Update TREE_NAME.
884            let tree_name = String::from(tree.get_name());
885            if let Some(var) = tree.variables.get_mut(constants::TREE_NAME) {
886                var.set_expr(tree_name.to_string());
887                var.set_value(tree_name);
888            }
889            // Extract the tree's path.  Skip invalid/unset entries.
890            let tree_path = match tree.path_as_ref() {
891                Ok(path) => String::from(path),
892                Err(_) => continue,
893            };
894            // Update TREE_PATH.
895            if let Some(var) = tree.variables.get_mut(constants::TREE_PATH) {
896                var.set_expr(tree_path.to_string());
897                var.set_value(tree_path);
898            }
899        }
900    }
901
902    // Calculate the "path" field for each tree.
903    // If specified as a relative path, it will be relative to garden.root.
904    // If specified as an asbolute path, it will be left as-is.
905    fn update_tree_paths(&mut self, app_context: &ApplicationContext) {
906        // Gather path and symlink expressions.
907        let mut path_values = Vec::new();
908        let mut symlink_values = Vec::new();
909
910        for (name, tree) in &self.trees {
911            path_values.push((name.clone(), tree.path.get_expr().clone()));
912            if tree.is_symlink {
913                symlink_values.push((name.clone(), tree.symlink.get_expr().clone()));
914            }
915        }
916
917        // Evaluate the "path" expression.
918        for (name, value) in &path_values {
919            let result = self.eval_tree_path(app_context, value);
920            if let Some(tree) = self.trees.get_mut(name) {
921                tree.path.set_value(result);
922            }
923        }
924
925        // Evaluate the "symlink" expression.
926        for (name, value) in &symlink_values {
927            let result = self.eval_tree_path(app_context, value);
928            if let Some(tree) = self.trees.get_mut(name) {
929                tree.symlink.set_value(result);
930            }
931        }
932    }
933
934    /// Create an implicit "." tree when no trees exist.
935    fn synthesize_default_tree(&mut self) {
936        if !self.commands.is_empty() && self.trees.is_empty() {
937            let dirname_string = self.dirname_string();
938            let mut tree = Tree::default();
939            tree.path.set_expr(dirname_string.clone());
940            tree.path.set_value(dirname_string);
941            tree.description = string!("The default tree for garden commands.");
942            tree.add_builtin_variables();
943            tree.set_name(string!(constants::DOT));
944            self.trees.insert(string!(constants::DOT), tree);
945        }
946    }
947
948    /// Return a path string relative to the garden root
949    pub(crate) fn tree_path(&self, path: &str) -> String {
950        if std::path::PathBuf::from(path).is_absolute() {
951            // Absolute path, nothing to do
952            path.into()
953        } else {
954            // Make path relative to root_path
955            let mut path_buf = self.root_path.to_path_buf();
956            path_buf.push(path);
957
958            path_buf.to_string_lossy().into()
959        }
960    }
961
962    /// Return a pathbuf relative to the garden root.
963    pub(crate) fn relative_pathbuf(&self, path: &str) -> std::path::PathBuf {
964        let pathbuf = std::path::PathBuf::from(path);
965        if pathbuf.is_absolute() {
966            // Absolute path, nothing to do
967            if let Ok(pathbuf_canon) = path::canonicalize(&pathbuf) {
968                pathbuf_canon
969            } else {
970                pathbuf
971            }
972        } else {
973            // Make path relative to root_path
974            let mut path_buf = self.root_path.to_path_buf();
975            path_buf.push(path);
976
977            path_buf
978        }
979    }
980
981    /// Evaluate and return a path string relative to the garden root.
982    fn eval_tree_path(&mut self, app_context: &ApplicationContext, path: &str) -> String {
983        let value = eval::value(app_context, self, path);
984        self.tree_path(&value)
985    }
986
987    /// Resolve a pathbuf relative to the config directory.
988    pub(crate) fn config_pathbuf(&self, path: &str) -> Option<std::path::PathBuf> {
989        let path_buf = std::path::PathBuf::from(path);
990        if path_buf.is_absolute() {
991            // Absolute path, nothing to do
992            Some(path_buf)
993        } else if let Some(dirname) = self.dirname.as_ref() {
994            // Anchor relative paths to the configuration's dirname.
995            let mut abs_path_buf = dirname.to_path_buf();
996            abs_path_buf.push(path_buf);
997
998            Some(abs_path_buf)
999        } else {
1000            None
1001        }
1002    }
1003
1004    /// Resolve a pathbuf relative to specified include file or the config directory.
1005    /// Returns the first file found. The include file's directory is checked first.
1006    fn config_pathbuf_from_include(
1007        &self,
1008        include_path: &std::path::Path,
1009        path: &str,
1010    ) -> Option<std::path::PathBuf> {
1011        let mut path_buf = std::path::PathBuf::from(path);
1012        if path_buf.is_absolute() {
1013            // Absolute path, nothing to do
1014            return Some(path_buf);
1015        }
1016
1017        // First check if the path exists relative to the specified parent directory.
1018        if let Some(dirname) = include_path.parent() {
1019            // Make path relative to the include file.
1020            path_buf = dirname.to_path_buf();
1021            path_buf.push(path);
1022
1023            if path_buf.exists() {
1024                return Some(path_buf);
1025            }
1026        }
1027
1028        // Make path relative to the configuration's dirname.
1029        self.config_pathbuf(path)
1030    }
1031
1032    /// Resolve a path string relative to the config directory.
1033    fn config_path(&self, path: &str) -> String {
1034        if let Some(path_buf) = self.config_pathbuf(path) {
1035            path_buf.to_string_lossy().to_string()
1036        } else {
1037            self.tree_path(path)
1038        }
1039    }
1040
1041    /// Return a directory string for the garden config directory.
1042    /// This typically returns GARDEN_CONFIG_DIR but falls back to
1043    /// the current directory when unset.
1044    fn dirname_string(&self) -> String {
1045        match self.dirname {
1046            Some(ref dirname) => dirname.to_string_lossy().to_string(),
1047            None => path::current_dir_string(),
1048        }
1049    }
1050
1051    /// Return a path for running commands that should always exist.
1052    pub(crate) fn fallback_execdir_string(&self) -> String {
1053        if self.root_path.exists() {
1054            return self.root_path.to_string_lossy().to_string();
1055        }
1056        if let Some(dirname) = self.dirname.as_ref() {
1057            if dirname.exists() {
1058                return dirname.to_string_lossy().to_string();
1059            }
1060        }
1061        path::current_dir_string()
1062    }
1063
1064    /// Evaluate and resolve a path string and relative to the config directory.
1065    pub(crate) fn eval_config_path(&self, app_context: &ApplicationContext, path: &str) -> String {
1066        let value = eval::value(app_context, self, path);
1067        self.config_path(&value)
1068    }
1069
1070    /// Evaluate and resolve a pathbuf relative to the config directory for "includes".
1071    pub(crate) fn eval_config_pathbuf_from_include(
1072        &self,
1073        app_context: &ApplicationContext,
1074        include_path: Option<&std::path::Path>,
1075        path: &str,
1076    ) -> Option<std::path::PathBuf> {
1077        let value = eval::value(app_context, self, path);
1078
1079        if let Some(include_path) = include_path {
1080            self.config_pathbuf_from_include(include_path, &value)
1081        } else {
1082            self.config_pathbuf(&value)
1083        }
1084        .or_else(|| Some(std::path::PathBuf::from(&value)))
1085    }
1086
1087    /// Reset resolved variables
1088    pub(crate) fn reset_variables(&mut self) {
1089        for var in self.variables.values() {
1090            var.reset();
1091        }
1092        for env in &self.environment {
1093            env.reset();
1094        }
1095
1096        reset_map_variables(&self.commands);
1097
1098        for tree in self.trees.values() {
1099            tree.reset_variables();
1100        }
1101    }
1102
1103    /// Set the ConfigId from the Arena for this configuration.
1104    pub(crate) fn set_id(&mut self, id: ConfigId) {
1105        self.id = Some(id);
1106    }
1107
1108    pub(crate) fn get_id(&self) -> Option<ConfigId> {
1109        self.id
1110    }
1111
1112    /// Set the parent ConfigId from the Arena for this configuration.
1113    pub(crate) fn set_parent(&mut self, id: ConfigId) {
1114        self.parent_id = Some(id);
1115    }
1116
1117    /// Set the config path and the dirname fields
1118    pub(crate) fn set_path(&mut self, path: &dyn AsRef<std::path::Path>) {
1119        let config_path = path.as_ref().to_path_buf();
1120        let mut dirname = config_path.clone();
1121        dirname.pop();
1122
1123        self.dirname = Some(dirname);
1124        self.path = Some(config_path);
1125    }
1126
1127    /// Get the config path if it is defined.
1128    pub(crate) fn get_path(&self) -> Result<&std::path::PathBuf, errors::GardenError> {
1129        self.path.as_ref().ok_or_else(|| {
1130            errors::GardenError::AssertionError("Configuration path is unset".into())
1131        })
1132    }
1133
1134    /// Get a path string for this configuration.
1135    /// Returns the current directory when the configuration does not have a valid path.
1136    pub(crate) fn get_path_for_display(&self) -> String {
1137        let default_pathbuf = std::path::PathBuf::from(constants::DOT);
1138        self.path
1139            .as_ref()
1140            .unwrap_or(&default_pathbuf)
1141            .display()
1142            .to_string()
1143    }
1144
1145    /// Return true if the configuration contains the named graft.
1146    pub(crate) fn contains_graft(&self, name: &str) -> bool {
1147        let graft_name = syntax::trim(name);
1148        self.grafts.contains_key(graft_name)
1149    }
1150
1151    /// Return a graft by name.
1152    pub(crate) fn get_graft(&self, name: &str) -> Result<&Graft, errors::GardenError> {
1153        let graft_name = syntax::trim(name);
1154        self.grafts.get(graft_name).ok_or_else(|| {
1155            errors::GardenError::ConfigurationError(format!("{name}: no such graft"))
1156        })
1157    }
1158
1159    /// Parse a "graft::value" string and return the ConfigId for the graft and the
1160    /// remaining unparsed "value".
1161    pub(crate) fn get_graft_id<'a>(
1162        &self,
1163        value: &'a str,
1164    ) -> Result<(ConfigId, &'a str), errors::GardenError> {
1165        let (graft_name, remainder) = match syntax::split_graft(value) {
1166            Some((graft_name, remainder)) => (graft_name, remainder),
1167            None => {
1168                return Err(errors::GardenError::ConfigurationError(format!(
1169                    "{value}: invalid graft expression"
1170                )))
1171            }
1172        };
1173        let graft = self.get_graft(graft_name)?;
1174        let graft_id = graft
1175            .get_id()
1176            .ok_or(errors::GardenError::ConfigurationError(format!(
1177                "{graft_name}: no such graft"
1178            )))?;
1179
1180        Ok((graft_id, remainder))
1181    }
1182
1183    /// Find a tree by name and return a reference if it exists.
1184    pub fn get_tree(&self, name: &str) -> Option<&Tree> {
1185        self.trees.get(name)
1186    }
1187
1188    /// Find a garden by name and return a reference if it exists.
1189    pub fn get_garden(&self, name: &str) -> Option<&Garden> {
1190        self.gardens.get(name)
1191    }
1192
1193    /// Return a pathbuf for the specified Tree index
1194    pub(crate) fn get_tree_pathbuf(&self, tree_name: &str) -> Option<std::path::PathBuf> {
1195        self.get_tree(tree_name)
1196            .map(|tree| tree.canonical_pathbuf())
1197            .unwrap_or(None)
1198    }
1199}
1200
1201/// Parse a named boolean value into a bool warn if the value is not a valid bool value.
1202fn set_bool(name: &str, expr: &str, output: &mut bool) {
1203    if let Some(value) = syntax::string_to_bool(expr) {
1204        *output = value;
1205    } else {
1206        error!(
1207            "'{}' is not a valid value for \"{}\". Must be true, false, 0 or 1",
1208            name, expr
1209        );
1210    }
1211}
1212
1213#[derive(Clone, Debug, Default)]
1214pub struct Graft {
1215    id: Option<ConfigId>,
1216    name: String,
1217    pub root: String,
1218    pub config: String,
1219}
1220
1221impl Graft {
1222    pub fn new(name: String, root: String, config: String) -> Self {
1223        Graft {
1224            id: None,
1225            name,
1226            root,
1227            config,
1228        }
1229    }
1230
1231    pub fn get_name(&self) -> &String {
1232        &self.name
1233    }
1234
1235    pub fn get_id(&self) -> Option<ConfigId> {
1236        self.id
1237    }
1238
1239    pub(crate) fn set_id(&mut self, id: ConfigId) {
1240        self.id = Some(id);
1241    }
1242}
1243
1244// TODO EvalContext
1245#[derive(Clone, Debug)]
1246pub(crate) struct EvalContext<'a> {
1247    pub(crate) app_context: &'a ApplicationContext,
1248    pub(crate) config: &'a Configuration,
1249    pub(crate) graft_config: Option<&'a Configuration>,
1250    pub(crate) tree_context: &'a TreeContext,
1251}
1252
1253impl EvalContext<'_> {
1254    /// Construct a new EvalContext.
1255    pub(crate) fn new<'a>(
1256        app_context: &'a ApplicationContext,
1257        config: &'a Configuration,
1258        graft_config: Option<&'a Configuration>,
1259        tree_context: &'a TreeContext,
1260    ) -> EvalContext<'a> {
1261        EvalContext {
1262            app_context,
1263            config,
1264            graft_config,
1265            tree_context,
1266        }
1267    }
1268
1269    /// Create an EvalContext from an ApplicationContext and TreeContext.
1270    pub(crate) fn from_app_context<'a>(
1271        app_context: &'a ApplicationContext,
1272        tree_context: &'a TreeContext,
1273    ) -> EvalContext<'a> {
1274        let config = app_context.get_root_config();
1275        let graft_config = tree_context
1276            .config
1277            .map(|config_id| app_context.get_config(config_id));
1278        EvalContext::new(app_context, config, graft_config, tree_context)
1279    }
1280
1281    /// Evaluate a tree variable.
1282    pub(crate) fn tree_value(&self, value: &str) -> String {
1283        eval::tree_value(
1284            self.app_context,
1285            self.config,
1286            self.graft_config,
1287            value,
1288            &self.tree_context.tree,
1289            self.tree_context.garden.as_ref(),
1290        )
1291    }
1292
1293    /// Evaluate a Variable with a tree scope.
1294    pub(crate) fn tree_variable(&self, var: &Variable) -> String {
1295        eval::tree_variable(
1296            self.app_context,
1297            self.config,
1298            self.graft_config,
1299            &self.tree_context.tree,
1300            self.tree_context.garden.as_ref(),
1301            var,
1302        )
1303    }
1304}
1305
1306#[derive(Clone, Debug)]
1307pub struct TreeContext {
1308    pub tree: TreeName,
1309    pub config: Option<ConfigId>,
1310    pub garden: Option<GardenName>,
1311    pub group: Option<String>,
1312}
1313
1314impl TreeContext {
1315    /// Construct a new TreeContext.
1316    pub fn new(
1317        tree: &str,
1318        config: Option<ConfigId>,
1319        garden: Option<GardenName>,
1320        group: Option<String>,
1321    ) -> Self {
1322        TreeContext {
1323            tree: TreeName::from(tree),
1324            config,
1325            garden,
1326            group,
1327        }
1328    }
1329}
1330
1331#[derive(Debug, Default)]
1332pub struct TreeQuery {
1333    pub query: String,
1334    pub pattern: glob::Pattern,
1335    pub is_default: bool,
1336    pub is_garden: bool,
1337    pub is_group: bool,
1338    pub is_tree: bool,
1339    pub include_gardens: bool,
1340    pub include_groups: bool,
1341    pub include_trees: bool,
1342}
1343
1344impl TreeQuery {
1345    pub fn new(query: &str) -> Self {
1346        let mut is_default = false;
1347        let mut is_tree = false;
1348        let mut is_garden = false;
1349        let mut is_group = false;
1350        let mut include_gardens = true;
1351        let mut include_groups = true;
1352        let mut include_trees = true;
1353
1354        if syntax::is_garden(query) {
1355            is_garden = true;
1356            include_groups = false;
1357            include_trees = false;
1358        } else if syntax::is_group(query) {
1359            is_group = true;
1360            include_gardens = false;
1361            include_trees = false;
1362        } else if syntax::is_tree(query) {
1363            is_tree = true;
1364            include_gardens = false;
1365            include_groups = false;
1366        } else {
1367            is_default = true;
1368        }
1369        let glob_pattern = syntax::trim(query);
1370        let pattern = glob::Pattern::new(glob_pattern).unwrap_or_default();
1371
1372        TreeQuery {
1373            query: query.into(),
1374            is_default,
1375            is_garden,
1376            is_group,
1377            is_tree,
1378            include_gardens,
1379            include_groups,
1380            include_trees,
1381            pattern,
1382        }
1383    }
1384}
1385
1386#[derive(
1387    Clone,
1388    Debug,
1389    Default,
1390    PartialEq,
1391    Eq,
1392    strum_macros::EnumString,
1393    strum_macros::Display,
1394    strum_macros::VariantNames,
1395)]
1396#[strum(ascii_case_insensitive, serialize_all = "kebab-case")]
1397pub enum ColorMode {
1398    /// Enable color when a tty is detected
1399    #[default]
1400    Auto,
1401    /// Enable color
1402    #[strum(
1403        serialize = "1",
1404        serialize = "on",
1405        serialize = "true",
1406        serialize = "always",
1407        serialize = "y",
1408        serialize = "yes"
1409    )]
1410    On,
1411    /// Disable color
1412    #[strum(
1413        serialize = "0",
1414        serialize = "off",
1415        serialize = "false",
1416        serialize = "never",
1417        serialize = "n",
1418        serialize = "no"
1419    )]
1420    Off,
1421}
1422
1423impl ColorMode {
1424    /// Parse a color mode from a string using strum's from_str().
1425    pub fn parse_from_str(string: &str) -> Result<ColorMode, String> {
1426        ColorMode::from_str(string).map_err(|_| format!("choices are {:?}", Self::VARIANTS))
1427    }
1428
1429    pub fn is_enabled(&self) -> bool {
1430        match self {
1431            ColorMode::Auto => std::io::stdout().is_terminal(),
1432            ColorMode::Off => false,
1433            ColorMode::On => true,
1434        }
1435    }
1436
1437    pub fn update(&mut self) {
1438        if *self == ColorMode::Auto {
1439            // Speedup future calls to is_enabled() by performing the "auto"
1440            // is_terminal() check once and caching the result.
1441            if self.is_enabled() {
1442                *self = ColorMode::On;
1443            } else {
1444                *self = ColorMode::Off;
1445            }
1446        }
1447
1448        if *self == ColorMode::Off {
1449            yansi::disable();
1450        }
1451    }
1452}
1453
1454#[derive(
1455    Clone,
1456    Debug,
1457    Default,
1458    PartialEq,
1459    Eq,
1460    strum_macros::EnumString,
1461    strum_macros::Display,
1462    strum_macros::VariantNames,
1463)]
1464#[strum(ascii_case_insensitive, serialize_all = "kebab-case")]
1465pub enum TreeSortMode {
1466    #[default]
1467    None,
1468    Name,
1469    Time,
1470}
1471
1472impl TreeSortMode {
1473    /// Parse a color mode from a string using strum's from_str().
1474    pub(crate) fn parse_from_str(string: &str) -> Result<TreeSortMode, String> {
1475        TreeSortMode::from_str(string).map_err(|_| format!("choices are {:?}", Self::VARIANTS))
1476    }
1477}
1478
1479#[derive(Debug)]
1480pub struct ApplicationContext {
1481    pub options: cli::MainOptions,
1482    arena: UnsafeCell<Arena<Configuration>>,
1483    root_id: ConfigId,
1484}
1485
1486/// Safety: ApplicationContext is not thread-safe due to its use of internal mutability.
1487/// Furthermore, we cannot use mutexes internally due to our use of Rayon and
1488/// [fundamental issues](https://github.com/rayon-rs/rayon/issues/592).
1489/// that lead to deadlocks when performing parallel computations.
1490///
1491/// ApplicationContext is designed to be cloned whenever operations are performed
1492/// across multiple threads. The RefCells stored inside of the Variable struct are
1493/// used to provider interior mutability. Even though the methods are const, the
1494/// methods mutate interior data in a non-thread-safe manner.
1495///
1496/// Likewise, even if we were able to hold mutexes when using Rayon, our access pattern
1497/// is not logically sound were we to attempt to evaluate data in parallel. The evaluation
1498/// machinery is context-specific and can lead to different results dependending on which
1499/// TreeContext initiated the eval. This is the core reason why cloning is required in
1500/// order to evaluate variables across multiple threads.
1501unsafe impl Sync for ApplicationContext {}
1502
1503/// ApplicationContext performs a deepcopy of its internal arena to ensure that
1504/// none of its internally-mutable data is shared between threads.
1505impl Clone for ApplicationContext {
1506    fn clone(&self) -> Self {
1507        let mut arena: Arena<Configuration> = Arena::new();
1508        if let Some(self_arena) = unsafe { self.arena.get().as_ref() } {
1509            for node in self_arena.iter() {
1510                arena.new_node(node.get().clone());
1511            }
1512        }
1513        Self {
1514            arena: UnsafeCell::new(arena),
1515            options: self.options.clone(),
1516            root_id: self.root_id,
1517        }
1518    }
1519}
1520
1521impl ApplicationContext {
1522    /// Construct an empty ApplicationContext. Initialization must be performed post-construction.
1523    pub fn new(options: cli::MainOptions) -> Self {
1524        let mut arena = Arena::new();
1525        let config = Configuration::new();
1526        let root_id = arena.new_node(config);
1527
1528        let app_context = ApplicationContext {
1529            arena: UnsafeCell::new(arena),
1530            root_id,
1531            options,
1532        };
1533        // Record the ID in the configuration.
1534        let config = app_context.get_root_config_mut();
1535        config.set_id(root_id);
1536
1537        app_context
1538    }
1539
1540    /// Initialize an ApplicationContext and Configuration from cli::MainOptions.
1541    pub fn from_options(options: &cli::MainOptions) -> Result<Self, errors::GardenError> {
1542        let app_context = Self::new(options.clone());
1543        let config_verbose = options.debug_level(constants::DEBUG_LEVEL_CONFIG);
1544
1545        app_context.get_root_config_mut().update(
1546            &app_context,
1547            options.config.as_deref(),
1548            options.root.as_deref(),
1549            config_verbose,
1550            None,
1551        )?;
1552        app_context.get_root_config_mut().update_options(options)?;
1553        config::read_grafts(&app_context)?;
1554
1555        Ok(app_context)
1556    }
1557
1558    /// Construct an ApplicationContext from a path and root using default MainOptions.
1559    pub fn from_path_and_root(
1560        path: &dyn AsRef<std::path::Path>,
1561        root: Option<&std::path::Path>,
1562    ) -> Result<Self, errors::GardenError> {
1563        let options = cli::MainOptions::new();
1564        let app_context = Self::new(options.clone());
1565        let config_verbose = options.debug_level(constants::DEBUG_LEVEL_CONFIG);
1566        app_context.get_root_config_mut().update(
1567            &app_context,
1568            Some(path.as_ref()),
1569            root,
1570            config_verbose,
1571            None,
1572        )?;
1573        // Record the ID in the configuration.
1574        config::read_grafts(&app_context)?;
1575
1576        Ok(app_context)
1577    }
1578
1579    /// Construct an ApplicationContext from a path using default MainOptions.
1580    pub fn from_path(path: &dyn AsRef<std::path::Path>) -> Result<Self, errors::GardenError> {
1581        if let Some(root_dir) = path.as_ref().parent().map(std::path::Path::to_owned) {
1582            Self::from_path_and_root(path, Some(&root_dir))
1583        } else {
1584            Self::from_path_and_root(path, None)
1585        }
1586    }
1587
1588    /// Construct an ApplicationContext from a path using default MainOptions.
1589    pub fn from_path_string(path: &str) -> Result<Self, errors::GardenError> {
1590        Self::from_path(&std::path::PathBuf::from(path))
1591    }
1592
1593    /// Construct an ApplicationContext from a string using default MainOptions.
1594    pub fn from_string(string: &str) -> Result<Self, errors::GardenError> {
1595        let options = cli::MainOptions::new();
1596        let app_context = Self::new(options);
1597
1598        config::parse(&app_context, string, 0, app_context.get_root_config_mut())?;
1599        config::read_grafts(&app_context)?;
1600
1601        Ok(app_context)
1602    }
1603
1604    pub fn get_config(&self, id: ConfigId) -> &Configuration {
1605        unsafe { (*self.arena.get()).get(id).unwrap().get() }
1606    }
1607
1608    #[allow(clippy::mut_from_ref)]
1609    pub(crate) fn get_config_mut(&self, id: ConfigId) -> &mut Configuration {
1610        unsafe { (*self.arena.get()).get_mut(id).unwrap().get_mut() }
1611    }
1612
1613    pub fn get_root_id(&self) -> ConfigId {
1614        self.root_id
1615    }
1616
1617    pub fn get_root_config(&self) -> &Configuration {
1618        self.get_config(self.get_root_id())
1619    }
1620
1621    pub fn get_root_config_mut(&self) -> &mut Configuration {
1622        self.get_config_mut(self.get_root_id())
1623    }
1624
1625    /// Add a child Configuration graft onto the parent ConfigId.
1626    pub(crate) fn add_graft(&self, parent: ConfigId, config: Configuration) -> ConfigId {
1627        let graft_id = unsafe { (*self.arena.get()).new_node(config) }; // Take ownership of config.
1628        if let Some(arena) = unsafe { self.arena.get().as_mut() } {
1629            parent.append(graft_id, arena);
1630        }
1631
1632        self.get_config_mut(graft_id).set_id(graft_id);
1633
1634        graft_id
1635    }
1636
1637    /// Attach a graft to the configuration specified by ConfigId.
1638    pub(crate) fn add_graft_config(
1639        &self,
1640        config_id: ConfigId,
1641        graft_name: &str,
1642        path: &std::path::Path,
1643        root: Option<&std::path::Path>,
1644    ) -> Result<(), errors::GardenError> {
1645        let path = path.to_path_buf();
1646        let config_verbose = self.options.debug_level(constants::DEBUG_LEVEL_CONFIG);
1647        let mut graft_config = Configuration::new();
1648        // Propagate the current config's settings onto child grafts.
1649        graft_config.tree_branches = self.get_config(config_id).tree_branches;
1650        graft_config.shell_exit_on_error = self.get_config(config_id).shell_exit_on_error;
1651        graft_config.shell_word_split = self.get_config(config_id).shell_word_split;
1652        // Parse the config file for the graft.
1653        graft_config.update(self, Some(&path), root, config_verbose, Some(config_id))?;
1654
1655        // The app Arena takes ownership of the Configuration.
1656        let graft_id = self.add_graft(config_id, graft_config);
1657        // Record the graft's config ID in the graft.
1658        if let Some(graft_config) = self.get_config_mut(config_id).grafts.get_mut(graft_name) {
1659            graft_config.set_id(graft_id);
1660        }
1661        // Read child grafts recursively.
1662        config::read_grafts_recursive(self, graft_id)?;
1663
1664        Ok(())
1665    }
1666}
1667
1668/// Represent the different types of Git worktree.
1669#[derive(Clone, Debug, PartialEq, Eq)]
1670pub enum GitTreeType {
1671    /// A worktree with child worktrees attached to it.
1672    Parent,
1673    /// A child worktree created with "git worktree".
1674    Worktree(std::path::PathBuf),
1675    /// A regular Git clone / worktree created with "git clone/init".
1676    Tree,
1677    /// A bare repository.
1678    Bare,
1679}
1680
1681/// Represent "git worktree list" details queried from Git.
1682#[derive(Clone, Debug)]
1683pub struct GitTreeDetails {
1684    pub branch: String,
1685    pub tree_type: GitTreeType,
1686}
1687
1688/// Return true if the context has not been filtered out and refers to a valid configured tree.
1689pub(crate) fn is_valid_context(
1690    app_context: &ApplicationContext,
1691    pattern: &glob::Pattern,
1692    context: &TreeContext,
1693) -> bool {
1694    if !pattern.matches(&context.tree) {
1695        return false;
1696    }
1697    let tree_opt = match context.config {
1698        Some(graft_id) => app_context.get_config(graft_id).trees.get(&context.tree),
1699        None => app_context.get_root_config().trees.get(&context.tree),
1700    };
1701    let tree = match tree_opt {
1702        Some(tree) => tree,
1703        None => return false,
1704    };
1705    // Skip symlink trees.
1706    if tree.is_symlink {
1707        return false;
1708    }
1709
1710    true
1711}