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:
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'
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.
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
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.
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)