jj / How jj config list works

How jj config list works

jj config list prints config variables one per line as name = value. Each line can be tagged with the layer and file it came from.

On this page
  1. 1. Command-line surface
  2. 2. Configuration
  3. 3. End-to-end flow
  4. 4. Core architecture touchpoints
  5. 5. What it reads, and snapshotting
  6. 6. Algorithm and invariants
  7. Collecting values
  8. Marking overridden values
  9. Filtering
  10. 7. Errors and edge cases
  11. 8. Example output

In short: the config is already loaded when the command runs. config list walks every layer from the top down, flattening tables into dotted names and marking each value that a higher layer shadows. It then filters by level, defaults and overridden values, and renders each survivor through a template. It never loads the repo and never snapshots.

The command is cmd_config_list. Its help text: "List variables set in config files, along with their values" (source). It is the List variant of ConfigCommand, with the visible alias jj config l (source, dispatch).

1. Command-line surface

ArgumentMeaning
NAME (optional)A dotted name to list. A table name lists everything under it. With no name, the whole config is listed (source, root).
--user, --repo, --workspaceKeep only values from that layer. At most one can be given. The shared level group is required for other config commands; list makes it optional (source, group, mapping).
--include-defaultsAlso list jj's built-in defaults, which are hidden otherwise. Cannot be combined with a level flag (source).
--include-overriddenAlso list values that a higher layer shadows (source).
-T, --templateRender each value with this template instead of templates.config_list (source).

--config NAME=VALUE and --config-file add a command-line layer, which shows up in the list as source cli. -R chooses whose repo and workspace config are included.

The global flags are on the CLI page.

2. Configuration

KeyEffect
templates.config_listThe default template, used when -T is not given (source). Its default is builtin_config_list (source).
builtin_config_listPrints format_config_item(self) and a newline, labelled overridden when shadowed (source). format_config_item prints name = value, and comments the line out with # when it is overridden (source).
builtin_config_list_detailedThe same, followed by # <source> <path> (source).
colors."config_list …"Names are green, values yellow, sources blue, paths magenta, and overridden lines bright black (source).

The template keywords are name, value, source, path and overridden (source). value is rendered in TOML syntax with surrounding whitespace trimmed (source). self is the whole AnnotatedValue, so json(self) works too (test).

3. End-to-end flow

jj config list: call flow At startup the CLI loads and resolves the config layers. The command parses the template from -T or templates.config_list, then calls resolved_config_values, which walks the layers from the top down, flattening tables into dotted names and marking shadowed values as overridden. The list is then filtered by level flag or by default layer, and by overridden. If anything is left, each value is rendered through the template to stdout, via the pager. Otherwise a warning goes to stderr. startup: load and resolve layers default … user, repo, workspace … cli parse template -T, else templates.config_list resolved_config_values(NAME) flatten every layer; mark overridden filter level or defaults, then overridden nothing left warning on stderr, exit 0 render each value pager, then stdout

4. Core architecture touchpoints

PieceRole here
ConfigSourceThe layer kinds, from lowest to highest precedence. They print as default, system, env, user, repo, workspace and cli; both environment layers print as env (source).
AnnotatedValueOne listed value: dotted name, value, source, file path if any, and whether it is overridden. It is serializable, which is what json(self) prints.
resolved_config_valuesCollects the values under a name from every layer, with override marks. It does not hide disabled aliases values (source).
CommandHelper::parse_templateParses the template with the user's template aliases, so -T can name builtin_config_list_detailed. Parse warnings go to stderr.

5. What it reads, and snapshotting

It never snapshots and never loads the repo. Everything it reads was loaded before dispatch: the system and user config files, then the repo and workspace config of the workspace containing the current directory (source), replaced by those of -R's workspace if given (source). Repo config is read only if its file exists (source). It writes nothing.

6. Algorithm and invariants

Collecting values

resolved_config_values walks the layers from the highest precedence down. In each layer it looks up the name. A layer without it is skipped. If a parent of the name is not a table, the name is recorded as shadowing everything below (source). Tables are expanded depth first into their leaves, so test-table.y.foo is listed as its own line. Inline tables are expanded too, because their fields can be overridden separately (source, test). Arrays stay one value (test).

Marking overridden values

The walk keeps the set of names already seen in upper layers. A leaf is overridden if that set holds its own name or a name beneath it, or if a parent was already shadowed. A table is shadowed if an upper layer set its name to a plain value (source). At the end, the list is reversed, so lines come out from the lowest layer to the highest (source).

Filtering

With a level flag, only values from that layer are kept. Without one, defaults are dropped unless --include-defaults. Then overridden values are dropped unless --include-overridden (source). The override marks are computed before filtering, as the comment there explains (source). So --user hides a user value that the repo config overrides, unless --include-overridden is also given (test).

7. Errors and edge cases

SituationResult
Name set nowhereWarning: No matching config key for: <name> on stderr, nothing on stdout, exit 0 (test).
No name, nothing left after filteringWarning: No config to list., exit 0 (test).
--include-defaults with --user, --repo or --workspaceA clap usage error, since the flags conflict (source).
Template fails to parseThe command fails before reading any values (source).
Same key in several layersOnly the highest one is printed. With --include-overridden, lower ones are printed first, commented out (test).

8. Example output

From upstream's test: the user config sets test-key and test-layered-key, the repo config sets test-layered-key again, and the test environment sets a few values through environment variables (source):

$ jj config list -Tbuiltin_config_list_detailed --config test-cli-key=test-cli-val
test-key = "test-val" # user $TEST_ENV/config/config.toml
test-layered-key = "test-layered-val" # repo $TEST_ENV/home/.config/jj/repos/0757f5ec8418b4f0983d/config.toml
user.name = "Test User" # env
user.email = "test.user@example.com" # env
debug.commit-timestamp = "2001-02-03T04:05:11+07:00" # env
debug.randomness-seed = 5 # env
debug.operation-timestamp = "2001-02-03T04:05:11+07:00" # env
operation.hostname = "host.example.com" # env
operation.username = "test-username" # env
test-cli-key = "test-cli-val" # cli

The user's test-layered-key is missing because the repo value overrides it. Another test shows the overridden lines (source):

$ jj config list merge-tools.vimdiff.program --include-overridden --config 'merge-tools.vimdiff.program="command-arg"'
# merge-tools.vimdiff.program = "user"
# merge-tools.vimdiff.program = "repo"
merge-tools.vimdiff.program = "command-arg"