From 5a549738166a5fe52203ee6a7014d1675bbb5d88 Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Mon, 4 Dec 2023 00:06:01 -0800 Subject: [PATCH] commands: display tree's current branch alongside the tree name Add a `tree_branches` field to the configuration and default it to true. Teach garden to display branch names alongside tree names. Add a "garden.tree-branches" configuration value to disable the feature. Closes: #18 --- doc/src/changelog.md | 3 ++ doc/src/configuration.md | 11 ++++++++ src/cmd.rs | 2 +- src/cmds/cmd.rs | 6 ++-- src/cmds/grow.rs | 2 +- src/config/reader.rs | 4 +++ src/git.rs | 23 ++++++++++++++++ src/model.rs | 59 ++++++++++++++++++++++++++++++++++------ 8 files changed, 95 insertions(+), 15 deletions(-) diff --git a/doc/src/changelog.md b/doc/src/changelog.md index ea5e818..1f897df 100644 --- a/doc/src/changelog.md +++ b/doc/src/changelog.md @@ -4,6 +4,9 @@ **Features** +- Commands now display the tree's current branch alongside the tree name. + ([#18](https://github.com/davvid/garden/issues/18)) + - `garden -vv exec` and `garden -vv shell` now display the command being run. ## v0.9.1 diff --git a/doc/src/configuration.md b/doc/src/configuration.md index 07dba3c..2f20d0f 100644 --- a/doc/src/configuration.md +++ b/doc/src/configuration.md @@ -76,6 +76,17 @@ when `garden.shell` is omitted. The first one found is the one that's used. * `bash` * `sh` +## Tree Display + +Garden will display the tree's current branch when running commands. +While this has a marginal performance impact, this feature can be disabled by either +passing the `garden -D garden.tree-branches=0` option or by configuring the +`garden.tree-branches` option to `false` in the `garden.yaml` configuration. + +```yaml +garden: + tree-branches: false +``` ## Includes diff --git a/src/cmd.rs b/src/cmd.rs index 481b289..0fd768d 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -132,7 +132,7 @@ where path = tree.path_as_ref()?; // Sparse gardens/missing trees are ok -> skip these entries. - if !model::print_tree(tree, verbose, quiet) { + if !model::print_tree(tree, config.tree_branches, verbose, quiet) { return Ok(()); } } else { diff --git a/src/cmds/cmd.rs b/src/cmds/cmd.rs index 5a2b9f7..0d52df0 100644 --- a/src/cmds/cmd.rs +++ b/src/cmds/cmd.rs @@ -224,7 +224,7 @@ pub fn run_cmd_breadth_first( // Run each command in the tree's context let path = tree.path_as_ref()?.to_string(); // Sparse gardens/missing trees are ok -> skip these entries. - if !model::print_tree(tree, verbose, quiet) { + if !model::print_tree(tree, config.tree_branches, verbose, quiet) { continue; } @@ -282,12 +282,10 @@ pub fn run_cmd_depth_first( } // Evaluate the tree environment let env = eval::environment(app_context, config, context); - // Run each command in the tree's context let path = tree.path_as_ref()?.to_string(); - // Sparse gardens/missing trees are ok -> skip these entries. - if !model::print_tree(tree, verbose, quiet) { + if !model::print_tree(tree, config.tree_branches, verbose, quiet) { continue; } diff --git a/src/cmds/grow.rs b/src/cmds/grow.rs index 7661671..795c224 100644 --- a/src/cmds/grow.rs +++ b/src/cmds/grow.rs @@ -83,7 +83,7 @@ fn grow_tree_from_context( }; let path = tree.path_as_ref()?.clone(); - model::print_tree_details(tree, verbose, quiet); + model::print_tree_details(tree, config.tree_branches, verbose, quiet); let pathbuf = std::path::PathBuf::from(&path); let parent = pathbuf.parent().ok_or_else(|| { diff --git a/src/config/reader.rs b/src/config/reader.rs index 8f960eb..dd4b6f2 100644 --- a/src/config/reader.rs +++ b/src/config/reader.rs @@ -66,6 +66,10 @@ fn parse_recursive( if get_str(&doc["garden"]["shell"], &mut config.shell) && config_verbose > 0 { debug!("yaml: garden.shell = {}", config.shell); } + // garden.tree-branches + if get_bool(&doc["garden"]["tree-branches"], &mut config.tree_branches) && config_verbose > 0 { + debug!("yaml: garden.tree-branches = {}", config.tree_branches); + } // GARDEN_ROOT and GARDEN_CONFIG_DIR are relative to the root configuration. // Referencing these variables from garden files included using garden.includes diff --git a/src/git.rs b/src/git.rs index d47065c..99c40c2 100644 --- a/src/git.rs +++ b/src/git.rs @@ -88,3 +88,26 @@ pub fn branches(path: &std::path::Path) -> Vec { branches } + +/// Return the current branch name for the specified repository path. +pub fn branch(path: &std::path::Path) -> Option { + let cmd = ["git", "symbolic-ref", "--quiet", "--short", "HEAD"]; + let exec = cmd::exec_in_dir(&cmd, &path); + if let Ok(capture_data) = cmd::capture_stdout(exec) { + let output = cmd::trim_stdout(&capture_data); + if !output.is_empty() { + return Some(output); + } + } + // Detached head? Show an abbreviated commit ID. This respects `git config core.abbrev`. + let cmd = ["git", "rev-parse", "--short", "HEAD"]; + let exec = cmd::exec_in_dir(&cmd, &path); + if let Ok(capture_data) = cmd::capture_stdout(exec) { + let output = cmd::trim_stdout(&capture_data); + if !output.is_empty() { + return Some(output); + } + } + // Unknown branch is an empty string. + None +} diff --git a/src/model.rs b/src/model.rs index 9dec5e7..977d620 100644 --- a/src/model.rs +++ b/src/model.rs @@ -3,6 +3,7 @@ use super::collections::{append_hashmap, append_indexset}; use super::config; use super::errors; use super::eval; +use super::git; use super::path; use super::syntax; @@ -460,6 +461,7 @@ pub struct Configuration { /// highest precedence and override variables defined by any configuration or tree. pub override_variables: VariableHashMap, pub verbose: u8, + pub tree_branches: bool, pub parent_id: Option, id: Option, } @@ -473,6 +475,7 @@ impl Configuration { id: None, parent_id: None, shell: get_default_shell(), + tree_branches: true, ..std::default::Default::default() } } @@ -602,8 +605,17 @@ impl Configuration { } else { error!("unable to split '{}'", k_eq_v); } - self.override_variables - .insert(name, Variable::new(expr, None)); + // Allow overridding garden.tree-branches using "garden -D garden.tree-branches=false". + if name == "garden.tree-branches" { + if let Some(value) = syntax::string_to_bool(&expr) { + self.tree_branches = value; + } else { + error!("'{}' is not a valid value for \"garden.tree-branches\". Must be true, false, 0 or 1", expr); + } + } else { + self.override_variables + .insert(name, Variable::new(expr, None)); + } } Ok(()) @@ -1145,21 +1157,46 @@ pub fn display_missing_tree(tree: &Tree, path: &str, verbose: u8) -> String { } } -pub fn display_tree(tree: &Tree, path: &str, verbose: u8) -> String { +pub fn display_tree(tree: &Tree, path_str: &str, tree_branches: bool, verbose: u8) -> String { if verbose > 0 { + if tree_branches { + if let Some(path) = tree.canonical_pathbuf() { + if let Some(branch) = git::branch(&path) { + return format!( + "{} {} ({}) {}", + Color::cyan("#"), + Color::blue(&tree.name).bold(), + Color::blue(&branch), + Color::blue(&path_str) + ); + } + } + } format!( - "{} {} {}", + "{} {} {}", Color::cyan("#"), Color::blue(&tree.name).bold(), - Color::blue(&path) + Color::blue(&path_str) ) } else { + if tree_branches { + if let Some(path) = tree.canonical_pathbuf() { + if let Some(branch) = git::branch(&path) { + return format!( + "{} {} ({})", + Color::cyan("#"), + Color::blue(&tree.name).bold(), + Color::blue(&branch) + ); + } + } + } format!("{} {}", Color::cyan("#"), Color::blue(&tree.name).bold()) } } /// Print a tree if it exists, otherwise print a missing tree -pub fn print_tree(tree: &Tree, verbose: u8, quiet: bool) -> bool { +pub fn print_tree(tree: &Tree, tree_branches: bool, verbose: u8, quiet: bool) -> bool { if let Ok(path) = tree.path_as_ref() { // Sparse gardens/missing trees are ok -> skip these entries. if !std::path::PathBuf::from(&path).exists() { @@ -1169,7 +1206,7 @@ pub fn print_tree(tree: &Tree, verbose: u8, quiet: bool) -> bool { return false; } - print_tree_details(tree, verbose, quiet); + print_tree_details(tree, tree_branches, verbose, quiet); return true; } else if !quiet { eprintln!("{}", display_missing_tree(tree, "[invalid-path]", verbose)); @@ -1179,10 +1216,10 @@ pub fn print_tree(tree: &Tree, verbose: u8, quiet: bool) -> bool { } /// Print a tree -pub fn print_tree_details(tree: &Tree, verbose: u8, quiet: bool) { +pub fn print_tree_details(tree: &Tree, tree_branches: bool, verbose: u8, quiet: bool) { if !quiet { if let Ok(path) = tree.path_as_ref() { - eprintln!("{}", display_tree(tree, path, verbose)); + eprintln!("{}", display_tree(tree, path, tree_branches, verbose)); } } } @@ -1322,6 +1359,10 @@ impl ApplicationContext { let path = path.to_path_buf(); let config_verbose = self.options.debug_level("config"); let mut graft_config = Configuration::new(); + // Propogate the current config's "garden.tree-branches" setting down to child grafts. + // The graft's configuration override the parent's "garden.tree-branches" setting. + graft_config.tree_branches = self.get_config(config_id).tree_branches; + // Parse the config file for the graft. graft_config.update(self, Some(&path), root, config_verbose, Some(config_id))?; // The app Arena takes ownershp of the Configuration.