How to configure multiple git personalities for gpg-agent

In your developer life you may have multiple personalities with separate credentials for each one, for example, a private personality for your private projects with your private email, nickname, github/gitlab/whatever profile, and a set of gpg/ssh keys, a work personality with your work email, github profile, formal name and title, and a separate set of gpg/ssh keys. Usually these are managed by creating a workspace folder for each personality, instructing git to use the appropriate email, name, and gpg key depending on the filepath, and finally adapting the ssh config file to use the correct ssh key depending on the hostname (the detailed instructions are easy to find on the internet).

But, what if you use GnuPG to manage both your gpg and auth keys together and do not have ssh keys readily accessible in your home .ssh folder? In this case you can probably find the appropriate ssh keys in the .gnupg folder and try to use them, although you would probably need to mold them into the correct format. Anyway, this solution sounds highly error-prone and hacky.

But, what if in addition you also use gpg-agent instead of ssh-agent for caching and serving your ssh keys (as described, for example, here)? After all, what is the point of running 2 key-serving agents when all the keys are already managed by GnuPG? In this case ssh asks gpg-agent for keys directly and the content of ssh/config does not even matter. So how do you provide the correct ssh key?

Let us take a closer look at how ssh obtains a key in our scenario. Say you run a git fetch command with an upstream server configured for ssh protocol. First git asks ssh to fetch the information from the server, then ssh establishes a connection and queries gpg-agent (through its gpg-agent.ssh socket set in $SSH_AUTH_SOCK environment variable) for ssh keys. As far as I could find out, in contrast to ssh-agent the gpg-agent does not care for the server hostname and always returns the same list of keys, which ssh then tries out one by one until any one fits. In theory this works fine if the keys are host-independent, but if you have both private and work accounts on github then there is a conflict as any key can let you in. In fact, the list of ssh keys gpg-agent will serve is specified explicitly in the .gnupg/sshcontrol file.

With that knowledge the only thing we need in order to configure multiple personalities in gpg-agent is to find a way to put the correct key into the sshcontrol file before executing ssh. This can be done in git by setting the core.sshCommand config option for each of your profiles to a simple wrapper script that does just that. The example config for one of the personalities may look something like this:

In your ~/.config/git/config file:

...
[includeIf "gitdir:~/code/work/"]
  path = ./config_work

In your ~/.config/git/config_work file:

[user]
  email = work@work.com
  name = Work Name
  signingkey = WORK_GPG_KEY
[core]
  sshCommand = ~/.config/git/work_ssh_wrapper

Finally, in your ~/.config/git/work_ssh_wrapper file:

#!/usr/bin/env bash

if [ -f ~/.gnupg/sshcontrol_general ]; then
  echo "ssh connection with non-default profile already ongoing; exiting" >&2
  exit 1
fi

mv ~/.gnupg/sshcontrol ~/.gnupg/sshcontrol_general
trap "mv ~/.gnupg/sshcontrol_general ~/.gnupg/sshcontrol" EXIT
echo WORK_SSH_KEY > ~/.gnupg/sshcontrol
ssh "$@"