tl;dr - Easily manage multiple git personas by naming config stanzas with aliases in your ~/.ssh/config
and the names when you git clone
(i.e. git clone git@your-alias:user/project.git
. Or, you could just use HTTPS to clone. Either way, make sure to use git config --local
to change the user.name
and user.email
values afterwards.
I got some more excellent feedback from u/tpenguinltg on Reddit on an alternative way to do things that's even *more* centralized and easy to manage, via git configs. Huge thanks to u/tpenguinltg, that tip might be better than this entire post -- scroll to the bottom (or click the link in the previous sentence) for the goods!
You’d think in 2019, after over a decade of using git
I’d be super comfortable with managing identities for different public/private projects in Git, but the other week I realized I wasn’t – in particular, I mistakenly used my personal identity to commit to a private project that I was doing for a client. Looking into it, It took me tens of minutes to find the “right” way to manage this, and a lot of the answers were very non-satisfactory.
This is going to be a quick post going over one way I’ve found very useful, in particular naming your ~/.ssh/config
config stanzas, and using the names in there to actually connect. It’s tedious, but it gives me good separation between projects, with a good predictable system. The interplay between ssh
and git
(ssh
is used by git
) makes this a little tricky and can be confusing, and while ssh
is supposed to try all the keys it has, you can actually run into issues where ssh
tries too many keys.
Before we start, I want to point out that there’s a very easy solution – you could just use HTTPS. For some projects I did that, but it felt like a cop-out, since I didn’t take the time to understand and solve the problems with the tools at hand (git
and ssh
).
Of course, if we’re going to be managing SSH identities, we’re going to need some keys! I personally like to do the following:
$ mkdir ~/.ssh/<company-or-project-name> # make an on-disk folder for the company/project
$ ssh-keygen -t rsa -b 2048 -C <email address> -f ~/.ssh/<company-or-project-name>
Here’s a worked example, if I was working with a company called “acme”.
$ mkdir ~/.ssh/acme
$ ssh-keygen -t rsa -b 2048 -C vados+acme@vadosware.io -f ~/.ssh/acme/id_rsa
NOTE I prefer to get prompted for the password, you could submit it with the -N
option as well. Check out the ssh-keygen MAN page for more options.
It’s hard to remember all the arguments you need for ssh-keygen
every time, but you can get by with the minimal ssh-keygen -t rsa -b 2048
and let it ask you for the rest. Be very careful not to overwrite your personal one (usually @ ~/.ssh/id_rsa
) during the prompts, since it’s the default location that shows up. It might even be a good idea to move that to a folder like ~/.ssh/personal
or something.
~/.ssh/config
for the new persona(s)Now that you’ve got some new persona(s) to integrate, let’s modify your SSH configuration. Keeping with the “acme” example used above, we’ll need to add a config stanza that looks like this:
Host *
ServerAliveInterval 240
AddKeysToAgent yes
IdentitiesOnly yes
# ... other stuff ...
Host acme-gitlab
Hostname gitlab.com
User git
IdentityFile ~/.ssh/acme/id_rsa
AddKeysToAgent yes
This will add a alias (basically a fake address) that ssh
will recognize called acme-gitlab
with the settings above.
To test your settings (and add your keys to ssh-agent
since AddKeysToAgent
was specified), you can ssh to the your repository hosts:
$ ssh git@acme-gitlab
PTY allocation request failed on channel 0
Welcome to GitLab, @<user>!
Connection to gitlab.com closed.
And here’s what it looks like for Github:
$ ssh git@github.com
PTY allocation request failed on channel 0
Hi <user>! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.
Assuming you see a message like the above, you’re good to go. If not, you’ll need to go back and check your configuration.
Now we can finally do what we set out to do – you can pull the git repository like so:
$ git clone git@acme-gitlab:<owner>/<project>.git
... git output ...
Under the covers, ssh
does the translation for acme-gitlab
to github.com
and uses the right IdentityFile
(no need to guess!).
In a perfect world we could have ended at Step 3, but there’s the small issue of which identity commits get attributed to. In particular, we need to use git config
with the --local
option in the cloned repository to set the identity for the repository. Unfortunately I haven’t found a good way to do this in a centralized way, but for now I just remember to do it after I git clone
.
Continuing with the “acme” example, the commands would look like this:
$ git config --local user.name vados-acme
$ git config --local user.email vados+acme@vadosware.io
There’s also GPG signing of commits which I won’t get into, but you should also set up.
Thanks to u/tpenguinltg on Reddit for offering another solution – editing files like ~/.config/git/config-<project>
to manage the SSH command that’s used. I do like to organize my projects by folders (for example $WORK/<company>/<repo>
), so this is also a great way to do it, and possibly even better than the approach above.
Paraphrasing from the comment:
For example, suppose I have a config for this account that looks like this in ~/.config/git/config-tpenguinltg:
[user]
name = tPenguinLTG
email = tpenguinltg@example.com
signingKey = 931XXZ0V214U7W47
[core]
sshCommand = ssh -i ~/.ssh/example-tpenguinltg -F /dev/null
[credential "https://github.com"]
username = tpenguinltg
[credential "https://gist.github.com"]
username = tpenguinltg
This config sets my name and email to the proper values, sets my username to tpenguinltg for GitHub over HTTP and uses the key located at ~/.ssh/example-tpenguinltg over SSH, and sets my GPG signing key.
And in ~/.gitconfig
you must place a line like this:
[IncludeIf "gitdir:~/dev/tpenguinltg"]
path = ~/.config/git/config-tpenguinltg
NOTE If you want the configuration to apply to every subdirectory of a given folder, make sure to add a trailing /
(so [IncludeIf "gitdir:~/path/to/container/folder/"]
– See the Git documentation for more details.
I think this is even better than my solution because it widely applies under a directory, which is fantastic for multiple repositories which are logically connected somehow (usually by the company/organization they’re for). As u/tpenguinltg noted in the comment, this setup does require Git 2.13 (from mid 2017), but I assume most people these days are using recent versions of git
.
This was a short one – hopefully it helps at least one frustrated developer out there.
If you know a better way of handling this stuff (preferably without introducing too many new tools), please feel free to contact me and I’ll update the post so we can share this knowledge!