Up Your Git Game With Advanced Git Aliases

June 27, 2017William Duclot4 min read

thumbnail

As programmers, we use Git everyday.
The time saved by a good Git config is remarkable!
In particular, amongst the most useful features of Git is the ability to create your own Git commands.

Aliases

You probably know about aliases.
Who still has time to type git status? In 2017?
We all have the usual co = checkout or st = status in our .gitconfig, we're here for sexier stuff.
Here is a compilation of useful aliases I've created, adapted or shamelessy stolen from the interwebs.
Feel free to take and share:

[alias]
    ###########################################
    # The essentials
    ###########################################
    # -sb for a less verbose status
    st = status -sb
    # Easy commits fixup. To use with git rebase -i --autosquash
    fixup = commit --fixup
    # If you use Hub by Github
    ci = ci-status

    ###########################################
    # The command line sugar
    ###########################################
    # Pop your last commit out of the history! No change lost, just unindexed
    pop = reset HEAD^
    # Fix your last commit without prompting an editor
    oops = commit --amend --no-edit
    # Add a file/directory to your .gitignore
    ignore = "!f() { echo \"$1\" >> .gitignore; }; f"
    # A more concise and readable git log
    ls = log --pretty=format:"%C(yellow)%h\\ %Creset%s%Cblue\\ [%cn]\\%Cred%d" --decorate
    # Same as above, with files changed in each commit
    ll = ls --numstat
    # Print the last commit title & hash
    last = --no-pager log -1 --oneline --color

    ###########################################
    # This much sugar may kill you
    ###########################################
    # Show which commits are safe to amend/rebase
    unpushed = log @{u}..
    # Show what you've done since yesterday to prepare your standup
    standup = log --since yesterday --author $(git config user.email) --pretty=short
    # Show the history difference between a local branche and its remote
    divergence = log --left-right --graph --cherry-pick --oneline $1...origin/$1
    # Quickly solve conflicts using an editor and then add the conflicted files
    edit-unmerged = "!f() { git diff --name-status --diff-filter=U | cut -f2 ; }; vim `f`"
    add-unmerged = "!f() { git diff --name-status --diff-filter=U | cut -f2 ; }; git add `f`"

What is the bang for?

Note the git ignore command, alias to "!f() { echo \"$1\" >> .gitignore; }; f".
This looks weird, let's explain it:

The ! allows to escape to a shell, like bash or zsh.
Then, we define a function f() that does what we want (here, appending the first argument to .gitignore).
Finally, we call this function.

I've had a lot of trouble understanding why using a function is necessary, as the shell escaping does interpret positional parameters such as $1.
It's actually a neat trick: git appends your parameters to your expanded command (it is an alias after all) which leads to unwanted behavior.
Let's see an example:

Let's say you have the alias echo = !echo "echoing $1 and $2".

$ git echo a b
echoing a and b a b

Wow! What happened?

Git expanded your alias escaping to a shell, which interpreted positional parameters.
It means that $ git echo a b was equivalent to $ echo "echoing a and b" a b, hence the output.

Now wrapped in a function: echo = "!f(){ echo "echoing $1 and $2"; };f".
In this case, $ git echo a b is equivalent to $ f(){ echo "echoing $1 and $2" }; f a b.
The parameters still are appended (not interpreted by the shell because they're function parameters), but they are used in the call to the f function!

Writing your own commands

Aliases are great, and their power is almost unlimited when using the !f(){...};f trick.
But you need to escape quotes and new lines, you don't have syntaxical coloration and it makes your ~/.gitconfig very long and unreadable.
What if you want to do really complex stuff?

Doeth not despair, for I have the solution.
It happens that Good Guy Git is looking in your $PATH when you call it with a command: typing $ git wow will look for an executable named git-wow everywhere in your $PATH!
This means you can define your own git commands easily by writing eg bash, python or by compiling an executable.
Let's do that.

Here is a simple git-wip bash script, that takes all changes and commit them with a "WIP" commit message.
If the last commit message already was "WIP", amend this commit instead:

#!/usr/bin/env bash

git add -A

if [ "$(git log -1 --pretty=%B)" = "WIP" ]; then
    git commit --amend --no-edit
else
    git commit -m "WIP"
fi

And its friend, git-unwip that undo the last commit if its commit message was "WIP", else it's a no-op:

#!/usr/bin/env bash

if [ "$(git log -1 --pretty=%B)" = "WIP" ]; then
    git pop # defined as an alias, remember!
else
    echo "No work in progress"
fi

Put these two scripts in your $PATH (/usr/local/bin for exemple), and you can call git wip or git unwip until your fingers bleed.

That's all folks

Now run along kids, and go create your own aliases and custom commands!
Why not a script to start a new feature branch (sync with remote, prune local branches, create a new branch), or a script to open GitHub pull requests on multiple branches?

William Duclot

William Duclot

Web Developer at Theodo