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