feat(config): local-context-first config via git local config#23
feat(config): local-context-first config via git local config#23guodong-sq merged 8 commits intoblock:mainfrom
Conversation
…mantics
Add wt_read_git_config() to lib/wt-common, enabling repos to carry their
own wt configuration via standard `git config --local wt.*` keys. This
removes the hard dependency on ~/.wt/repos/<name>.conf for repos that
prefer self-contained configuration.
New four-tier config precedence (highest to lowest):
1. Git local config (wt.* keys in .git/config)
2. Environment variables
3. Context .conf file (~/.wt/repos/<name>.conf)
4. Hardcoded defaults
The function reads wt.* keys atomically: all four required keys
(wt.mainRepoRoot, wt.worktreesBase, wt.ideaFilesBase, wt.baseBranch)
must be present or the entire git config source is ignored with a
diagnostic warning. This prevents confusing partial-config states.
Key design choices:
- All-or-nothing validation prevents half-configured repos
- Works transparently from worktrees (git config --local reads from
the main repo's .git/config automatically)
- Case-insensitive key matching per git convention
- Bash 3.2 compatible (uses tr instead of ${key,,})
- Read-only: wt never writes to git config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
wt.mainRepoRoot is redundant when using git local config — the main repo root is always derivable via `git rev-parse --git-common-dir` (its parent directory), which works correctly from both the main repo and any worktree. Reduce required git config keys from 4 to 3: - wt.worktreesBase (required) - wt.ideaFilesBase (required) - wt.baseBranch (required) - wt.mainRepoRoot (optional override, auto-derived if absent) This means the minimal setup is now: git config --local wt.worktreesBase ~/src/repo-worktrees git config --local wt.ideaFilesBase ~/src/repo-idea git config --local wt.baseBranch main Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mainRepoRoot is always derivable from git — there's no reason to let users set it, and doing so just creates a point of conflict between what git says and what the config says. Now always derived via git-common-dir; the key is no longer recognized even if present in .git/config (silently ignored). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In zsh, `local var` on an already-declared variable prints its current value to stdout. Declaring `lkey` inside the while loop caused it to print on every iteration after the first. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
wt_show_help() force-reloads config from .conf to pick up context changes, but this clears the git local config values. Add wt_read_git_config after the reload so help displays the actual effective values. Only wt-help gets this treatment — context switching and completions intentionally reload from .conf only, since they serve the context system (not the local repo). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add an explicit opt-in gate so repos don't accidentally pick up local config. Users must set `git config --local wt.enabled true` before any other wt.* keys take effect. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
guodong-sq
left a comment
There was a problem hiding this comment.
do the values set in wt_read_git_config need to go through _wt_expand_path()?
| # are present. | ||
| # | ||
| # Sets variables unconditionally — git local config has highest priority. | ||
| wt_read_git_config() { |
There was a problem hiding this comment.
should this be called by wt_load_context_config?
There was a problem hiding this comment.
It seem to me that wt_load_context_config's purpose is to load config from a context's *.conf files. I think it's wrong to override that with git local configs. I see these two as the two possible sources to pick from.
Perhaps what we need is a wt_load_config --from=(git|context|ordered)?
--from gitload from local git config only--from contextload from current context only--from orderedtry loading fromgitfirst, thencontextfallback (the default behavior to achieve local-context-first)
There was a problem hiding this comment.
makes sense, also this will prevent issues in completion, as the implementation calls wt_load_config which will wipe all the variables set in here.
lib/wt-common
Outdated
There was a problem hiding this comment.
This is no longer true with the added wt_read_git_config
There was a problem hiding this comment.
4c6c244 addresses this: this function is renamed to wt_read_context_config (it only reads from context configs), and the new wt_read_config orchestrator documents the full precedence behavior.
Rename wt_read_config → wt_read_context_config for the context-only reader, then build a new wt_read_config orchestrator that supports --mode=(ordered|git|context) and --force flags. - ordered (default): git config first, context fills gaps - git: git-local config only - context: context .conf only - --force: clears all WT_* vars before loading wt_read_git_config now returns non-zero on failure (not in repo, not enabled, incomplete config). Deleted the deprecated wt_load_context_config shim from lib/wt-context. Updated all call sites (source-time load, wt-help, bash/zsh completions) and tests accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Claude Code <noreply@anthropic.com> Ai-assisted: true
Co-authored-by: Claude Code <noreply@anthropic.com> Ai-assisted: true
Summary
Adds support for reading wt configuration directly from a repo's
.git/config, enabling local-context-first semantics: a repo can carry its own wt configuration without requiring~/.wt/repos/<name>.conforwt context addsetup.This means a user can configure a repo once:
...and all
wtcommands Just Work from inside that repo or any of its worktrees, with no global context setup needed.How it works
A new
wt_read_git_config()function inlib/wt-commonis called at source-time, before the existingwt_read_config(). It checks for thewt.enabled=truegate, readswt.*keys from the repo's local git config viagit config --local --get-regexp '^wt\.', validates them, and sets the correspondingWT_*shell variables.Config precedence (highest to lowest)
wt.*in.git/config)wt.enabled=trueand all required keyswt-commonsourced.conffile~/.wt/repos/<name>.confhas the valueGit config keys
wt.enabledtrue)wt.worktreesBaseWT_WORKTREES_BASEwt.ideaFilesBaseWT_IDEA_FILES_BASEwt.baseBranchWT_BASE_BRANCHwt.activeWorktreeWT_ACTIVE_WORKTREEwt.metadataPatternsWT_METADATA_PATTERNSWT_MAIN_REPO_ROOTis always auto-derived fromgit rev-parse --git-common-dirand is not a configurable key. This eliminates a potential source of conflict between what git says the repo root is and what the user configured.All-or-nothing validation
All three required keys must be present and non-empty. If any is missing, no git config values are applied and a diagnostic warning is printed to stderr naming the missing keys.
Key design choices
wt.enabled=truemust be set — repos don't accidentally pick up local configgit config --localin a worktree reads from the main repo's.git/configautomatically, so config set once in the main repo is visible from all worktreesgit-common-dirto avoid stale/conflicting valuesgit configcommandstrfor case-insensitive key matching (macOS ships bash 3.2)wt.enabled=trueare silently skipped; the context system and env vars continue to work exactly as beforewt helpcallswt_read_git_configafter a force-reload — context switching and completions intentionally reload from.confonlyWhat's NOT changing
wt configsubcommand (users usegit configdirectly)wt context add/install.shsetup flows.conffile format or context systemTest plan
wt-remove --mergedis unrelated)git config --local wt.enabled true+ set 3 required keys,wt helpshows valuescdinto a worktree of that repo — same resultwt.enabled false, runwt help— falls back to.conf/defaultswt.baseBranch(incomplete), runwt help— warning on stderrwt helpshows.conf/default values, no warning🤖 Generated with Claude Code
Changelog
refactor: unified config API with
wt_read_configorchestratorRefactored the config-reading API to provide a single entry point with explicit mode control:
wt_read_config→wt_read_context_config(public, reads context.conffiles)wt_read_configorchestrator with--mode=(ordered|git|context)and--forceflags:--mode=ordered(default): git config first, context fills gaps; returns non-zero only if both fail--mode=git: git-local config only--mode=context: context.confonly--force: clears allWT_*vars before loadingwt_read_git_confignow returns non-zero on failure (not in repo, not enabled, incomplete config)wt_load_context_config()shim fromlib/wt-contextwt-help, bash/zsh completionswt_read_config --force