“How to Use Multiple GitHub Accounts”

2024-05-14 10:41:09

Git is a widely used version control system in the field of software development. Developers often need to manage multiple Git accounts, for example, using one account for personal projects and another for workplace jobs. How to properly configure and switch between these Git accounts often becomes a technical challenge. This article aims to introduce

Git is a widely used version control system in the field of software development. Developers often need to manage multiple Git accounts, for example, using one account for personal projects and another for workplace jobs. How to properly configure and switch between these Git accounts often becomes a technical challenge. This article aims to introduce the functionalities that Git can offer in terms of account configuration, the existing limitations, and an efficient solution for automatically switching accounts based on the location of the project’s parent directory.

Account Configuration

In Git, account configuration mainly consists of two separate parts. First and most importantly, you must establish a connection with the remote repository. Although there are various ways to achieve this, popular remote Git service providers such as GitHub have gradually phased out the practice of using usernames and passwords for HTTPS connections. Instead, the Secure Shell Protocol (SSH) has become the standard connection mechanism in the Git environment. Here, we will perform all remote Git operations through SSH.

Secondly, once set up, the account’s email address and username will be recorded in every change history, which is referred to as a commit in Git. It is important to note that there is no direct correlation between the information of the committer and SSH credentials. Improper configuration may result in personal email addresses being incorrectly marked in work records, thus contaminating Git’s historical record.

SSH Key Connection

SSH provides several ways to implement remote host connections, and using SSH keys is one of the popular methods. Major Git service providers, such as GitHub, GitLab, and Bitbucket, all support SSH key connections. These keys must be securely stored on your computer to be invoked when you need to perform SSH remote operations.

SSH keys are typically stored in the /.ssh/ directory. On Unix systems, it is usually ~/.ssh/, while on Windows systems, it is %userprofile%\.ssh\. To generate a personal SSH key, for example, to create a key signed by the Ed25519 algorithm named “id_ed25519_personal,” you can execute the following command on a Unix system:

ssh-keygen -t ed25519 -f "~/.ssh/id_ed25519_personal" -C "[email protected]"

And in the Windows system, run:

ssh-keygen -t ed25519 -f "%userprofile%/.ssh/id_ed25519_personal" -C "[email protected]"

During the execution of the above command, the ssh-keygen tool will prompt you to enter a password to create an encrypted key file.

Using a passphrase is a method to protect the private key from being copied and used without authorization, and it still offers protection even if there are issues with the computer. However, one inconvenience of using a passphrase is that you need to enter it each time you establish an SSH connection. To alleviate this inconvenience, you can cache the passphrase in the keychain to reduce the frequency of input. For MacOS and Windows systems, you can set this up following specific guidelines.

After entering a null value in the terminal, I created an unencrypted file. The -C flag adds a string comment that can be ignored during the connection. It is wise to add a corresponding email as a comment so that you can verify the account key used. Executing the ssh-keygen command will generate a pair of keys: a public key with the “.pub” suffix “id_ed25519_personal.pub” and a private key without a suffix “id_ed25519_personal”.

Remote Git providers typically allow users to add SSH public keys in their account settings. Refer to the related instructions for GitHub and GitLab to ensure that you have added the public key content correctly and have not provided unnecessary comments. For “Organization A” and “Organization B”, I also generated SSH keys, resulting in the following file structure:


<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

The code example will inherit and use this file structure style. Another way is to group keys and place them in different directories:


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

In Git, the basic unit of work is the repository, which is a directory used to store files. To enable Git in any directory, you can execute the following commands:


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

The commands above will create a hidden folder named “.git” in the current path of the terminal.

Our directory has now been transformed into a Git repository which can track each change to the files, allowing a user to commit changes and store snapshots of these files in the “.git” directory. An essential concept in Git is branches. Each branch represents a snapshot of the repository’s files at a particular version. By switching between different branches, we can modify the files, diverging from the original development process.

