Have you ever had the problem where you need different git settings depending on the repo you are working with? Or have you ever wanted to customize something about your workflow for only a subset of your local repos? I have these problems frequently!
In this post I am going to show you some tricks for working with multiple configuration files and the structure I use to do this. Along the way I’m also going to show you some other great features about git!
The Problem
If you are like me, you may have side projects, personal projects and everyday work. All of these require different git configurations, credentials, githooks and any other number of differences.
This has led to commits under the wrong author, the wrong hooks being applied, etc. It can get downright confusing! So how does one fix this once and for all? The answer lies in .gitconfig
First, let’s start with the simplest configuration. This sets up my identity for authoring commits.
[user]
name = Ross Edman
email = "my.personal.email@gmail.com"
On my local machine I will be authoring those commits against a directory of code repositories in the structure shown below.
~/code
├── github.com
│ ├── rossedman # requires personal email
│ │ ├── repo1
│ │ ├── repo2
│ │ └── repo3
└── git.jobcorp.com # requires work email
└── team
├── repo1
├── repo2
└── repo3
Each of these repos require different author emails for the commits to be valid. I don’t want to author commits at work under my personal email. The outcome I want is when I’m under ~/code/git.jobcorp.com/team/*
I need my ~/.gitconfig
to become:
[user]
name = Ross Edman
email = "my.work.email@gmail.com"
Luckily, git has numerous ways to solve this problem! Let’s dig in.
The Big Easy
The first way to solve this problem is the easiest and least scalable, just set the config each time you init a new repo, like this:
git config user.email "my.work.email@gmail.com"
That doesn’t seem to hard but forget a few a times or do it hundreds of times and you will realize this isn’t great and doesn’t cover more complicated needs.
A Little Better
The second way to solve this is by using git-templates
. These templates are copied whenever you clone or init a new repo. You could have two template repos that you could init from like this.
~/my-git-templates
├── personal
│ ├── .gitconfig
└── work # requires work email
└── .gitconfig
And to use these you could run
git clone --template ~/my-git-templates/work git.jobcorp.com/team/repo1.git
This gets you somewhat further but still feels clunky and error prone because you have to remember to use the --template
flag.
Conditional Includes
Now to our final answer, conditional includes! Hooray! In git
there is a declaration called includeif
in the git config syntax that does exactly what we need.
You can include a config file from another conditionally by setting a includeIf..path variable to the name of the file to be included.
Here is an example of how this would be used in a .gitconfig
file
[includeIf "gitdir:~/path/to/recognize/"]
path = ~/path/to/git/config
This would include the config ~/path/to/git/config
in any git repo under this directory: ~/path/to/recognize/*
. That’s pretty slick.
Referencing our code layout structure from before, we only want to change our email when working under ~/code/git.jobcorp.com/
. Taking the example from above, our .gitconfig
should then become:
[user]
name = Ross Edman
email = "my.work.email@gmail.com"
+[includeIf "gitdir:~/code/git.jobcorp.com/"]
+ path = ~/.gitconfig.work
And then create a new file, ~/.gitconfig.work
that has.
[user]
email = "my.work.email@gmail.com"
Notice this has very little in the file. That’s because the original file is merged with this and only the user.email
reference is overwritten when in the work directories. Now if you commit from a personal and work directory you should see different emails in the git log but the same user.name
!
More Patterns
The use case above doesn’t seem complex but what other patterns could we use this for?
Conditional GPG Keys
In some cases you may be required to uses GPG keys just for certain email addresses, maybe your personal one doesn’t require one, but your work email does. We could solve this by add this to our .gitconfig.work
file
[user]
email = "my.work.email@gmail.com"
+ signingkey = "3AA5C34371567BD2"
This would then load our signing key just for work commits! So secure!
Conditional Githooks
I use global hooks (more posts on that later) and they may be different per the environment I’m working on. Maybe at work you are expected to format your PRs a certain way and you want a git hook that applies to all code in ~/code/git.jobcorp.com/
. How would we accomplish this?
In your .gitconfig.work
you could add
[user]
email = "my.work.email@gmail.com"
+[core]
+ hooksPath = ~/.githooks.work
Under the path ~/.githooks.work
you could then add your commit-msg
hooks or whatever you need!
Conclusion
As you can see, this pattern is very powerful for managing your workflow across many different teams, projects and environments. Since I have added this to my workflow I have not had to reauthor a git history and I feel more knowledgable about ways I can leverage git
to help fit my workflow. I hope this helps you as well!