ok/jj
1
0
Fork 0
forked from mirrors/jj

feat(config): add jj config get for scripting

The motivating use-case was this `jj signoff` script: https://gist.github.com/thoughtpolice/8f2fd36ae17cd11b8e7bd93a70e31ad6

Which includes lines like this:

```sh
NAME=$(jj config list user.name | awk '{split($0, a, "="); print a[2];}' | tr -d '"')
MAIL=$(jj config list user.email | awk '{split($0, a, "="); print a[2];}' | tr -d '"')
```

There is no reason that we should have to clumsily parse out the config values. This `jj config get` command supports scripting use-cases like this.
This commit is contained in:
Waleed Khan 2023-06-23 11:55:11 -07:00
parent d1701a5d95
commit 24ea8478cb
3 changed files with 110 additions and 0 deletions

View file

@ -54,6 +54,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `jj git fetch` now supports a `--branch` argument to fetch some of the
branches only.
* `jj config get` command allows retrieving config values for use in scripting.
* `jj config set` command allows simple config edits like
`jj config set --repo user.email "somebody@example.com"`

View file

@ -189,6 +189,8 @@ impl ConfigArgs {
enum ConfigSubcommand {
#[command(visible_alias("l"))]
List(ConfigListArgs),
#[command(visible_alias("g"))]
Get(ConfigGetArgs),
#[command(visible_alias("s"))]
Set(ConfigSetArgs),
#[command(visible_alias("e"))]
@ -208,6 +210,22 @@ struct ConfigListArgs {
// TODO(#1047): Support ConfigArgs (--user or --repo).
}
/// Get the value of a given config option.
///
/// Unlike `jj config list`, the result of `jj config get` is printed without
/// extra formatting and therefore is usable in scripting. For example:
///
/// $ jj config list user.name
/// user.name="Martin von Zweigbergk"
/// $ jj config get user.name
/// Martin von Zweigbergk
#[derive(clap::Args, Clone, Debug)]
#[command(verbatim_doc_comment)]
struct ConfigGetArgs {
#[arg(required = true)]
name: String,
}
/// Update config file to set the given option to a given value.
#[derive(clap::Args, Clone, Debug)]
struct ConfigSetArgs {
@ -1096,6 +1114,7 @@ fn cmd_config(
) -> Result<(), CommandError> {
match subcommand {
ConfigSubcommand::List(sub_args) => cmd_config_list(ui, command, sub_args),
ConfigSubcommand::Get(sub_args) => cmd_config_get(ui, command, sub_args),
ConfigSubcommand::Set(sub_args) => cmd_config_set(ui, command, sub_args),
ConfigSubcommand::Edit(sub_args) => cmd_config_edit(ui, command, sub_args),
}
@ -1143,6 +1162,43 @@ fn cmd_config_list(
Ok(())
}
fn cmd_config_get(
ui: &mut Ui,
command: &CommandHelper,
args: &ConfigGetArgs,
) -> Result<(), CommandError> {
let value = command
.settings()
.config()
.get_string(&args.name)
.map_err(|err| match err {
config::ConfigError::Type {
origin,
unexpected,
expected,
key,
} => {
let expected = format!("a value convertible to {expected}");
// Copied from `impl fmt::Display for ConfigError`. We can't use
// the `Display` impl directly because `expected` is required to
// be a `'static str`.
let mut buf = String::new();
use std::fmt::Write;
write!(buf, "invalid type: {unexpected}, expected {expected}").unwrap();
if let Some(key) = key {
write!(buf, " for key `{key}`").unwrap();
}
if let Some(origin) = origin {
write!(buf, " in {origin}").unwrap();
}
CommandError::ConfigError(buf.to_string())
}
err => err.into(),
})?;
writeln!(ui, "{value}")?;
Ok(())
}
fn cmd_config_set(
_ui: &mut Ui,
command: &CommandHelper,

View file

@ -438,6 +438,58 @@ fn test_config_edit_repo_outside_repo() {
"###);
}
#[test]
fn test_config_get() {
let test_env = TestEnvironment::default();
test_env.add_config(
r###"
[table]
string = "some value 1"
int = 123
list = ["list", "value"]
overridden = "foo"
"###,
);
test_env.add_config(
r###"
[table]
overridden = "bar"
"###,
);
let stdout = test_env.jj_cmd_failure(test_env.env_root(), &["config", "get", "nonexistent"]);
insta::assert_snapshot!(stdout, @r###"
Config error: configuration property "nonexistent" not found
For help, see https://github.com/martinvonz/jj/blob/main/docs/config.md.
"###);
let stdout = test_env.jj_cmd_success(test_env.env_root(), &["config", "get", "table.string"]);
insta::assert_snapshot!(stdout, @r###"
some value 1
"###);
let stdout = test_env.jj_cmd_success(test_env.env_root(), &["config", "get", "table.int"]);
insta::assert_snapshot!(stdout, @r###"
123
"###);
let stdout = test_env.jj_cmd_failure(test_env.env_root(), &["config", "get", "table.list"]);
insta::assert_snapshot!(stdout, @r###"
Config error: invalid type: sequence, expected a value convertible to a string
For help, see https://github.com/martinvonz/jj/blob/main/docs/config.md.
"###);
let stdout = test_env.jj_cmd_failure(test_env.env_root(), &["config", "get", "table"]);
insta::assert_snapshot!(stdout, @r###"
Config error: invalid type: map, expected a value convertible to a string
For help, see https://github.com/martinvonz/jj/blob/main/docs/config.md.
"###);
let stdout =
test_env.jj_cmd_success(test_env.env_root(), &["config", "get", "table.overridden"]);
insta::assert_snapshot!(stdout, @"bar");
}
fn find_stdout_lines(keyname_pattern: &str, stdout: &str) -> String {
let key_line_re = Regex::new(&format!(r"(?m)^{keyname_pattern}=.*$")).unwrap();
key_line_re