Multiple Git Accounts

the Darkside

Thursday, October 10, 2019

When you are working with multiple Git accounts across different git-providers it’s hard to manage access to all these repositories. When you search for a solution you will find different ways to solve this problem.

Basically it comes down to two SSH-based solutions;

  1. Use named SSH configurations in the ~/.ssh/config file.
  2. Use the git includeIf directive.

Both methods will be explained in detail but before we dive into each method I first want to set a baseline on the SSH-keys.

multi-git

SSH-keys

Each method relies on the SSH-keys being uploaded to your git-provider (.pub only please!). The upload process is different for each provider.

The SSH-keys are stored by default in the directory ~/.ssh/.

The naming convention used for the SSH-keys is [git-user.name]@[git-provider].ed25519.

  • git-user.name, the user name at your git provider.
  • git-provider, the name of the git provider, including domain to separate possible internal and external git providers.
  • ed25519, instead of the default RSA key I use Ed255191

The command to create the SSH-key pair:

syntax

ssh-keygen -t ed25519 -f ~/.ssh/[git-user.name]@[git-provider].ed25519 -C "[git-user.name]@[computer-name]"

example

ssh-keygen -t ed25519 -f ~/.ssh/tisgoed@github.com.ed25519 -C "tisgoed@MacFlurry"

As best practice add a comment ('-C' argument) with your git-user.name and the computer-name. It helps to identify the SSH-keys at your git-provider.

I re-use my SSH-keys for a specific git account per computer but not shared across computers. If for some reason your workstation is no longer available, you can identify and disable the SSH-keys at your git-provider.

Method 1: Use named SSH configurations

In this method a “named” entry is created in the .ssh/config file. The entry content refers to the git-provider specific SSH-keys. The name of the entry is used in the git configuration’s remote origin.

This method gives you full flexibility on the location of your local repositories.

Diagram for the named SSH configuration flow

The following diagram describes the configuration for a new git account:

diagram method 1

The basic steps to set up a new git-provider:

  1. Create SSH-keys
  2. Add the named SSH-section
  3. Set the remote origin

The baseline for setting the SSH-keys is already described in the second paragraph. Moving on to step 2.

Step 2: Add a named SSH-section

For this step you need to modify the SSH-configuration file (~/.ssh/config).

See the template and example of the named section below on what to add for your new git-provider.

A template of a named (git account) section in the ~/.ssh/config file:

...
# GitProvider description [your git-user.name] account
Host [git-user.name]-[git-provider]
   HostName [git-provider]
   user git
   IdentityFile ~/.ssh/[git-user.name]@[git-provider].ed25519
   IdentitiesOnly yes
...

An example of two fictional named sections in the ~/.ssh/config file:

...
# tisgoed @ GitHub.com
Host tisgoed-github.com
   HostName github.com
   user git
   IdentityFile ~/.ssh/tisgoed@github.com.ed25519
   IdentitiesOnly yes

# tisgoed @ BitBucket.com
Host tisgoed-bitbucket.com
   HostName bitbucket.com
   user git
   IdentityFile ~/.ssh/tisgoed@bitbucket.com.ed25519
   IdentitiesOnly yes
...

The SSH-Host identifies the account to be used to login to the git-provider. For each git account you will need to add a different section in your ~/.ssh/config file.

Step 3: Set the remote origin

When you clone a new repository you end up with the default remote origin in your .git/config configuration.

