Working with GitHub

Version control is essential for any non-trivial project. GitHub is a cloud-based Git (distributed version control system) repository manager that allows developers to store, share, and collaborate on a codebase. This chapter covers two basic concepts — Initial setup for SSH access, and Managing multiple git accounts from same machine.

Initial Setup: Creating and Configuring SSH

The approach supports multiple GitHub accounts. In this chapter, we would use two emails username@company.com and username@gmail.com for accessing the organisation's and personal repositories respectively. Accordingly, we would setup two GitHub accounts for these two identities — each with its own SSH key-pair, all on the same machine.

Step 1: Create or Log in to GitHub Account

First step is to just navigate to our GitHub instance (e.g. github.com or the organisation's self-hosted instance) and either sign-up or sign-in.

We can just use our emails username@company.com and username@gmail.com to create two accounts on GitHub. For Enterprise Managed Users (EMU), the system might have automatically created the GitHub accounts for the employees, and they just need to sign-in through internal identity system (like Okta or Azure) for accessing company account.

When we signup, we need to provide unique username that would be associated with our GitHub account. This username must be completely unique across the entire GitHub platform - which includes all personal and organisational accounts together. For our example, let's assume that two accounts associated with emails username@company.com and username@gmail.com are work-user and personal-user respectively.

Step 2: Generate an SSH Key-Pair

Let's check whether a .ssh directory already exists:

% ssh -V
OpenSSH_10.2p1, LibreSSL 3.3.6
 
% cd .ssh/
cd: no such file or directory: .ssh/

Since no SSH key exist yet, we need to generate our ed25519 (recommended over RSA and others) key-pairs from scratch on the terminal. We must have separate keys for different GitHub accounts to avoid the unintentional access/updates of the files. We start with the company account associated with email id username@company.com, and have accordingly named our key as id_ed25519_github_work_user. These values should be substituted as desired:

% ssh-keygen -t ed25519 -C "username@company.com"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/username/.ssh/id_ed25519): /Users/username/.ssh/id_ed25519_github_work_user
Created directory '/Users/username/.ssh'.
Enter passphrase for "/Users/username/.ssh/id_ed25519_github_work_user" (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /Users/username/.ssh/id_ed25519_github_work_user
Your public key has been saved in /Users/username/.ssh/id_ed25519_github_work_user.pub
The key fingerprint is:
...

Saving to a custom filename — id_ed25519_github_work_user instead of the default name id_ed25519 — allows us to maintain multiple key-pairs for different accounts or projects. Also, adding the keyword github in the name helps in identifying that this key is specifically for GitHub account. We can follow the same approach as mentioned above for creating another SSH key-pair, named as id_ed25519_github_personal, for our personal account associated with username@gmail.com.

Another important part is to add a passphrase while creating the SSH key. The primary advantage of using a passphrase is defense in depth — it prevents an unauthorized person from using the SSH key when the local computer or storage drive is compromised, stolen, or hacked. The only hurdle about passphrases is having to type them every single time we push the code to Git (or connect to a server for other usage of SSH key). We can entirely bypass this friction by using an SSH Agent (ssh-agent) or storing it in Keychain Access app in the macbook:

% ssh-add --apple-use-keychain ~/.ssh/id_ed25519_github_work_user
Enter passphrase for /Users/username/.ssh/id_ed25519_github_work_user: 
Identity added: /Users/username/.ssh/id_ed25519_github_work_user (username@company.com)

% ssh-add --apple-use-keychain ~/.ssh/id_ed25519_github_personal
Enter passphrase for /Users/username/.ssh/id_ed25519_github_personal: 
Identity added: /Users/username/.ssh/id_ed25519_github_personal (username@gmail.com)

To make these settings permanent, so that macOS doesn't forget the association when we restart the computer, we need to configure the SSH config file ~/.ssh/config:

% nano ~/.ssh/config

The ~/.ssh/config file needs to be edited as follows (again the suffixes company & personal and filenames id_ed25519_github_work_user & id_ed25519_github_personal needs to be substituted with actual names):

# Company Account Configuration
Host github.com-company
  HostName github.com
  User git
  AddKeysToAgent yes
  UseKeychain yes
  IdentityFile ~/.ssh/id_ed25519_github_work_user

# Personal Account Configuration
Host github.com-personal
  HostName github.com
  User git
  AddKeysToAgent yes
  UseKeychain yes
  IdentityFile ~/.ssh/id_ed25519_github_personal

Step 3: Add the Public Key to GitHub

Once the keys are generated and configured in local system, we need to provide the public keys to respective GitHub accounts. We can copy the public key to the clipboard using following command:

% tr -d '\n' < ~/.ssh/id_ed25519_github_work_user.pub | pbcopy

In GitHub, we need to navigate to Keys (Avatar → Settings → SSH and GPG Keys), and click on New SSH key button. We can paste the copied key here, add a title — clicking on Add SSH key saves the public key at GitHub's end.

We can follow the same approach for personal account as well.

Step 4: Verify the Connection

We can check if SSH key is working fine and git is successfully connected using following command:

% ssh -T git@github.com-company
Hi work-user! You've successfully authenticated, but GitHub does not provide shell access.

% ssh -T git@github.com-personal                             
Hi personal-user! You've successfully authenticated, but GitHub does not provide shell access.

% ssh -T git@github.com            
Hi work-user! You've successfully authenticated, but GitHub does not provide shell access.

We see the successful authentication messages which indicates that our primary setting of SSH key pairs are done. We can also see that when full Host alias (example, git@github.com-company) is provided as per ~/.ssh/config file, the account is resolved correctly with corresponding GitHub username — however when we provide default Host git@github.com, it utilises the first SSH key in ~/.ssh/config file, reflecting the work-user GitHub account. Rest of this chapter covers how these multiple accounts can be managed seamlessly by setting up correct defaults in different working folders.

Managing Multiple Accounts

If we look at the edits done in ~/.ssh/config file in step 2, we see the line User git appearing twice — once for each account — indicating both the users are named as git. This is further confirmed in step 4, where the part before the '@' symbol is the username ('git' for both the users). This means we always log into GitHub's servers as a user named git, regardless of whether it is a personal account, a company account, or someone else's account. GitHub does not create separate operating system accounts for millions of developers; everyone logs into the exact same gateway.

GitHub relies entirely on our SSH public-private key pair to figure out who we actually are. Our system sends a public key during the connection handshake. GitHub looks through its massive database to see which specific account (e.g., our work account) has that exact public key registered. Once it finds the match, GitHub maps the account to company/repo.git based on employee's specific permissions. This is why we must never reuse the exact same SSH key across two different GitHub accounts. If we upload the same key to both a personal and a company account, GitHub's database wouldn't know which account is trying to log in when we connect as git.

Once, we have different SSH key associated with two accounts and ~/.ssh/config file is updated, we can access both accounts as per the need. However, just relying on the sequencing of the keys in ~/.ssh/config file is not reliable. In fact, in the Step 4 of previous section, priority was given to first SSH key in ~/.ssh/config file only because it was an "SSH Server Authentication" and not a "Git Authoring Commit". When we use the git commands for authoring commits, the identity fallback mechanism (auto-detection) is completely different.

Once we have different SSH keys associated with two accounts and the ~/.ssh/config file is updated, we can access both accounts as needed. However, as seen in Step 4 of the previous section, using the default host git@github.com causes the system to read the ~/.ssh/config file from top to bottom and default to the first key listed (the work key). This routing handles "SSH Server Authentication" (pushing/pulling code). On the other hand, when we actually write and create a commit, Git uses a completely separate "Git Commit Authoring" identity fallback mechanism (auto-detection).

Git generally looks at the environment variables $USER or $LOGNAME on Linux/macOS or system's password database (/etc/passwd) to retrieve the account's full name. It also auto-generates the email using OS username and the system's hostname. Depending on the specific Git installation configuration, Git will do one of the following:

  • Successfully make the commit using the auto-detected credentials and print a warning in the terminal stating that it had to guess the identity.
  • Completely block the commit and throw the error message: "Please tell me who you are".

In summary, it can be dangerous to use Git with multiple accounts without updating the git configurations properly. Follwoing sub-sections describes the cleanest way to set this up on our system.

Step 1: Organise the Project Folders

We start with making sure that our local repositories are separated into distinct folders on our system. Here we create two folders — one for all work related repositiries and one for all personal repositories.

% mkdir ~/Projects/work ~/Projects/personal

Step 2: Set the Global Defaults

It is always better to set the global config to the personal identity. This acts as the safety net for any random folder outside of ~/Projects/work/, and keeps the work related confidential repositories safe.

% git config --global user.name "User Name"
% git config --global user.email "username@gmail.com"

In the above commands, user.name is just the display name and can be as per user's choice that would be shown against any git action. However, user.email needs to be exactly as per email-id associated with the personal GitHub account. GitHub does not look at the user.name string to link a commit to a profile — instead, it relies entirely on the user.email.

Step 3: Configure Git to Auto-Switch to Work Profile

Once the global default is set to personal identity, it becomes the fallback profile across all folders in the system. However, we want to explicitly use only work profile for all repositories in ~/Projects/work/ directory.

Git always first tries to resolve the identity based on the content in the ~/.gitconfig file. Currently this file contains following lines:

[user]
    name = User Name
    email = username@gmail.com

To use work profile identity when we are inside ~/Projects/work/ directory, we need to create and edit another parallel config file, let's say ~/.gitconfig-work:

% nano ~/.gitconfig-work

Inside the ~/.gitconfig-work file, we need add following content:

[user]
    name = User Name
    email = username@company.com

Once ~/.gitconfig-work is saved with work profile identity, we need to edit ~/.gitconfig file with the instruction to override global default identity with the content of ~/.gitconfig-work whenever we use git from ~/Projects/work/ directory.

% nano ~/.gitconfig

We need to append includeIf block in the ~/.gitconfig file to make final content as below:

[user]
    name = User Name
    email = username@gmail.com

[includeIf "gitdir:~/Projects/work/"]
    path = ~/.gitconfig-work

This is all that needs to be done. Let's use it and see how it behaves.

Step 4: Check Multiple Profile Configurations

We navigate through different directory and check the Git profile associated with those directories as below:

% git config user.email
username@gmail.com

% cd ~/Projects/work
% git config user.email 
username@company.com

% cd ~/Projects/personal
% git config user.email 
username@gmail.com

% cd ..
% git config user.email
username@gmail.com

We see that work profile is activated only when we are in the ~/Projects/work/ directory - everywhere else, Git always resolves it to the personal identity. This is the desired behaviour that we wanted to get for managing multiple GitHub profiles from same machine.

Step 5: Common Git Commands

Below are some commonly used commands for using Git:

# Clone a repository using your custom personal alias
% git clone git@github.com-personal:personal-user/project_name.git

# Stage changes
% git add .
 
# Commit
% git commit -m "Comments for commit"
 
# Rebase before pushing to keep history linear
% git pull -r
 
# Push
% git push

Note: Always run git pull -r (pull with rebase) before pushing. This keeps commit history linear and avoids unnecessary merge commits. Add a .gitignore file to exclude system artefacts such as .DS_Store, __pycache__/, *.pyc, and .ipynb_checkpoints/. A Python template is available at GitHub.