Alex Pearwin

TIL: Conditional Git configuration

When you start your version control adventure you might have told git about yourself.

git config --global user.name "Max Power"
git config --global user.email "max@example.com"

This information is added to each commit you make so other folks know who the author was. Git persists the values in its global configuration file, typically at ~/.config/git/config or ~/.gitconfig.

But what if you have multiple sets of information? Say you have a personal email you’d like to use most of the time, but a work email you’d like to use for work stuff. You want to override the ‘global’ configuration on a per-repository basis.

One way is to explicitly tell git the specific information when working within one of those special repositories.

$ cd ~/Work/backend-repo
$ git config user.email "max@work.example.org"

These overrides are stored in the repository-local configuration file .git/config.

You can then verify that git will use your personal email everywhere except these special repositories.

$ cd ~/Code/blog-repo
$ git config user.email
max@example.com

$ cd ~/Work/backend-repo
$ git config user.email
max@work.example.org

But how boring! We have to go into every work repository and run this git config command. Plus, we have to remember to do that for each new repository we create or clone. So tedious.

Luckily this problem is so outrageously common that Git has a solution built in.

Conditional configuration

Git’s configuration file supports many different fields, in addition to the user.name and user.email fields used above.

One of those fields is called includeIf and tells git to include other configuration files only if certain conditions are met.

If we want to use our work email when working on all repositories under our ~/Work folder, we first need to create that other configuration file somewhere. This could be ~/.config/git/work, for example.

[user]
  email = "max@work.example.org"

Then we need to add the includeIf configuration to the end of our global configuration file, again typically at ~/.config/git/config or ~/.gitconfig.

[includeIf "gitdir:~/Work/"]
  path = "~/.config/git/work"

This tells git:

  1. If the current repository is in a folder underneath the path ~/Work;
  2. Then merge the configuration file at ~/.config/git/work with the global configuration.

Now any repository under ~/Work will use our work-specific configuration!

$ cd ~/Work/backend-repo
$ git config user.email
max@work.example.org

$ cd ~/Work/frontend-repo
$ git config user.email
max@work.example.org

$ cd ~/Code/blog-repo
$ git config user.email
max@example.com

As you might expect, we can add any configuration we like into the work-specific configuration file, and we can have as many includeIf blocks in our global configuration as we like.

Bonus: Nix Home Manager support

I manage my Git configuration using Nix’s Home Manager.

Home Manager supports Git’s includeIf natively, and in a particularly cool way that means we don’t even need to create a separate configuration file!

{ config, pkgs, ... }:

{
  # Other Home Manager configuration omitted for brevity.
  programs = {
    git = {
      enable = true;
      userName = "Max Power";
      userEmail = "max@example.com";
      includes = [
        {
          contents = {
            user = {
              email = "max@work.example.org";
            };
          };
          condition = "gitdir:~/Work/";
        }
      ];
    };
  };
}

This will generate a global Git configuration like this:

[user]
  email = "max@example.com"
  name = "Max Power"

[includeIf "gitdir:~/Work/"]
  path = "/nix/store/94a894qn78iq13s3kw4xic2i0yh3m9rl-hm_gitconfig"

The override configuration we specified is placed in an auto-generated file in the Nix store, and looks like this.

[user]
  email = "max@work.example.org"

Fabulous.