A piggy bank of commands, fixes, succinct reviews, some mini articles and technical opinions from a (mostly) Perl developer.

Git hooks, config and prompt

The config parts are superceded by my .gitconfig file on github.

Git hooks

Git hooks allow you to define a script that will get run at key points in your git workflow.
There are some examples in the xt directory in every repo in the .git/hooks/ directory.
To make them work, either rename the existing ones to remove the .sample suffix, or create a symbolic link like this:
cd working_dir
ln -s .git/hooks/post-commit /home/user/scripts/git/your-post-commit-script
Remember to make scripts executable (chmod +x filename)
To bypass any git hooks, pass the --no-verify flag to the relevant git command

Some useful hooks

pre-commit

Check for common mistakes:
  • SQL files missing transaction commands BEGIN or COMMIT
  • Merge markers accidentally left in: ======== <<<<<<<< >>>>>>>>
  • To do: Check for trailing whitespace (Perl critic does this on Jenkins)
  • To do: Check for missing use strict and use warnings, or their equivalent (e.g. use NAP::policy - Perl critic does this)
  • To do: Prevent merging more than one commit to master (best practice says they should be squashed into a single commit)

pre-rebase

Prevent rebasing for commits that exist in more than one branch (i.e. have been pushed or pulled).
Stops you accidentally rebasing when it's going to confuse your colleagues working on the same branch or downstream.
To bypass this check, simply pass the --no-verify flag to 'git rebase'
Warning: Always run it with rebase -i (for interactive). it could behave unpredictably if you miss out the -i.

post-checkout

Sometimes I forget to do 'git fetch' when I pull, so this hook always runs 'git fetch' after 'git checkout'.
You can easily see if you then need to do a pull, by using the intelligent git prompt below.

Useful configuration

Diff3

More useful diffs when merging
git config --global merge.conflictstyle diff3
(source)

Rerere

Setting this makes git remember your conflict resolutions, and apply them again automatically if you happen to make the same merge again. This has saved me lots of time.
git config --global rerere.enabled 1

More explanation

Pull --rebase

Rebase and merge don't play nicely together. If two people are merging to a common feature branch and one of them rebases, you need to make sure you always do a git pull --rebase instead of the default git pull (which does a merge). Otherwise you'll end up with a lot of confusing duplicated commits. We really don't want those ending up in master.
These commands make git pull always do a rebase instead of a merge:
# make 'git pull' on master always use rebase 
git config branch.master.rebase true

# setup rebase for every tracking branch
git config --global branch.autosetuprebase always

No fast-forward

The fast forward flag is set for merges by default, meaning if a merge can be fast forwarded then there will be no extra commit to represent the merge. Unsetting this flag makes there always be a separate commit to represent a merge.
It's useful because it makes it more obvious what was merged, and a merge can also easily be reverted if there is a problem, by reverting a single commit.
git config --global merge.ff false

Safer push --force

Prevent yourself from accidentally force-pushing to all branches by mistake:
git config --global push.default upstream

Useful commands/aliases

gitbranch

Quickly create a new branch (-b) that tracks the current branch (-t) and check it out. Tracking means 'git status' tells you how far ahead it is from this branch.
git checkout -t -b new_branch_name

gitlog

Display the log with branch names/tags (--decorate), with full not truncated filenames (--stat=200,200), the shortest possible commit hashes (--abbrev-commit) and accept any other options passed ($@).
git log --color --stat=200,200 --decorate --abbrev-commit --relative "$@"

Intelligent prompt

This prompt works best with the post-checkout hook above, ensuring git always knows the state of the branch at origin and the upstream branch. It's intended to take up the least amount of screen space while providing the most information.

Examples

New branch, no edits
(feature-branch) clean ~/dev/source/xt $
New branch, some uncommited edits
(feature-branch) ~/dev/source/xt $
New branch, two commits, no other edits
(feature-branch) 2> clean ~/dev/source/xt $
New branch, two commits, plus some other uncommited edits
(feature-branch) 2> ~/dev/source/xt $
New branch, no edits, but upstream has moved on by five commits
(feature-branch) 5< clean ~/dev/source/xt $
You have made two new commits, but upstream has moved on by five commits (branch has diverged)
(feature-branch) 5< !! 2> clean ~/dev/source/xt $

Code

I replaced the update_prompt function in my ~/.bashrc_nap file in my VM with the following:
function __git_commits_behind {                                                if [[ -n "$gitstatus" ]]                                                   then                                                                           echo $gitstatus | perl -ne'm{Your branch is behind.+by (\d+) commit} && print "$1< "'
    fi                                                                     }                                                                                
function __git_commits_ahead {                                                 if [[ -n "$gitstatus" ]]                                                   then                                                                           echo $gitstatus | perl -ne'm{Your branch is ahead of.+ by (\d+) commit} && print "$1> "'
    fi                                                                     }                                                                                
function __git_branch_diverged {                                               if [[ -n "$gitstatus" ]]                                                   then                                                                           echo $gitstatus | perl -ne'm{and have (\d+) and (\d+) different commit\(s\) each, respectively} && print "$2< !! $1> "'
    fi                                                                     }                                                                                
function __git_branch_clean {                                                  if [[ -n "$gitstatus" ]]                                                   then                                                                           echo $gitstatus | perl -ne'm{nothing to commit} && m{working directory clean} && print "clean "'
    fi                                                                     }                                                                                
function update_prompt {                                                       export branch=$(git branch --no-color 2>/dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
      
    if [ -f .git/index ]                                                       then                                                                           # we are in a git working directory                                        # display the git prompt                                                   export gitstatus=$(git status)                                             export PS1="\[\033[${branch_colour}m\](${branch}) $(__git_commits_behind)$(__git_commits_ahead)$(__git_branch_diverged)$(__git_branch_clean)\[\033[00m\]\w \$ "
    else                                                                           # normal prompt                                                            export PS1="\[\033[${host_colour}m\]\h\[\033[00m\]/\u \[\033[${branch_colour}m\]${branch}\[\033[00m\]\t \w\n\$ "
    fi                                                                     }                                                                                
export PROMPT_COMMAND=update_prompt


(copied and pasted from Confluence, so please excuse any CSS)