Up Your Git Game With Advanced Git Aliases
William Duclot5 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 # 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?