Multiple git configs

Published on 2022-05-29

I recently set up a new machine, and it took me longer than I had hoped to get my dual work-personal configs working again. This blog post can be a reminder to myself so in the future I'll be able to find it much more quickly. The solution is to use conditional includes, as documented on git-scm.com. This lets me configure my email address as my work email for any git repo under one directory, and my email address as my personal email for any git repo cloned under a different directory, without having to set per-repo config every time I clone. It can also be used for any config, not just email, but that's all I use it for.

tl;dr

The "root" .gitconfig file is in home (user profile) directory. On Windows this will be C:\Users\username\.gitconfig, and on linux ~/.gitconfig. Edit it so it's something like:

[user]
    name = Andy Zivkovic
    email = default@email.test
[includeIf "gitdir/i:D:/src/personal/"]
    Path = .gitconfig-personal

Note that the /i in gitdir/i tells git to consider paths as case insensitive, which you'll want on Windows (technically, any filesystem where paths are case insensitive). On Linux (technically, any filesystem where paths are case sensitive), remove the /i, so it just uses gitdir:~/src/personal. Hopefully it's obvious that you should edit the path to where you clone your personal repos. If you want to put the personal config file in the personal directory, rather than the home directory, on Windows it's easiest to convert the forward slashes to back slashes (for example, d:/src/personal/.gitconfig).

In your .gitconfig-personal:

[user]
    email = personal@email.test

Validating

In my example config above, I have the includeIf path set to D:\src\personal\. You need to be in a git repo under that directory. Any directory that is not a git repo will not match the gitdir expression. An easy way to test is to run mkdir test ; cd test ; git init, or just git clone a repo that you're going to work on anyway.

Once in a git directory, you can use git config --get user.email to output the email address that will be used in commits. This is quicker and easier than actually committing a change then checking the log.

A useful option is --show-origin, which will tell you which config file the config was defined in. Coupled with --get-all, rather than --get, it will show you all config files that define that setting, and what the values are. Finally, also consider the -l (list) option (git config --show-origin -l). It will show all config files, and what values they contain.

Quirks

It took me multiple attempts to get this to work, for the following reasons. I'm not suggesting any of these are bad choices, only that if git had chosen different behaviours, I would have been successful more quickly.

Path value must escape forward slashes

On Windows, if you want to put the config file in a directory other than home, you either need to convert forward slashes to back slashes, or escape them with another forward slash. For example, both D:/src/personal/.gitconfig and D:\\src\\personal\\.gitconfig work. However, D:\src\personal\.gitconfig does not work.

The escaping for forward slashes is documented in syntax section of the docs, but I couldn't find docs on the back slash equivalent. My guess is that's because Windows itself handles the equivalence, so git, just like any other program, doesn't have to.

On Windows, files and directories are not allowed to have certain characters, like /, \, *, ', ", and probably more. However, this restriction doesn't seem to exist on Linux. So, on Windows it doesn't really make sense to allow for escaping of paths, it's technically necessary to support all edge cases on Linux. My guess is that this wasn't even considered though, and was more likely less effort to implement "escape all values in the config", rather than choosing when to escape based on whether or not a config value is a path.

Back slashes only on gitdir paths

On Windows, the default path separator is the forward slash \. On Linux and Mac, the default separator is the back slash /. Most apps can handle either slash in Windows. Git handles it fine when running commands like git add, and as discussed above, it even works for the path to the file to include. But for some reason the includeIf does not (even if you escape the slashes), so you must use back slashes. I didn't see this documented anywhere.

Trailing slash

This is documented, but I missed it at first in my rush to try to get it working. Here is the quote:

If the pattern ends with /, ** will be automatically added. For example, the pattern foo/ becomes foo/**. In other words, it matches "foo" and everything inside, recursively.

In other words, without a trailing / or /**, it appears that it expects the value to be the exact git directory. This is of limited usefulness since you can set git config values in the local repo. But I guess if you clone repos to the same path on multiple computers, and you sync your .gitconfig file(s) across those computers, then this would let you set repo-specific config in one place. More realistically, however, is that it's just a consequence of using glob patterns, rather than an intentional feature.

Include is relative to the current file

When specifying path = .gitconfig-personal, there's a possible ambiguity with what directory is the relative path evaluated from. The answer is the home directory.

Although I didn't see this documented anywhere, after reading the docs it feels like the only reasonable default. Although I used D:\src\personal\ as the gitdir condition, includeIf also supports onbranch and hasconfig conditions. Also, since the trailing / gets converted into a /** glob, it's not clear if D:\src\personal\repo1 should use the personal directory as the current directory for the relative path, or the repo1 directory. By only using the home directory as the root for relative paths. Always using the home directory for relative paths seems like a fine choice to me.