Multiple Personalities in Git
Chicago: National Prtg. & Engr. Co., used under Creative Commons.
At Collective Idea, we use Git all the time. Right now, I have 50 Git repositories cloned on my computer and they fall into a few categories:
- application work for clients
- open source projects
- personal pet projects
My problem is that I need to commit from a different email address depending on which type of project I'm working on. I explored three ways to do this, looking for the least intrusive.
GitHub recently wrote a blog post describing features of the newly-released Git 2.8. One of those features is the
user.useConfigOnly configuration which in the absence of a globally configured
user.email, forces you to configure your Git user on a per-repository basis.
First, I set my global
user.useConfigOnly and unset my global
$ git config --global --add user.useConfigOnly true $ git config --global --unset-all user.email
Now, whenever I clone or initialize a Git repository and try to make my first commit, I'm greeted with this lovely error message:
fatal: user.useConfigOnly set but no mail given
This is my reminder to set a local
user.email configuration value for that repository based on what type of project I'm working on:
$ git config --local --add user.email [email protected]
Finally, I can work as I normally would, but I need to repeat this process for every repository I commit to.
Git also has an
include.path configuration that allows for inclusion of an external Git configuration file. Relative paths are supported.
At first, I thought this meant that I could organize my projects into directories for each type of work and include a
.gitemail file at each level:
/Users/laserlemon/Code/ ▾ clients/ ▸ awesome-inc/ ▸ fantastic-corp/ .gitemail ▾ open-source/ ▸ interactor/ ▸ rails/ ▸ rspec-wait/ .gitemail ▾ personal/ ▸ barn-doors/ ▸ string-cheese/ .gitemail
One of the
.gitemail files might look like this:
[user] email = [email protected]
By setting my global
include.path, I should be able to tell Git to include configuration values from the
.gitemail file that lives one directory higher:
$ git config --global --set include.path ../.gitemail
This does not work! It turns out that relative include paths are relative to the original configuration file, not to the repository. Moving on…
direnvis an environment switcher for the shell. It knows how to hook into bash, zsh, tcsh and fish shell to load or unload environment variables depending on the current directory. This allows to have project-specific environment variables and not clutter the "~/.profile" file.
Rather than trying to manipulate Git configuration files, Git allows certain configuration values to be overridden via environment variables. For instance,
GIT_COMMITTER_EMAIL environment variables will override any Git-configured
You can install
direnv with Homebrew:
$ brew install direnv $ echo 'eval "$(direnv hook bash)"' >> ~/.bashrc $ source ~/.bashrc
Note: I use Bash. You can find installation instructions for Zsh and other shells at direnv.net.
Now I can use the directory structure from my
include.path idea above, only with
.envrc files instead of
/Users/laserlemon/Code/ ▾ clients/ ▸ awesome-inc/ ▸ fantastic-corp/ .envrc ▾ open-source/ ▸ interactor/ ▸ rails/ ▸ rspec-wait/ . envrc ▾ personal/ ▸ barn-doors/ ▸ string-cheese/ . envrc
.envrc file is what
direnv looks for to set any directory-specific environment variables. One of the
.envrc files might look like this:
cd into one of my client projects, I see the following message:
direnv: loading .envrc direnv: export +GIT_AUTHOR_EMAIL direnv: export +GIT_COMMITTER_EMAIL
Now when I commit, Git uses the email address I set in
.envrc rather than what's in my global Git configuration.
I like this approach because I don't have to repeat configuration steps for every single repository. One
.envrc file automatically applies to every repository that lives underneath it. This method also enables me to further customize Git's behavior for different project types using any number of other environment variables that Git supports.
This is working great for me but as with all things Git, it feels like a feature that does this must already exist and that I'm just not seeing it. Do you manage multiple Git identities and if so, how?