Skip to content

Instantly share code, notes, and snippets.

@junegunn
Last active March 26, 2024 09:56
Show Gist options
  • Save junegunn/8b572b8d4b5eddd8b85e5f4d40f17236 to your computer and use it in GitHub Desktop.
Save junegunn/8b572b8d4b5eddd8b85e5f4d40f17236 to your computer and use it in GitHub Desktop.
# GIT heart FZF
# -------------
is_in_git_repo() {
git rev-parse HEAD > /dev/null 2>&1
}
fzf-down() {
fzf --height 50% --min-height 20 --border --bind ctrl-/:toggle-preview "$@"
}
_gf() {
is_in_git_repo || return
git -c color.status=always status --short |
fzf-down -m --ansi --nth 2..,.. \
--preview '(git diff --color=always -- {-1} | sed 1,4d; cat {-1})' |
cut -c4- | sed 's/.* -> //'
}
_gb() {
is_in_git_repo || return
git branch -a --color=always | grep -v '/HEAD\s' | sort |
fzf-down --ansi --multi --tac --preview-window right:70% \
--preview 'git log --oneline --graph --date=short --color=always --pretty="format:%C(auto)%cd %h%d %s" $(sed s/^..// <<< {} | cut -d" " -f1)' |
sed 's/^..//' | cut -d' ' -f1 |
sed 's#^remotes/##'
}
_gt() {
is_in_git_repo || return
git tag --sort -version:refname |
fzf-down --multi --preview-window right:70% \
--preview 'git show --color=always {}'
}
_gh() {
is_in_git_repo || return
git log --date=short --format="%C(green)%C(bold)%cd %C(auto)%h%d %s (%an)" --graph --color=always |
fzf-down --ansi --no-sort --reverse --multi --bind 'ctrl-s:toggle-sort' \
--header 'Press CTRL-S to toggle sort' \
--preview 'grep -o "[a-f0-9]\{7,\}" <<< {} | xargs git show --color=always' |
grep -o "[a-f0-9]\{7,\}"
}
_gr() {
is_in_git_repo || return
git remote -v | awk '{print $1 "\t" $2}' | uniq |
fzf-down --tac \
--preview 'git log --oneline --graph --date=short --pretty="format:%C(auto)%cd %h%d %s" {1}' |
cut -d$'\t' -f1
}
_gs() {
is_in_git_repo || return
git stash list | fzf-down --reverse -d: --preview 'git show --color=always {1}' |
cut -d: -f1
}
if [[ $- =~ i ]]; then
bind '"\er": redraw-current-line'
bind '"\C-g\C-f": "$(_gf)\e\C-e\er"'
bind '"\C-g\C-b": "$(_gb)\e\C-e\er"'
bind '"\C-g\C-t": "$(_gt)\e\C-e\er"'
bind '"\C-g\C-h": "$(_gh)\e\C-e\er"'
bind '"\C-g\C-r": "$(_gr)\e\C-e\er"'
bind '"\C-g\C-s": "$(_gs)\e\C-e\er"'
fi
join-lines() {
local item
while read item; do
echo -n "${(q)item} "
done
}
() {
local c
for c in $@; do
eval "fzf-g$c-widget() { local result=\$(_g$c | join-lines); zle reset-prompt; LBUFFER+=\$result }"
eval "zle -N fzf-g$c-widget"
eval "bindkey '^g^$c' fzf-g$c-widget"
done
} f b t r h s
@robinmitra
Copy link

This is bloody amazing! In my case, the functions weren't working, and as it turns out it was because these conflicted with some existing function somewhere. So, I just prefixed all of your functions with fn- (e.g. fn-gt rather than gt) and now all seem to work beautifully.

@adah1972
Copy link

I had some problems with gb and gr, due to ambiguity between names of branches, remotes, and paths. Here are the revised functions:

gb() {
  is_in_git_repo || return
  git branch -a --color=always | grep -v '/HEAD\s' | sort |
  fzf-down --ansi --multi --tac --preview-window right:70% \
    --preview 'git log --oneline --graph --date=short --pretty="format:%C(auto)%cd %h%d %s" $(sed s/^..// <<< {} | cut -d" " -f1) -- | head -'$LINES |
  sed 's/^..//' | cut -d' ' -f1 |
  sed 's#^remotes/##'
}

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

@adah1972
Copy link

Just solved another problem due to the fact that grep -o "[a-f0-9]\{7,\}" will not only match the Git hashes, but also a string like "feedback". gh is fixed as follows:

gh() {
  is_in_git_repo || return
  git log --date=short --format="%C(green)%C(bold)%cd %C(auto)%h%d %s (%an)" --graph --color=always |
  fzf-down --ansi --no-sort --reverse --multi --bind 'ctrl-s:toggle-sort' \
    --header 'Press CTRL-S to toggle sort' \
    --preview 'grep -o "[a-f0-9]\{7,\}" <<< {} | head -1 | xargs git show --color=always | head -'$LINES |
  grep -o "[a-f0-9]\{7,\}" | head -1
}

