---
title: Powerful Git Macros For Automating Everyday Workflows
teaser: 'Improve your everyday git workflows by automating the boring stuff.

  '
tags: git,devtools,unix,shell,productivity
author: Wil Hall
published_on: 2020-05-14
---

I try to identify the things in my work that I do most often or those that are
most error prone and automate those first. There is always room for
improvement, but what you'll actually need necessarily changes with what you're
working on and who you're working with. So rather than finding the 'perfect
setup', I like to let it develop and change naturally over time.

Everyday [git] workflows are a great candidate for automation. I spend a lot of
time working with git each day. Most of that time is spent working with git on
the command line, with the exception of [Sublime Merge] and [Fork] which
I prefer for visual merging and staging of large or complex changesets.

I find working with git from the shell to be a more customizable experience
than working in a graphical git client. You can perform common unixisms like
piping its output to another command, or build your own macros that compose git
functionality to automate things in your everyday workflow.

[git]: https://git-scm.com/
[Sublime Merge]: https://www.sublimemerge.com/
[Fork]: https://git-fork.com/

## A comprehensive set of shell aliases

Either the [zsh git plugin] or [bash-it] is a good place to start if you want
a comprehensive set of shell aliases for git. Most of the aliases provided
don't compose any behavior, but they save you a lot of characters for commands
you type all the time. For example: tired of typing `git commit`? Now you can
type `gc` instead.

These provide a few useful shell functions as well such as `gwip` and `gunwip`
for automating the creation/deletion of WIP (work-in-progress) commits, but for
the mostpart they are a collection of aliases for existing git commands.

For any commands that you do type out, you can avoid prefixing them with `git`
by using [gitsh] - an interactive shell for git. You can even utilize your
existing git aliases, modify your git config for the duration of the shell
session, and more.

[zsh git plugin]: https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/git
[bash-it]: https://github.com/Bash-it/bash-it/blob/master/aliases/available/git.aliases.bash
[gitsh]: https://github.com/thoughtbot/gitsh

## GitHub from the command line

If you use [GitHub], it's worth checking out [hub], the official command line
tool that allows you to perform everyday GitHub tasks from the shell such as
creating pull requests or browsing issues.

From the help docs:

```shell
These GitHub commands are provided by hub:

   api            Low-level GitHub API request interface
   browse         Open a GitHub page in the default browser
   ci-status      Show the status of GitHub checks for a commit
   compare        Open a compare page on GitHub
   create         Create this repository on GitHub and add GitHub as origin
   delete         Delete a repository on GitHub
   fork           Make a fork of a remote repository on GitHub and add as remote
   gist           Make a gist
   issue          List or create GitHub issues
   pr             List or checkout GitHub pull requests
   pull-request   Open a pull request on GitHub
   release        List or create GitHub releases
   sync           Fetch git objects from upstream and update branches
```

Even some of the smallest conveniences like `hub browse` can be a huge time
saver!

[GitHub]: https://github.com/
[hub]: https://hub.github.com/

## Whitespace diffs

When I want to get an overview of my changes while I am working, I'll often look
at a diff on the command line. I prefer looking at a whitespace diff because it
reduces visual noise by hiding changes that only change whitespace characters.
We can reduce even more visual noise by hiding the `+`/`-` indicators in the
diff. My whitespace diff alias looks like the following:

```gitconfig
[alias]
    wdiff = diff -w --word-diff=color
```

The `-w` [ignores whitespace], and `--word-diff=color` distinguishes additions
and deletions [by color only].

[ignores whitespace]: https://git-scm.com/docs/git-diff#Documentation/git-diff.txt--w
[by color only]: https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---word-diffltmodegt 

## Navigating to the root of a repository

There are two macros I find useful for working with the root of the current git
repository - either to `cd` back to it, or to run commands on it.

The first is a git alias that outputs the root directory using the `rev-parse`
[--show-toplevel] flag:

```gitconfig
[alias]
    root = rev-parse --show-toplevel
```

[--show-toplevel]: https://git-scm.com/docs/git-rev-parse#Documentation/git-rev-parse.txt---show-toplevel

The second is a shell alias which provides an easy way to `cd` back to the root
directory: 

```gitconfig
alias grcd='cd $(git root)'
```

## Setting the upstream branch

When you have a local branch, git needs to know which remote branch (if any)
should be used when performing operations like `push` or `pull`. As a result,
you're probably familiar with messages like the following:

```
fatal: The current branch master has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin master
```

And if you're like me, you've copied the suggested `push --set-upstream`
command thousands of times.

I have since defined an git alias to automate this; I prefer it as a seperate
command, but you could integrate it into a `push` alias as well:

```gitconfig
[alias]
    set-upstream = !git branch --set-upstream-to=origin/`git symbolic-ref --short HEAD`
```

