Skip to content

Instantly share code, notes, and snippets.

Last active August 24, 2023 09:44
  • Star 69 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?
Browsing git commit history with fzf
# fshow - git commit browser (enter for show, ctrl-d for diff, ` toggles sort)
fshow() {
local out shas sha q k
while out=$(
git log --graph --color=always \
--format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" |
fzf --ansi --multi --no-sort --reverse --query="$q" \
--print-query --expect=ctrl-d --toggle-sort=\`); do
q=$(head -1 <<< "$out")
k=$(head -2 <<< "$out" | tail -1)
shas=$(sed '1,2d;s/^[^a-z0-9]*//;/^$/d' <<< "$out" | awk '{print $1}')
[ -z "$shas" ] && continue
if [ "$k" = ctrl-d ]; then
git diff --color=always $shas | less -R
for sha in $shas; do
git show --color=always $sha | less -R
Copy link

avshyz commented Oct 10, 2020

@Melkster - I've managed to accomplish that using sk (skim), which is an alternative to fzf.
I've also used ripgrep to highlight the matches, so you may need to install them both to make the following code work:

ilog() {
  git status >& /dev/null
  if [ $? -eq 128 ]; then return; fi

  sk \
    --ansi \
    -ic"git log --all --oneline --color -G\"{}\" $*" \
    --preview="[[ -n "{1}" ]] && git show --color -G {cq} {1} | rg  --colors=match:fg:blue --colors=line:style:intense --color=always --passthru {cq}" \
    --bind="ctrl-j:preview-down,ctrl-k:preview-up" | awk '{print $1}'

It took me way to long to perfect this alias (starting after asking here on this gist, a year or so ago), but it has served me well ever since

Copy link

@avshyz Wow, that's really neat. I like how you can search through the diffs of commits to find relevant code. Thank you for sharing

Copy link

@avshyz fzf provides a way to dynamically or manually reload the list which I believe is more flexible than the way skim approaches the problem.

Copy link

avshyz commented Oct 11, 2020

@maheshsundaram always glad to share :)

@junegunn Ohhhh that's super interesting and useful! I'll definitely find use to that in future scripts! It's just that when I wrote that shell function (a year or two ago) fzf didn't have that option, and I never bothered rewriting it to use fzf instead of skim (though it may be worthwhile to do so)

Copy link

Frederick888 commented Apr 22, 2021

Many thanks to everyone for sharing. After struggling with gitconfig's escaping for a while, I managed to put @rndware's script into .gitconfig with a few tweaks to use ripgrep, git-delta and clipboard-cli:

	pager = delta --line-numbers
    fzf = !"                                                                                                                                                                              \
        function gfzf() {                                                                                                                                                                 \
            local filter;                                                                                                                                                                 \
            if [ -n $@ ] && [ -e $@ ]; then                                                                                                                                               \
                filter=\"-- $@\";                                                                                                                                                         \
            fi;                                                                                                                                                                           \
            export LESS='-R'                                                                                                                                                              \
            export BAT_PAGER='less -S -R -M -i';                                                                                                                                          \
            git log                                                                                                                                                                       \
                --graph --color=always --abbrev=7                                                                                                                                         \
                --format=format:\"%C(bold blue)%h%C(reset) %C(dim white)%an%C(reset)%C(bold yellow)%d%C(reset) %C(white)%s%C(reset) %C(bold green)(%ar)%C(reset)\" $@ |                   \
                    fzf --ansi --no-sort --layout=reverse --tiebreak=index                                                                                                                \
                        --preview=\"f() { set -- \\$(echo -- \\$@ | rg -o '\\b[a-f0-9]{7,}\\b'); [ \\$# -eq 0 ] || git show --color=always \\$1 $filter | delta --line-numbers; }; f {}\" \
                        --bind=\"ctrl-d:half-page-down,ctrl-u:half-page-up,ctrl-j:preview-down,ctrl-k:preview-up,ctrl-f:preview-page-down,ctrl-b:preview-page-up\"                        \
                        --bind=\"ctrl-m:execute:                                                                                                                                          \
                                (rg -o '\\b[a-f0-9]{7,}\\b' | head -1 |                                                                                                                   \
                                xargs -I % -- git show --color=always %) << 'FZFEOF'\n                                                                                                    \
                                {}                                                                                                                                                        \
                                \nFZFEOF\"                                                                                                                                                \
                        --bind=\"ctrl-y:execute-silent:                                                                                                                                   \
                                (rg -o '\\b[a-f0-9]{7,}\\b' | head -1 | tr -d \\$'\\n' | clipboard) << 'FZFEOF'\n                                                                         \
                                {}                                                                                                                                                        \
                                \nFZFEOF\"                                                                                                                                                \
                        --preview-window=right:60%;                                                                                                                                       \
        };                                                                                                                                                                                \
        gfzf                                                                                                                                                                              \
    diffFilter = delta --color-only --line-numbers

Copy link

ultrox commented Jun 19, 2021

Copy link

@ultrox Glad to know that you found it useful. I've actually updated the subcommand for a few times since then:


Copy link

ultrox commented Jun 19, 2021

@Frederick888 I'll check it out later. My use-case is actually different , I'm looking to use fzf to find commit to fixup :).

git fixup <command>

I would then go to fzf and look for commit based on the title, enter would return hash.

Copy link

@Frederick888 thanks for sharing this, so useful—just bumped my git efficiency up a few notches!

Copy link

carlfriedrich commented Feb 23, 2022

Thanks everybody for sharing your versions. All of this was so helpful!

After spending some hours of optimizing, I'd like to throw my version on the table as well. It adds the diff stats in the preview window, using a dimmed presentation:


When entering a commit, another fzf instance opens up containing a list of files changed in that commit, with the preview window showing the diff for that file:


Entering a file shows the diff on that file with its complete context.

The git-fuzzy-diff function detects whether diff-so-fancy is installed and, if so, uses it.

Furthermore I tried to split and structure the code a bit in order to not have just one huge intransparent command. I started off from @victorbrca's version and now it looks like this:

	--bind shift-down:preview-down
	--bind shift-up:preview-up
	--bind pgdn:preview-page-down
	--bind pgup:preview-page-up
	--bind q:abort

git-fuzzy-diff ()
	PREVIEW_PAGER="less --tabs=4 -Rc"
	if [ -x "$(command -v diff-so-fancy)" ]; then
		PREVIEW_PAGER="diff-so-fancy | ${PREVIEW_PAGER}"
		ENTER_PAGER="diff-so-fancy | sed -e '1,4d' | ${ENTER_PAGER}"

	# Don't just diff the selected file alone, get related files first using
	# '--name-status -R' in order to include moves and renames in the diff.
	# See for reference:
	PREVIEW_COMMAND='git diff --color=always '$@' -- \
		$(echo $(git diff --name-status -R '$@' | grep {}) | cut -d" " -f 2-) \

	# Show additional context compared to preview
	ENTER_COMMAND='git diff --color=always '$@' -U10000 -- \
		$(echo $(git diff --name-status -R '$@' | grep {}) | cut -d" " -f 2-) \

	git diff --name-only $@ | \
		fzf ${GIT_FZF_DEFAULT_OPTS} --exit-0 --preview "${PREVIEW_COMMAND}" \
		--preview-window=top:85% --bind "enter:execute:${ENTER_COMMAND}"

git-fuzzy-log ()
		set -- $(echo -- "$@" | grep -o "[a-f0-9]\{7\}")
		[ $# -eq 0 ] || (
			git show --no-patch --color=always $1
			git show --stat --format="" --color=always $1 |
			while read line; do
				tput dim
				echo " $line" | sed "s/\x1B\[m/\x1B\[2m/g"
				tput sgr0
			done |
			tac | sed "1 a \ " | tac
	}; f {}'

	ENTER_COMMAND='(grep -o "[a-f0-9]\{7\}" | head -1 |
		xargs -I % bash -ic "git-fuzzy-diff %^1 %") <<- "FZF-EOF"

	git log --graph --color=always --format="%C(auto)%h %s%d " | \
		fzf ${GIT_FZF_DEFAULT_OPTS} --no-sort --tiebreak=index \
		--preview "${PREVIEW_COMMAND}" --preview-window=top:15 \
		--bind "enter:execute:${ENTER_COMMAND}"

In my gitconfig I have set up aliases for it:

	fd = !bash -ic 'git-fuzzy-diff \"$@\"' x
	fl = !bash -ic 'git-fuzzy-log \"$@\"' x

Copy link

mawkler commented Feb 23, 2022

Perhaps someone should turn this into a zsh plugin?

Copy link

@melkster Actually there is a project called forgit, which does something similar based on fzf and is available as a zsh plugin.

Copy link

mawkler commented Mar 3, 2022

@carlfriedrich Interesting, I'll check it out!

Copy link

@melkster FYI I edited my post above with an updated version, adding a git-fuzzy-diff command which shows a diff for each changed file separately.

I think one could plug this into forgit, if it would allow for customizing the preview command, which is not possible at the moment. I created an issue for that. If you are interested in that as well, maybe you could add a comment there to show that I'm not the only person who might want this. :-)

Copy link

slerer commented Apr 23, 2023

@Frederick888 I'll check it out later. My use-case is actually different , I'm looking to use fzf to find commit to fixup :).

git fixup <command>

I would then go to fzf and look for commit based on the title, enter would return hash.

How did you hook it up at the end? a fuzzy-find for fixup sounds awesome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment