DEV Community

Wesley Schwengle
Wesley Schwengle

Posted on • Edited on

Four Simple Ways To Use Multiple Git Accounts On One Computer

This isn’t the definitive guide to handling multiple Git identities. It’s just what I’ve tried, refined, and used over years — across clients, keys, and config quirks.

TL;DR: Four working setups — from .ssh/config to a custom wrapper that adapts based on repo owner.

A single account of a git-forge such as gitlab or github is quite common and only requires a little configuration. But when your employer or client uses the same forge and requires you to have a second account on that forge, it could become a bit tricky, especially when you have them within the same repository. There are ways to solve this problem. This post will explain at least three of them.

We'll start off with my least favorite:

A. Change the remote URI via ssh_config

And by ssh_config, I mean $HOME/.ssh/config.

The idea behind this is that by using .ssh/config, you can use aliases for a GitForge and configure a specific identity for that alias. In this case, you will use clientforge as your client's host and the original name gitforge.com for your projects. Your .ssh/config will look a little something like this:

Host gitforge.com
   User git
   Hostname gitforge.com
   IdentityFile ~/.ssh/id_rsa

Host clientforge
    User git
    Hostname gitforge.com
    IdentityFile ~/.ssh/id_client
Enter fullscreen mode Exit fullscreen mode

In your project's repository, you'll need to change the remote of your liking with the set-url command for remotes:

$ git remote set-url upstream git@clientforge:client/themoneymaker.git
Enter fullscreen mode Exit fullscreen mode

The downside to this approach is that you need to remember which host to use for which project, and you can no longer copy/paste specific commands from the forges after you've created a new repository. The upside is that it doesn't require any additional git configuration changes.

Quick Git Config Recap

Before we head into the other options, it is essential to know that git stores its configuration in several locations. You have a system-wide configuration in /etc/git/config, a global one in $HOME/.gitconfig, and a local one in your repository $GIT_DIR/.git/config. This allows you to hone your configuration from global to very specific at the repository level. There are also several include directives, such as the includeIf directive. Now you can include a separate configuration based on the path of your projects, which we will use in the upcoming methods.

B. Use a different SSH command (or options) for a project

As said, we have a per-repository config. For a single project, you can set a custom core.sshCommand command or ssh-options so you only use the key associated with your work account. You can do this by running the following command:

git config core.sshCommand \
  '-i ~/.ssh/id_client -o IdentityOnly=yes -F /dev/null'
Enter fullscreen mode Exit fullscreen mode

This should result in a line similar to this in $GIT_DIR/.git/config:

[core]
  sshCommand = -i ~/.ssh/id_client -o IdentityOnly=yes -F /dev/null
Enter fullscreen mode Exit fullscreen mode

The identityOnly=yes is only there to prevent SSH from looping over all your SSH keys and potentially using a different SSH key. The -F /dev/null disables using your .ssh/config. You could also use a different SSH config for just the sshCommand, e.g., sshCommand = -F ~/.ssh/config-client, and set the correct SSH options in that file. You could also use the entire command here, e.g., ssh -i ~/.ssh/id_client -o IdentityOnly=yes -F /dev/null.

This option only scales well when you have only one or two projects, which leads me to the next option.

C: Different SSH commands based on the path of your projects

You can use different git configurations depending on the project directory you are in. For this, we use the previously mentioned [includeIf] directive, which allows you to include a separate configuration based on the path of your projects. So all projects in $HOME/work/client share the same git configuration. In your $HOME/.gitconfig, you need to add the following snippet:

[includeIf "gitdir:~/work/client/"]
   path = ~/.config/git/client.config
Enter fullscreen mode Exit fullscreen mode

Now in $HOME/.config/git/client.config, you can configure the core.sshCommand as done in the previous example. You can also use this for configuring other bits of git for your project(s), such as a different e-mail address, name, etc. This way, you can manage multiple projects with a single configuration with ease.

D. Custom ssh wrapper

The nicest of them all, and can work as a combination of the last two options. The problem that I tried solving while coming up with this solution is that I had two accounts on the same GitForge, and I have a ton of repositories. Because the hostnames of the repositories are placed in the myrepos configuration file, I cannot just quickly change the host in .ssh/config. And I also didn't want to change host for all my personal projects. In the past, I could use my account to commit to the company's repos. However, after they underwent ISO certification, we had to log in with SSO, and only those accounts were authorized to make changes. So I had to create two separate accounts with two distinct SSH keys.

As I was already using the includeIf directives for various directories, I wanted to be able to select a different SSH key for specific remotes. Now there is an includeIf directive that supports including a configuration when a remote has a specific endpoint:

[includeIf "hasconfig:remote.*.url:https://example.com/**"]
Enter fullscreen mode Exit fullscreen mode

This approach will not work as expected because it includes the configuration, regardless of where you push your changes. So we will use an SSH-wrapper script and configure it as the core.sshCommand command.

First, you need to know how git sends its SSH command. You can test this by making a script and printing the things to STDERR or using set -x. Printing to STDOUT from within the script will issue warnings:

protocol error: bad line length character: git@
Enter fullscreen mode Exit fullscreen mode

You can also use GIT_TRACE=1 while issuing a command that triggers something on your remote (git fetch, git pull, git push, etc). Git has several options on how and what it sends to SSH. You can change these options by setting the ssh.variant option. I started by setting it to simple, changed it to ssh, and now it is back to the default auto.

A small script like this will do the job for testing purposes:

set -x

echo $@ >&2
ssh $*
Enter fullscreen mode Exit fullscreen mode

And you issue the command

git config --local core.sshCommand /path/to/wrapper-script.sh
Enter fullscreen mode Exit fullscreen mode

After which you run git fetch:

+ echo -o SendEnv=GIT_PROTOCOL git@gitlab.com git-upload-pack 'waterkip/themoneymaker.git'
-o SendEnv=GIT_PROTOCOL git@gitlab.com git-upload-pack 'waterkip/themoneymaker.git'
+ ssh -o SendEnv=GIT_PROTOCOL git@gitlab.com git-upload-pack 'waterkip/themoneymaker.git'
Enter fullscreen mode Exit fullscreen mode

As you can see, the last bit of the parameters is what we are interested in. It has the repository, and we can select the correct ssh-key based on that. We want to instruct our script to use the SSH key ~/.ssh/id_client if the repository's owner or group is client. We supply these as arguments to the wrapper script.

# Your relevant git config file should have the following snippet
[core]
  sshCommand = /path/to/custom-ssh-wrapper client ~/.ssh/id_client
Enter fullscreen mode Exit fullscreen mode

The wrapper script:

#!/usr/bin/env zsh

name=$1
identity=$2
shift;
shift;

# $@ = git@host 'git-cmd \'user/repo.git\''
git_cmd=${@[$#]}
git_cmd=("${(@s: :)git_cmd}")
# git_cmd = ( git-cmd 'user/repo.git' )
repo=$(echo "${git_cmd[${#git_cmd[@]}]}" | sed -e "s/'//g");
# repo = user/repo.git
repo=("${(@s:/:)repo}")
# repo = ( user repo.git )

group_oder_user=$repo[1]

ssh_opts=""
[ $group_oder_user = $name ] \
  && ssh_opts="-i $identity -o IdentitiesOnly=yes -F /dev/null"

eval ssh $ssh_opts $*
Enter fullscreen mode Exit fullscreen mode

This allows me to use different accounts for each remote within the same repository. Support for a third account is also possible. It requires more work on the argument handling in the wrapper script. Or by taking a different approach and having a configuration that maps the repo-user to SSH keys. But I'll leave that as an exercise for the reader.

Conclusion

Method Description Pros Cons
A. SSH Config Alias (.ssh/config) Use host aliases and identity files in ~/.ssh/config to switch identities per-host Simple to set up; no Git config changes needed Must remember aliases; doesn't match Git hostnames directly
B. Per-Repo core.sshCommand Set a specific SSH command in a repo’s local Git config Works per-repo; avoids global conflicts Doesn’t scale well for many repos
C. includeIf for Path-Based Config Use Git’s includeIf to apply config (e.g. sshCommand, name/email) based on repo path Scales well; central config for client/work projects Requires project folder structure discipline
D. Custom SSH Wrapper Script Use a script as core.sshCommand to dynamically pick SSH key based on remote repo group/owner Highly flexible; works even with multiple remotes in same repo Requires scripting; more complex to maintain

Top comments (0)