BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles How to Use Multiple GitHub Accounts

How to Use Multiple GitHub Accounts

Key Takeaways

  • Account configuration in Git has two separate sections: SSH credentials and changes committer.
  • Git does not allow account configuration for a group of repositories in one place.
  • Mixing personal and work accounts may lead to a polluted Git history.
  • The presented solution automatically loads Git credentials based on a directory path using a special global Git configuration, which does not require additional tools.
  • An alternative solution using the "direnv" tool is incompatible with IDEs.
  • An alternative solution with "SSH hosting aliases" requires alias memorization and specifying a custom Git URL during a repository clone operation.

Git is a popular tool for version control in software development. It is not uncommon to use multiple Git accounts. You might have one account for personal projects and a separate account for your work. Correctly configuring and switching Git accounts is challenging. In this article, I will show what Git provides for account configuration, its limitations, and the solution to switch accounts automatically based on a project parent directory location.

Account Configuration

Account configuration in Git has two separate sections.

First, you need to connect to a remote repository. There are multiple ways to do it, but one of the popular remote Git providers, Github, deprecated HTTPS connections with username and password. Today, the Secure Shell Protocol, or SSH for short, is the standard connection protocol used in Git. In this article, I will also use SSH for remote Git operations.

Second, if available, the account email and name are added to each unit of history change, marked as "commit" in Git. This committer account has nothing in common with SSH credentials, and if misconfigured, it can lead to a polluted Git history with a personal email used at work.

In the following sections, I will review each configuration area in detail.

SSH Connection with Keys

SSH provides multiple ways to connect to a remote host. One popular method is using an SSH key. Major Git providers, including GitHub, GitLab, and Bitbucket, support this type of connection. A key should be stored securely on your computer and referenced during a remote operation request with SSH.

The default directory for SSH keys storage is <USER_HOME_DIR>/.ssh/, which, for Unix-based systems, is ~/.ssh/ and for Windows, is %userprofile%\.ssh\.

First, I will generate a personal key called "id_ed25519_personal" with the Ed25519 signature signing system. In Unix-based systems, run:

> ssh-keygen -t ed25519 -f "~/.ssh/id_ed25519_personal" -C 
"your_email@example.com"

And in Windows:

> ssh-keygen -t ed25519 -f "%userprofile%/.ssh/id_ed25519_personal" 
-C "your_email@example.com"

During the command execution, ssh-keygen will ask you about passphrase encryption for the key file. Using a passphrase safeguards your private key against unauthorized duplication and usage, even in the event of a compromised computer. However, the inconvenience of passphrases arises from the necessity to input them each time you establish an SSH connection. It is possible to cache the password in a keychain to reduce passphrase input, and you can follow the guides for MacOS and Windows to set it up yourself. I put an empty value in the terminal, which resulted in an unencrypted file.

The "-C" flag adds a string comment, which is not used during connections. It is a good practice to add an email comment to understand what account you plan to use with the key.

Executing the ssh-keygen command will produce two keys: the public key "id_ed25519_personal.pub" with the ".pub" postfix and the private key "id_ed25519_personal" without a postfix.

Remote Git providers allow the addition of an SSH public key to their account settings. Check the instructions for GitHub and GitLab, and ensure you added your public key content without the comment to your provider.

Similarly, I generated SSH keys for "Organization A" and "Organization B." The changes lead to the next file structure result:

<home-directory>
└── .ssh
    ├── id_ed25519_personal
    ├── id_ed25519_personal.pub
    ├── id_ed25519_organization_a
    ├── id_ed25519_organization_a.pub
    ├── id_ed25519_organization_b
    ├── id_ed25519_organization_b.pub

I will use this location style in all the code examples.
An alternative way is to group keys into directories, as shown in the following schema.

<home-directory>
└── .ssh
    ├── personal
    │   ├── id_ed25519
    │   └── id_ed25519.pub
    ├── organization-a
    │   ├── id_ed25519
    │   └── id_ed25519.pub
    └── organization-b
        ├── id_ed25519
        └── id_ed25519.pub

Git Repositories and Branches

The base unit of work in Git is a directory called a repository. You can choose any directory to store your files and enable Git in it by running:

> cd <your-project-directory>
> git init

Git will create a hidden folder called ".git" inside your current terminal path. Your directory has now become a repository. Git will track all changes to your files, allow you to commit the changes, and store file snapshots inside the ".git" folder.

Branching is a crucial Git concept. Each branch holds a snapshot with specific versions of all your repository files. You can deviate the development flow with files changing by switching between branches.

Historically, the first branch that Git creates is called "master", but you can change it, and GitHub recommends using the "main" name by executing the following rename command:

> git branch --move master main

Run the following command to see all your local branches and the active one:

> git branch

To be able to upload changes to a remote repository, you can link a remote URL with the following command:

