fix(config): show reflects the #3461 object-store project-config fallback#3477
Conversation
wt config show read project_config_path() and load_project_config() independently. In the #3461 state — a bare repo whose default branch is checked out in no worktree — the config resolves from the object store, but project_config_path() returns the (missing) on-disk path, so config show reported the config as absent while its hooks run: - JSON emitted "exists": false alongside a populated "config", with "path" pointing at a file that isn't the source. - Text keyed off project_config_path() alone and printed "Not found", never showing the config that is actually loaded. Both paths now consult the object-store fallback when no on-disk file resolves: JSON surfaces the revision spec as "path" and keys "exists" off the loaded config; text renders the source labeled "(from object store)" and dumps the config. Adds a regression test asserting config show output in the config-present parked case (previously uncovered). Closes #3476
worktrunk-bot
left a comment
There was a problem hiding this comment.
Self-review. The correctness change is sound — both render paths now consult the object-store fallback, the "No project config" vs Not found distinction survives the restructure, and exists = project_config.is_some() stays consistent with the on-disk cases (empty file → default Some, missing → None). One coverage nit worth pre-empting the codecov/patch gate: the .or_else(|| on_disk.clone()) fallback in the JSON path is unreachable and will show as an uncovered patch line — inline suggestion below.
| _ if config.is_some() => repo | ||
| .default_branch_project_config_content() | ||
| .map(|(_, spec)| spec) | ||
| .or_else(|| on_disk.clone()), |
There was a problem hiding this comment.
This arm is entered only when config.is_some() and no on-disk file resolved — which in this codebase means the config was loaded from the object store (ProjectConfig::load returns Some only via on-disk or default_branch_project_config_content). So within a single invocation default_branch_project_config_content() here is guaranteed to return Some, and .or_else(|| on_disk.clone()) never runs. It's dead defensively, and since the new test (like every reachable state) always takes the Some path, the closure body will show as an uncovered patch line on codecov/patch. Dropping it also removes the only way path/exists could disagree, which is the invariant the PR is establishing.
| _ if config.is_some() => repo | |
| .default_branch_project_config_content() | |
| .map(|(_, spec)| spec) | |
| .or_else(|| on_disk.clone()), | |
| _ if config.is_some() => repo | |
| .default_branch_project_config_content() | |
| .map(|(_, spec)| spec), |
There was a problem hiding this comment.
Applied in 74f851f. Dropped .or_else(|| on_disk.clone()) — as noted, the arm is entered only when config.is_some() with no on-disk file, i.e. config came from the object store, so default_branch_project_config_content() is guaranteed Some and the fallback was dead. Removing it also removes the only way path/exists could disagree, which is the invariant this PR establishes.
config_show module still green (134 passed).
The lone `)?;` of the multi-line `No project config` writeln landed on its own line, so the infallible error branch of `?` (writing to a String never fails) showed as an uncovered patch line. Collapse the macro so the `?` shares a line with the executed write.
The .or_else(|| on_disk.clone()) arm is dead: it runs only when config.is_some() and no on-disk file resolved, which means config came from the object store, so default_branch_project_config_content() is guaranteed Some. Dropping it removes an uncovered codecov/patch line and the only way path/exists could disagree — the invariant this PR establishes.
Problem
wt config showdidn't reflect the object-store project-config fallback that #3461 introduced. In the exact state that fix targets — a bare repo whose default branch is checked out in no worktree —config showreported no project config whilewt switchresolved and ran the default branch's committed hooks. The command meant to be the user's window into effective config contradicted what runs.Root cause:
config showreadproject_config_path()andload_project_config()independently.load_project_config()already falls back to the committed default-branch config viagit show <default>:.config/wt.toml, butproject_config_path()still returns the (missing) on-disk path with no knowledge of that fallback."exists": falsealongside a populated"config": { … }, with"path"pointing at a file that isn't the real source.project_config_path()alone: it printedNot foundand returned without ever showing the config that is actually loaded.This is a diagnostics inconsistency only — hooks still run correctly. But #3461's motivating scenario is an agent-driven workflow that parks the primary off-branch, so it's exactly the users who'd inspect
config showto understand hook behavior, and it told them the opposite of reality.Solution
Both render paths now consult the object-store fallback when no on-disk file resolves:
pathsurfaces the revision spec (main:.config/wt.toml) andexistsis keyed off the loaded config rather thanpath.exists(), sopath/exists/configagree.ProjectConfig::loaddoes — on-disk file when present, else the committed default-branch config from the object store — labeled@ main:.config/wt.toml (from object store), and dumps the config through the existing deprecation/validation rendering.Non-fallback cases are unchanged:
existsstill equals "config resolved" for on-disk configs (an empty file loads to a defaultSome, a parse error already errors the command), so no existing snapshot or JSON assertion shifts.Testing
test_bare_repo_config_show_reflects_object_store_fallbacksets up the config-present parked state (bare repo,maincarries.config/wt.toml, primary parked onfeature-x, invoked from a linked worktree whose branchgit rm'd the config) and asserts both JSON (path/exists/configagree) and text ((from object store)label, config dumped, neverNot found). This case was previously uncovered.{"config":{"post-start":"echo hi"},"exists":false,"path":".../other/.config/wt.toml"}.config_show(162) andbare_repositorymodules pass — no snapshot churn.cargo clippy --bins --libclean.Maintainer call on presentation
The original issue flagged the exact source label as a judgment call. This PR proposes
@ main:.config/wt.toml (from object store)for text and the raw revision spec for JSONpath; both are easy to adjust if you'd prefer different wording. The correctness change — showing the config that actually loads — is the substance; the label is cosmetic.Closes #3476 — automated triage