Optimising my git checkout workflow

Carlos Arrastia
Tech at Holvi
Published in
7 min readNov 20, 2017

TL;DR: I forget the name of git branches I recently checked out and I built a git command to help me remember

I believe it is natural to every person to optimise their flows. Any task performed repeatedly is done better in the shortest time possible, while retaining execution quality. Trial and error of small changes when executing a task helps to refine time spent on it. For some other situations, like in sports, training and analysis helps improving the quality of a move and improve accuracy or reduce times.

Most of us in the software world are continuously improving our ways of working. We research and try out new tools, learn keyboard shortcuts, configure our desktop environment, rearrange windows and icon locations, etc. so that we get shortest reaction times on our engineering tasks.

Recently I have made a small improvement to my workflow I would like to share with you. It is nothing revolutionary and possibly you might not have the need for it, but just as an example on how spending an hour writing some code can help optimise your workflow.

Few months ago I changed a bit my role at Holvi. As the organisation grows and we get more engineers on board across multiple teams I have started to overview our development practices and processes and trying to align the way we work. This includes reviewing a lot of code, helping prepare releases and assist our engineers when they get stuck or have questions. My day has moments full of context switching and jumping back and forth to different ongoing tasks.

We use git for our version control at Holvi, and certainly I believe this is one of the most powerful tools, if not the most, in contemporary software engineering. Git completely falls into what I mentioned earlier about optimising flows, it is extremely flexible and it seems you never finish finding new features.

Now coming back to the moments of the day in which I am busy helping around and I am constantly switching branches. I realised I tend to forget branch names I have been working on a few minutes earlier. It requires a bit of memory exercise, and many times I have very similar names for temporary branches I have created along the way. Sometimes it took me up to 3 minutes to come back to the branch. This is something I want to avoid and began my optimisation quest.

Reflog

The git tool fitting my needs is reflog. It provides a list of all the actions in order that you have performed them. Here is a real life example

> git reflogcf4a7bf HEAD@{0}: checkout: moving from test to test
cf4a7bf HEAD@{1}: checkout: moving from petra-holvilabs-switch-payments-debt to test
cbf6f6d HEAD@{2}: checkout: moving from harrastia-fix-idempotency-tests to petra-holvilabs-switch-payments-debt
a5a489c HEAD@{3}: checkout: moving from test to harrastia-fix-idempotency-tests
cf4a7bf HEAD@{4}: commit (cherry-pick): Update changelog manually
e270ffc HEAD@{5}: checkout: moving from staging to test
aaf1118 HEAD@{6}: commit: Update changelog manually
e5f1ead HEAD@{7}: checkout: moving from test to staging
e270ffc HEAD@{8}: cherry-pick: Merge pull request #5190 from holvi/samuel-pin-retrieval-after-activation-fix
9a30248 HEAD@{9}: cherry-pick: Merge pull request #5178 from holvi/gediminas-new-wd-api-fix
f60838d HEAD@{10}: checkout: moving from staging to test
e5f1ead HEAD@{11}: pull: Fast-forward
6dfd534 HEAD@{12}: checkout: moving from staging to staging
6dfd534 HEAD@{13}: reset: moving to origin/staging
f60838d HEAD@{14}: merge origin/test: Fast-forward
6dfd534 HEAD@{15}: checkout: moving from test to staging
f60838d HEAD@{16}: commit (merge): Merge tag ‘2017.11.16.1’ into test
171a815 HEAD@{17}: pull: Fast-forward
...

This is cool, now I can see any checkout: lines and see from and to which branch I changed. However it is a bit difficult to scan visually. Let’s grep a bit:


> git reflog | grep “checkout” | less
cf4a7bf HEAD@{0}: checkout: moving from test to test
cf4a7bf HEAD@{1}: checkout: moving from petra-holvilabs-switch-payments-debt to test
cbf6f6d HEAD@{2}: checkout: moving from harrastia-fix-idempotency-tests to petra-holvilabs-switch-payments-debt
a5a489c HEAD@{3}: checkout: moving from production to harrastia-fix-idempotency-tests
e270ffc HEAD@{5}: checkout: moving from staging to test
e5f1ead HEAD@{7}: checkout: moving from test to staging
f60838d HEAD@{10}: checkout: moving from staging to test
6dfd534 HEAD@{12}: checkout: moving from staging to staging