> git remote add origin https://<provider>/<path>/<repository>.git 

The "origin" value is a conventional name. You can specify any other remote repository alias and even add multiple remote URLs to one repository, for example:

> git remote add remote-provider2 
https://<provider2>/<path>/<repository>.git 

To list all linked remote repositories, run the following:

> git remote -v

Then, it becomes possible to get changes from a remote repository and merge them to your local version with the "git pull <remote-id> <remote-branch>" command. For example:

> git pull origin main

Similarly, you can push changes to a remote repository with the "git push <remote-id> <remote-branch>" command.

For brevity, I will skip describing how to commit changes to a local repository, but you can check Atlasian, GitHub, and GitLab articles.

Inside every repository, Git stores configuration in a particular file located at .git/config.

Repository configuration settings include the default branch name and remote repository links. A GitHub version can have the following content:

[remote "origin"]
	url = git@github.com:<organization>/<repository>.git
[branch "main"]
	remote = origin

Git Configuration Hierarchy

Git settings can be spread across multiple locations, and Git will merge them according to the hierarchy.

The first configuration level that overrides all other settings is inside the repository and is called local. The configuration is located at <repository-path>/.git/config.

To see your repository Git settings, run

>  git config --list --local

The second configuration level is called global and is applied to all repositories for the current system user, or in other words, everything in <USER_HOME_DIR>. Global configuration is optional, and when it exists, the default file location is at "~/.gitconfig" in Unix-based systems and "%userprofile%\.gitconfig" in Windows. To see the global configuration settings, execute:

> git config --list --global

The command can result in an error if the global configuration does not exist.

The third one is called system level and applies to all operating system users and their repositories. The configuration file is optionally located at "/etc/gitconfig" for Unix-based systems and "C:\ProgramData\Git\config" or "<git-installation-path>\etc\config" for Windows. To check the Git system level configuration, run the following:

> git config --list --system

As the file is optional, the command can also result in an error.

The local level has precedence over all other levels, and the global level has precedence over the system level. There is one more level below the local called worktree, but its usage is outside the scope of this article.

Git merges all the configuration settings for a repository: unique records are added, and settings with the same keys are overridden according to the level hierarchy.

To see the final merged configuration result, run inside a repository the command without a level argument:

> git config --list

Git Account Switch Challenge

For every committed change, Git adds an author name and email to the history when specified.

To set your committer settings for the current repository, you can run:

> git config --local user.name "Your name"
> git config --local user.email "your_email@example.com"

The file "<repository>/.git/config" will include the following content:

[user]
	name = Your name
	email = your_email@example.com

You can also specify default committer settings for all your repositories at the global level by executing the following:

> git config --global user.name "Your default name"
> git config --global user.email "your_default_email@example.com"

Specifying user settings at the system level is less practical as users will likely have different names and emails. So, there are only two practical options for committer settings in Git: the global level for all your repositories and the local level for one repository.

It is convenient to group repositories under their parent folder (personal, organization-a, or organization-b) and to have the next directories structure:

<path-to-projects>
└── personal
    ├── open-source-repository-1
    └── open-source-repository-N
└── organization-a
    ├── organization-a-repository-1
    └── organization-a-repository-N
└── organization-b
    ├── organization-b-repository-1
    └── organization-b-repository-N

For working with open-source repositories, I want to use personal email as a Git committer, for Organization A - a company email, and for Organization B  - another email. As was shown earlier, for that case, Git demands that user configuration be specified at the local level at each repository. The global level will not help here due to its too broad application to all repositories. So, Git configuration assumes the use of the following structure:

<path-to-projects>
└── personal
│   ├── open-source-repository-1
│    │   ├──.gitconfig
│   └── open-source-repository-N
│        └── .gitconfig
├── organization-a
│   ├── organization-a-repository-1
│    │   ├──.gitconfig
│   └── organization-a-repository-N
│        └── .gitconfig
└── organization-b
    ├── organization-b-repository-1
     │   ├──.gitconfig
    └── organization-b-repository-N
         └── .gitconfig

To download a remote repository to your computer, use the "git clone" command. For example, for Organization A:

> cd <path-to-projects>/organization-a
> git clone git@github.com:organization-a/<repository>.git

When cloning, make sure that you use the Git SSH URL. GitHub has a dedicated tab for that:

The executed command will automatically add remote URL settings to your local Git configuration level, but you must manually specify a user name and user email settings. That manual configuration should be done each time you clone a repository.

A better solution would be configuring user settings once per repository group folder. The optimized solution should have the following file structure:

<path-to-projects>
└── personal
    ├──.gitconfig
    ├── open-source-repository-1
    └── open-source-repository-N
└── organization-a
    ├──.gitconfig
    ├── organization-a-repository-1
    └── organization-a-repository-N
└── organization-b
    ├──.gitconfig
    ├── organization-b-repository-1
    └── organization-b-repository-N

