Improvement of Git commands with fzf
Abstract
We manage source codes with Git in development projects. In general, when we check the branches, we use a git branch command below to find the candidate and we retrieve the branch with git checkout in later step. Or we directly checkout a branch when we know the name beforehand.
$ git branch
bug/RICHIKA-1178
bug/RICHIKA-2234
bug/RICHIKA-2510
feature/RICHIKA-1141
feature/RICHIKA-1143
feature/RICHIKA-1155
feature/RICHIKA-1364
feature/RICHIKA-1390
...
$ git checkout feature/RICHIKA-1155
This post describes a utility shell script to assist the Git command operations with fzf command such as git branche, git checkout and git log.
General usage of fzf
fzf is a powerful Linux command and enables interactive filtering which can be used for any purpose with receiving stdout of any commands.
The fzf command is packaged in major Linux distributions. For Ubuntu, it can be installed by apt command.
sudo apt install fzf
For examples of the usage of fzf, I interactively find files under a specified directory with filtering parts of the file path as blow. And it can show a preview window which we can customize what kind of data is shown. In this sample, I showed the syntax highlighted content of the selected file. This command may be described on another post as well.
Filtering Git branches by fzf
There are three commands below.
| Command | Feature |
|-------------+--------------------------------------|
| git-br-fzf | Filtering Git branches |
| git-co-fzf | Checkout with filtering Git branches |
| git-log-fzf | Filtering Git commit logs |
The shell script combining git + fzf to assist Git operations is below. The dependent command is only fzf.
is_in_git_repo() {
# git rev-parse HEAD > /dev/null 2>&1
git rev-parse HEAD > /dev/null
}
# Filter branches.
git-br-fzf() {
is_in_git_repo || return
local tags branches target
tags=$(
git tag | awk '{print "\x1b[31;1mtag\x1b[m\t" $1}') || return
branches=$(
git branch --all | grep -v HEAD |
sed "s/.* //" | sed "s#remotes/[^/]*/##" |
sort -u | awk '{print "\x1b[34;1mbranch\x1b[m\t" $1}') || return
target=$(
(echo "$tags"; echo "$branches") |
fzf --no-hscroll --no-multi --delimiter="\t" -n 2 \
--ansi --preview="git log -200 --pretty=format:%s $(echo {+2..} | sed 's/$/../' )" ) || return
echo $(echo "$target" | awk -F "\t" '{print $2}')
}
# Filter branches and checkout the selected one with <enter> key,
git-co-fzf() {
is_in_git_repo || return
git checkout $(git-br-fzf)
}
# Filter commit logs. The diff is shown on the preview window.
git-log-fzf() { # fshow - git commit browser
is_in_git_repo || return
_gitLogLineToHash="echo {} | grep -o '[a-f0-9]\{7\}' | head -1"
_viewGitLogLine="$_gitLogLineToHash | xargs -I % sh -c 'git show --color=always %'"
git log --graph --color=always \
--format="%C(auto)%h%d [%an] %s %C(black)%C(bold)%cr" "$@" |
fzf --ansi --no-sort --reverse --tiebreak=index --bind=ctrl-s:toggle-sort \
--preview="$_viewGitLogLine" \
--bind "ctrl-m:execute:
(grep -o '[a-f0-9]\{7\}' | head -1 |
xargs -I % sh -c 'git show --color=always % | less -R') << 'FZF-EOF'
{}
FZF-EOF"
}
git-br-fzf and git-co-fzf work like this.
git-log-fzf works like this.