Skip to content

feat(doctor,why,use): detect local .git/config shadowing a binding#37

Merged
OfirHaim1 merged 1 commit into
mainfrom
feat/detect-local-config-shadowing
Jun 18, 2026
Merged

feat(doctor,why,use): detect local .git/config shadowing a binding#37
OfirHaim1 merged 1 commit into
mainfrom
feat/detect-local-config-shadowing

Conversation

@OfirHaim1

Copy link
Copy Markdown
Member

What

A repo's local .git/config wins over the includeIf gitswitch writes (git precedence: local > global). So when user.name / user.email / user.signingkey / core.sshCommand are set in a repo's .git/config, they silently defeat a gitswitch binding — commits and pushes go out under the local identity, while gitswitch why happily reports the binding is active (the effective email matches only by coincidence).

This is the #1 adoption trap: anyone who set per-repo identities the old way (git config user.email …) before adopting gitswitch hits it, and the tool gives them false confidence instead of a warning.

gitswitch keeps control at the identity layer (still includeIf, which is its whole value — one binding covers a tree, including repos cloned later) but now makes the conflict visible and resolvable.

Changes

  • git: LocalOverrides(dir) detects identity keys set in a repo's local .git/config; UnsetLocal(dir, key) clears them.
  • why: replaces the misleading (matches binding ✓) with ⚠ binding shadowed by local .git/config, lists the offending keys, and shows the take-over command.
  • doctor: folds the same detection into the final summary, so it can no longer print ✓ all layers agree while a binding is silently shadowed.
  • use: after binding, detects shadowing local config and prompts (y/N) to remove it so the binding actually takes effect. On decline or no-TTY it keeps the keys and prints the exact manual git config --local --unset commands; the closing message no longer promises automatic switching when overrides remain.
  • tests: cover LocalOverrides + UnsetLocal (detect, no-op on clean repo, clear, idempotent re-unset).

Before / after — why in a shadowed repo

# before  (false confidence)
✓ active identity: private
  user.email:        ofir474@gmail.com   (matches binding ✓)

# after  (the truth)
⚠ binding shadowed by local .git/config
  user.email:        ofir474@gmail.com   (from local .git/config, not the binding)
  ...
⚠ this repo's local .git/config overrides the binding
  set locally, so they win over the includeIf above:
      user.name, user.email, core.sshCommand
  gitswitch isn't really in control here. Take over with:
  gitswitch use private <dir>   (it will offer to remove the local overrides)

Design note (non-goal)

gitswitch does not take over auth/credential management (gh account, HTTPS credential helper) — that's machine-wide, gh/keychain-owned state. This PR is detection + opt-in cleanup of git config shadowing only, consistent with why/doctor's "tell me when my identity layers disagree" mission.

Test plan

  • go build ./..., go vet ./... — clean
  • go test ./internal/... — git package tests pass (incl. new ones)
  • Ran why / doctor / use against a real shadowed repo: shadowing is detected; use prompts and (on confirm) clears it; a non-shadowed repo shows no warning (no false positives).

A repo's local .git/config wins over the includeIf gitswitch writes
(git precedence: local > global). When user.name/email/signingkey or
core.sshCommand are set locally, they silently defeat a gitswitch
binding — commits/pushes go out under the local identity while
`why` happily reports the binding is active (the effective email
matches only by coincidence). This is the #1 adoption trap: people
who set per-repo identities the old way before adopting gitswitch.

gitswitch now keeps control of the identity layer (still via
includeIf) but makes the conflict visible and resolvable:

- git: add LocalOverrides(dir) / UnsetLocal(dir, key) to detect and
  clear identity keys set in a repo's local .git/config.
- why: replace the false "matches binding ✓" with a clear
  "⚠ binding shadowed by local .git/config" warning that lists the
  offending keys and the take-over command.
- doctor: fold the same detection into the summary so it can no
  longer print "✓ all layers agree" while a binding is shadowed.
- use: after binding, detect shadowing local config and prompt
  (y/N) to remove it so the binding actually takes effect; on
  decline or no-TTY it keeps them and prints the manual unset
  commands, and the closing message no longer promises automatic
  switching when overrides remain.
- tests: cover LocalOverrides + UnsetLocal.
@OfirHaim1 OfirHaim1 merged commit 9c54811 into main Jun 18, 2026
1 check passed
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.

1 participant