June 27, 2017 William Duclot 4 min read

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 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?