From dedbc474f1230316b2faf112449ffb7268803ad0 Mon Sep 17 00:00:00 2001 From: Bryce Berger Date: Wed, 22 Jan 2025 16:33:27 -0500 Subject: [PATCH] config: propogate --when.command through cli --- CHANGELOG.md | 3 ++ cli/src/cli_util.rs | 20 ++++++++- cli/src/config.rs | 9 +++++ cli/tests/test_config_command.rs | 69 ++++++++++++++++++++++++++++++-- docs/config.md | 28 +++++++++---- 5 files changed, 116 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d71a42b348..b3dfd68669e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,6 +116,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * New `subject(pattern)` revset function that matches first line of commit descriptions. +* Conditional configuration now supports `--when.command` to change configuration + based on subcommand. + ### Fixed bugs * Fixed diff selection by external tools with `jj split`/`commit -i FILESETS`. diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index 35a12732dee..a615bda59c3 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -3701,6 +3701,23 @@ impl CliRunner { process_global_args_fn(ui, &matches)?; } + let subcommand_combined = + if let Some((subcommand, mut subcommand_matches)) = matches.subcommand() { + let mut subcommand_combined = String::from(subcommand); + while let Some((subcommand, new_matches)) = subcommand_matches.subcommand() { + subcommand_combined.push(' '); + subcommand_combined.push_str(subcommand); + subcommand_matches = new_matches; + } + config = config_env + .resolve_config_with_command(&raw_config, Some(&subcommand_combined))?; + migrate_config(&mut config)?; + ui.reset(&config)?; + Some(subcommand_combined) + } else { + None + }; + let maybe_workspace_loader = if let Some(path) = &args.global_args.repository { // TODO: maybe path should be canonicalized by WorkspaceLoader? let abs_path = cwd.join(path); @@ -3712,7 +3729,8 @@ impl CliRunner { .map_err(|err| map_workspace_load_error(err, Some(path)))?; config_env.reset_repo_path(loader.repo_path()); config_env.reload_repo_config(&mut raw_config)?; - config = config_env.resolve_config(&raw_config)?; + config = config_env + .resolve_config_with_command(&raw_config, subcommand_combined.as_deref())?; migrate_config(&mut config)?; Ok(loader) } else { diff --git a/cli/src/config.rs b/cli/src/config.rs index 0f8dd3b9d70..0fe53f1cdc2 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -395,9 +395,18 @@ impl ConfigEnv { /// Resolves conditional scopes within the current environment. Returns new /// resolved config. pub fn resolve_config(&self, config: &RawConfig) -> Result { + self.resolve_config_with_command(config, None) + } + + pub fn resolve_config_with_command( + &self, + config: &RawConfig, + command: Option<&str>, + ) -> Result { let context = ConfigResolutionContext { home_dir: self.home_dir.as_deref(), repo_path: self.repo_path.as_deref(), + command, }; jj_lib::config::resolve(config.as_ref(), &context) } diff --git a/cli/tests/test_config_command.rs b/cli/tests/test_config_command.rs index 65ace23d86c..d3713898dc4 100644 --- a/cli/tests/test_config_command.rs +++ b/cli/tests/test_config_command.rs @@ -1078,12 +1078,25 @@ fn test_config_conditional() { &user_config_path, indoc! {" foo = 'global' + baz = 'global' + qux = 'global' + [[--scope]] --when.repositories = ['~/repo1'] foo = 'repo1' [[--scope]] --when.repositories = ['~/repo2'] foo = 'repo2' + + [[--scope]] + --when.commands = ['config'] + baz = 'config' + [[--scope]] + --when.commands = ['config get'] + qux = 'get' + [[--scope]] + --when.commands = ['config list'] + qux = 'list' "}, ) .unwrap(); @@ -1093,16 +1106,38 @@ fn test_config_conditional() { insta::assert_snapshot!(stdout, @"global"); let stdout = test_env.jj_cmd_success(&repo1_path, &["config", "get", "foo"]); insta::assert_snapshot!(stdout, @"repo1"); + // baz should be the same for `jj config get` and `jj config list` + // qux should be different + let stdout = test_env.jj_cmd_success(&repo1_path, &["config", "get", "baz"]); + insta::assert_snapshot!(stdout, @"config"); + let stdout = test_env.jj_cmd_success(&repo1_path, &["config", "get", "qux"]); + insta::assert_snapshot!(stdout, @"get"); let stdout = test_env.jj_cmd_success(test_env.env_root(), &["config", "list", "--user"]); - insta::assert_snapshot!(stdout, @"foo = 'global'"); + insta::assert_snapshot!(stdout, @r#" + foo = 'global' + baz = 'config' + qux = 'list' + "#); let stdout = test_env.jj_cmd_success(&repo1_path, &["config", "list", "--user"]); - insta::assert_snapshot!(stdout, @"foo = 'repo1'"); + insta::assert_snapshot!(stdout, @r#" + foo = 'repo1' + baz = 'config' + qux = 'list' + "#); let stdout = test_env.jj_cmd_success(&repo2_path, &["config", "list", "--user"]); - insta::assert_snapshot!(stdout, @"foo = 'repo2'"); + insta::assert_snapshot!(stdout, @r#" + foo = 'repo2' + baz = 'config' + qux = 'list' + "#); // relative workspace path let stdout = test_env.jj_cmd_success(&repo2_path, &["config", "list", "--user", "-R../repo1"]); - insta::assert_snapshot!(stdout, @"foo = 'repo1'"); + insta::assert_snapshot!(stdout, @r#" + foo = 'repo1' + baz = 'config' + qux = 'list' + "#); // set and unset should refer to the source config // (there's no option to update scoped table right now.) @@ -1113,24 +1148,50 @@ fn test_config_conditional() { insta::assert_snapshot!(stderr, @""); insta::assert_snapshot!(std::fs::read_to_string(&user_config_path).unwrap(), @r#" foo = 'global' + baz = 'global' + qux = 'global' bar = "new value" + [[--scope]] --when.repositories = ['~/repo1'] foo = 'repo1' [[--scope]] --when.repositories = ['~/repo2'] foo = 'repo2' + + [[--scope]] + --when.commands = ['config'] + baz = 'config' + [[--scope]] + --when.commands = ['config get'] + qux = 'get' + [[--scope]] + --when.commands = ['config list'] + qux = 'list' "#); let (_stdout, stderr) = test_env.jj_cmd_ok(&repo1_path, &["config", "unset", "--user", "foo"]); insta::assert_snapshot!(stderr, @""); insta::assert_snapshot!(std::fs::read_to_string(&user_config_path).unwrap(), @r#" + baz = 'global' + qux = 'global' bar = "new value" + [[--scope]] --when.repositories = ['~/repo1'] foo = 'repo1' [[--scope]] --when.repositories = ['~/repo2'] foo = 'repo2' + + [[--scope]] + --when.commands = ['config'] + baz = 'config' + [[--scope]] + --when.commands = ['config get'] + qux = 'get' + [[--scope]] + --when.commands = ['config list'] + qux = 'list' "#); } diff --git a/docs/config.md b/docs/config.md index c139bd25f06..d16146f92e2 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1371,6 +1371,9 @@ You can conditionally enable config variables by using `--when` and `[[--scope]]` tables. Variables defined in `[[--scope]]` tables are expanded to the root table. `--when` specifies the condition to enable the scope table. +If no conditions are specified, table is always enabled. If multiple conditions +are specified, the intersection is used. + ```toml [user] name = "YOUR NAME" @@ -1387,13 +1390,22 @@ Condition keys: * `--when.repositories`: List of paths to match the repository path prefix. -Paths should be absolute. Each path component (directory or file name, drive -letter, etc.) is compared case-sensitively on all platforms. A path starting -with `~` is expanded to the home directory. On Windows, directory separator may -be either `\` or `/`. (Beware that `\` needs escape in double-quoted strings.) + Paths should be absolute. Each path component (directory or file name, drive + letter, etc.) is compared case-sensitively on all platforms. A path starting + with `~` is expanded to the home directory. On Windows, directory separator may + be either `\` or `/`. (Beware that `\` needs escape in double-quoted strings.) + + Use `jj root` to see the workspace root directory. Note that the repository path + is in the main workspace if you're using multiple workspaces with `jj + workspace`. + + +* `--when.command`: List of subcommands to match. -Use `jj root` to see the workspace root directory. Note that the repository path -is in the main workspace if you're using multiple workspaces with `jj -workspace`. + Subcommands are space-separated and matched by prefix. -If no conditions are specified, table is always enabled. + ```toml + --when.command = ["file"] # matches `jj file show`, `jj file list`, etc + --when.command = ["file show"] # matches `jj file show` but *NOT* `jj file list` + --when.command = ["file", "log"] # matches `jj file` *OR* `jj log` (or subcommand of either) + ```