Wednesday, March 30, 2022

Set git behavior based on the repository path

I maintain a handful of git accounts at GitHub.com 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 GitHub.com

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/work.github.com  
 ssh-keygen -t ed25519 -P '' -f ~/.ssh/personal.github.com  

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

Use ~/.ssh/work.github.com.pub and ~/.ssh/personal.github.com.pub (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:
 [user]  
  email = chris@work.domain  
 [core]  
  sshCommand = "ssh -i ~/.ssh/work.github.com"  
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:

 [init]  
  defaultBranch = main  
   
 [user]  
  name = Chris Marget  
   
 [core]  
  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.