gw
Switch between multiple git/GitHub accounts, per repository, and see which account is active right in your shell prompt.
Install
pipx install gwitch
Published on PyPI as gwitch; installs the gw command. Or, from a
checkout of this repository:
pipx install .
Quick start
gw account add personal
gw account add work
cd ~/code/some-repo
gw use personal
gw status
Shell prompt
gw init zsh # or bash / fish
prints a small shell function, plus a one-line instruction for wiring it
into your prompt — paste both into your shell config yourself. gw never
edits your shell rc files automatically.
gw init zsh --install # or bash
--install does the wiring for you, for zsh/bash only (not fish
yet): it writes a gw-prompt script to ~/.local/bin/gw-prompt and a
gw-check script to ~/.local/bin/gw-check (the cd-triggered account-check
described below), and appends two marked blocks to your rc file — one per
script. It's idempotent — safe to re-run. Note for macOS bash users:
~/.bashrc isn't read by login shells (Terminal and iTerm start bash as a
login shell), so unless you've already wired ~/.bashrc to be sourced from
~/.bash_profile, you'll need to add that yourself for the installed
integration to take effect.
What gw use <account> changes
user.name/user.email— local to the current repo only (git config --local)core.sshCommand— forces the SSH key registered for that accountcredential.https://<host>.helper— scoped to this repo and this host, so HTTPS pushes/pulls use this account's token instead of whatever's cached in the OS keychain orgh's globally active account (see below)gw.account— a marker read bygw statusand the shell prompt functiongw-remote-map.<remote URL>.account— records this repo's remote in global git config, for the cd-triggered mismatch check below
gw use also records this repo's remote-to-account mapping in global
git config (for the cd-triggered mismatch check, see below) — everything
else stays local-only. None of this touches ~/.ssh/config or ssh-agent.
HTTPS credential support
If a repo's remote uses HTTPS instead of SSH, gw use <account> sets a
repo-scoped git credential helper (credential.https://<host>.helper,
cleared and reset each time you switch, so it's never a blanket override —
only this repo, only this host) that points back at gw credential-helper.
When git needs to authenticate an HTTPS push/pull, it invokes that
internal command, which looks up the account currently active in this
repo and fetches a token for it via gh auth token — gw never stores a
credential itself, it just asks gh for the one it already has. This
gives an HTTPS remote the same per-repo isolation SSH already had, without
a stale token cached by the OS keychain or gh's globally active account
silently winning instead.
Requires the gh CLI to be installed and already logged in to the account
in question; if it isn't, gw credential-helper produces no output and
git falls back to its normal (interactive) credential prompt for that
repo, rather than silently reusing a wrong cached credential.
Remote-account mismatch check
With gw init <shell> --install, a gw-check script is wired to run every
time your shell cds into a different directory. If the repo you land in
has an origin remote that gw use <account> has previously recorded, and
the account currently active in that repo doesn't match, it prints a
warning to stderr telling you which account to switch to. This cd-triggered
check never blocks or changes anything on its own — it's purely
informational.
For a blocking version of the same check — useful since the cd-triggered
check above only fires in an interactive shell and is invisible to AI
coding agents, CI, or any other non-interactive git caller — run gw install-hooks once in a repo. It installs pre-commit/pre-push git
hooks that run the same check and, on a real mismatch, reject the commit
or push outright (exit 1). The block message always names git's own
--no-verify flag as the way to bypass it deliberately. If the repo
already has a pre-commit/pre-push hook, gw install-hooks preserves it
(renamed to <hookname>.gw-original) and chains to it after its own check
passes, rather than overwriting it.
Known limitations
gh (GitHub CLI) authentication is global, not per-repo. GitHub CLI's
authenticated account is global to your machine (gh keeps one active user
per host), not per-repo — that's inherent to how gh itself works. gw use <account> will call gh auth switch for you, which changes gh's active
account machine-wide, even for other repos/terminal tabs, until you gw use
a different account somewhere else. Your git identity and SSH key stay
correctly isolated per-repo regardless.
gw status shows both this repo's git identity and gh's currently active
account as separate lines, so if they've drifted apart it's visible rather
than silent.
Linked git worktrees share git identity. git config --local in a
linked worktree (created via git worktree add) resolves to the same
shared config file as the main checkout — git only isolates local config
per-worktree if the repo opts into extensions.worktreeConfig, which gw
does not assume. That means gw use <account> run inside a linked worktree
changes the identity for the main checkout and every sibling worktree of
that repo too, not just the worktree you ran it in. gw use detects this
and prints a warning when you run it inside a linked worktree, but it does
not block the switch or set up per-worktree config automatically.
The remote-account check is last-write-wins, URL-exact, and cd-triggered
only for the shell-integration version. The gw-remote-map registry has
no per-clone tracking: running gw use <account> anywhere for a given
remote overwrites what "the right account" means for every other clone of
that remote — this is intentional, not a bug. It also matches on the exact
remote URL string, so an SSH clone and an HTTPS clone of the same repo are
treated as two unrelated remotes. The cd-triggered gw-check script only
runs on cd, so a shell already sitting in a mismatched repo when it
starts won't warn until you cd away and back — gw install-hooks's
blocking hooks don't have this gap, since they fire on the git action
itself.
Commands
gw account add <name>— register an account (prompts for git name/email, GitHub username; can generate a new SSH key for you)gw account list— list registered accountsgw account remove <name>— remove a registered accountgw account edit <name>— interactively update an existing account's git name/email, GitHub username/host, or SSH key path (current values are the defaults; press Enter to keep any of them)gw use <name>— activate an account for the current repogw status/gw whoami— show the active account for the current repogw init <shell>— print the shell prompt snippet forzsh,bash, orfish; add--install(zsh/bashonly) to write and wire it up automaticallygw install-hooks— install blockingpre-commit/pre-pushgit hooks in the current repo that reject a commit/push when the wrong account is active (bypassable withgit's own--no-verify)gw uninstall-hooks— cleanly reversegw install-hooks: removes the gw-managed hooks and restores any hook they chained to. Leaves the sharedgw-checkscript (used bygw init --install'scd-triggered warning) alone — that script is shared machine-wide state, not scoped to this repogw credential-helper <get|store|erase>— internal, invoked by git itself as this repo's HTTPS credential helper; not meant to be run directlygw doctor— check that everything is set up correctly:ghCLI installed, each account's SSH key present,ghlogin status, shell prompt/remote-check integration, repo hooks, and whether a newergwitchis available on PyPI; prints a remedy for anything that needs fixing
Contributing
See CONTRIBUTING.md for dev setup and how to run the tests. Please follow the Code of Conduct. For security issues, see SECURITY.md instead of opening a public issue.
License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gwitch-0.3.0.tar.gz.
File metadata
- Download URL: gwitch-0.3.0.tar.gz
- Upload date:
- Size: 60.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9a66871f9297aba39b6647a4ed51bdd5326deb93799af76726874f36d83876dd
|
|
| MD5 |
d0271883aaf233cc752ae19a0589d55b
|
|
| BLAKE2b-256 |
a305a0052402f60ca7264c0260812b01941111813e247856201eff662f146a48
|
Provenance
The following attestation bundles were made for gwitch-0.3.0.tar.gz:
Publisher:
publish.yml on isamrish/gwitch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gwitch-0.3.0.tar.gz -
Subject digest:
9a66871f9297aba39b6647a4ed51bdd5326deb93799af76726874f36d83876dd - Sigstore transparency entry: 2764312914
- Sigstore integration time:
-
Permalink:
isamrish/gwitch@cadf2ca43dfc64f52b2cee18f7519a566a8ba685 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/isamrish
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cadf2ca43dfc64f52b2cee18f7519a566a8ba685 -
Trigger Event:
release
-
Statement type:
File details
Details for the file gwitch-0.3.0-py3-none-any.whl.
File metadata
- Download URL: gwitch-0.3.0-py3-none-any.whl
- Upload date:
- Size: 23.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c1714692c72bec8f9d057ef3664d10f91cc4d147463f26fd9dd290b7f6a460c
|
|
| MD5 |
bbde20ade5cf867d4c2720cc54737d24
|
|
| BLAKE2b-256 |
d683db30bd91cb7e119b9c87133fd4ca460dab0f0fd07b9c75aeb8676c597539
|
Provenance
The following attestation bundles were made for gwitch-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on isamrish/gwitch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gwitch-0.3.0-py3-none-any.whl -
Subject digest:
3c1714692c72bec8f9d057ef3664d10f91cc4d147463f26fd9dd290b7f6a460c - Sigstore transparency entry: 2764312931
- Sigstore integration time:
-
Permalink:
isamrish/gwitch@cadf2ca43dfc64f52b2cee18f7519a566a8ba685 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/isamrish
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cadf2ca43dfc64f52b2cee18f7519a566a8ba685 -
Trigger Event:
release
-
Statement type: