jj / How jj config get works

How jj config get works

jj config get NAME prints one config value, merged from every config layer, with no extra formatting, so scripts can use it.

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. Looking a name up
  8. Printing it
  9. 7. Errors and edge cases
  10. 8. Example output

In short: the config was already loaded and resolved before the command ran. config get looks the name up through every layer, from the highest precedence down, merging tables along the way. It prints strings raw and everything else as TOML. It never loads the repo and never snapshots.

The command is cmd_config_get. Its help text: "Get the value of a given config option", followed by an example contrasting it with jj config list (source). It is the Get variant of ConfigCommand, with the visible alias jj config g (source, dispatch).

1. Command-line surface

ArgumentMeaning
NAME (required)A dotted config name such as user.name or table (source). It is parsed as a TOML key, so a component with dots or spaces can be quoted (source). Shell completion offers the leaf keys of jj's config schema (source).

There are no level flags: unlike set or path, it never asks which file, only what the merged result is. --config NAME=VALUE and --config-file are layers like any other, so jj --config user.name=x config get user.name prints x. -R chooses whose repo and workspace config are included.

The global flags are on the CLI page.

2. Configuration

The command reads configuration instead of being shaped by it. Any key can be asked for, including the built-in defaults, which are the lowest layer: in an empty config, config get still returns their values (test). The output format is fixed; there is no template.

3. End-to-end flow

jj config get: call flow At startup the CLI loads system, user, repo and workspace config layers, adds layers from command-line arguments, and resolves conditional scopes into a stacked config. The command then calls get_value, which walks the layers from the top down, merging tables, and fails with "Value not found" if nothing matched. A string is printed raw; any other value is printed in TOML syntax. Both end with a newline on stdout. startup: load config layers system, user, then cwd's repo + workspace --config, -R, then resolve_config conditional scopes → StackedConfig settings().get_value(name) UserSettings → StackedConfig get_merged_item: top layer down tables merge; a scalar shadows nothing found Config error: Value not found for NAME string → raw; else TOML then a newline on stdout

4. Core architecture touchpoints

PieceRole here
ConfigSourceThe layer kinds, in precedence order from lowest to highest: defaults, system, base environment variables, user, repo, workspace, override environment variables, command-line arguments.
ConfigEnv::resolve_configTurns the raw layers into the StackedConfig the command reads, resolving conditional scopes against the home directory, repo path, workspace path, command name, hostname and environment.
UserSettings::get_valueWhat the command calls; it delegates to StackedConfig::get_value.
From<ConfigGetError>Makes a lookup failure a config error, which is printed as Config error: … with a pointer to the config docs, exit 1 (source).

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

Looking a name up

get_merged_item walks the layers from the highest precedence down. A layer with no value at that name is skipped. A non-table value found before any table is the answer at once. Tables are collected; a non-table found after a table, or a layer where a parent of the name is not a table, shadows everything below and stops the walk. The collected tables are then merged from the bottom up, each upper table's fields overriding the lower's recursively (source). If nothing was found, the result is NotFound (source).

Printing it

A string is printed as its bare value, without quotes. Integers, floats, booleans, datetimes, arrays and inline tables are printed in TOML syntax with surrounding whitespace removed (source). So a merged table comes out as one inline table. Because strings lose their quotes, false and "false" print the same; upstream's own test notes this (source).

7. Errors and edge cases

SituationResult
Name set nowhereConfig error: Value not found for <name> plus the docs pointer, exit 1 (test).
Name is a table set in several layersPrinted as one merged inline table, upper layers winning field by field (test).
Same scalar set in two filesThe higher-precedence one wins (test).
No argumentA clap usage error: NAME is required (source).
Run outside any workspaceWorks; only the default, system, user, environment and command-line layers apply (test, run from the test root).

8. Example output

From upstream's test, with two user config files: the first sets table.string, table.int, table.list and table.overridden = "foo", and the second sets table.overridden = "bar" (source):

$ jj config get table.string
some value 1
$ jj config get table.int
123
$ jj config get table.list
["list", "value"]
$ jj config get table
{ string = "some value 1", int = 123, list = ["list", "value"], overridden = "bar" }
$ jj config get table.overridden
bar
$ jj config get nonexistent
Config error: Value not found for nonexistent
For help, see https://docs.jj-vcs.dev/latest/config/ or use `jj help -k config`.