In Git convention, the first branch created is called “master”. However, this name can now be changed, for example, GitHub recommends using the branch name “main”. We can rename the branch with the following command:

> git branch –move master main

We can execute the following command to see all local branches and the currently active branch:

> git branch

To push changes to a remote repository, we need to associate the local repository with a remote URL with the following command:

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

Here, “origin” is a default name, but users can use any other name to name the remote repository. You can also add multiple remote URLs to the repository like this:

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

To view all connected remote repositories, you can use the following command:

> git remote -v

Afterward, we can fetch changes from the remote repository and merge them into our local branch with the following command:

> git pull origin main

Similarly, we can push changes to the remote repository with the following command:

> git push <remote-id> <remote-branch>

We can find detailed descriptions of how to commit changes to the local repository in various materials, such as articles from Atlassian, GitHub, and GitLab. Moreover, Git saves the configuration of each repository in a specific file located in .git/config. The settings for repository configuration include the default branch name and remote repository links, for example, the configuration content for the GitHub version might look like this:

[remote “origin”]
url = [email protected]:<organization>/<repository>.git
[branch “main”]
remote = origin

Git configurations are hierarchical, with settings that can be distributed across multiple locations, and Git will merge them according to this hierarchy.

As a version control system, GIT provides a flexible configuration hierarchy to accommodate various workflows. The most basic level of GIT configuration is the local configuration, which is specific to a single repository. These configurations are stored in the /.git/config file and have the highest priority, capable of overriding all other levels of configuration. To view the configuration of a specific repository, use the following command:

git config –list –local

The next level is the global configuration, which applies to all GIT repositories for the current system user. Located in the user’s home directory, the file location varies depending on the operating system; in Unix systems, it is ~/.gitconfig, and in Windows systems, it is %userprofile%\.gitconfig. The global configuration can be viewed using the following command:

git config –list –global

Finally, there is the system-level configuration, which applies to all users and their repositories in the operating system. The configuration file is located in /etc/gitconfig on Unix systems and C:\ProgramData\Git\config or \etc\config on Windows systems. The command to check the system-level configuration is:

git config –list –system

Another level of configuration is the worktree level, but this goes beyond the scope of this article. GIT integrates settings from different configuration levels, overrides items with duplicate keys, and forms the final configuration according to the hierarchical priority. To view all merged configurations, use:

git config –list

In GIT work, for each commit, the system adds the author’s name and email address to the record based on the configuration. To set user information for the current repository, you can execute:

git config –local user.name “Your name”
git config –local user.email “[email protected]

This will add the following entries in the /.git/config file:

[user]
    name = Your name
    email = [email protected]

To set user information globally for all repositories, you can execute:

git config –global user.name “Your default name”
git config –global user.email “[email protected]

Setting user information at the system level is uncommon because there may be multiple users with different names and email addresses on one system.

In the world of Git, configuring the committer’s information is a fundamental operation. In practice, there is often a need to set different committer information for different projects or organizations.

Git provides two levels of settings for configuring committer information:

  • Global level – these are settings that apply to all repositories of the user.
  • Local level – these are settings that apply only to a single repository.

To manage repositories, an effective organizational method is to group them according to the categories they belong to, such as personal projects, Organization A, and Organization B, etc. This can form a clear directory 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 example, in personal projects, you may want to use your personal email as the Git committer information; while in Organization A’s project, use the email provided by the organization; and in Organization B’s project, similarly use a different email provided by that organization.

Considering this need, you must configure user information at the local level for each repository, as the global configuration is too broad and cannot meet the requirements for precise configuration. Therefore, your Git configuration structure should look as follows:

        <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
    

When you need to clone a remote repository to a local computer, you can achieve this using the “git clone” command. For example, to clone an Organization A’s repository, you could execute the following command:

        > cd <path-to-projects>/organization-a
        > git clone [email protected]:organization-a/<repository>.git
    

When performing the clone operation, make sure you are using the correct Git SSH URL to point to the remote repository you wish to download.

