Using Multiple GitHub Accounts

The Problem

You have multiple GitHub accounts, and you want git to automatically use the "right" GitHub account.

A good way to do that is to put work-related stuff under a single directory, say, ~/Work. Then, configure git to use your work account for any project in your ~/Work directory.

In this article I set things up so that:

  • Projects in ~/Work/ use your work GitHub account
  • Projects elsewhere use your home GitHub account

My git configuration on GitHub is generated from this article.

The SSH Config

Assuming you use the git protocol to clone repositories (not https), you must create at least two SSH keys for GitHub – one for each GitHub account (GitHub will not allow you to use the same SSH key for both accounts, but will allow you to use the PGP key for both accounts).

Once you've created your SSH keys, your ~/.ssh directory will look something like:

$ tree ~/.ssh/
/Users/cfclrk/.ssh/
├── config
├── github-home
├── github-home.pub
├── github-work
└── github-work.pub

Ensure these SSH keys work:

ssh -i ~/.ssh/github-home -T git@github.com 2>&1
ssh -i ~/.ssh/github-work -T git@github.com 2>&1

We don't need to modify the ~/.ssh/config file. Instead, we'll explicitly tell git which SSH keys to use in our git config files.

The Git Config

Here we tell git to use different git configurations for different directories. Git supports this with the includeIf directive (read about it here). Do this by creating the following two files:

~/.config/git/config

[user]
    name = Chris Clark
    email = cfclrk@gmail.com
    signingkey = C6ECD046467701AB
[core]
    sshCommand = "ssh -i ~/.ssh/github-home"
[commit]
    gpgsign = true
[init]
    defaultBranch = main
[github]
    user = cfclrk
[includeIf "gitdir:~/Work/"]
    path = work.gitconfig

~/.config/git/work.gitconfig

[user]
    email = cclark@splashfinancial.com
[core]
    sshCommand = "ssh -i ~/.ssh/github-work"
[github]
    user = cclark-splash

Ignore the [github] sections; those are for Emacs forge. And of course, update the values for your name, email, and – if you're using Signed Commits – your PGP signingkey id.

That's it for the git config! Move on to Signed Commits. The next subsection explains the above, if you are interested.

More about Git Config Loading

Git loads all applicable config files. If one option is set multiple times (potentially form differnt files), the last value wins.

For example: assume you have the git config files above, which has:

[includeIf "gitdir:~/Work/"]
    path = work.gitconfig

Now, when you run a git command in a project located at ~/Work/projectA/, git does something like:

  • Load the default ~/.config/git/config file
    • Set core.sshCommand to ssh -i ~/.ssh/github-home
  • Does [includeIf "gitdir:~/Work/"] apply?
    • Yes! Load the work.gitconfig file
    • Set core.sshCommand to ssh -i ~/.ssh/github-work

In this case, both config files were loaded. The value of core.sshCommand is ssh -i ~/.ssh/github-work, because that value was loaded last.

The determining factor is the order in which the configuration is defined, and the last value wins.

Signed Commits

Rationale

Why sign commits?

The value of user.email is the only piece of information GitHub uses when determining what profile picture to display next to a commit. You can set that email address to anything! E.g. set it to torvalds@linux-foundation.org, and GitHub will happily put Linus Torvalds' picture next to your git commits. And to be sure, that happens.

If you don't want other people impersonating you, you can partially mitigate this problem of attribution by using signed commits.

GitHub allows you to upload a PGP key to your GitHub account, and GitHub displays a "Verified" badge on commits that proprely verify (i.e. git verify-commit <commit> works). For a commit to verify,

  1. The email address in the commit must match your GitHub account email address
  2. The commit must be signed by a PGP key that has been uploaded to your GitHub account

Prerequisites

First install the necessary tools:

brew install gnupg
brew install pinentry-mac

pinentry-mac saves passphrases in the MacOS keychain.

Create PGP key

Although GitHub has some instructions for creating a PGP key, I find it easier to use keybase.

To use keybase, first install the native app (this installs the keybase CLI program).

You can import preexisting PGP keys from keybase, but it's a better idea to create a new PGP key for every device. To view your existing PGP keys in keybase:

keybase pgp list

And to list your local PGP keys (this may be empty):

gpg --list-secret-keys --keyid-format=long

Now, create one new PGP key. We'll add both your home and work email addresses to this key, then upload the public key to both GitHub accounts (GitHub will allow the same PGP key to be used for multiple accounts). Alternatively, you could create two different PGP keys: one for each GitHub account.

keybase pgp gen --multi
  • It will prompt you for email addresses one at a time. Add both your home and work email addresses.
  • Add a passphrase.

Update trust level

Update this key's trust level to "ultimate"; this is not strictly necessary, but will make some gpg CLI output cleaner.

gpg --edit-key $keyid

At the prompt, type trust, after which you'll see some output like this:

Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Choose 5 for ultimate trust. Then, at the prompt, type save.

Add key ID to git config

Get the GPG key ID of your PGP key. To get the key ID:

gpg --list-secret-keys --keyid-format=long

In the above, the key ID is 80027A20FC0B6207 (is there some command that prints out the key id?). Update your git config like so:

[user]
    signingkey = 80027A20FC0B6207
[commit]
    gpgsign = true

Update gpg-agent.conf

Add the following to ~/.gnupg/gpg-agent.conf. This allows for automatic signing (on MacOS anyway).

# Connects gpg-agent to the MacOS keychain. This enables
# automatic key signing.
pinentry-program /opt/homebrew/bin/pinentry-mac

Make a git commit

If you used a passphrase on your PGP key, your first git commit will probably fail. For your first git commit:

export GPG_TTY=$(tty)

Now, when you make a git commit, it should prompt you for the PGP key's passphrase. After you do this once, you should never have to do it again.

Add public key to GitHub

TODO: keybase pgp export $keyid and gpg --armor --export $keyid create different things. Do they both work? Which is more appropriate to put in GitHub?

gpg --armor --export $keyid | pbcopy

Add this key to GitHub.

Appendix

Keybase

If you want to import an existing PGP key from keybase instead of creating a new one:

# Import the public key
keybase pgp export -q $keyid \
    | gpg --import
# Import the private key
keybase pgp export -q $keyid \
        --secret \
    | gpg --allow-secret-key \
          --import