Skip to content
makubexDPublic

About

Use several git accounts on one machine: each repo owns its identity, so you move between repos with no switch command and never push the wrong one.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

ย 

History

335 Commits

Folders and files

Repository files navigation

repown

npm CI Release Node License: MIT

Stop committing and pushing as the wrong account when one machine has two or more. Each repository owns its identity (that's the name: repo own), so you move from a work repo to a personal one to a client one, on any host or sign-in method, with no switch command. A push carrying commits under the wrong identity is refused before it leaves.

Day to day:

~/code/work      $ git push         # ๐ŸŸข signs in as octo-work
~/code/personal  $ git push         # ๐ŸŸข signs in as octocat, no switch command
~/code/personal  $ git push         # ๐Ÿ”ด refused: a commit authored as octo-work
~/code/client    $ repown status    # ๐ŸŸก guard off: pushes are not checked

Status: pre-1.0: the commands may still change. What changed in each version is in CHANGELOG.md.

Contents: ๐Ÿค” Why repown ยท ๐Ÿ“ฆ Install ยท โšก Quick start ยท โŒจ๏ธ Commands ยท ๐Ÿฉบ Troubleshooting ยท ๐Ÿ› ๏ธ Set up by hand ยท ๐Ÿงน Uninstall ยท ๐Ÿ“š More docs

Why repown

The problem:

  • โŒ Every clone inherits the identity in your global git config. A commit's author address becomes permanent once it's pushed to a public repository.
  • โŒ gh auth switch changes the account for the whole machine. While gh (the GitHub CLI) is git's credential helper (the program git asks for a password or token), every switch breaks the other account's repositories. And if your organisation signs you in through single sign-on (SSO), there's no password you could type at that prompt anyway (ADR-001).

What repown does:

  • โœ… Pins each clone. It writes the commit identity and the push account into that clone's own .git/config. That's what pinned means here: set once, never switched.
  • โœ… Lets one credential serve each account. Git Credential Manager (GCM) stores one sign-in per account and picks the one each clone names. repown doctor checks GCM is the helper; if gh has become it (gh auth login offers that), repown fix removes gh's entries after showing them. gh stays your account store for gh pr create and gh api.
  • โœ… Guards every push. A git pre-push hook (a script git runs before every push), the guard, checks the commits being pushed, not just today's config.

Where it works:

Commit identity Guard Push sign-in pinned
GitHub over https โœ… โœ… โœ… (github.com)
GitHub over SSH โœ… โœ… โž– your key decides which account pushes
Azure DevOps and other hosts โœ… โœ… โŒ not pinned (ADR-009)

Every scenario, with diagrams: docs/HOW-IT-WORKS.md.

Install

You need:

  • Node 20+ and git, on Windows, macOS or Linux.
  • Git Credential Manager for per-account sign-in on GitHub. Git for Windows includes it; on macOS and Linux, follow GCM's install guide.
  • gh is optional: repown reads it when it's there.
npm install -g repown
  • Install it globally: the guard calls the installed repown. To try it first, npx repown runs commands that only read (repown status, repown doctor, repown scan), but don't turn the guard on through npx: its hook would call a temporary copy (card 12).
  • Update with npm install -g repown@latest; guarded clones keep working (ADR-003).
  • What gets installed: one optional dependency, for the arrow-key prompts (without it, repown setup and the start screen ask with numbered choices), pinned exactly (ADR-016). The package is published from CI with provenance (how).

Quick start

repown doctor          # once per machine: is Git Credential Manager ready for sign-ins?
cd ~/code/my-repo
repown                 # in a terminal: starts the guided setup of this clone
  1. repown doctor, once per machine. It ends with a verdict:

    • ๐ŸŸข ready: each clone signs in as its own account through Git Credential Manager
    • ๐Ÿ”ด 1 problem: gh answers git's sign-in requests: run repown fix

    If gh is the helper, or something is missing, see Troubleshooting.

  2. Clone as usual, then from inside a new clone just type repown. That starts the guided setup. repown setup still works. (Cloning a private repository signs in first: pick the account that owns it.) It asks how setup should work. Recommended is the default. Answers it fills in still appear in the review.

    Question Recommended Step by step Flag
    Which account should this clone belong to? Asked. repown setup <account> skips the mode question and uses Recommended, so a recorded account goes straight to the review when nothing else must be asked. A new account also asks where it's hosted, the name and the email. Asked <account>, --host, --name, --email
    Push through the remote instead of a URL? Asked when the branch pushes to a URL (which can carry its own sign-in) that names the same repository as a remote here. The review shows the key and the remote, never the URL. Answers Yes without asking Asked, default Yes --repoint
    Fetch the remote first? Asked when commits by another address wait behind a remote this clone has never fetched, so repown can count which it already has. Prompts are off; a failed fetch is a warning. Answers Yes without asking Asked, default Yes --fetch
    Let this clone push to origin's owner? Asked when origin belongs to someone else, like an organisation. Asked, default Yes. No means the guard refuses pushes there. Asked, default Yes --allow-owner <owner>
    Turn the guard on? Answers Yes without asking Asked --guard
    Push branches without -u? Only on git 2.37+. It sets push.autoSetupRemote in this clone only; the guard still checks that first push. Answers Yes without asking Asked, default Yes --auto-upstream
    gh. "Switch gh too" makes gh pr create match. Switches without asking when gh already lists the account and the clone is not already pinned to it. A sign-in, which opens a browser, is asked, default No. Skips the question when the clone is already pinned to that account. Asked --gh
    Take git's sign-ins back from gh? Machine-wide. Asked, default No. Answer Yes if repown doctor said gh is the helper, or pushes from your other account's clones fail. Asked, default No --fix
    Re-author your unpushed commits by another address as this account? Asked when commits you made before pinning (or an IDE made) carry another email. Only if you made them. It runs last, as repown reauthor --yes: it fetches first, rewrites only what no remote has, keeps a backup, and never pushes. Asked, default No. Enter at the review is Decline when this is a step. Asked, default No; Enter at the step is Skip --reauthor

    Step by step asks every question: --step-by-step. --no-input asks nothing (scripts and CI).

    • In your first clone, it lists the accounts it can already see on GitHub (origin's owner, gh's accounts, Git Credential Manager's) and suggests origin's owner when that owner is a user, or asks for the login. On GitHub, use your noreply address, shown at github.com/settings/emails (like 1234+octocat@users.noreply.github.com).
    • In later clones, it lists the accounts it knows: pick one, or a new account.
  3. Check the review, then choose Run them. Nothing changes before that:

    โ—‡  Review: nothing has changed yet โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
    โ”‚                                                                         โ”‚
    โ”‚  This clone will commit and push as octocat.                            โ”‚
    โ”‚                                                                         โ”‚
    โ”‚  When you choose Run:                                                   โ”‚
    โ”‚  1. Let this clone push to octo-org's repositories                      โ”‚
    โ”‚       git config --local --add repown.allowOwner octo-org               โ”‚
    โ”‚  2. Pin this clone to octocat: its commit name, email and push sign-in  โ”‚
    โ”‚       repown use octocat                                                โ”‚
    โ”‚  3. Turn on the push guard: each push is checked first                  โ”‚
    โ”‚       repown guard on                                                   โ”‚
    โ”‚  4. Push branches without -u: the first push sets the upstream (this    โ”‚
    โ”‚     clone only)                                                         โ”‚
    โ”‚       git config --local push.autoSetupRemote true                      โ”‚
    โ”‚                                                                         โ”‚
    โ”‚  These are ordinary commands: run them yourself, or in a script.        โ”‚
    โ”‚  This clone's settings go in its .git/config, which is never pushed.    โ”‚
    โ”‚                                                                         โ”‚
    โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
    โ”‚
    โ—†  Run these 4 steps?
    โ”‚  โ— Run them
    โ”‚  โ—‹ Back
    โ”‚  โ—‹ Change an answer
    โ”‚  โ—‹ Decline
    โ”‚  โ†‘/โ†“ to navigate โ€ข Enter: confirm
    โ””
    
  4. Push. The first push from each account signs in once: GCM opens a browser, and you sign in as that clone's account, not whichever you used last. After that, pushes from that clone use it without asking. When setup set push.autoSetupRemote, the first git push of a branch without an upstream also creates it on origin. When it did not (older git, a version repown could not read, or you answered No), use git push -u origin <branch> once.

Keys: โ†‘/โ†“ choose, Enter confirms, Esc or Ctrl-C stops with nothing changed. From the second question on, each list ends with โ† Back; at a typed answer, enter <. In a plain terminal the questions come as numbered choices (when); there, Ctrl-C stops.

Running it again. Run repown in a clone that's already set up, or repown setup with no account and no flags, and setup opens on this screen, before any question:

โ—‡  This clone is already set up โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚                                                                    โ”‚
โ”‚  commits as  Octo Cat <octocat@users.noreply.github.com>           โ”‚
โ”‚  pushes as   octocat                                               โ”‚
โ”‚  guard       on: every push is checked before it leaves            โ”‚
โ”‚  upstream    origin/main                                           โ”‚
โ”‚                                                                    โ”‚
โ”‚  Nothing needs to change.                                          โ”‚
โ”‚                                                                    โ”‚
โ”‚  Checked: the settings git uses here are octocat's, as recorded.   โ”‚
โ”‚  See it any time: repown status (this clone), repown doctor (this  โ”‚
โ”‚  machine)                                                          โ”‚
โ”‚                                                                    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
โ”‚
โ—†  What now?
โ”‚  โ— Done (change nothing)
โ”‚  โ—‹ Use another account
โ”‚  โ†‘/โ†“ to navigate โ€ข Enter: confirm
โ””

Done changes nothing. Use another account asks which account this clone should use. When gh acts as someone else, a third option is Sign in to gh as <account> (or Make <account> gh's active account when gh already lists it) and runs repown use <account> --gh. With unpushed commits by another address, Re-author them is offered too (repown reauthor --yes). The upstream line shows the tracked branch (for example origin/main) when there is one, otherwise set on the first push (push.autoSetupRemote) when that setting is on, otherwise nothing. It is the same value repown status shows, and repown status shows all of this without asking. Every screen: card 13.

Commands

After setup, moving between accounts needs no command: cd into any pinned clone, then commit and push.

When Command What it does
Once per machine repown doctor what serves credentials on this machine, and whether each account is signed in to git and gh
repown fix [--dry-run] [--yes] undo gh auth setup-git, so each clone's pinned account is used; shows what it removes, and the undo, first
Once per clone repown setup [<account>] guided setup of this clone. Questions, flags and the review: Quick start. A clone that is already set up opens on that screen
repown use <account> [--gh] what setup runs: pin this clone to an account. --gh switches gh's active account, or signs the account in to gh when needed (in a terminal). --name with --email skips the registry, the file where repown remembers accounts
repown reauthor [--yes] give this branch's unpushed commits by another address your pinned identity, so the guard lets them through. Fetches the push destination first, rewrites only what no remote has, keeps a backup ref, never pushes
repown guard on | off | status install, remove or show the pre-push hook (bare repown guard shows it)
Any time repown status this clone's and this machine's settings, and what to fix. It ends ready: โ€ฆ, or the next push will fail: โ€ฆ with what is in the way. Exits 1 on a problem; warnings alone exit 0
repown scan [dir...] [--emails] [--depth <n>] [--format json] every clone under the folders (default: this one, 3 levels deep): owner, host, identity, guard, and which email domains its history has. Changes nothing
Rarely repown accounts list | add | remove the accounts this machine knows (bare repown accounts lists them; add takes --name, --email, --host github|azdo|generic; list --format json for scripts)
repown off unpin this clone, leaving global config alone; warns if the guard is still on

Bare repown:

Where What it does
A terminal, inside a clone (pinned or not) Starts repown setup. A line inside that screen says the clone isn't set up yet, or that a pinned clone is being checked (card 5)
A terminal, outside a clone or in a bare repository Opens the start screen (card 14)
stdout redirected (stdin and stderr still terminals) Status inside a clone. The top help outside a clone, exit 0
No terminal (stdin or stderr is not one) Status. Exit 1 outside a clone

The start screen lists the accounts on this machine and the clones it found, then offers the next command. The summary is in the frame. Picking a command closes the frame with that command (Record an account first asks the login, host, name and email in it; Remove an account asks which, then to confirm). Nothing changes until you pick one. After recording or removing an account, checking the machine or stopping gh, it comes back with a fresh summary.

Who is this clone? repown status prints:

$ repown status

repown status ยท current settings of this clone
  ~/code/personal  (branch main)

This clone
  commits as     Octo Cat <octocat@users.noreply.github.com>
  pushes as      octocat
  account        octocat  (recorded)
  origin         octocat  (GitHub)
  upstream       origin/main
  push guard     on

This machine
  default        Octo Work <octo-work@example.invalid>
  helper         manager
  gh active      octo-work

OK    identity   this clone is pinned, and its credential mechanism honours it
NOTE  gh         active as "octo-work", so `gh pr create` here would act as that account. git pushes are unaffected; this only matters if you use gh here.
       fix: gh auth switch -u octocat

ready: commits and pushes use octocat ยท gh: optional (see the note above)

Here git is right, and only gh commands would act as another account. That line is a note, not a warning. Every warning it can print: card 5.

  • Help: repown --help, repown help <command> (or repown <command> --help), and repown help guard on one level deeper. Help never changes anything.
  • Every command takes --cwd <dir>. Version: repown --version (or -v).

Exit codes:

Command 0 1 2 Other
In general Success Failure or refusal Usage error 130 when setup or the start screen is cancelled
repown scan Whatever it finds (read its output or JSON) A missing folder or a bad --depth
repown setup Done, or already set up Decline, or it can't start Flags that don't fit, or no terminal without --no-input 130 when cancelled or interrupted; a failing step's own code
repown status Success. Warnings alone exit 0 A problem

Every setup case: Scripts and CI.

Troubleshooting

What happened What to do
repown doctor says gh is the helper repown fix (card 1)
repown doctor shows GCM โ€ฆ not found install Git Credential Manager (Install), then repown doctor again
repown doctor shows helper โ€ฆ none configured git credential-manager configure, then repown doctor again
A push asked for a password repown status (this clone) or repown doctor (this machine): each names the cause (gh as the helper, or no helper) and the fix
A push failed right after signing in (SSO) card 6
The guard refused a push it says which commit and the fix. Most often a commit made before pinning: git commit --amend --reset-author --no-edit fixes the last one (card 8 for older ones, and every other refusal)
gh pr create acts as the wrong account gh auth switch -u <account> when gh lists it, otherwise repown use <account> --gh
git push says the branch has no upstream repown setup --auto-upstream, or once git push -u origin <branch>
The first push opened a browser Expected, once per account. Git Credential Manager stores it
The guard said the push goes to another owner if you're a member or collaborator there: git config --local --add repown.allowOwner <owner>
The guard said GH_TOKEN (or GITHUB_TOKEN, GIT_AUTHOR_EMAIL, GIT_COMMITTER_EMAIL) is set unset it, then push again: the email variables override the pinned identity, and the token variables make gh serve that token
husky or another tool owns the pre-push hook have that hook run repown guard check --remote="$1" --url="$2", passing its stdin through; if the hook is committed, make it skip teammates without repown (card 10)
An Azure DevOps push is refused as "wrong owner" the owner there is the organisation: allow it with git config --local --add repown.allowOwner <organisation>
"repown cannot be found" on push card 12

A refused push:

FAIL  guard      1 commit(s) bound for refs/heads/main were not authored as octocat@users.noreply.github.com, or were committed by someone else.
         6e7b31adb  someone@example.invalid  someone else's commit

       These addresses become permanent once pushed.


Push stopped by the repown identity guard (above).
Override this one push with: git push --no-verify

To push once without the check: git push --no-verify, only when you mean to publish those addresses. What the guard can't catch (other git clients, submodule pushes): residual risks.

Set up by hand

The same commands repown setup runs, if you'd rather type them:

  1. Check the machine, once: repown doctor.
  2. Record each account, once: repown accounts add octocat --name "Octo Cat" --email octocat@users.noreply.github.com. Optional: on a terminal, the first repown use of an unknown account asks. On GitHub it suggests the name from the public profile and the noreply email (through gh) (card 2).
  3. In each clone: repown use octocat, then repown guard on (card 3). If the repository belongs to another owner, such as an organisation, use prints the line that allows it.

In a GitHub clone over https (elsewhere the identity line has no push-as:, and use says repown pins no credential there):

$ cd ~/code/dotfiles
$ repown use octocat
OK    identity   Octo Cat <octocat@users.noreply.github.com>  push-as:octocat

  No stored credential for "octocat" yet -- the first push signs in
  once, then never again. Verify it afterwards: repown doctor

  Next: repown guard on    (check every push before it leaves)

$ repown guard on
OK    guard      on -- every push is checked before it leaves
  ~/code/dotfiles/.git/hooks/pre-push

.git/config and .git/hooks are never pushed, so teammates see nothing of this setup. Every key repown writes: docs/CONFIGURATION.md.

Uninstall

Turn the guard off first: a guard that can't find repown refuses every push.

repown scan <dir>        # the "guard" column shows where it's on
repown guard off         # in each of those clones
repown off               # optional: also drop the pinned identity
npm uninstall -g repown

Left behind, on purpose: the account registry (where), which you can delete, and the sign-ins in your OS credential store. If repown fix took the helper role from gh and you want it back: gh auth setup-git. Details, and submodules: card 12.

More docs

I wantโ€ฆ Read
every scenario, with diagrams docs/HOW-IT-WORKS.md
environment variables, where accounts are kept, per-clone keys, scripts and JSON docs/CONFIGURATION.md
short answers, and how repown compares with includeIf, SSH aliases and gh auth switch docs/FAQ.md
to contribute, see where each feature lives in the code, or try it on throwaway repositories CONTRIBUTING.md
to report a vulnerability privately SECURITY.md
why it works this way, one ADR per decision docs/decisions/
what changed in each version, and how a release is cut CHANGELOG.md, docs/RELEASING.md

License

MIT

About

Use several git accounts on one machine: each repo owns its identity, so you move between repos with no switch command and never push the wrong one.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages