Identity follows the repository, not the directory.
gitwho resolves your git identity and credentials from a repository's remote URL rather than where it sits on disk — so a worktree, a relocated clone or a temp directory still commits as the right person.
Quick start
Installing it is one line. The part that takes thought is the file it hands you afterwards — so the steps below go as far as a machine that doctor calls healthy, not just as far as a binary on PATH.
Install the binary
brew install DanielCarmingham/tap/gitwhoor, with no package manager
curl -LsSf https://github.com/DanielCarmingham/gitwho/releases/latest/download/gitwho-installer.sh | shor, with a rust toolchain
cargo install gitwhomacOS and Linux, on x86-64 and arm64. Every route puts a
gitwhobinary in~/.cargo/bin, so that directory needs to be on yourPATH. You also need git ≥ 2.36 —includeIf "hasconfig:remote.*.url:"landed there.Pick the location once and leave it alone:
initbakes the binary's absolute path into everything it generates, so moving it later breaks that config silently.Run
init, and let it stopOne command, run twice, with your accounts written in between. The first is a dry run: it lists every step it would take and writes nothing.
gitwho init gitwho init --writeThe second creates the store, scaffolds an empty
accounts.toml, and halts:created ~/.config/gitwho/identity.key (owner-only) created ~/.config/gitwho/accounts.toml Now the part only you can do: 1. edit ~/.config/gitwho/accounts.toml replace the example accounts with yours 2. gitwho secret set <Account> <VAR> once per token 3. gitwho init --write re-run to finishIt stops on purpose. Generating rules from a template of placeholders would give you a machine that looks configured and resolves every repository to an account that does not exist — working-but-wrong, which is the failure this tool exists to prevent. Nothing has gone wrong; step 3 is your half of it.
Write your accounts
Open
~/.config/gitwho/accounts.toml. It names environment variables and never holds their values, which is what lets it live in a dotfiles repo. A complete two-account config:[defaults] account = "Personal" gitName = "Your Name" [[accounts]] name = "Personal" provider = "github" email = "you@example.com" gitCredential = "GH_TOKEN" sshKey = "~/.ssh/id_ed25519_personal" match = ["github.com/your-personal-username/**"] env = ["GH_TOKEN"] paths = ["~/src/personal/"] [[accounts]] name = "Work" provider = "github" email = "you@acme.example.com" gitName = "Your Name (Acme)" gitCredential = "GH_TOKEN" match = [ "github.com/acme-corp/**", "github.com/acme-labs/**", ] env = ["GH_TOKEN"] paths = ["~/src/work/"]matchruns againsthost/path, which is why two GitHub accounts on one host can be told apart — by organisation, with no directory layout implied.Not your shape? Recipes has three more complete configs, andthe annotated template explains every field.
Store the tokens, then finish
gitwho secret set Personal GH_TOKEN gitwho secret set Work GH_TOKEN gitwho init --writesecret setreads the value from stdin, so it never entersargv, becausepsis public. Stored values are printed back only as fingerprints.If
ghis already logged in as that account you can skip storing a token altogether —env = [{ var = "GH_TOKEN", from = "gh", user = "..." }]reads it fromghon demand, so there is no copy to keep in sync.Installing gitwho covers when that is worth doing.initis idempotent. Re-run it whenever you add an account; every step reportsokwhen there was nothing to do, so a second run tells you exactly what changed.
Then, after any change:
gitwho doctorA healthy run prints ok for every check and exits zero — the store's permissions are intact, every token the config names is present, and git's credential helper actually reaches gitwho. What that looks like is below.
The problem
Working across several accounts — personal, two employers, a self-hosted Gitea — means every repository needs a different author identity and different credentials for whatever touches it: gh, tea, an IDE, an MCP server. Getting it wrong is quiet. You commit as the wrong person, or authenticate as the wrong account, and nothing tells you.
The usual approaches key off the filesystem path — includeIf "gitdir:" rules plus per-directory environment loading. That breaks as soon as a repository is not where the rule expects: a clone made somewhere else, or a worktree placed in a tool's own root. It breakssilently, falling back to whichever account is the default.
So gitwho keys off the repository instead. Identity resolves from the remote URL, credentials come from a helper git hands the URL it is about to contact, and CLI credentials are injected into one process for one invocation. One file declares your accounts; nothing else needs editing when you add one.
What doctor reports
gitwho doctor is the one to run after any change. It is read-only, exits non-zero on problems, and never prints a secret value — only fingerprints — so its output is safe to paste when you are asking for help.
ok [permissions] /Users/you/.config/gitwho and its files are owner-only
ok [backend] secrets are in the age file at /Users/you/.config/gitwho/secrets.age, chosen by default
ok [secrets] Personal/GH_TOKEN 4c1f9ab30e77
ok [secrets] Work/GH_TOKEN 9e02b7d5164a
warn [ambient] GH_TOKEN is set in the environment; every process launched from this shell inherits itThat [ambient] line is left visible on purpose. It means a provider token is sitting in the environment, where every process launched from that shell inherits it — which is the exposure this tool exists to remove, not a check to quieten. The README reports the same warning against the author's own machine rather than hiding it.
One process, one invocation
The property that matters is not the encryption. It is that a token is fetched by the one process that needs it, at the moment it needs it, instead of sitting in your environment where everything you launch inherits it.
Platform reality
Stated precisely, because the gap between these rows is easy to paper over.
| macOS | verified — this is where it runs every day |
|---|---|
| Linux | the test suite passes on x86-64 in CI. On Debian/aarch64 the whole init flow has also been driven by hand: store modes, identity, the credential helper, a shim executed for real, and the .bashrc PATH line |
| Windows | the %APPDATA% path, the .cmd shim and the PATHEXTlookup are unit-tested as pure functions from macOS, and have never run on Windows. Do not treat them as working |
One more gap, in the tool rather than the platform: jj takes its credentials from git, so pushing works — but it keeps author identity in its own config and does not read gitconfig's includeIf rules, so in a colocated repository jjand git can commit as different people without saying so.
Reading
- docs/installsetting it up step by step, with a check after each one and the traps that pass falsely
- docs/configthe annotated accounts.toml the tool itself ships, every field explained inline
- docs/recipesfour complete configs, checked against that schema at build time
- docs/designwhy it is shaped this way: the resolution algorithm, the evidence, the threat model
- docs/securitywhat the encryption defends against, and what it plainly does not
- github.com/DanielCarmingham/gitwhothe source, the issue tracker, and the README this page is built from