Nice! But it is still difficult to scan visually to extract the branch names. Let’s find some other solution based on this.

Custom git commands to the rescue

Git allows defining custom commands with actions defined in a script. You can read more about it here. This is as simple as having an executable file starting with git- in the PATH for the user executing git.

So I created my git command git-cor where cor stands for checkout recent. I wrote my command in shell script for that (not even bash) to add up a bit of a challenge and ensure portability (Mac + Linux) — and using commands sed grep egrep wc. The idea is that the command will give me a set (a list without duplicate items) with the most recently checked out branches. Then by selecting one of them it will be checked out.

You can see the script I wrote in this gist:

#!/bin/sh

# Carlos Arrastia (Holvi) 2017
#
# This script adds the git command "cor" which stands for CheckOut Recent.
# it lists the 10 most recently checked out branches and allows the user to select
# one for check out
#
# Usage:
#
# git cor
#

# extract the branch names out of the reflog checkout lines
lines=`git reflog | grep "checkout:" | sed -nE 's/.*moving from ([^ ]*).*/\1/p'`

# get a set with the 10 latest branches checked out
recent=""
for line in $lines; do
# add the branch to the list if it doesn't exist
match=`echo $recent | grep "\\b$line\\b"`
if [ -z "$match" ]; then
recent="$recent $line"
# and break the loop if the list has more 10 items or more
if [ `echo $recent|wc -w` -gt 9 ]; then
break;
fi
fi
done

# print the branches
echo "\nThese are the the 10 most recently checked out branches\n"
counter=0
for branch in $recent; do
echo "$counter) $branch"
counter=$((counter+1))
done

# ask user for selection
while true; do
echo ""
read -p "Select the branch number to check out 0-9 or q to quit: " sel
case $sel in
# if it is a number check out the branch matching the index
[0-9] )
counter=0
for branch in $recent; do
if [ $counter -eq $sel ]; then
git checkout $branch
break;
fi
counter=$((counter+1))
done
break;;
# exit if q is entered
[Qq]* ) exit;;

# notify of error with any other choice
* ) echo "Invalid choice.";;
esac
done

The main thing is matching and return only the branch names recently “moved away from”

git reflog | grep "checkout:" | sed -nE 's/.*moving from ([^ ]*).*/\1/p'

Then we produce a set iterating over the results. We do this creating a string with branch names separated with spaces (as branch names can’t include spaces). We will add items to that end of the string only if the name of the branch we are looking at doesn’t exist. This way we remove duplicates:

recent=""
for line in $lines; do
# add the branch to the list if it doesn't exist
match=`echo $recent | grep "\\b$line\\b"`
if [ -z "$match" ]; then
recent="$recent $line"
# and break the loop if the list has more 10 items or more
if [ `echo $recent|wc -w` -gt 9 ]; then
break;
fi
fi
done

The rest is just reading the user input and checking out the branch.

Let’s see how this looks in action:

> git corThese are the the 10 most recently checked out branches0) test
1) petra-holvilabs-switch-payments-debt
2) harrastia-fix-idempotency-tests
3) staging
4) harrastia-token-code-verify-refactor
5) harrastia-debt-status-change-signal
6) harrastia-debt-image-fix
7) muhis-patch-company-address-endpoint
8) sakari-opsware-upgrade-user
9) gediminas-debt-api-detail-idempotent
Select the branch number to check out 0-9 or q to quit: 2

Switched to branch 'harrastia-fix-idempotency-tests'

Looking good! now I can save few seconds every time I don’t remember what I was working on a couple of hours ago and use the extra time to sip some tea.

What next?

There are some things I have in mind for this script, but I copied it with the most reduced amount of functionality for simplicity. You can check the gist from time to time to see if I add anything. The first thing coming to mind that can be very useful is to pass a --stash argument which will automatically git stash changes before checking out the selected branch, and optionally with another argument which will do git stash pop after the branch is checked out. And same could be done with --reset-hard which could do a git reset --hard right before checking out the selected branch.

I hope this short demonstration on how to set up simple git custom commands tickles your brain to analyse how you can improve your git workflows with little effort. And remember that you can use your favourite scripting language to write these commands as long a they are executable in the PATH. Perhaps writing this in Python would have been way easier and only with a fraction of the lines of code, but I just tried to stay with the most basic tools available.

--

--

Carlos Arrastia
Tech at Holvi

I make. Things happen. Sometimes I also make things happen.