Skip to content

fix(use,delete,rename): support multiple directories per identity#39

Merged
OfirHaim1 merged 1 commit into
mainfrom
fix/multi-dir-same-identity
Jun 18, 2026
Merged

fix(use,delete,rename): support multiple directories per identity#39
OfirHaim1 merged 1 commit into
mainfrom
fix/multi-dir-same-identity

Conversation

@OfirHaim1

Copy link
Copy Markdown
Member

The bug

Binding two directories to the same identity silently broke — the second binding clobbered the first.

Each includeIf block in ~/.gitconfig was keyed by the bare identity name (sentinel # >>> gitswitch:<name>), and use/rename strip the old block before writing the new one. So:

gitswitch use private ~/work        # writes block "gitswitch:private" → ~/work
gitswitch use private ~/site        # STRIPS it, writes "gitswitch:private" → ~/site

Now config.json records both bindings, but ~/.gitconfig holds one block. The un-blocked directory falls back to the global identity with no warning — gitswitch why/doctor would even report the binding as active because config.json looks right. Binding several repos to one identity (e.g. all your personal projects → personal) is completely normal, and it failed quietly.

The fix

Key each block per (identity, directory) instead — sentinel # >>> gitswitch:<name>:<dirhash> — so multiple directories can share an identity without clobbering.

  • New syncBindingBlocks(cfg, name, path) rewrites all of an identity's blocks from config.json (the source of truth), one per bound directory, and removes the legacy bare-name block — migrating existing users forward on their next use/rename.
  • use, delete, rename, and --unbind all route through per-binding keys / the sync helper.
  • Idempotency fix in blocks: appendBlock now normalises trailing newlines (instead of only ever adding them) and stripBlock swallows the trailing blank separator. Without this, the repeated strip+re-append in syncBindingBlocks leaked a blank line into ~/.gitconfig on every run.

Before / after

# before — second bind wins, first dir reverts to global, no warning
$ git config --global --get-regexp includeif
includeif.gitdir:~/site/.path  .../private.gitconfig      # only one block

# after — one block per directory, both active
includeif.gitdir:~/work/.path  .../private.gitconfig
includeif.gitdir:~/site/.path  .../private.gitconfig

Test plan

  • go build ./..., go vet ./... — clean
  • go test ./internal/... — all pass, including new tests:
    • blocks: upsert/remove idempotency across many blocks (no blank-line leak)
    • cmd: multi-dir sync (one block per dir, legacy block removed, other identities untouched, idempotent); per-binding name distinct + stable
  • Verified live: a real config with two directories bound to one identity now produces two includeIf blocks and both directories resolve to the correct identity; re-running use is byte-stable (no leaked blank lines).

Each includeIf block in ~/.gitconfig was keyed by the bare identity
name (sentinel '# >>> gitswitch:<name>'), and every use/rename
stripped the old block before writing the new one. So binding a
SECOND directory to an identity overwrote the first directory's
block: config.json recorded both bindings, but ~/.gitconfig held
only one, and the un-blocked directory silently fell back to the
global identity. Binding two repos to one 'private' identity is a
completely normal thing to want, and it broke quietly.

Key each block per (identity, directory) instead — sentinel
'# >>> gitswitch:<name>:<dirhash>' — so multiple directories can
share an identity without clobbering. A shared syncBindingBlocks
helper rewrites all of an identity's blocks from config.json (the
source of truth) and removes the legacy bare-name block, migrating
existing users forward on their next use/rename.

Also make blocks idempotent: appendBlock now normalises trailing
newlines instead of only ever adding them, and stripBlock swallows
the trailing blank separator — without this, the repeated
strip+re-append in syncBindingBlocks leaked a blank line into
~/.gitconfig on every run.

Tests: multi-dir sync (one block per dir, legacy removed, others
untouched, idempotent); per-binding name distinct+stable; blocks
upsert/remove idempotency.
@OfirHaim1 OfirHaim1 merged commit 09355e9 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