Skip to content

Commit

Permalink
cli: Add --show-origin option to config list
Browse files Browse the repository at this point in the history
Add a new `--show-origin` option to `jj config list` to display the
source type and file path (when present) of config values. In cases
where the user config path is a directory, the individual file
containing a given value will be displayed.

The method that captures config file paths,
`config::FileSourceFile::resolve()`, converts them to relative paths.
These may be difficult to recognize when the current working directory
is distant from a config file's location. We therefore attempt to
canonicalize all file paths. Should this fail, we use the path as-is.
  • Loading branch information
Will Chandler committed Feb 13, 2024
1 parent 7aedbcf commit a7d637f
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 2 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#2971](https://github.com/martinvonz/jj/issues/2971)). This may become the
default depending on feedback.

* `jj config list` gained a `--show-origin` flag to display the source of
config values.

### Fixed bugs

* On Windows, symlinks in the repo are now materialized as regular files in the
Expand Down
29 changes: 28 additions & 1 deletion cli/src/commands/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

use std::io::Write;
use std::path::Path;

use clap::builder::NonEmptyStringValueParser;
use itertools::Itertools;
Expand Down Expand Up @@ -84,6 +85,11 @@ pub(crate) struct ConfigListArgs {
/// Allow printing overridden values.
#[arg(long)]
pub include_overridden: bool,
/// Display the source of all listed config values with type (default, env,
/// usercfg, repocfg, command line) and the source file path for usercfg
/// and repocfg.
#[arg(long)]
pub show_origin: bool,
/// Target the user-level config
#[arg(long)]
user: bool,
Expand Down Expand Up @@ -204,9 +210,16 @@ pub(crate) fn cmd_config_list(
if !args.include_defaults && *source == ConfigSource::Default {
continue;
}

let origin = if args.show_origin {
format_origin(value.origin(), source)
} else {
String::from("")
};

writeln!(
ui.stdout(),
"{}{}={}",
"{origin}{}{}={}",
if *is_overridden { "# " } else { "" },
path.join("."),
serialize_config_value(value)
Expand Down Expand Up @@ -304,3 +317,17 @@ pub(crate) fn cmd_config_path(
)?;
Ok(())
}

fn format_origin(origin: Option<&str>, source: &ConfigSource) -> String {
let Some(origin) = origin else {
return format!("{}: ", source.description());
};

// `config::FileSourceFile::resolve()` returns a relative path. Try to
// convert them to absolute paths for easier recognition, falling back
// to the original value on failure.
let canon = Path::new(origin).canonicalize();
let path = canon.as_deref().unwrap_or_else(|_| Path::new(origin));

format!("{} {}: ", source.description(), path.display())
}
13 changes: 12 additions & 1 deletion cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,23 @@ pub enum ConfigError {
pub enum ConfigSource {
Default,
Env,
// TODO: Track explicit file paths, especially for when user config is a dir.
User,
Repo,
CommandArg,
}

impl ConfigSource {
pub fn description(&self) -> &str {
match self {
ConfigSource::Default => "default",
ConfigSource::Env => "env",
ConfigSource::User => "usercfg",
ConfigSource::Repo => "repocfg",
ConfigSource::CommandArg => "cmdline",
}
}
}

#[derive(Clone, Debug, PartialEq)]
pub struct AnnotatedValue {
pub path: Vec<String>,
Expand Down
4 changes: 4 additions & 0 deletions cli/tests/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,10 @@ List variables set in config file, along with their values
Possible values: `true`, `false`
* `--show-origin` — Display the source of all listed config values with type (default, env, usercfg, repocfg, command line) and the source file path for usercfg and repocfg
Possible values: `true`, `false`
* `--user` — Target the user-level config
Possible values: `true`, `false`
Expand Down
66 changes: 66 additions & 0 deletions cli/tests/test_config_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,72 @@ fn test_config_list_all() {
"###);
}

#[test]
fn test_config_list_show_origin() {
let mut test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");

// Create multiple user configs.
test_env.add_config(
r#"
user-key-1 = "user-val-1"
"#,
);

test_env.add_config(
r#"
user-key-2 = "user-val-2"
"#,
);

// Env
test_env.add_env_var("env-key", "env-value");

// Repo
test_env.jj_cmd_ok(
&repo_path,
&["config", "set", "--repo", "repo-key", "repo-val"],
);

let stdout = test_env.jj_cmd_success(
&repo_path,
&[
"config",
"list",
"--config-toml",
"cmd-key='cmd-val'",
"--show-origin",
],
);

// Paths starting with `$TEST_ENV` confirm that the relative path returned by
// `Value.origin()` has been converted to an absolute path.
insta::assert_snapshot!(stdout, @r###"
usercfg $TEST_ENV/config/config0001.toml: template-aliases.format_time_range(time_range)="time_range.start() ++ \" - \" ++ time_range.end()"
usercfg $TEST_ENV/config/config0002.toml: user-key-1="user-val-1"
usercfg $TEST_ENV/config/config0003.toml: user-key-2="user-val-2"
repocfg $TEST_ENV/repo/.jj/repo/config.toml: repo-key="repo-val"
env: debug.commit-timestamp="2001-02-03T04:05:09+07:00"
env: debug.operation-timestamp="2001-02-03T04:05:09+07:00"
env: debug.randomness-seed="3"
env: operation.hostname="host.example.com"
env: operation.username="test-username"
env: user.email="[email protected]"
env: user.name="Test User"
cmdline: cmd-key="cmd-val"
"###);

// Run again with defaults shown. Rather than assert the full output which
// will change when any default config value is added or updated, check only
// one value to validate the formatting is correct.
let stdout = test_env.jj_cmd_success(
&repo_path,
&["config", "list", "--include-defaults", "--show-origin"],
);
assert!(stdout.contains(r#"default: colors.diff header="yellow""#));
}

#[test]
fn test_config_list_layer() {
let mut test_env = TestEnvironment::default();
Expand Down

0 comments on commit a7d637f

Please sign in to comment.