@zanza00
Copy link

zanza00 commented Sep 20, 2018

for anyone who has oh-my-zsh there are already some alias so the solution is to add a prefix to the functions and modify the helper

fzf_gf() {
...
}

fzf_gb() {
...
}

fzf_gt() {
...
}

fzf_gh() {
...
}

fzf_gr() {
...
}

bind-git-helper() {
  local c
  for c in $@; do
    eval "fzf-g$c-widget() { local result=\$(fzf_g$c | join-lines); zle reset-prompt; LBUFFER+=\$result }"
    eval "zle -N fzf-g$c-widget"
    eval "bindkey '^g^$c' fzf-g$c-widget"
  done
}

@baiwfg2
Copy link

baiwfg2 commented Nov 22, 2018

Thanks for the work. There's a tiny issue that I met. When started with --preview option, it's really slow to scroll the preview pane with mouse. If there're many lines there, it'll take a long time. Any ideas to scroll faster ?

@rawaludin
Copy link

I can't get this working with pure prompt https://github.com/sindresorhus/pure

Always getting this:

prompt_pure_async_callback:11: bad set of key/value pairs for associative array

@rawaludin
Copy link

nvm, its conflict with existing gf function. I just prefixed it

@ztou
Copy link

ztou commented Jan 17, 2019

I am using git bash on Windows and I am getting error like << was unexpected at this time. on those function using <<< {}, any thoughts?

f_gh() {                                                                                                                  
  is_in_git_repo || return                                                                                                
  git log --date=short --format="%C(green)%C(bold)%cd %C(auto)%h%d %s (%an)" --graph --color=always |                     
  fzf-down --ansi --no-sort --reverse --multi --bind 'ctrl-s:toggle-sort' \                                               
    --header 'Press CTRL-S to toggle sort' \                                                                              
    --preview 'grep -o "[a-f0-9]\{7,\}" <<< {} | xargs git show --color=always | head -'$LINES | grep -o "[a-f0-9]\{7,\}" 
}                                                                                                                         

but actually below script works:

huangjoh@SHAPC0N4NVD MINGW64 /m/x-hub/cosv2/flask-app (test-branch-1)
$ grep -o "[a-f0-9]\{7,\}" <<< "* 2019-01-04 2884e18"
2884e18

I've to change the function to use --preview 'echo {} | grep -o "[a-f0-9]\{7,\}" | xargs git show --color=always | head -'$LINES | grep -o "[a-f0-9]\{7,\}" and it works fine.

@albertogalan
Copy link

Great tool!

@deecewan
Copy link

hmmm. I can't seem to get bindings of the form ^G^$c to work. if i bind the widget directly to a single keypress (bindkey '^B' fzf-gb-widget) it works as expected, but with multi-key bindings, I get nothing.

ZSH in vi-mode

@jeebak
Copy link

jeebak commented Jun 25, 2019

hmmm. I can't seem to get bindings of the form ^G^$c to work. if i bind the widget directly to a single keypress (bindkey '^B' fzf-gb-widget) it works as expected, but with multi-key bindings, I get nothing.

ZSH in vi-mode

Do you have ^G already bound to something else? I had to remove a binding for my setup (with bindkey -r "^G") before I could use the the ^G prefix.

@deecewan
Copy link

That was it! Also in this process, I've discovered that bindkey "<binding>" will print the existing functionality of the key. ^G in my case was list-expand

@jeebak
Copy link

jeebak commented Jun 26, 2019

That was it! Also in this process, I've discovered that bindkey "<binding>" will print the existing functionality of the key. ^G in my case was list-expand

Ah, cool. It looks like gists won't let me add a ":+1:" to comments :). Yep, and bindkey w/out any arguments will list EVERY bindings.

@khromalabs
Copy link

This is beyond awesome. Thank you for making my console perfect with this tools.

@dagadbm
Copy link

dagadbm commented Nov 18, 2019

the main problem with me was the function names.

Maybe june could update his gist to rename all functions fzf_gr or something similar

i also couldnt use Ctl-G Ctrl-H because of my tmux/vim bindings on Ctrl-H which would change pane so I had to rebrand that to Ctrl-M for comMits or maybe Ctrl-G Ctrl-G (since the hash is the building block of git after all it also makes some sense)

@dylankb
Copy link

dylankb commented Feb 9, 2020

When I put any of these bindings into my ~/.inputrc I lose the ability to enter b into my terminal and iTerm2. When I enter b it instead sends a " character and makes the invalid input sound (displays the bell as well in iTerm). I'm not seeing anything under bind -P that catches my eye, but I'm not really sure what I'm looking for. I have no custom bindings I know of, my bash version is 5.0.11(1)-release and I'm on MacOS.

@dagadbm
Copy link

dagadbm commented Feb 10, 2020

i never used inputrc so i have no idea what that is.
have you tried doing the suggestions recommended above?
add a fzf_ prefix to all functions . make sure you have everything correctly copy pasted etc.
also bonus points for you: try kitty terminal. iterm2 is just so so slow