In the above, we use the [symbolic-ref] command to resolve the current branch
name; `HEAD` refers to the ref we want to resolve the name of (the tip of the
current branch) and passing the [--short option] shortens (for example)
`refs/heads/master` to `master` giving us the non-fully-qualified branch name.

We then pass the current branch name to the `--set-upstream-to` flag as the
fully-qualified name of the remote branch (in our example: `origin/master`).

[symbolic-ref]: https://git-scm.com/docs/git-symbolic-ref
[--short option]: https://git-scm.com/docs/git-symbolic-ref#Documentation/git-symbolic-ref.txt---short

## Getting a good look at branches

You can list all branches in a repository with `git branch`, but the output
leaves much to be desired.

In the [thoughtbot dotfiles] we have a [git branches] alias which lists out all
the remote branches along with the last modification date and author, sorted in
descending order by last commit date:

```gitconfig
[alias]
    branches = for-each-ref --sort=-committerdate --format=\"%(color:blue)%(authordate:relative)\t%(color:red)%(authorname)\t%(color:white)%(color:bold)%(refname:short)\" refs/remotes
```

It looks complex, but most of it is the `--format` option. It uses git's
[for-each-ref] command to loop over all refs matching the provided pattern. In
this case, we want all refs matching `refs/remotes` (for all the remote
branches) and we specify `--sort=-committerdate` to sort descending (note the
preceding `-`) by `committerdate`.

Two big benefits of this alias are:

1. It provides more information than `git branch`, but keeps the output concise
1. It uses colors to help visually distinguish the output, improving its
   readability

A similar alias I like to define for working with branches allows us to see the
ten most recently used local branches, sorted descending by last commit date.
I often use this to remind myself what I've been working on lately:

```gitconfig
[alias]
    mru = for-each-ref --sort=-committerdate --count=10 refs/heads/ --format='%(HEAD) %(color:yellow)%(refname:short)%(color:reset) - %(color:red)%(objectname:short)%(color:reset) - %(contents:subject) - %(authorname) (%(color:green)%(committerdate:relative)%(color:reset))'
```

This is very similar to the `branches` alias above, except we pass `refs/heads`
to get the local branches instead of the remote branches, limit the number of
results with `--count=10`, and show some more information in the formatted
output.

It looks like this:

