loading...
Cover image for Create an Interactive Bash Command for searching and checking out Git Branches on the Terminal using fzf

Create an Interactive Bash Command for searching and checking out Git Branches on the Terminal using fzf

aalises profile image Albert Alises ・4 min read

✏️ Cover art by Michael Rayback on Dribble


Git is an essential and powerful tool for any software developer. As part of your developer workflow, you might find yourself juggling between different projects and features all the time.

Given so, it's very hard to keep track of all the names of the branches between projects, especially when coming back to it after some time. So one common cumbersome procedure is looking up the name of the branch you want to get on that specific project and git checkout to it.

To achieve so, you first need to get all the branch names of the current project. Fortunately for us, git has a command for this, for-each-ref. What it does is iterate over all the references, which is a readable name that points to the SHA-1 ids of git artifacts (e.g commits), given a pattern. For example, getting refs/heads returns a list of all the references of the head commits for each branch.

You can iterate on these references to print all the branches on the terminal by using the following command:

git for-each-ref --sort='authordate:iso8601' --format='%(authordate:relative)%09%(refname:short)' refs/heads
Figure 1: command for showing all the git branches ordered by descendent author-date

Of course, you can format and sort the results however you like. The next step would be to be able to interact with that list of branches, like selecting one, copying one, searching, and/or filtering the results.

A pretty useful command for that is the well-known grep, which allows us to find patterns in the results. For example, we could chain the output of the branches command to grep pattern to search for branches that match the pattern.

I want to only see the branches which are prefixed with fix because I want to come back to fix that pesky bug? no problem, git branches | grep fix would do the filtering (pst, adding an alias to the branches command is pretty useful). Then you can copy the branch name and check out to it

... But what if we want to interactively filter the branches and navigate through them, as well as binding keyboard commands to actions? (to, for example, check out to a branch using enter!)... you can easily do that with Fzf.

Introducing Fzf

Fzf is a grep on steroids. According to the description, it is a general-purpose command-line fuzzy finder.

GitHub logo junegunn / fzf

🌸 A command-line fuzzy finder

fzf - a command-line fuzzy finder travis-ci

fzf is a general-purpose command-line fuzzy finder.

It's an interactive Unix filter for command-line that can be used with any list; files, command history, processes, hostnames, bookmarks, git commits etc.

Pros

  • Portable, no dependencies
  • Blazingly fast
  • The most comprehensive feature set
  • Flexible layout
  • Batteries included
    • Vim/Neovim plugin, key bindings and fuzzy auto-completion

Table of Contents

Installation

fzf project consists of the following components:

  • fzf executable
  • fzf-tmux script for launching fzf in…

Fzf grabs the output of any command, being it a list, processes, text... and lets you search and filter these results interactively and navigate through them. You can also assign key bindings to commands on your search.

With it, we can search and navigate through any directory easily and look for files, also adding some key bindings e.g opening the file in vscode when clicking enter and copying the filename path with tab.

ls | fzf --preview 'bat --style=numbers --color=always {}' --bind 'enter:execute(code {}),tab:execute-silent(echo {} | pbcopy)+abort' | head -100
Figure 2: command for searching and navigating through a directory with key bindings using fzf

Output of the ls command using fzf

Figure 3: output of the ls command using fzf

The above example uses bat instead of cat for previewing files, as it includes some nice features like syntax highlighting and also shows modifications on files by integrating with git.

GitHub logo sharkdp / bat

A cat(1) clone with wings.

bat - a cat clone with wings
Build Status license Version info
A cat(1) clone with syntax highlighting and Git integration

Key FeaturesHow To UseInstallationCustomizationProject goals, alternatives
[中文] [日本語] [한국어] [Русский]

Syntax highlighting

bat supports syntax highlighting for a large number of programming and markup languages:

Syntax highlighting example

Git integration

bat communicates with git to show modifications with respect to the index (see left side bar):

Git integration example

Show non-printable characters

You can use the -A/--show-all option to show and highlight non-printable characters:

Non-printable character example

Automatic paging

bat can pipe its own output to less if the output is too large for one screen.

File concatenation

Oh.. you can also use it to concatenate files 😉. Whenever bat detects a non-interactive terminal (i.e. when you pipe into another process or into a file), bat will act as a drop-in replacement for cat and fall back to printing the plain…

Creating the interactive branch navigator with Fzf

With the fzf command in place, we can pipe our git branches command [Fig. 1] to the fzf part of [Fig. 2] with some tweaking of the key bindings, and that will allow us to filter, copy and check out branches, just like that:

git for-each-ref --sort='authordate:iso8601' --format='%(authordate:relative)%09%(refname:short)' refs/heads | fzf --tac --bind 'enter:execute(echo {} | rev | cut -f1 | rev | xargs git checkout)+abort,tab:execute-silent(echo {} | rev | cut -f1 | rev | pbcopy)+abort'
Figure 4: command for interactively searching git branches,checking them out by pressing `enter` and copying the branch name by using `tab`

You can then bind the command to some git alias in ~/.gitconfig, I have it assigned to git brs.

git alias for the command

Figure 5: assigning a git alias for the command

Fzf includes numerous built-in key bindings as exiting with escape or navigating with scrolling/arrows, to name a couple. Try extending the command by adding more key bindings that fit your workflow!

Here's a short example of the command in action:

interactive branch navigation command example

Figure 6: interactive branch navigation command example on the terminal

That's it! ✨


And you, what other amazing fzf commands do you use, or would you create to improve your development workflow?

Discussion

pic
Editor guide
Collapse
kantord profile image
Daniel Kantor

I wonder if there's a way to hack some "previews" for the branches/refs.

I imagine sth like

# Branch name
Created: 16 April 2020
Updated: 18 May 2020

Authored: Albert Alises, John Doe, Mary Jane

Commits:
4as4d234 Lorem ipsum
5fg4hz32 Dolor sit amet

Afaik fzf requires actual files for previews, but those files could be generated periodically by a script, which would have the added benefit of being able to run git fetch --all in the script, and thus never having to fetch before changing branches.

Collapse
b2aff6009 profile image
b2aff6009

Hey Albert,
that' pretty cool. I used fzf already for searching and opening files with vim or vscode, but until now without preview. Here a short question, the preview is not updating when I switch the line? Do I need to config something for it?
NOTE: I added a ' after color=always as there was a missing ' and for me it looked like it should close the preview command.

A more detailed point: Is there a way to even go down into folders? I already thought about a keybind, which would basically retrigger the command itself, but with a different path.

git brs works like a charm!

Thank you!

Collapse
aalises profile image
Albert Alises Author

Thanks for the words!!

The command for ls contained a typo. For the preview to work, you need to pass the path to bat using {}, that is: --preview 'bat --style=numbers --color=always {}'.

I have updated the command in the article, thanks for noticing!

As for the navigation, I guess you could trigger the command recursively when clicking on the right arrow, with the new path, and abort the current command. Clicking the left arrow would trigger ls .., hit me up if you manage to get it working!