@dylankb
Copy link

dylankb commented Mar 7, 2020

Thanks for the response - I will try and kitty when I get some time.
I actually got the key-bindings working using a bash line editor replacement, ble.sh, that I use for fish-like autosuggestions and more. Here's a note about getting these bindings to work.

@ericbn
Copy link

ericbn commented Oct 18, 2021

Cool gist! keybinding.zsh can be further simplified to just:

() {
  local c
  for c in $@; do
    eval "fzf-g$c-widget() { local -r result=(\${(f)\"\$(_g$c)\"}); zle reset-prompt; LBUFFER+=\${(j: :)\${(q)result}} }"
    eval "zle -N fzf-g$c-widget"
    eval "bindkey '^g^$c' fzf-g$c-widget"
  done
} f b t r h s

@rockyzhang24
Copy link

For some friends using zsh vi mode but the keybindings do not work:
^g is already assigned to zsh builtin standard widget list-expand, so we should remove it first by bindkey -r '^g'.

@DanSM-5
Copy link

DanSM-5 commented May 23, 2022

I am using git bash on Windows and I am getting error like << was unexpected at this time. on those function using <<< {}, any thoughts?

@ztou I found that issue today. Turns out you need to start your git bash session with -l flag, so the subprocess that fzf starts for the preview is still gitbash. then the <<< operator will work.

You can even do some crazy stuff like creating a string to make it work from powershell using git bash.

function _gh () {
  if (-not (git rev-parse HEAD 2> $null)) { return }
  bash -lc @'
    git log --date=short --format='%C(green)%C(bold)%cd %C(auto)%h%d %s (%an)' --graph --color=always |
    fzf --height 50% --min-height 20 --border --bind ctrl-/:toggle-preview --ansi --no-sort --reverse --multi --bind 'ctrl-s:toggle-sort' \
      --header 'Press CTRL-S to toggle sort' \
      --preview 'grep -o \"[a-f0-9]\{7,\}\" <<< {} | xargs git show --color=always | bat -p --color=always' |
    grep -o '[a-f0-9]\{7,\}'
'@
}

@DanSM-5
Copy link

DanSM-5 commented May 23, 2022

I'd like to ask the reason why some commands in the pipes are after fzf

Here is what I mean. Take _gb as an example.

_gb() {
  is_in_git_repo || return
  git branch -a --color=always | grep -v '/HEAD\s' | sort |
  fzf-down --ansi --multi --tac --preview-window right:70% \
    --preview 'git log --oneline --graph --date=short --color=always --pretty="format:%C(auto)%cd %h%d %s" $(sed s/^..// <<< {} | cut -d" " -f1)' |
  sed 's/^..//' | cut -d' ' -f1 |
  sed 's#^remotes/##'
}

The part sed 's/^..//' | cut -d' ' -f1 | sed 's#^remotes/##' at the bottom does not make much sense to me as fzf will display the results before these commands. Is there any reason to call them at the end?

As I can see, the above command can be simplified

_gb() {
  is_in_git_repo || return
  git branch -a --color=always | grep -v '/HEAD\s' | sort |
  sed 's/^..//' | cut -d' ' -f1 |
  fzf-down --ansi --multi --tac --preview-window right:70% \
    --preview 'git log --oneline --graph --date=short --color=always --pretty="format:%C(auto)%cd %h%d %s" {}' 
}

or if you want to include the sed part remove the word "remotes/"

_gb() {
  is_in_git_repo || return
  git branch -a --color=always | grep -v '/HEAD\s' | sort |
  sed 's/^..//' | cut -d' ' -f1 | sed 's#remotes/##'
  fzf-down --ansi --multi --tac --preview-window right:70% \
    --preview 'git log --oneline --graph --date=short --color=always --pretty="format:%C(auto)%cd %h%d %s" {}' 
}

@rockyzhang24
Copy link

rockyzhang24 commented May 24, 2022

@DanSM-5 Pipe a command after fzf means pass the selection(s) from fzf into this command for further processing.

To implement a feature, you can process the data first and then pass them into fzf and make selections to get what you need, or you can first pass the data into fzf and then post-process the selections to get what you need.

@DanSM-5
Copy link

DanSM-5 commented May 24, 2022

@rockyzhang24 I see, I was pretty much using this for the visualization but that's a nice part too.
For the _gb function, I just noticed that moving those commands above will remove the * for the current branch. So it is likely intentional to leave it in fzf list output. I was only paying attention to the color highlight so I really missed that.

@eric-bentley
Copy link

eric-bentley commented Jun 6, 2022

If you use a non-standard diff tool (such as bcompare), you'll need to tell git to use the built-in with the --no-ext-diff flag:

_gf() {
  is_in_git_repo || return
  git -c color.status=always status --short |
  fzf-down -m --ansi --nth 2..,.. \
    --preview '(git diff --no-ext-diff --color=always -- {-1} | sed 1,4d; cat {-1})' |
  cut -c4- | sed 's/.* -> //'
}

@junegunn
Copy link
Author

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