Fuzzy branches: a brief example of a git custom command.
Tags: git, productivity, tutorial
Published: 2019-10-13 10:34:48

---
title: Fuzzy branches: a brief example of a git custom command.
published: true
description: Living life in the express checkout lane.
tags: git, productivity, tutorial
cover_image: https://thepracticaldev.s3.amazonaws.com/i/2dmlcglsvhiurr2qe7wm.jpg
series: Extending things
---

It's popular for people to do things like this in order to save a couple of keystrokes:

```sh
alias gco='git checkout'
```
Honestly, [I'm not a fan](https://dev.to/moopet/the-case-against-aliases-2mb1), so I thought I'd see if I could do better.

## What do I want to achieve?
I want to lower the effort in changing branches. It's a modest goal, and one that's theoretically addressed by the alias above. But say you want to switch between two long-names feature branches:

```sh
$ git branch

    develop
    feature/TCKT-123-bug-frobinator-crashes-doohickey
  * feature/TCKT-124-feat-add-flux-capacity
```

Now, you can have some kind of tab completion installed for git, so you can type `git checkout f<tab>3<tab>` and get where you want to go. That's one for the vimmers among us; let's say we don't care for that way of working and sweep that whole solution under the Rug Of Digression.

We can make our own command instead.


## Let it work on as many systems as possible
Instead of using a shebang like `#!/bin/bash` or `#!/usr/bin/env bash` we'll use `#!/bin/sh`. If we don't have any requirements beyond POSIX, why add any dependencies?
Here's a relevant Stack Overflow question about [the recommended POSIX shebang](https://stackoverflow.com/questions/53116226/what-is-the-recommended-posix-sh-shebang?noredirect=1) but suffice it to say that this one is more likely to exist than `bash`, and that `env` might not even be available if `/usr` isn't mounted yet.

```sh
#!/bin/sh
```

## Ensure we're in a git repository
We want to bail out of the script early if it's not going to work.
We don't want to see an initial git error and then a bunch of artifacts from our script.

```sh
if ! git status >/dev/null; then
  exit
fi
```

## Pass through git's regular error messages and exit status
Redirecting STDOUT to `/dev/null` means that if we _are_ in a git repo, the command won't spew irrelevant status information to the console.
We could include a redirect for STDERR with `2>&1`, but if the command fails, we _want_ it to display an error. Likewise, we could provide our own error code with something like `exit 1` to show there's been a problem, but `exit` on its own will pass through the previous error code. It's effectively the same as `exit $?`. This way we don't need to worry about compatibility with other scripts, they will get the appropriate git exit code.


## Add support for fuzzy matching
We can take the output from `git branch` and pass it through `fzf` which will do the heavy lifting for us.

```sh
    if command -v fzf >/dev/null; then
      branch="$(git branch -a | grep -oE '[^ ]+$' | fzf)"

      if [ -n "$branch" ]; then
        git checkout "$branch"
      fi
```

This looks a little scary! It's searching for a branch that contains the first argument to the script, but what's that odd-looking `grep` for?
`-o` means to only return the bit of the line that matches the pattern, not the whole line as in `grep`'s default behaviour.
`-E '[^ ]+$'` is an extended regex to grab the last word on the line[^4].
Why is this important? It's because if one of the branches is the _current_ branch, then it will have a prefix like ' * '. Check the example with the feature branches above to see what I mean. We need to make sure we have the branch name and _only_ the branch name.

## Make it pretty!

We can use `fzf`'s `--preview` option to get something helpful for each result. The obvious choice is a `git show` of the branch:

```sh
git branch -a | grep -oE '[^ ]+$' | fzf --preview='git show --color=always --stat {}
```




## Degrade gracefully if there's no available fuzzy matching tool
We can tell if a command is available like this[^2]:
```sh
if command -v fzf >/dev/null; then
  # Do stuff
fi
```

### Use `grep` to work with a _partial_ match instead.

We'd like to be able to checkout a branch using a unique string, such as the ticket numbers in the example above. This uses a cut-down version of our fuzzy branch finding command from earlier.

```sh
matches=$(git branch | grep -i "$1" | grep -oE '[^ ]+$')
```

## Do something sensible if multiple matches are found
We can count the matches with `grep -c` and if it returns more than one, print[^3] the possibilities.

This time we don't need to enclude all the crazy regex, because we're not interested having a sanitised result.

```sh
count=$(git branch | grep -ci "$1")

if [ "$count" = "1" ]; then
  shift
  git checkout "$matches" "$@"
elif [ "$count" = "0" ]; then
  printf "No match for '%s'" "$1"
else
  printf "Multiple matches:\n"

  for match in $matches; do
    printf "%s\n" "$match"
  done
fi
```

## Do something sensible if no branch is specified
In the fuzzy route, we open `fzf` by default, so what can we do that's as similar as possible? How about listing all local branches?

```sh
if command -v fzf >/dev/null; then
  git branch -a
fi
```

## Provide usage instructions if passed the `--help` flag
We're going to use a `case` statement as a little dispatcher.
Each possible command line argument will get matched against a different pattern and we'll use that to decide what to do next.

```sh
case "$1" in
  --help|-h) 
    printf "Usage: %s [pattern]\n" "$(basename "$0")"
    ;;

# ...

esac
```
Here, we can explicitly catch both `-h` and `--help` and display a help message.

## Safely handle unexpected flags
We don't have any other flags in this simple script, and we don't want to pass anything through to downstream executables like `git` or `grep` that starts with a `-` in case they interpret it as a flag. 
We're keeping things clean by quoting variables (hence `"$1"` instead of `$1`), but it pays to program defensively. Right? Right. I know you're with me on this one.
In fact, this is the only place we want to supply our own 'error' exit code.

```sh
case "$1" in
  --help|-h) 
    printf "Usage: %s [pattern]\n" "$(basename "$0")"
    ;;

  -*)
    printf "Usage: %s [pattern]\n" "$(basename "$0")"
    exit 1
    ;;

# ...

esac
```

It goes after the `--help` pattern because otherwise that would take precedence.

## Use this command to _extend_ git.
If we call it `git-co` and save it somewhere on our `PATH`, then it will work exactly as if it was a native git subcommand[^1]

# Putting it all together

![Time's up. Let's do this. -- Leeroy Jenkins](https://thepracticaldev.s3.amazonaws.com/i/xpue1lbvokfku65m6zx7.png)

A little bit of nesting things and swapping conditionals and we get to the tl;dr of this post!

```sh
#!/bin/sh

# co: "Check Out".
# Checkout an existing branch based on a partial match.
#
# If FZF is installed, this script will use it.

if ! git status >/dev/null; then
  exit
fi

case "$1" in
  --help|-h) 
    printf "Usage: %s [pattern]\n" "$(basename "$0")"
    ;;

  -*)
    printf "Usage: %s [pattern]\n" "$(basename "$0")"
    exit 1
    ;;

  "")
    if command -v fzf >/dev/null; then
      branch="$(git branch -a | grep -oE '[^ ]+$' | fzf --preview='git show --color=always --stat {}')"

      if [ -n "$branch" ]; then
        # remove the "remotes/origin/..." branch name prefix.
        branch="$(echo "$branch" | sed -e "s#^remotes/[^/]*/##")"
        git checkout "$branch"
      fi
    else
      git branch -a
    fi
    ;;

  *)
    matches=$(git branch | grep -i "$1" | grep -oE '[^ ]+$')
    count=$(git branch | grep -ci "$1")

    if [ "$count" = "1" ]; then
      shift
      git checkout "$matches" "$@"
    elif [ "$count" = "0" ]; then
      printf "No match for '%s'" "$1"
    else
      printf "Multiple matches:\n"

      for match in $matches; do
        printf "%s\n" "$match"
      done
    fi
    ;;
esac
```

## Postscript

This post was partly inspired by comments about fzf in [Neovim for web development](https://dev.to/ryanlanciaux/neo-vim-for-web-development-56n9) and the fact that there are a _lot_ of [posts about aliases](https://dev.to/search?q=aliases) here. 

I've also talked more generally about [extending command-line programs](https://dev.to/moopet/extendable-heroes-jef) before.

Cover image by [Zach Reiner](https://unsplash.com/@_zachreiner_) on [Unsplash](https://unsplash.com)

[^1]: Well, it won't support `git help co`, because we haven't gone so far as to make an installer and manual file. `¯\_(ツ)_/¯`
[^2]: `command` is more portable than `which`, but for either, we have to redirect its STDOUT to keep our output clean.
[^3]: I'm using `printf` here because it's more portable than `echo`.
[^4]: There are multiple ways to match the last word, this is just the way I happened to choose.