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 [email protected]
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 = [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…
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 [email protected] export [email protected]
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?
Comments
Steve,
Do you still use this method or have you found something even easier to maintain? If that is even possible.
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
I like this great article here it is the amazing.