A partial example of the default `[remote “origin”] in your fresh git-configuration:

...
[remote "origin"]
    url = git@github.com:tisgoed/my-new-repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*
...

As you can see the url points to your repository but the way it wants to connect does not use the named configuration.

We need to modify the default remote origin to use our named SSH configuration.

See the syntax and the example below on how to modify the remote origin through the command line.

The syntax:

git clone git@github.com:[git-user.name]/[repo-name].git
cd [repo-name]
git remote add origin git@[git-user.name]-[git-provider]:[git-user.name]/[repo-name].git

An example:

git clone git@github.com:tisgoed/silly-repo-name.git
cd silly-repo-name
git remote add origin git@tisgoed-github.com:tisgoed/silly-repo-name.git

The url in the [remote "origin"] of the git-configuration should be changed similar to the example below:

...
[remote "origin"]
    url = git@tisgoed-github.com:tisgoed/silly-repo-name.git
    fetch = +refs/heads/*:refs/remotes/origin/*
...

Altough this is a good solution, and it gives you full flexibility on the working directory, the alternative option simplifies it even further without touching the ~/.ssh/config file and setting the specific remote origin for each repository.

New repo workflow (method 1)

When you need to create a new repo at an existing git-provider, the following steps apply:

  1. Browse to your (git-provider)
  2. Login to your account (git-user.name)
  3. Create a new repository (repo-name
  4. git clone git@[git-provider]:[git-user.name]/[repo-name].git
  5. cd [repo-name]
  6. git remote add origin git@[git-user.name]-[git-provider]:[git-user.name]/[repo-name].git
  7. touch README.md
  8. git add .
  9. git commit -m "Initial commit"
  10. git push -u origin master

Step 4 and 6 could be combined in this workflow. I have tried this workflow for a while and noticed that I needed all ten steps simply because I could not remember the named of the SSH-section.

git clone git@tisgoed-github.com:tisgoed/silly-repo-name.git

Method 2: Use the git includeIf directive

The includeIf-method uses the git directive includeIf that enables you to include files when a certain condition is true.

If you use git within a certain path, the content of a specific file is added to the active git-configuration. The included file contains information to access a specific git-provider.

This method requires more steps to set up but there is no need to remember a named SSH-section.

Diagram with the includeIf flow

The following diagram describes the configuration for a new git account:

diagram method 2

The basic steps to set it up for a new git-provider:

  1. Create SSH-keys
  2. Set up a directory structure
  3. Create the include file(s)
  4. Add the “includeIf” to the git configuration
  5. Verify the loading of the conditional git config (optional)

The baseline for setting the SSH-keys is already described in the second paragraph. Moving on to step 2.

Step 2: Git directory structure

Different directory structures are possible. I tried various structures like:

  • “~/git/[git-provider]/[git-user.name]” and
  • “~/[git-provider]/[git-user.name]”
  • “~/git/[git-user.name]@[git-provider]/”

My current preferred directory structure is “~/git/[git-user.name]@[git-provider]/”.

An example of my git “root” directories:

...
git
├── tisgoed@bitbucket.com
├── tisgoed@gitlab.com
└── tisgoed@github.com
...

The further description of the includeIf-method is based on this directory structure.

Step 3: The git-include file(s)

In my user-root (~) directory next to the .gitignore and the .gitconfig I created a directory named .gitinclude to store the include files.

...
.gitconfig
.gitignore
.gitinclude
├── tisgoed@bitbucket.com.include
├── tisgoed@gitlab.com.include
└── tisgoed@github.com.include
...

The following naming convention is used for the include files:

[git-user.name]@[git-provider].include

The content of an include file requires at least the following two sections:

  • user-section, user data
    • name, the user name at the git-provider.
    • email, the email address at the git-provider.
  • core-section,
    • sshCommand, the reference to the provider specific SSH-key

An include template:

[user]
    name = [git-user.name]
    email = [git-user.email]

[core]
    sshCommand = "ssh -i ~/.ssh/[user-name]@[git-provider].ed25519"

An include example, ~/.gitinclude/tisgoed@github.com.include

[user]
    name = tisgoed
    email = a.w.alberts@tisgoed.nl

[core]
    sshCommand = "ssh -i ~/.ssh/tisgoed@github.com.ed25519"

Step 4: The ~/.gitconfig file

Once an include file is created we can add the includeIf section to the ~/.gitconfig file.

At first the syntax for the includeIf directive is a bit confusing. Basically it comes down to the following pseudocode:

if (git-repository is placed within path) then
    add configuration from file
end if

The template for the includeIf section in the ~/.gitconfig file:

...
# Git includes

# git-user.name @ git-provider
[includeIf "gitdir/i:~/git/[git-user.name]@[git-provider]/"]
    path = ~/.gitinclude/[git-user.name]@[git.provider].include
...

An example with three different git accounts in the ~/.gitconfig file.

...
# Git includes

# tisgoed @ bitbucket.com
[includeIf "gitdir/i:~/git/tisgoed@bitbucket.com/"]
    path = ~/.gitinclude/tisgoed@bitbucket.com.include

# tisgoed @ github.com
[includeIf "gitdir/i:~/git/tisgoed@github.com/"]
    path = ~/.gitinclude/tisgoed@github.com.include

# tisgoed @ gitlab.com
[includeIf "gitdir/i:~/git/tisgoed@gitlab.com/"]
    path = ~/.gitinclude/tisgoed@gitlab.com.include
...

Step 5: Verify the git configuration

While setting things up you can use the git config --list command to check if the include file was loaded into the git configuration. It should display the user.name and the user.email from the include file.

⚠️ The following command only works in a ‘git’ directory.

The command:

git config --list | grep "user."

A sample of the expected output

user.name=tisgoed
user.email=a.w.alberts@tisgoed.nl

New repo workflow (method 2)

When you need to create a new repo at an existing git-provider, the following steps apply:

  1. Browse to your (git-provider)
  2. Login to your account (git-user.name)
  3. Create a new repository (repo-name)
  4. cd ~/git/[git-provider]
  5. git clone git@[git-provider]:[git-user.name]/[repo-name].git
  6. cd [repo-name]
  7. touch README.md
  8. git add .
  9. git commit -m "Initial commit"
  10. git push -u origin master

Conclusion

Pro’s and con’s of both solutions:

  • Use named SSH configurations in the ~/.ssh/config file.
    • Full flexibility on the location of your repositories.
    • Requires setting git-account per git-provider in ~/.ssh/config.
    • Requires setting the remote origin SSH-setting in the repository.
    • Requires you to remember the name of the SSH-section.
  • Using the git includeIf directive.
    • Static “root”-directory per git-account.
    • Create git-include file per git-account.
    • Requires changing your ~/.gitconfig file per git-account.

I tried both methods and personally I prefer the second option. It forces you to keep a clear separation between the different git-accounts, there is no need to change the default remote origin and there is no need to remember the name of the SSH-section.

References

Git conditional includes:

Git tooling:

ED25519:


  1. ED25519 is an elliptic curve cryptography(ECC) signature algorithm. The Ed25519 signature alghorithm offers high security signatures with a small signature size. ↩︎