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.

Git's user.useConfigOnly

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.name or 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 user.email:

$ 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 steve@collectiveidea.com

Finally, I can work as I normally would, but I need to repeat this process for every repository I commit to.

Git's include.path

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 = steve@collectiveidea.com

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…

Using direnv

From direnv.net:

direnv is 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_AUTHOR_EMAIL and GIT_COMMITTER_EMAIL environment variables will override any Git-configured user.email values.

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 .gitemail files:

/Users/laserlemon/Code/
    ▾ clients/
      ▸ awesome-inc/
      ▸ fantastic-corp/
        .envrc
    ▾ open-source/
      ▸ interactor/
      ▸ rails/
      ▸ rspec-wait/
        . envrc
    ▾ personal/
      ▸ barn-doors/
      ▸ string-cheese/
        . envrc

The .envrc file is what direnv looks for to set any directory-specific environment variables. One of the .envrc files might look like this:

export GIT_AUTHOR_EMAIL=steve@collectiveidea.com
export GIT_COMMITTER_EMAIL=steve@collectiveidea.com

Whenever I 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?

Photo of Steve Richert

Steve is a Senior Developer working with Ruby/Rails and JavaScript. He’s an active open source contributor and the lead developer for Interactor. Steve is also involved in documenting and improving Collective Idea’s software development practices.

Comments

  1. dave@davidcastellani.com
    David Castellani
    August 29, 2017 at 15:30 PM

    Steve,

    Do you still use this method or have you found something even easier to maintain? If that is even possible.

  2. June 25, 2018 at 17:39 PM

    This is even easier now with the addition of conditional includes in Git v2.13. You can use the same folder structure/.gitemail files that you mention but reference them from the top down rather than relatively.

    If the include condition ends in a trailing “/”, the path is matched recursively to all subdirectories, making the whole structure work.

    [includeIf “gitdir:C:/Apache2.2/htdocs/jm/”]
    path = C:/Apache2.2/htdocs/jm/.gitconfig

    More information can be found at:
    https://git-scm.com/docs/git-config#_conditional_includes

    And an example at:
    https://stackoverflow.com/a/43884702/6798110

  3. January 27, 2019 at 9:37 AM

    I like this great article here it is the amazing.