diff --git a/src/cli_util.rs b/src/cli_util.rs index 0376318a5..37e173680 100644 --- a/src/cli_util.rs +++ b/src/cli_util.rs @@ -52,6 +52,7 @@ use jujutsu_lib::{dag_walk, file_util, git, revset}; use thiserror::Error; use tracing_subscriber::prelude::*; +use crate::config::LayeredConfigs; use crate::formatter::Formatter; use crate::merge_tools::{ConflictResolveError, DiffEditError}; use crate::templater::TemplateFormatter; @@ -1780,9 +1781,12 @@ impl CliRunner { pub fn run(self, ui: &mut Ui) -> Result<(), CommandError> { let cwd = env::current_dir().unwrap(); // TODO: maybe map_err to CommandError? - let mut settings = crate::config::read_config()?; - ui.reset(settings.config()); - let string_args = expand_args(&self.app, std::env::args_os(), settings.config())?; + let mut layered_configs = LayeredConfigs::from_environment(); + layered_configs.read_user_config()?; + let config = layered_configs.merge(); + ui.reset(&config); + let string_args = expand_args(&self.app, std::env::args_os(), &config)?; + let mut settings = UserSettings::from_config(config); let (matches, args) = parse_args( ui, &self.app, diff --git a/src/config.rs b/src/config.rs index f8fe3ec25..37e475733 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,7 +17,6 @@ use std::path::{Path, PathBuf}; use std::process::Command; use std::{env, fmt}; -use jujutsu_lib::settings::UserSettings; use thiserror::Error; #[derive(Error, Debug)] @@ -28,6 +27,61 @@ pub enum ConfigError { AmbiguousSource(PathBuf, PathBuf), } +/// Set of configs which can be merged as needed. +/// +/// Sources from the lowest precedence: +/// 1. Default +/// 2. Base environment variables +/// 3. User config `~/.jjconfig.toml` or `$JJ_CONFIG` +/// 4. TODO: Repo config `.jj/repo/config.toml` +/// 5. TODO: Workspace config `.jj/config.toml` +/// 6. Override environment variables +/// 7. TODO: Command-line arguments `--config-toml` +#[derive(Clone, Debug)] +pub struct LayeredConfigs { + default: config::Config, + env_base: config::Config, + user: Option, + env_overrides: config::Config, +} + +impl LayeredConfigs { + /// Initializes configs with infallible sources. + pub fn from_environment() -> Self { + LayeredConfigs { + default: default_config(), + env_base: env_base(), + user: None, + env_overrides: env_overrides(), + } + } + + pub fn read_user_config(&mut self) -> Result<(), ConfigError> { + self.user = config_path()? + .map(|path| read_config_path(&path)) + .transpose()?; + Ok(()) + } + + /// Creates new merged config. + pub fn merge(&self) -> config::Config { + let config_sources = [ + Some(&self.default), + Some(&self.env_base), + self.user.as_ref(), + Some(&self.env_overrides), + ]; + config_sources + .into_iter() + .flatten() + .fold(config::Config::builder(), |builder, source| { + builder.add_source(source.clone()) + }) + .build() + .expect("loaded configs should be merged without error") + } +} + fn config_path() -> Result, ConfigError> { if let Ok(config_path) = env::var("JJ_CONFIG") { // TODO: We should probably support colon-separated (std::env::split_paths) @@ -154,17 +208,6 @@ fn read_config_path(config_path: &Path) -> Result Result { - let mut config_builder = config::Config::builder() - .add_source(default_config()) - .add_source(env_base()); - if let Some(path) = config_path()? { - config_builder = config_builder.add_source(read_config_path(&path)?); - } - let config = config_builder.add_source(env_overrides()).build().unwrap(); - Ok(UserSettings::from_config(config)) -} - /// Command name and arguments specified by config. #[derive(Clone, Debug, Eq, Hash, PartialEq, serde::Deserialize)] #[serde(untagged)]