Skip to content

feat(config): read [[context]] rules from global git config - #147

Merged
timvw merged 1 commit into
mainfrom
feat/context-git-config
Aug 20, 2026
Merged

feat(config): read [[context]] rules from global git config#147
timvw merged 1 commit into
mainfrom
feat/context-git-config

Conversation

@timvw

@timvw timvw commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Closes #146. Follow-up to #145, which shipped [[context]] rules with the config file as their only source.

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"

wt.context.<name>.<key> is a git subsection, so <name> is a handle that makes the rule removable with git config --global --remove-section 'wt.context.work'.

The loader change

gitConfigFn returned map[string]string, keeping only the last value per key. That is correct for every setting that existed — all scalars — but a rule sets env once per variable with --add, so all but the last would be dropped. It now returns []gitConfigEntry in the order git listed them, and gitConfigValues collapses that to last-wins for the scalars, leaving their behaviour untouched.

Order is retained because it is what decides composition: first appearance of a name fixes a rule's position, so a later entry for an earlier rule merges into it rather than moving it to the end.

Composition, not replacement

Git config rules are evaluated first, then the config file's, under the same "later definitions win per variable" rule that already governs rules within a single source.

The alternative — the config file replacing the git config rules wholesale — would have matched how scalars behave, but it makes adding one unrelated [[context]] block to config.toml silently delete every rule in ~/.gitconfig. Appending gets the documented precedence (the file wins wherever both cover the same path) out of the mechanism that was already there, with no second rule to learn.

Scope

Global only. Not --local: a path rule scoped to a single repository is redundant, since that repository could set wt.pattern directly, and .git/config is shared by every worktree of a repo. Keeping rules out of it holds the same user-owned line that already keeps them out of a committed .wt.toml.

Not --system either, but for a duller reason than the issue implied: wt reads no system git config for any setting. Making wt.context.* obey a scope that wt.root ignores would be a worse inconsistency than the gap. Noted in the docs.

Parsing details

  • env values are NAME=VALUE, split on the first =; name and value are trimmed. No =, or an empty name, is skipped rather than guessed at — there is no reading of a bare WT_CATEGORY that is obviously right, and inventing an empty value would collapse a path segment.
  • A rule name may contain dots (wt.context.acme.api.env); the split is on the last one.
  • git preserves subsection case, so the wt.context. prefix is matched case-insensitively while the rule's name keeps the case it was written with.
  • Unknown fields and malformed entries leave no empty rule behind.

Tests

TestContextRulesFromGitConfig covers multi-value env, ordering, dotted names, subsection case, and the malformed-entry cases. TestContextRulesFromRealGitConfig runs the same thing through actual git config --add in a scratch repo, so the documented key shape is verified against git rather than against my expectation of it. TestGlobalGitConfigLoadsContextRules, TestLocalGitConfigCannotSupplyContextRules and TestConfigFileRulesComposeOverGitConfigRules cover the loader end to end.

Both the append-vs-replace choice and the case-insensitive prefix were mutation-checked — reverting either makes a test fail with a message naming the behaviour.

Existing loader tests were updated for the new signature and given an explicit git config stub: several called loadWorktreeConfig() without one, so a real wt.context.* in the developer's own ~/.gitconfig would have leaked into their assertions.

#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
@timvw
timvw enabled auto-merge (squash) August 20, 2026 11:23
@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 44.42%. Comparing base (47629d7) to head (3758ddf).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #147      +/-   ##
==========================================
+ Coverage   43.70%   44.42%   +0.72%     
==========================================
  Files          35       35              
  Lines        3647     3689      +42     
==========================================
+ Hits         1594     1639      +45     
+ Misses       2053     2050       -3     
Files with missing lines Coverage Δ
cmd/config.go 89.23% <100.00%> (+0.46%) ⬆️
cmd/context.go 93.67% <100.00%> (+4.78%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timvw
timvw merged commit 7947a5f into main Aug 20, 2026
16 checks passed
@timvw
timvw deleted the feat/context-git-config branch August 20, 2026 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

config: read [[context]] rules from global git config

1 participant