![A screenshot of the output of `git mru`](https://images.thoughtbot.com/blog-vellum-image-uploads/agbYnTq6T8ufCF1eE2mZ_Image%202020-05-09%20at%2012.58.49%20PM.png)

[thoughtbot dotfiles]: https://github.com/thoughtbot/dotfiles
[git branches]: https://github.com/thoughtbot/dotfiles/blob/master/gitconfig#L10
[for-each-ref]: https://git-scm.com/docs/git-for-each-ref

## Querying changes to repository files

I frequently want to answer the question: When was this file added? You might
also want to answer similar questions about deletions, renames, or really any
type of change. It's easy to do with the `git log` option [--diff-filter].

I define an alias `whatadded` to answer the first question, because it's the one
I ask most frequently:

```gitconfig
[alias]
    whatadded = log --diff-filter=A
```

Now I can just run `git whatadded src/some/file`.

The `A` means to filter to only commits that _add_ the specified file, but you
can pass other arguments too like `D` for deletions or `M` for modifications.
You can also compose these to see, for example, when a file was added, deleted,
or renamed:

```shell
git log --diff-filter=ADR src/some/file
```

[--diff-filter]: https://git-scm.com/docs/git-log#Documentation/git-log.txt---diff-filterACDMRTUXB82308203

## Finding the difference between the current branch and its base branch

Let's say you have a branch `feature` branched off of `develop`. At some point
they diverge, and you want a quick overview of how they differ in terms of
commits. You could run `git log feature` and `git log develop` and compare the
two. But git can also do that for you!

I define two aliases for this purpose: `gbc` (git branch changes) to see what
the current branch has that the base branch does not, and `gbbc` (git base
branch changes) to see what the base branch has that the current branch does
not. Both are intended to be used while you have the `feature` branch checked
out, and require you to specify the base branch (such as `develop`) as an
argument.

Their definitions are below:

```shell
gbc() {
  git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative $@..$(git rev-parse --abbrev-ref HEAD)
}

gbbc() {
  git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative $(git rev-parse --abbrev-ref HEAD)..$@
}
```

From a feature branch, we can see what changes the current branch has that
`master` does not using `gbc master`:

![A screenshot of the output of 'gbc master'](https://images.thoughtbot.com/blog-vellum-image-uploads/H0ZAskxGSTWDWfp9WwXU_Image%202020-05-09%20at%201.13.45%20PM.png)

From a feature branch, we can see what changes `master` has that the current
branch does not using `gbbc master`: 

![A screenshot of the output of 'gbbc master'](https://images.thoughtbot.com/blog-vellum-image-uploads/KzVOGnrJSxy97urPUycg_Image%202020-05-09%20at%201.14.03%20PM.png)

We use `rev-parse` again to resolve the name of the current branch, and `$@`
refers to whatever argument we pass when calling the function. The [--graph]
and [--pretty] flags are just changing the visuals of the output.
[--abbrev-commit] shows us shortened commit hashes, and [--date=relative] shows
us relative dates.

If we have `feature` checked out and we run `gbc develop`, we are effectively
running `git log develop..feature`. And if we run `gbbc develop` it's the same
as `git log feature..develop`. Except you don't have to type all that out!

These commands can be especially useful when you're trying to find the right
commit hash for a rebase, such as when using [git rebase --onto] to specify
which commit you want a rebase to start from.

[--graph]: https://git-scm.com/docs/git-log#Documentation/git-log.txt---graph
[--pretty]: https://git-scm.com/docs/git-log#Documentation/git-log.txt---prettyltformatgt 
[--abbrev-commit]: https://git-scm.com/docs/git-log#Documentation/git-log.txt---abbrev-commit
[--date=relative]: https://git-scm.com/docs/git-log#Documentation/git-log.txt---dateltformatgt
[git rebase --onto]: https://thoughtbot.com/blog/rebasing-your-branch-with-git-rebase-onto

## Rebasing a branch

### Updating a feature branch with changes from the base branch

First, let's look at incorporating changes from a base branch `develop` into
a feature branch `feature`. We want to make sure we have the latest changes from
both the base branch and our feature branch, and then we want to perform the
rebase. Let's call it `gqrb` (git-quick-rebase):

```shell
gqrb() {
    git set-upstream
    git fetch origin "$@:$@" && git pull && git rebase "$@"
}
```

Now with `feature` checked out we can run `gqrb develop` to incorporate changes
from `develop` into `feature`.

Firstly we run `git set-upstream` because this will fail if we don't have an
upstream branch. Next, we run `git fetch` for only the `develop` branch. Then
we pull the current branch (`feature`). And finally, we run `git rebase
develop`. If any of those steps fails, `&&` will stop execution of the
subsequent commands. 

If you find yourself wanting to do this sort of thing with a dirty working
tree, you might want to consider adding the [--autostash] flag to the rebase,
which stashes changes before the rebase and reapplies them after it completes.

[--autostash]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---autostash

### Reworking the commits on a feature branch to reword, squash, or fixup

For rebase operations that involve commits on the current branch, I often want
to [perform an interactive rebase], but I only care about rebasing with commits
that are exclusive to the current branch.

[perform an interactive rebase]: https://thoughtbot.com/blog/git-interactive-rebase-squash-amend-rewriting-history

For example, I might branch `feature` off of `develop` and make two commits,
and now I want to squash those two commits. I could run `git rebase -i HEAD~2`,
or we can build a macro that works for any number of commits and any action so
that you don't have to go hunting for the commit you're looking for on your
branch.

I call this function `gbir` (git-branch-interactive-rebase):

```shell
gbir() {
  git rebase -i --autosquash $(git merge-base --fork-point "$@" $(git rev-parse --abbrev-ref HEAD))
}
```

In our example, if you have `feature` checked out you can run `gbir develop` to
drop into an interactive rebase with all the commits from `feature` since
it diverged from `develop`.

We use `rev-parse` again to get the current branch name, and then pass the
argument (`develop`) and the current branch name to the git [merge-base
--fork-point] command which finds the common ancestor commit between the two
branches.

We then pass that ancestor commit to `rebase -i` to drop into the interactive
rebase with commits since the common ancestor.

I like to add the [--autosquash] flag to the rebase here so that if I have run
`git commit --squash` or `git commit --fixup` on the branch, starting
a rebase this way with the [--fixup] flag will [automatically perform the squash
and fixup operations].

[merge-base --fork-point]: https://git-scm.com/docs/git-merge-base#Documentation/git-merge-base.txt---fork-point
[--autosquash]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---autosquash
[--fixup]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---fixupltcommitgt
[automatically perform the squash and fixup operations]: https://thoughtbot.com/blog/autosquashing-git-commits

## Quick fixups

Using the `gbir` function from above, it's pretty painless to perform fixups on
a branch. But sometimes I want to make a fixup to the last thing I commited,
and so it feels a little heavy-handed to commit it and then rebase it away.
That's two whole commands! Could we make a single command to accomplish that?

If we have some working tree changes we want to apply to the last commit
(without changing its message) we could do something like this:

```shell
gfu() {
  git commit --amend --no-edit
}
```

And then we can just run `gfu` to apply the changes in our working tree to the
last commit. This is conceptually the same as performing a fixup on the last
commit, but we use the [--no-edit] flag along with the amend to avoid changing
the commit message.

[--no-edit]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---no-edit

## Build your own!

Hopefully some of these macros will prove to be useful in your git workflow as
well. But even more importantly, I hope they will inspire you to build your
own. All of these solutions started out as roadbumps that I kept running into.
If you run into those as well, I encourage you to write them down, and work to
build things that help you overcome them.
