multiple git identities with home-manager and fzf
If you have multiple git identities (a personal and a work email, say) and would like to use both in different projects on the same machine it can be a hassle. Hopefully you've never committed to your work repo with your personal email address! If this happens you're looking at an unwieldy git filter-branch invocation to fix it.
To avoid the issue in the future you can use a feature of git called
conditional includes,
but I still find them easy to forget to include in each repo that needs
a different identity (and I don't use a separate ~/dev/work
and
~/dev/personal
so there isn't a good place to set and forget them). I
found the solution that works best for me in a blog post by Micah
Henning. You
should go check it out, but I'm quoting the first paragraph because it
is almost exactly the solution I was looking for.
Working on many projects across multiple identities can be difficult to manage. This is a procedure for leveraging git aliases to set an identity at the project level for any project with support for GPG-based commit signing.
Henning's needs include PGP which I won't be touching on here, but my
approach will work if you do need pgp keys as well. The other difference
is that I use
home-manager which
allow you to declaratively define all your dotfiles including their
dependencies (copy your home.nix
to a new machine and watch as
everything is setup just the way you want it, it's magical the first
time you see it work). home-manager uses nix as a
package manager. The downside (such as it is) is that nix wants
everything in the nix language so that it can track it and give you
atomic upgrades and painless rollbacks.
So, with the table-setting out of the way, how did I combine the ideas
of the git config in Henning's blog post with the git support in
home-manager? It starts with a home.nix
that contains at least
home.nix
:
{ config, pkgs, ... }:
# Let Home Manager install and manage itself.
programs.home-manager.enable = true;
targets.genericLinux.enable = true;
imports = [
./git
];
# This value determines the Home Manager release that your
# configuration is compatible with. This helps avoid breakage
# when a new Home Manager release introduces backwards
# incompatible changes.
#
# You can update Home Manager without changing this value. See
# the Home Manager release notes for a list of state version
# changes in each release.
home.stateVersion = "20.03";
}
The important piece here is:
imports = [
./git
];
when you import ./git
in your home.nix
it will attempt to import the
file called default.nix
from that directory.
git/default.nix
:
{ config, lib, pkgs, ... }:
let
# put a shell script into the nix store
gitIdentity =
pkgs.writeShellScriptBin "git-identity" (builtins.readFile ./git-identity);
in {
# we will use the excellent fzf in our `git-identity` script, so let's make sure it's available
# let's add the gitIdentity script to the path as well
home.packages = with pkgs; [
gitIdentity
fzf
];
programs.git = {
enable = true;
extraConfig = {
# extremely important, otherwise git will attempt to guess a default user identity. see `man git-config` for more details
user.useConfigOnly = true;
# the `work` identity
user.work.name = "Spider-Man";
user.work.email = "friendlyspidey@neighborhood.com";
# the `personal` identity
user.personal.name = "Peter Parker";
user.personal.email = "peter@parker.com";
# I think spider-man might be peter parker! somebody get j jonah jameson on the line
};
# This is optional, as `git identity` will call the `git-identity` script by itself, however
# setting it up explicitly as an alias gives you autocomplete
aliases = {
identity = "! git-identity";
id = "! git-identity";
};
};
}
the default.nix
relies on a script called git-identity
let
gitIdentity =
pkgs.writeShellScriptBin "git-identity" (builtins.readFile ./git-identity);
in {
so let's go ahead and create it. It is also in the ./git
directory
git/git-identity
:
#!/usr/bin/env bash
# get each set of usernames from the git config (which will be generated from our `default.nix` above)
IDENTITIES=$(git config --global --name-only --get-regexp "user.*..name" | sed -e 's/^user.//' -e 's/.name$//')
# filter them with fzf
ID=$(echo "${IDENTITIES}" | fzf -e -1 +m -q "$1")
if ! git config --global --get-regexp "user.${ID}.name" > /dev/null; then
echo "Please use a valid git identity
Options:"
git config --global --name-only --get-regexp "user.*..name" | sed -e 's/^user.//' -e 's/.name$//' -e 's/^/\t/'
exit 1
fi
# set the ID locally in each repo (eg in the repo's .git/config)
git config user.name "$(git config user.${ID}.name)"
git config user.email "$(git config user.${ID}.email)"
echo "Name: $(git config user.name)"
echo "Email: $(git config user.email)"
To make that all a little more concrete, once that is all in your
home.nix
and ./git
, run
home-manager switch
and attempt to commit something to your favorite repo
ยป git commit
Author identity unknown
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
fatal: no email was given and auto-detection is disabled
simply type git identity
to get an fzf
-powered list of all your various identities.