Solution

Parent Directory Configuration

First, add an empty ".gitconfig" file for each group folder, as shown in the previous section. Then, populate each configuration file with its relative user information. For example, "<path-to-projects>/organization-a/.gitconfig" might have the next content:

[user]
	name = Your name
	email = your_email@organization-a.com

Second, Git must know what SSH key to use when executing remote commands. There is a special Git configuration section called core.sshCommand for it. Add a path to an SSH key in each ".gitconfig" file similar to the following Organization A example:

[user]
	name = Your name
	email = your_email@organization-a.com
[core]
	sshCommand = ssh -i ~/.ssh/id_ed25519_organization_a

Global Conditional Configuration

The final step is to make Git load settings from the repositories’ parent folders.

Git introduced conditional configuration referencing in version 2.13 and fixed the related bugs in version 2.23. So, before applying the following approach, ensure that your Git version is no less than 2.23 by running the "git -v" command.

Add to your global Git configuration file, which is located at <USER_HOME_DIR>/.gitconfig, content similar to the following example:

[includeIf "gitdir:~/<path-to-projects>/personal/**"]
        path = ~/<path-to-projects>/personal/.gitconfig
[includeIf "gitdir:~/<path-to-projects>/organization-a/**"]
        path = ~/<path-to-projects>/organization-a/.gitconfig
[includeIf "gitdir:~/<path-to-projects>/organization-b/**"]
        path = ~/<path-to-projects>/organization-b/.gitconfig

Let me review the first configuration block in detail.
[includeIf "gitdir:~/<path-to-projects>/personal/**"]
        path = ~/<path-to-projects>/personal/.gitconfig

Let me review the first configuration block in detail.

[includeIf "gitdir:~/<path-to-projects>/personal/**"]
        path = ~/<path-to-projects>/personal/.gitconfig

The "includeIf" section says that another Git configuration file, located at ~/<path-to-projects>/personal/.gitconfig, should be included in the current one if the condition is met. The condition "gitdir:~/<path-to-projects>/personal/**" means that Git will check if the current repository is located at any deep level in the directory <USER_HOME_DIR>/<path-to-projects>/personal/.

After restarting your terminal session, Git will automatically load specific settings based on the configuration of the group repositories directories.

Testing Git Settings

To check the loaded settings, run inside a repository the following command:

> git config -l

For any repository of Organization A, the result should include the following lines:

user.name=Your name
user.email=your_email@organization-a.com
core.sshcommand=ssh -i ~/.ssh/id_ed25519_organization_a

The same lines should be shown if you run "git config -l" inside the parent directory at <path-to-projects>/organization-a>.

Similarly, other group folders, personal and organization-b, and their repositories should show specific settings accordingly.
It is also possible to check the exact parameters for Git configuration. For instance, to show only the user email, run

> git config --get user.email

All remote operations that are allowed to your SSH user account should also work without an error in Git.

Test downloading a repository to a group folder, for example, for Organization A:

> cd <path-to-projects>/organization-a
> git clone <remote-repository-ssh-path>

After the successful repository downloading, test changes retrieval from a remote URL:

>  cd <path-to-projects>/organization-a/org-A-repository-1
> git pull origin <branch-name>

Complete testing with your favorite integrated development environment (IDE), whether it is Visual Studio Code, Eclipse, Vim, or one of JetBrains products. All related Git functionality should work without a problem in an IDE.

Downsides of Alternative Solutions

There are many popular alternative solutions on the Internet, but they all have downsides compared to the presented one.

One alternative solution is to install the direnv tool. It allows you to execute custom scripts once you switch a directory in a shell. This approach has two downsides. First, you must install the tool that hooks inside an operating system terminal. Second, this solution does not work with IDEs. You can only run Git commands manually inside your terminal shell.

Another solution is to set different SSH hosting aliases in the ~/.ssh/config file. For GitHub, it can look as follows:

Host gh-personal
  Hostname github.com
  PreferredAuthentications publickey
  IdentityFile ~/.ssh/id_ed25519_personal

Host gh-organization-a
  Hostname github.com
  PreferredAuthentications publickey
  IdentityFile ~/.ssh/id_ed25519_ogranization_a

Then, every time you clone a repository, you need to reference one of the host aliases instead of github.com. For example:

> git clone git@gh-organization-a:organization-a/<repository>.git

That solution will work with IDEs. The downside of this approach is that you must memorize aliases and manually update the cloning URL each time you want to download a new repository.

The article’s solution has no such limitations. Once group folder settings are set up, Git operations will work for any new repository inside it.

Conclusion

Git does not allow you to configure account settings for a group of repositories in one place. However, it is possible to automatically load Git credentials based on a directory path with a particular global configuration. This solution requires no external library installation and is compatible with IDEs.

About the Author

Rate this Article

Adoption
Style

BT