Wednesday, March 30, 2022

Set git behavior based on the repository path

I maintain a handful of git accounts at and on private git servers, and have repeated committed to a project using the wrong personality.

My early attempts to avoid this mistake involved scripts to set per-project git parameters, but I've found a more streamlined option.

The approach revolves around the file hierarchy in my home directory: Rather than dumping everything in a single ~/projects directory, they're now in ~/projects/personal, ~/projects/work, etc...

Whenever cloning a new project, or starting a new one, as long as I put it in the appropriate directory, git will chose the behaviors and identity appropriate for that project.

Here's how it works, with 'personal' and 'work' accounts at

1. Generate an SSH key for each account

Not strictly required, I guess, but I like the privacy-preserving angle of using different keys everywhere, so I do this as a matter of habit.
 ssh-keygen -t ed25519 -P '' -f ~/.ssh/  
 ssh-keygen -t ed25519 -P '' -f ~/.ssh/  

2. Add each public key to its respective GitHub account.

Use ~/.ssh/ and ~/.ssh/ (note the .pub suffix).

Instructions here.

3. Create the project directories and .gitconfig files

 mkdir -p ~/projects/{personal,work}  
 touch ~/projects/{personal,work}/.gitconfig  

4. Set up a .gitconfig file in each directory

The git behaviors which should be differentiated by the parent folder belong in that folder's .gitconfig file. My ~/projects/work/.gitconfig for example:
  email = chris@work.domain  
  sshCommand = "ssh -i ~/.ssh/"  
Because I only use a single git service/server with each directory/domain (personal/work), I opted to select my private key using the -i <filename> option to the ssh command. In a more complicated scenario, it might make sense to use -F <filename> to select an alternate ssh configuration file and specify keys per host there.

5. Conditionally include project gitconfig from main gitconfig

My main .gitconfig looks something like this:

  defaultBranch = main  
  name = Chris Marget  
  excludesFile = ~/.gitignore  
 [includeIf "gitdir:~/projects/work/"]  
  path = ~/projects/work/.gitconfig  
 [includeIf "gitdir:~/projects/personal/"]  
  path = ~/projects/personal/.gitconfig  

That's it!

Any work done in ~/projects/personal/somerepo will automatically use the git behavior specified by ~/projects/personal/.gitconfig.

I wasn't confident that the gitdir condition would match when cloning an existing repo (the directory doesn't exist yet!) but it works the way I'd hoped. clone/commit/push operations all now use the intended directory-specific configuration options.