My favorite Git tricks to make life easier

Git sometimes is a confusing beast, but it can be awesome if you train it well

January 16, 2018
productivity hacks

Pretty much everything I do is code. I build cloud platforms; everything is code. I build tools; more code. This blog; again, code. All this code lives in Git. Either on Github, on Bitbucket, or on private-hosted company VCS servers. And Git is nice. But from time to time, Git is also extremely confusing, or requires terrifyingly long commands.

So here are some of my favorite Git tricks that make my life easier. Maybe they’ll work for you, too.

Git on MacOS

Apple has a tendency to ship somewhat out of date versions of tools with MacOS. Their version of Git is no exception, although it’s not as bad as their Bash version. Nonetheless, if you want a more recent version, Homebrew is your friend:

$ brew install git

Git and Github

If most of your ‘Git business’ resides on Github, Hub may be your friend. It is a tools that wraps Git and adds a ton of Github specific extra goodies, like creating (and listing) pull-requests in your terminal, or quickly opening the relevant repository in your browser with a single command.

$ brew install hub

To really wrap git, add the following to your Bash config:

# If Github's 'hub' is installed, alias 'git' to it
if which hub &> /dev/null ; then
  alias git=hub
fi

To check if it works:

$ git version
git version 2.15.1
hub version 2.2.9

For more info on what Hub can do for you and how to set it up, see the Hub webpage

Run git commands on multiple repositories

Chances are your work is spread across multiple repositories, in which case you might want to run some git command across all of them. Maybe you have a daily standup and you want to quickly check what you did yesterday. Your team works on 10 repositories? That would mean running this 10 times:

$ cd some_repo
$ git log --author="<your name here>" --since='yesterday 12am' --until='12am' --all --no-merges
$ cd ..

As an alternative you can setup a shell alias called cgit:

cgit() {
  c_repos=$(find . -maxdepth 2 -name .git -type d -print | cut -d'/' -f2)
  c_basedir=${PWD}

  for repo in ${c_repos}; do
    echo "## [${repo}] "
    git --git-dir="${c_basedir}/${repo}/.git" \
      --work-tree="${c_basedir}/${repo}" \
      "$@"
    echo -e ""
  done
}

Then, considering your repos are in ~/git/work, you do this:

$ cd ~/git/work
$ cgit log --author="<your name here>" --since='yesterday 12am' --until='12am' --all --no-merges

Short everything

Long commands are bad. So let’s have some short aliases for things in our .gitconfig, right?

[alias]
  # short aliases
  st = status
  co = checkout
  ci = commit
  br = branch
  df = diff
  lg = log -p
  rso = remote show origin
  me = config user.name
  smash = !git ir

  # interactive rebase
  ir = !sh -c 'git rebase -i origin/${1-master}' -

The magic pull

When I start working or something (or simply every morning) I make sure my local working copies are up to date with the remote. But what about local commits? What about uncommited work? What about submodules? There are many reasons why a simple git pull might fail.

I use: git up, which is configured in my .gitconfig:

[alias]
  up = !git pull --rebase --prune --autostash --recurse-submodules

Combined with cgit I can simply enter my ~/git/work dir and run cgit up and I’ll be up to date. Profit!

Prettier logs

The basic git log command works. It’s not perfect, but it works. But it also takes a ton of space, and I thought I could do a better job. Prettier logs, but also some aliases to quickly see what I did yesterday. Or what everyone did yesterday.

[alias]
  llog = !git log --pretty=format:'%Cgreen(%ci)%Creset %Cred%h%Creset %<(20,trunc)%C(blue)%an%Creset  %s %C(yellow)%d%Creset ' --abbrev-commit

  # all commits that aren't merges on all branches
  all = !git log --pretty=oneline --abbrev-commit --all --no-merges

  # all commits today for only me
  today = !git llog --since='12am' --all --no-merges --committer=\"`git me`\"

  # and yesterday
  yesterday = !git llog --since='yesterday 12am' --until='12am' --all --no-merges --committer=\"`git me`\"

  # and the same, but for everyone
  today-all = !git llog --since='12am' --all --no-merges
  yesterday-all = !git llog --since='yesterday 12am' --until='12am' --all --no-merges

Magic pull with relevant logs

I like staying on top of things, so when I pull in the latest changes, I don’t just want to be up to date, I would also like to see what changed since I last pulled. Meet git up-log:

[alias]
  up-log = !sh -c 'logstart=$(git rev-parse HEAD) && git pull --rebase --recurse-submodules --prune --autostash && echo "" && git --no-pager llog ${logstart}.. --stat'

Finish up after a pull-request has been merged

Nobody wants to celebrate their work being merged into master by manually checking out master, updating it, removing branches, etcetera. Just a single command, and get on with the fun stuff already! Just a simple git prdone and there you go:

[alias]

  # clean up after PR has been merged
  prdone = !sh -c 'git co ${1-master} && git up && git clean-merged'

  # `git clean-merged`
  # clean up merged branches
  clean-merged = "!git branch --merged | grep -v '\\^\\*\\|master' | xargs -n 1 git branch -d"

Putting it together

With all these aliases, working with git looks like this:

## Check in in the morning
$ cd ~/git/work
$ cgit up-log

...

## Pre standup
$ cd ~/git/work
$ git yesterday

## Start working on something
$ cd ~/git/work/foobar
$ git co -b feature
$ git ci -a -m 'did something'
$ git push origin feature

## Create pull request (using hub)
$ git pr

...

## Finishing up
$ git prdone
$ cd ..
$ cgit up-log

That’s it! You can also read about how I use multiple Git identities and how I deal with situation-specific shell configuration.