I work across 2 computers and 3 OS, all with nearly same development setup. For work I need some special care for my git config, since I want/need access to personal repositories.
The slightly more complex version control system configuration is due to the following requirements and expectations:
- I want to use my personal (and main) email address for my personal GitHub projects.
- I want to use my work email address for the workplace stuff.
- I want separate GPG keys for each email address.
- I want to sign my commits with the individual GPG keys for each of the addresses.
- I want separate SSH keys for each context (work, personal; but also for each computer).
- I want to use the individual SSH keys based on the project I work on.
- I want all of it happening as automatically as possible.
- I want to forget that it is there.
I know, I know, this looks like a huge list of requirements. But not only is this my personal preference, partially also my workplace has some strictler rules, especially when it comes to security.
So, if you cannot get rid of the first point and do need also access to your personal persona on GitHub (or any other git based hosting platform for that matter), this article might help you to achieve it.
By the way, this approach should work for both single and multiple GitHub accounts. Since GitHub makes it easy to keep using a single account while also being a member of an Enterprise organization, I haven't bothered testing it with a true multi-account configuration, but since you will use individual SSH and GPG keys for either way your computer won't really know the difference.
git does support the HTTPS transport, in most cases you will use the more preferred way of talking git+ssh instead. So let's tackle this lower level first:
If you working with GitHub you want to generate keys with the latest and greatest recommended algorithms:
ssh-keygen -t ed25519 -C "email@example.com"
If you work on other platforms, please check first, which algorithms are supported there.
In decreasing order of preference and security:
# prefer this if possible: ssh-keygen -t ed25519 # this is still quite okay: ssh-keygen -t ecdsa -b 521 # this only as a last resort option, should work everywhere: ssh-keygen -t rsa -b 4096 # avoid this, please; # also GitHub does not allow new keys with DSA anymore! ssh-keygen -t dsa
More details about key generations at: https://www.ssh.com/ssh/keygen/
### github ### -- PERSONAL/MAIN ACCOUNT -- Host github.com Hostname github.com User <YOUR GITHUB USERNAME> IdentityFile ~/.ssh/<YOUR PERSONAL SSH KEY> ### -- WORK PERSONA/ACCOUNT -- Host github.com-work Hostname github.com User <YOUR GITHUB USERNAME> IdentityFile ~/.ssh/<YOUR WORK SSH KEY> ### general Host * AddKeysToAgent yes IdentitiesOnly yes PreferredAuthentications publickey Compression yes
Important note: Do not change the order of the configuration.
For each parameter, the first obtained value will be used. The configuration files contain sections separated by ''Host'' specifications, and that section is only applied for hosts that match one of the patterns given in the specification. The matched host name is the one given on the command line.
Since the first obtained value for each parameter is used, more host-specific declarations should be given near the beginning of the file, and general defaults at the end. ssh_config(5) manual
Since we will use two different hosts, we must repeat the
Hostname line, but also want to specify
IdentityFile specifically. If you use your ssh config only for git and also have only a single user name, you could move that config line into the generic section in the bottom. Though I still prefer the explicit repetition for each specific Host block.
At this point you might wonder »How is github.com-work supposed to function?« Hold that thought, we will come back to it later.
GPG key management
You want to use GPG for signing commits. Your workplace might not require it (yet), but if you have any level of trust issues, or just want to be sure that a commit was made by you and be able to prove it, use commit signing.
Interestingly GitHub does not recommend you to use a more modern algorithm and requires you to pick RSA. A bit sad, but RSA with 4096 bits seems to be still fine for this purpose.
After you have created your keys, you should grab the IDs for them:
gpg --list-secret-keys --keyid-format LONG # I use shorter IDs and git doesn't seem to struggle gpg --list-secret-keys --keyid-format SHORT
/home/asaaki/.gnupg/pubring.kbx ------------------------------- sec rsa4096/D73D7242 2021-02-14 [SC] AE93078BDC15BF6A84767DBBA3CBBB61D73D7242 uid [ultimate] TEST KEY <firstname.lastname@example.org> ssb rsa4096/57047776 2021-02-14 [E]
The ID is the part after the
rsa4096/ from the
In the LONG variant it just used more characters from the whole fingerprint (which you can completely see in the line between sec and uid).
git command line interface (CLI) improved a lot over the years. I remember that I used a custom
GI_SSH=… wrapper in my shell environments to make this whole host/key mapping possible, but thanks to the power of git configs this is a thing of the past.
[init] defaultBranch = main [commit] gpgsign = true # other sections cut for brevity; add the following to the bottom: [user] name = Your Name useConfigOnly = true # optional (if you sometimes work outside of your regular directories) [include] path = ~/.gitconfig.personal [includeIf "gitdir:~/Development/Personal/"] path = ~/.gitconfig.personal [includeIf "gitdir:~/Development/Work/"] path = ~/.gitconfig.work
[user] email = email@example.com signingkey = <PERSONAL GPG SIGNING KEY ID>
[user] email = firstname.lastname@example.org signingkey = <WORK GPG SIGNING KEY ID> [url "email@example.com"] insteadOf = firstname.lastname@example.org
includeIf are the key components to separate out specific configurations based on where you are on your system.
include is always pulled in, this is useful if you really want to separate out parts of your main
If you only work in specific directories like
~/Development/Work/ — and this means where you also need to commit and push — then you could remove the general
[include] section entirely. I keep it around, because I might have checked out a repository somewhere else and don't want to move it for a commit.
There's probably a better way to organize this, but the above has served me well for quite some time now.
I recently added the global
[user] section for setting
useConfigOnly = true and the name, since I only have one.
useConfigOnly prevents git from guessing name and email and forces it to read it from the configuration files. If the email would be missing, git will complain the next time you try to commit or push. And you will know that something is broken in the configuration.
Important note: The trailing slashes (
/) on the
[includeIf …] lines are very important. If you forget them, then git would try to match only this very specific folder and ignore it for any folder within it. More details about conditional includes in the git documentation. (I totally missed that you can use them even for branches, too.)
signingkey values should be set appropriately based on the IDs you have noted from the previous section of this article. Now when you commit anything git will use the correct key based on where the repository directory lives.
To automatically enforce the commit signing, use
commit.gpgsign set to
Remember the question you had earlier about the weird looking Host configuration in SSH? The
[url "email@example.com"] section is the counterpart making it work, because git will do the translation when you are in your company's repos.
What will happen is the following:
- You are in a work related repository.
- You want to fetch/pull the latest changes or push your local state to the origin.
- Since git will load the work config, it replaces the regular URLs having
firstname.lastname@example.org it with the value of the
[url …], here
- git reaches one level down and uses ssh for the communication.
- SSH sees a
github.com-workhost and tries to find all matching configurations for that, including the exact one we have defined above.
- SSH will ignore our personal configuration, because the regular
github.comdoes not match anymore.
- SSH picks up your work identity file for authentication with GitHub's server.
- SSH will also use the actual
Host(translating it back again).
- Everyone is happy. \o/
What's with this
[init] block you haven't mentioned before?
Since Git 2.28 you can set a name for the default branch when creating a new repository. Be a good citizen and avoid offensive and negatively connotated terms. The wider developer community seems to like "main" as a good replacement for the previous default name. You can read more about renaming existing default brances on GitHub.
To quickly check which config is used execute the following command in a repository:
git remote -v
The URL should provide you a quick hint if the correct profile is used.
Alternatively you can also run:
git config --show-origin --get user.email
This will also print where the final value was retrieved from.
Git came a long way and I'm very glad and happy that we can have such setup without a lot of manual tinkering and workarounds. Gone are all my custom scripts, snippets, and
.envrc's for that purpose, which were also not completely platform agnostic.
Top comments (3)
Awesome article :)
It was really helpful to me.
I have some questions though. Pmed you in twitter
Do i have to write this for work email?
git clone email@example.com:username/repository-name.git
How do i switch between those 2 accounts locally when i'm about to push my code? Say, one project for personal and another project for work in GitHub
Will your config auto-switch between personal and work ssh/gpg key when i'm in work or personal related repo?
How the detection works from command line?
For Q1: No, you don't have to do that at all, that's why we have the following piece in the git config:
and the matching ssh config:
So when you do git stuff in your work repositories, git will first rewrite the URLs to
firstname.lastname@example.org the underlying SSH will then use this host to look up the related configuration part and make calls to the real
email@example.com becomes just a label between git and ssh, but you as a user shouldn't need to do anything manually.
For Q2: The switching is done through convention, meaning: it depends on where the repository lives on your disk.
The convention is, personal projects shall go into the
~/Development/Personal/folder, and work related repos in
includeIfis as the name suggest a conditional loading, the condition is "where on the computer is the action happening".
Note: to also support repos outside of those two locations, the personal profile is the default for everything. You can decide on your work machine to use instead the work profile instead; for that adjust the config location of the following piece:
To Q3: As already touched by the answer to Q2, the switching happens solely based on the location on your computer. That is literally the only "magic" in this whole approach. Place the repos into the right locations and the matching configuration will be used.
Does that clarify it for you?
Yes, I can't thank you enough.