You’ve probably heard me mention fzf before. It’s an amazing command line tool created by Junegunn Choi. It takes a list of data and turns it into a command line menu with fuzzy searching, multi-select, and can even preview each item in whatever way is appropriate. I’ve been using it in all kinds of scripts — where I used to have rudimentary numbered menus, I now have much friendlier and more flexible terminal navigation.

fzf is available via Homebrew, just run brew install fzf. See man fzf for very good documentation.

One great example of how fzf can change your command line life is the set of keybindings for git shared by the author. With these set up you can, for example, start typing a git command that requires a commit hash, hit Ctrl-G Ctrl-H, and get a searchable menu of all your commits. Select the one you’re looking for and the menu closes and the commit’s hash is inserted in your command. It’s crazy handy.

If you’re running Bash or Zsh, Junegunn already has you taken care of. Just add the code in this gist to the appropriate startup files and you’ll be flying through git commands in no time. You can stop reading here, really. The rest of this is for the Fish weirdos.

I run Fish, and the existing solutions needed a little hacking to do what I wanted. Alexandru Rosianu created a gist that works pretty well. I made some modifications for my own needs and it’s working great.

If you want to try it out, you can save the code below to a file and source it from ~/.config/fish/, then call the git_fzf_key_bindings function to bind the keys. Something like:

if status is-interactive && test -f ~/.config/fish/custom/
	source ~/.config/fish/custom/

Once it’s installed, open up a new session to load the init files, cd to a git repository, and type Ctrl-G Ctrl-H. You’ll see a list of your commits with previews on the right. Use the arrows or type to search the text and select the commit you want, and when you hit return its hash will be inserted at the prompt.

Here’s the code:

# @ttscoff's version of @max-sixty's revision of @aluxian's
# fish translation of @junegunn's fzf git keybindings
# 2021-12-23:
# - Fix hash returned by *git_log being truncated
# - Allow multiple selections for git_status
# - Allow multiple selections for git_log, insert HASH[1].. HASH[-1] range
# - bind ctrl-a for select all

# Deciphered from fzf-file-widget. Somewhat unclear why it doesn't exist already!
function fzf_add_to_commandline -d 'add stdin to the command line, for fzf functions'
	read -l result
	commandline -t ""
	commandline -it -- (string escape $result)
	commandline -f repaint

function fzf_add_multi_files_to_commandline -d 'add stdin to the command line without escaping, for fzf functions'
	read -l result
	set files (string split '*' $result)
	commandline -t ""
	for file in $files
		commandline -it -- (string escape $file)" "
	commandline -f repaint

function fzf_add_multi_hashes_to_commandline -d 'add multiple hashes as a range'
	read -d \n -z -a result
	set -l hashes
	for hash in $result
		if test -n (string trim $hash)
			set -a hashes $hash
	commandline -t ""
	if test (count $hashes) -gt 1
		commandline -it -- $hashes[1]".. "$hashes[-1]
		commandline -it -- $hashes[1]
	commandline -f repaint

function fzf-down
	fzf --height 50% --min-height 20 --border --bind ctrl-p:toggle-preview --bind ctrl-a:select-all $argv

function __git_fzf_is_in_git_repo
	command -s -q git
		and git rev-parse HEAD >/dev/null 2>&1

function __git_fzf_git_remote
	__git_fzf_is_in_git_repo; or return
	git remote -v | awk '{print $1 ":" $2}' | uniq | \
		fzf-down --ansi --tac \
		--preview 'git log --oneline --graph --date=short --pretty="format:%C(auto)%cd %h%d %s" --remotes={1} | head -200' | \
		cut -d ':' -f1 | \

function __git_fzf_git_status
	__git_fzf_is_in_git_repo; or return
	git -c color.status=always status --short | \
		fzf-down -m --ansi --preview 'git diff --color=always HEAD -- {-1} | head -500' | \
		cut -c4- | \
		sed 's/.* -> //' | \
		tr '\n' '*' | \
		sed 's/\*$//' | \

function __git_fzf_git_branch
	__git_fzf_is_in_git_repo; or return
	git branch -a --color=always | \
		grep -v '/HEAD\s' | \
		fzf-down -m --ansi --preview-window right:70% --preview 'git log --color=always --oneline --graph --date=short \
			--pretty="format:%C(auto)%cd %h%d %s %C(magenta)[%an]%Creset" \
			--print0 \
			--read0 \
			(echo {} | sed s/^..// | cut -d" " -f1) | head -'$LINES | \
		sed 's/^..//' | cut -d' ' -f1 | \
		sed 's#^remotes/##' | \

function __git_fzf_git_tag
	__git_fzf_is_in_git_repo; or return
	git tag --sort -version:refname | \
		fzf-down --ansi --preview-window right:70% \
		--preview 'git show --color=always {} | head -'$LINES | \

function __git_fzf_git_log
	__git_fzf_is_in_git_repo; or return
	git log --color=always --graph --date=short --format="%C(auto)%cd %h%d %s %C(magenta)[%an]%Creset" | \
		fzf-down -m --ansi --reverse --preview 'git show --color=always (echo {} | grep -o "[a-f0-9]\{7,\}") | head -'$LINES | \
		awk '{print $3}' \
		| fzf_add_multi_hashes_to_commandline

function git_fzf_key_bindings -d "Set custom key bindings for git+fzf"
	bind \cg\cf __git_fzf_git_status
	bind \cg\cb __git_fzf_git_branch
	bind \cg\ct __git_fzf_git_tag
	bind \cg\ch __git_fzf_git_log
	bind \cg\cr __git_fzf_git_remote

Here are the bindings:

Ctrl-G Ctrl-F
Shows modified and unstaged files, with their diff in the preview, selection inserts the filename
Ctrl-G Ctrl-B
Shows a menu of local and remote branches, selection inserts branch name
Ctrl-G Ctrl-T
Shows all tags, preview shows tag commit message, selection inserts tag name
Ctrl-G Ctrl-H
Shows all commits on current branch, preview shows commit’s message and diff, selection inserts hash
Ctrl-G Ctrl-R
Shows remotes and their url, selection inserts remote name

Hit Ctrl-P in any menu to turn off the preview. Have fun!