diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index a93aa71045..d2819686fa 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -3078,7 +3078,10 @@ impl ValueParserFactory for RevisionArg { } } -fn get_string_or_array(config: &config::Config, key: &str) -> Result, ConfigError> { +fn get_string_or_array( + config: &StackedConfig, + key: &'static str, +) -> Result, ConfigError> { config .get(key) .map(|string| vec![string]) @@ -3087,7 +3090,7 @@ fn get_string_or_array(config: &config::Config, key: &str) -> Result fn resolve_default_command( ui: &Ui, - config: &config::Config, + config: &StackedConfig, app: &Command, mut string_args: Vec, ) -> Result, CommandError> { @@ -3130,7 +3133,7 @@ fn resolve_default_command( fn resolve_aliases( ui: &Ui, - config: &config::Config, + config: &StackedConfig, app: &Command, mut string_args: Vec, ) -> Result, CommandError> { @@ -3240,7 +3243,7 @@ fn handle_early_args( } if !args.config_toml.is_empty() { config.add_layer(parse_config_args(&args.config_toml)?); - ui.reset(&config.merge())?; + ui.reset(config)?; } Ok(()) } @@ -3248,7 +3251,7 @@ fn handle_early_args( fn handle_shell_completion( ui: &Ui, app: &Command, - config: &config::Config, + config: &StackedConfig, cwd: &Path, ) -> Result<(), CommandError> { let mut args = vec![]; @@ -3296,7 +3299,7 @@ pub fn expand_args( ui: &Ui, app: &Command, args_os: impl IntoIterator, - config: &config::Config, + config: &StackedConfig, ) -> Result, CommandError> { let mut string_args: Vec = vec![]; for arg_os in args_os { @@ -3509,11 +3512,7 @@ impl CliRunner { } #[instrument(skip_all)] - fn run_internal( - self, - ui: &mut Ui, - mut stacked_config: StackedConfig, - ) -> Result<(), CommandError> { + fn run_internal(self, ui: &mut Ui, mut config: StackedConfig) -> Result<(), CommandError> { // `cwd` is canonicalized for consistency with `Workspace::workspace_root()` and // to easily compute relative paths between them. let cwd = env::current_dir() @@ -3532,12 +3531,11 @@ impl CliRunner { .workspace_loader_factory .create(find_workspace_dir(&cwd)) .map_err(|err| map_workspace_load_error(err, None)); - config_env.reload_user_config(&mut stacked_config)?; + config_env.reload_user_config(&mut config)?; if let Ok(loader) = &maybe_cwd_workspace_loader { config_env.reset_repo_path(loader.repo_path()); - config_env.reload_repo_config(&mut stacked_config)?; + config_env.reload_repo_config(&mut config)?; } - let config = stacked_config.merge(); ui.reset(&config).map_err(|e| { let user_config_path = config_env.existing_user_config_path(); let repo_config_path = config_env.existing_repo_config_path(); @@ -3559,9 +3557,9 @@ impl CliRunner { &self.app, &self.tracing_subscription, &string_args, - &mut stacked_config, + &mut config, ) - .map_err(|err| map_clap_cli_error(err, ui, &stacked_config))?; + .map_err(|err| map_clap_cli_error(err, ui, &config))?; for process_global_args_fn in self.process_global_args_fns { process_global_args_fn(ui, &matches)?; } @@ -3573,14 +3571,13 @@ impl CliRunner { .create(&cwd.join(path)) .map_err(|err| map_workspace_load_error(err, Some(path)))?; config_env.reset_repo_path(loader.repo_path()); - config_env.reload_repo_config(&mut stacked_config)?; + config_env.reload_repo_config(&mut config)?; Ok(loader) } else { maybe_cwd_workspace_loader }; // Apply workspace configs and --config-toml arguments. - let config = stacked_config.merge(); ui.reset(&config)?; // If -R is specified, check if the expanded arguments differ. Aliases @@ -3595,7 +3592,7 @@ impl CliRunner { } } - let settings = UserSettings::from_config(stacked_config); + let settings = UserSettings::from_config(config); let command_helper_data = CommandHelperData { app: self.app, cwd, @@ -3631,7 +3628,7 @@ impl CliRunner { .build() .unwrap(); let stacked_config = config_from_environment(config); - let mut ui = Ui::with_config(&stacked_config.merge()) + let mut ui = Ui::with_config(&stacked_config) .expect("default config should be valid, env vars are stringly typed"); let result = self.run_internal(&mut ui, stacked_config); let exit_code = handle_command_result(&mut ui, result); diff --git a/cli/src/complete.rs b/cli/src/complete.rs index c31f0936d4..a9d8264d93 100644 --- a/cli/src/complete.rs +++ b/cli/src/complete.rs @@ -676,19 +676,18 @@ fn get_jj_command() -> Result<(JjBuilder, UserSettings), CommandError> { // child process. This shouldn't fail, since none of the global args are // required. let app = crate::commands::default_app(); - let mut stacked_config = config_from_environment(default_config()); - let ui = Ui::with_config(&stacked_config.merge()).expect("default config should be valid"); + let mut config = config_from_environment(default_config()); + let ui = Ui::with_config(&config).expect("default config should be valid"); let cwd = std::env::current_dir() .and_then(|cwd| cwd.canonicalize()) .map_err(user_error)?; let mut config_env = ConfigEnv::from_environment()?; let maybe_cwd_workspace_loader = DefaultWorkspaceLoaderFactory.create(find_workspace_dir(&cwd)); - let _ = config_env.reload_user_config(&mut stacked_config); + let _ = config_env.reload_user_config(&mut config); if let Ok(loader) = &maybe_cwd_workspace_loader { config_env.reset_repo_path(loader.repo_path()); - let _ = config_env.reload_repo_config(&mut stacked_config); + let _ = config_env.reload_repo_config(&mut config); } - let config = stacked_config.merge(); // skip 2 because of the clap_complete prelude: jj -- jj let args = std::env::args_os().skip(2); let args = expand_args(&ui, &app, args, &config)?; @@ -704,7 +703,7 @@ fn get_jj_command() -> Result<(JjBuilder, UserSettings), CommandError> { // Try to update repo-specific config on a best-effort basis. if let Ok(loader) = DefaultWorkspaceLoaderFactory.create(&cwd.join(&repository)) { config_env.reset_repo_path(loader.repo_path()); - let _ = config_env.reload_repo_config(&mut stacked_config); + let _ = config_env.reload_repo_config(&mut config); } cmd_args.push("--repository".into()); cmd_args.push(repository); @@ -745,7 +744,7 @@ fn get_jj_command() -> Result<(JjBuilder, UserSettings), CommandError> { cmd: current_exe, args: cmd_args, }; - let settings = UserSettings::from_config(stacked_config); + let settings = UserSettings::from_config(config); Ok((builder, settings)) } diff --git a/cli/src/config.rs b/cli/src/config.rs index 9ba6a25e8b..426409b598 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -687,29 +687,28 @@ impl TryFrom> for NonEmptyCommandArgsVec { mod tests { use anyhow::anyhow; use assert_matches::assert_matches; + use indoc::indoc; use maplit::hashmap; use super::*; #[test] fn test_command_args() { - let config = config::Config::builder() - .set_override("empty_array", Vec::::new()) - .unwrap() - .set_override("empty_string", "") - .unwrap() - .set_override("array", vec!["emacs", "-nw"]) - .unwrap() - .set_override("string", "emacs -nw") - .unwrap() - .set_override("structured.env.KEY1", "value1") - .unwrap() - .set_override("structured.env.KEY2", "value2") - .unwrap() - .set_override("structured.command", vec!["emacs", "-nw"]) - .unwrap() - .build() - .unwrap(); + let mut config = StackedConfig::empty(); + config.add_layer( + ConfigLayer::parse( + ConfigSource::User, + indoc! {" + empty_array = [] + empty_string = '' + array = ['emacs', '-nw'] + string = 'emacs -nw' + structured.env = { KEY1 = 'value1', KEY2 = 'value2' } + structured.command = ['emacs', '-nw'] + "}, + ) + .unwrap(), + ); assert!(config.get::("empty_array").is_err()); diff --git a/cli/src/formatter.rs b/cli/src/formatter.rs index 34d7d16a2f..9b88a8fa37 100644 --- a/cli/src/formatter.rs +++ b/cli/src/formatter.rs @@ -30,6 +30,7 @@ use crossterm::style::SetBackgroundColor; use crossterm::style::SetForegroundColor; use itertools::Itertools; use jj_lib::config::ConfigError; +use jj_lib::config::StackedConfig; // Lets the caller label strings and translates the labels to colors pub trait Formatter: Write { @@ -159,7 +160,7 @@ impl FormatterFactory { FormatterFactory { kind } } - pub fn color(config: &config::Config, debug: bool) -> Result { + pub fn color(config: &StackedConfig, debug: bool) -> Result { let rules = Arc::new(rules_from_config(config)?); let kind = FormatterFactoryKind::Color { rules, debug }; Ok(FormatterFactory { kind }) @@ -296,11 +297,7 @@ impl ColorFormatter { } } - pub fn for_config( - output: W, - config: &config::Config, - debug: bool, - ) -> Result { + pub fn for_config(output: W, config: &StackedConfig, debug: bool) -> Result { let rules = rules_from_config(config)?; Ok(Self::new(output, Arc::new(rules), debug)) } @@ -404,7 +401,7 @@ impl ColorFormatter { } } -fn rules_from_config(config: &config::Config) -> Result { +fn rules_from_config(config: &StackedConfig) -> Result { let mut result = vec![]; let table = config.get_table("colors")?; for (key, value) in table { @@ -772,8 +769,7 @@ mod tests { // TODO: migrate off config::Config and switch to IndexMap let colors: HashMap = config.get("colors").unwrap(); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); for (label, color) in colors.iter().sorted() { formatter.push_label(label).unwrap(); write!(formatter, " {color} ").unwrap(); @@ -813,8 +809,7 @@ mod tests { // TODO: migrate off config::Config and switch to IndexMap let colors: HashMap = config.get("colors").unwrap(); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); for label in colors.keys().sorted() { formatter.push_label(&label.replace(' ', "-")).unwrap(); write!(formatter, " {label} ").unwrap(); @@ -839,8 +834,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); write!(formatter, " before ").unwrap(); formatter.push_label("inside").unwrap(); write!(formatter, " inside ").unwrap(); @@ -864,8 +858,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); formatter.push_label("red_fg").unwrap(); write!(formatter, " fg only ").unwrap(); formatter.pop_label().unwrap(); @@ -913,8 +906,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); formatter.push_label("not_bold").unwrap(); write!(formatter, " not bold ").unwrap(); formatter.push_label("bold_font").unwrap(); @@ -936,8 +928,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); write!(formatter, "before").unwrap(); formatter.push_label("red").unwrap(); write!(formatter, "first").unwrap(); @@ -959,8 +950,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); formatter.push_label("red").unwrap(); write!(formatter, "\x1b[1mnot actually bold\x1b[0m").unwrap(); formatter.pop_label().unwrap(); @@ -981,8 +971,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); write!(formatter, " before outer ").unwrap(); formatter.push_label("outer").unwrap(); write!(formatter, " before inner ").unwrap(); @@ -1006,8 +995,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); formatter.push_label("outer").unwrap(); write!(formatter, " not colored ").unwrap(); formatter.push_label("inner").unwrap(); @@ -1030,7 +1018,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let err = ColorFormatter::for_config(&mut output, &config.merge(), false) + let err = ColorFormatter::for_config(&mut output, &config, false) .unwrap_err() .to_string(); insta::assert_snapshot!(err, @@ -1047,7 +1035,7 @@ mod tests { "##, ); let mut output: Vec = vec![]; - let err = ColorFormatter::for_config(&mut output, &config.merge(), false) + let err = ColorFormatter::for_config(&mut output, &config, false) .unwrap_err() .to_string(); insta::assert_snapshot!(err, @@ -1066,8 +1054,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); formatter.push_label("outer").unwrap(); write!(formatter, "Blue on yellow, ").unwrap(); formatter.push_label("default_fg").unwrap(); @@ -1096,8 +1083,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); formatter.push_label("outer1").unwrap(); formatter.push_label("inner2").unwrap(); write!(formatter, " hello ").unwrap(); @@ -1117,8 +1103,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); formatter.push_label("outer").unwrap(); formatter.push_label("inner").unwrap(); write!(formatter, " hello ").unwrap(); @@ -1140,8 +1125,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); formatter.push_label("a").unwrap(); write!(formatter, " a1 ").unwrap(); formatter.push_label("b").unwrap(); @@ -1168,8 +1152,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); formatter.push_label("outer").unwrap(); formatter.push_label("inner").unwrap(); write!(formatter, " inside ").unwrap(); @@ -1187,7 +1170,7 @@ mod tests { "#, ); let mut output: Vec = vec![]; - let mut formatter = ColorFormatter::for_config(&mut output, &config.merge(), true).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, true).unwrap(); formatter.push_label("outer").unwrap(); formatter.push_label("inner").unwrap(); write!(formatter, " inside ").unwrap(); @@ -1207,7 +1190,7 @@ mod tests { ); let mut output: Vec = vec![]; let mut formatter: Box = - Box::new(ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap()); + Box::new(ColorFormatter::for_config(&mut output, &config, false).unwrap()); formatter .as_mut() .labeled("inner") @@ -1257,8 +1240,7 @@ mod tests { // Replayed output should be labeled. let config = config_from_string(r#" colors.inner = "red" "#); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); recorder.replay(&mut formatter).unwrap(); drop(formatter); insta::assert_snapshot!( @@ -1267,8 +1249,7 @@ mod tests { // Replayed output should be split at push/pop_label() call. let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); recorder .replay_with(&mut formatter, |formatter, range| { let data = &recorder.data()[range]; @@ -1295,16 +1276,14 @@ mod tests { // Replayed raw escape sequences are labeled. let config = config_from_string(r#" colors.inner = "red" "#); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); recorder.replay(&mut formatter).unwrap(); drop(formatter); insta::assert_snapshot!( String::from_utf8(output).unwrap(), @" outer1  inner1 inner2  outer2 "); let mut output: Vec = vec![]; - let mut formatter = - ColorFormatter::for_config(&mut output, &config.merge(), false).unwrap(); + let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); recorder .replay_with(&mut formatter, |_formatter, range| { panic!( diff --git a/cli/src/merge_tools/mod.rs b/cli/src/merge_tools/mod.rs index 167df32341..c4560156d4 100644 --- a/cli/src/merge_tools/mod.rs +++ b/cli/src/merge_tools/mod.rs @@ -444,7 +444,7 @@ mod tests { fn test_get_diff_editor_from_settings() { let get = |text| { let config = config_from_string(text); - let ui = Ui::with_config(&config.merge()).unwrap(); + let ui = Ui::with_config(&config).unwrap(); let settings = UserSettings::from_config(config); DiffEditor::from_settings( &ui, @@ -661,7 +661,7 @@ mod tests { fn test_get_merge_editor_from_settings() { let get = |text| { let config = config_from_string(text); - let ui = Ui::with_config(&config.merge()).unwrap(); + let ui = Ui::with_config(&config).unwrap(); let settings = UserSettings::from_config(config); MergeEditor::from_settings(&ui, &settings, ConflictMarkerStyle::Diff) .map(|editor| editor.tool) diff --git a/cli/src/text_util.rs b/cli/src/text_util.rs index a6e7e5f44c..a7f88dc659 100644 --- a/cli/src/text_util.rs +++ b/cli/src/text_util.rs @@ -488,18 +488,27 @@ pub fn parse_author(author: &str) -> Result<(String, String), &'static str> { mod tests { use std::io::Write as _; + use indoc::indoc; + use jj_lib::config::ConfigLayer; + use jj_lib::config::ConfigSource; + use jj_lib::config::StackedConfig; + use super::*; use crate::formatter::ColorFormatter; use crate::formatter::PlainTextFormatter; fn format_colored(write: impl FnOnce(&mut dyn Formatter) -> io::Result<()>) -> String { - let config = config::Config::builder() - .set_override("colors.cyan", "cyan") - .unwrap() - .set_override("colors.red", "red") - .unwrap() - .build() - .unwrap(); + let mut config = StackedConfig::empty(); + config.add_layer( + ConfigLayer::parse( + ConfigSource::Default, + indoc! {" + colors.cyan = 'cyan' + colors.red = 'red' + "}, + ) + .unwrap(), + ); let mut output = Vec::new(); let mut formatter = ColorFormatter::for_config(&mut output, &config, false).unwrap(); write(&mut formatter).unwrap(); diff --git a/cli/src/ui.rs b/cli/src/ui.rs index 1bd935d75e..333bce96a1 100644 --- a/cli/src/ui.rs +++ b/cli/src/ui.rs @@ -33,6 +33,7 @@ use std::thread::JoinHandle; use indoc::indoc; use itertools::Itertools as _; use jj_lib::config::ConfigError; +use jj_lib::config::StackedConfig; use minus::MinusError; use minus::Pager as MinusPager; use tracing::instrument; @@ -262,8 +263,8 @@ pub struct Ui { output: UiOutput, } -fn progress_indicator_setting(config: &config::Config) -> bool { - config.get_bool("ui.progress-indicator").unwrap_or(true) +fn progress_indicator_setting(config: &StackedConfig) -> bool { + config.get("ui.progress-indicator").unwrap_or(true) } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] @@ -301,16 +302,16 @@ impl fmt::Display for ColorChoice { } } -fn color_setting(config: &config::Config) -> ColorChoice { +fn color_setting(config: &StackedConfig) -> ColorChoice { config - .get_string("ui.color") + .get::("ui.color") .ok() .and_then(|s| s.parse().ok()) .unwrap_or_default() } fn prepare_formatter_factory( - config: &config::Config, + config: &StackedConfig, stdout: &Stdout, ) -> Result { let terminal = stdout.is_terminal(); @@ -331,8 +332,8 @@ fn prepare_formatter_factory( } } -fn be_quiet(config: &config::Config) -> bool { - config.get_bool("ui.quiet").unwrap_or_default() +fn be_quiet(config: &StackedConfig) -> bool { + config.get("ui.quiet").unwrap_or_default() } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize)] @@ -343,20 +344,20 @@ pub enum PaginationChoice { Auto, } -fn pagination_setting(config: &config::Config) -> Result { +fn pagination_setting(config: &StackedConfig) -> Result { config .get::("ui.paginate") .map_err(|err| config_error_with_message("Invalid `ui.paginate`", err)) } -fn pager_setting(config: &config::Config) -> Result { +fn pager_setting(config: &StackedConfig) -> Result { config .get::("ui.pager") .map_err(|err| config_error_with_message("Invalid `ui.pager`", err)) } impl Ui { - pub fn with_config(config: &config::Config) -> Result { + pub fn with_config(config: &StackedConfig) -> Result { let quiet = be_quiet(config); let formatter_factory = prepare_formatter_factory(config, &io::stdout())?; let progress_indicator = progress_indicator_setting(config); @@ -370,7 +371,7 @@ impl Ui { }) } - pub fn reset(&mut self, config: &config::Config) -> Result<(), CommandError> { + pub fn reset(&mut self, config: &StackedConfig) -> Result<(), CommandError> { self.quiet = be_quiet(config); self.paginate = pagination_setting(config)?; self.pager_cmd = pager_setting(config)?;