While using GitHub, there is a convenient option available for automatically setting the remote URL for local Git. However, users must manually set their personal username and email address. Although this process needs to be done each time when cloning a repository, it is not entirely satisfactory. A more efficient method is to configure user information just once within each project group folder.

To achieve this goal, we can establish an optimized file hierarchy, as shown below:

<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

Parent Directory Configuration

Firstly, create an empty file named “.gitconfig” for each group folder. Then, fill these configuration files with the corresponding user information. For example, “<path-to-projects>/organization-a/.gitconfig” might contain the following:

[user]
    name = Your name
    email = [email protected]

Secondly, we need to specify which SSH key Git should use when executing remote commands. This can be set through the special Git configuration section “core.sshCommand” to designate the path of the SSH key. Here is the configuration example for organization A:

[core]
sshCommand = ssh -i ~/.ssh/id_ed25519_organization_a

Global Conditional Configuration

Lastly, to enable Git to load configurations from a repository’s parent folder, we use the conditional configuration feature introduced in Git version 2.13. This feature allows Git to take into account the current repository path when loading settings. To use this method, please ensure your Git version is at least 2.23 by running the “git -v” command.

To set up a conditional configuration in the global Git configuration file, first edit the .gitconfig file located in your home directory. The specific setup is as follows, assuming you want to establish three configurations for different project paths:

[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

In the first configuration block’s case:

The includeIf command states a condition, if the condition holds, another Git configuration file will be included in the current configuration. The condition—gitdir:~/<path-to-projects>/personal/**—indicates that Git will check if the current repository is located in the <path-to-projects>/personal/ directory or its subdirectories under the user’s home directory.

After you restart your terminal or command line window, Git will automatically load the corresponding settings based on the repository directory you are in.

To verify whether Git has loaded the settings correctly, you can execute the following command in the repository directory to check:

> git config -l

For example, if you execute this command in any repository belonging to organization A, you should see the following configuration information:

user.name=Your name
[email protected]
core.sshcommand=ssh -i ~/.ssh/id_ed25519_organization_a

Similarly, if you run the git config -l command in the parent directory of <path-to-projects>/organization-a, it should display the same configuration information lines.

In a similar fashion, individual and organization B‘s folders and their respective repositories will also reflect corresponding settings. You can also verify specific Git configuration parameters. For example, to view the user email configuration, just execute the following command:

git config –get user.email

SSH user accounts allow all remote operations in Git, and should not generate any errors. During testing, an attempt will be made to clone the repository into the specified organization folder, using organization A as an example:

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

After the repository is successfully cloned, the next test is to update the change in the remote URL:

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

To complete the test, it is recommended to use your preferred Integrated Development Environment (IDE), whether that’s Visual Studio Code, Eclipse, Vim, or any JetBrains product. All features related to Git should function normally within the IDE.

However, many of the popular alternatives found on the internet are not as sufficient compared to the current solutions. For example, one alternative is to install the direnv tool, which allows us to execute predefined custom scripts upon switching shell directories. But this method has two main drawbacks: first, we need to install an extra tool that integrates with the system terminal; and second, this solution is not applicable within an IDE environment and requires manual execution of Git commands in the terminal shell.

Another alternative method is to set different SSH host aliases in the ~/.ssh/config file. For instance, with GitHub, you could set it up like this:

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

In such setup, each time you clone a repository, it is necessary to refer to the corresponding host alias instead of directly using github.com.

When cloning repositories in an IDE using Git, we usually encounter the issue of constantly having to manually update the clone URL. For example, using the following command:

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

However, the solution proposed in this article avoids this cumbersome process. By setting group folder configurations, Git operations can automatically apply to any new repositories without the need to remember aliases or manually update the clone URL.

The conclusion is that although Git itself does not allow us to configure account settings for a group of repositories at a single location, we can leverage directory paths with specific global configurations to automatically load Git credentials. This method requires no installation of external libraries and is seamlessly compatible with IDEs.