feat(config): set environment variables per directory with [[context]] rules - #145
Merged
Conversation
…] rules
Path patterns can already read {.env.X}, but supplying the value meant
exporting it per command or wiring up direnv. Add includeIf-style rules
that bind variables to a directory tree instead:
[[context]]
when_path = "~/dev/repos/work"
env = { WT_CATEGORY = "work" }
Matching is a path prefix (not a glob), evaluated after resolving
symlinks, case-insensitively on macOS and Windows. Every matching rule
applies, later rules winning per variable, mirroring how git config
treats repeated keys.
Which path is matched differs by command, and neither is the rendered
destination, so nothing is circular:
- repo-scoped commands match the repository's main checkout, so all
linked worktrees of a repo resolve to the same rule even when they
live in a different tree;
- wt clone matches the working directory, the only signal available
before a repo exists.
Rules are rendering-only: they are merged underneath os.Environ() into
the template's env map and never exported, so an actual export still
wins (including an empty one) and hooks see an unchanged environment.
They are read from the user config file only. A repo's .wt.toml is
committed by someone else and must not be able to redirect where
worktrees land. Reading them from git config needs multi-value support
in the wt.* parser and is left as a follow-up.
Closes #138
timvw
enabled auto-merge (squash)
August 20, 2026 10:03
This was referenced Aug 20, 2026
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #145 +/- ##
==========================================
+ Coverage 43.06% 43.78% +0.72%
==========================================
Files 34 35 +1
Lines 3597 3647 +50
==========================================
+ Hits 1549 1597 +48
- Misses 2048 2050 +2
🚀 New features to boost your workflow:
|
This was referenced Aug 20, 2026
timvw
added a commit
that referenced
this pull request
Aug 20, 2026
#145 shipped context rules with the config file as their only source, which left them as the one setting that could not live in git config — awkward for a feature modelled on git's own includeIf, which is git config. git config --global wt.context.work.whenpath "~/dev/repos/work" git config --global --add wt.context.work.env "WT_CATEGORY=work" git config --global --add wt.context.work.env "WT_ORG=acme" The loader kept one value per key, which is right for every scalar setting but loses all but the last variable of a rule. It now returns entries in the order git listed them, and gitConfigValues collapses them to last-wins for the scalars, so their behaviour is unchanged. Order is kept because it is what decides how rules compose. The two sources compose rather than replace: git config rules are evaluated first, then the config file's, under the same later-definitions-win rule that already governs rules within one source. So the config file wins wherever both cover the same path — the documented precedence — while a git config rule for an unrelated tree keeps working instead of vanishing the moment a [[context]] block is added to the file. Global scope only. A rule scoped to one repository is redundant, since that repository could set wt.pattern directly, and keeping rules out of --local holds the same user-owned line that already keeps them out of a committed .wt.toml. The system scope is skipped for a duller reason: wt reads no system git config for any setting, and making this the exception would be worse than the gap. Closes #146
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #138.
Path patterns can already read
{.env.X}, but supplying the value meant exporting it per command or wiring up direnv. This adds includeIf-style rules that bind variables to a directory tree instead:wt clone acme/api # ~/dev/repos/work/acme/api/main, no export neededSemantics
Matching is a path prefix, not a glob —
filepath.Matchhas no**, and a glob library would be a new dependency for a module with 8 direct ones. Paths are resolved (~expanded, absolute, symlinks followed) before comparison, and compared case-insensitively on macOS and Windows.~/dev/workdoes not match~/dev/workshop.Composition: every matching rule applies, later rules winning per variable — the same way git config treats repeated keys. A broad rule can set a default that a narrower one overrides.
Which path is matched differs by command, and in neither case is it the rendered destination, so nothing is circular:
wt create,wt co,wt pr,wt mr,wt migratewt cloneMatching the main checkout rather than the cwd means every linked worktree of a repo resolves to the same rule, even when
worktree_rootputs them in a completely different tree. Forwt cloneno repo exists yet, so the cwd is the only signal available.Rules are rendering-only. They are merged underneath
os.Environ()into the template's env map and never exported. An actual export still wins — including an empty one, soWT_CATEGORY= wt clonestill collapses the segment — and hooks see an unchanged environment.Source: user config file only. A repo's
.wt.tomlis committed by someone else; letting it inject variables would let a cloned repository redirect where its own worktrees land. Reading rules from git config needs multi-value support in thewt.*parser (cmd/config.go:153flattens into amap[string]string, so a rule with twoenventries would collide) — left as a follow-up on #138.Notes
docs/configuration.mdnow leads with[[context]]; the direnv recipe added in docs: add direnv recipe for per-directory categories #143 is kept as a "without wt configuration" subsection.wt config initemits a commented[[context]]example.cmd/context_test.gocover matching (including theworkshop/worksibling case), per-variable composition, export precedence, symlink resolution, nonexistent paths,~expansion, the config-file/.wt.tomlsplit, and both call sites.