Skip to content

Commit

Permalink
cli: Add --show-sources option to config list
Browse files Browse the repository at this point in the history
Add a new `--show-sources` 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 12, 2024
1 parent 211ee7d commit 8e1b7ed
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 @@ -23,6 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `jj git fetch` now accepts `-b` as a shorthand for `--branch`, making it more
consistent with other commands that accept a branch

* `jj config list` gained a `--show-sources` flag to display the origin 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_sources: 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_sources {
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}:\t");
};

// `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}:{}\t", 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 fmt::Display for ConfigSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConfigSource::Default => write!(f, "default"),
ConfigSource::Env => write!(f, "env"),
ConfigSource::User => write!(f, "usercfg"),
ConfigSource::Repo => write!(f, "repocfg"),
ConfigSource::CommandArg => write!(f, "command line"),
}
}
}

#[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 @@ -480,6 +480,10 @@ List variables set in config file, along with their values
Possible values: `true`, `false`
* `--show-sources` — 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_sources() {
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-sources",
],
);

// 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"
command line: 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-sources"],
);
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 8e1b7ed

Please sign in to comment.