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

Git workflow

See also "A successful Git branching model"

Warning: This practice only works if everyone on a team does the same thing. If there is even one developer who doesn't rebase then the whole system breaks down. A much simpler way to do things is to stick with the defaults of pulling via merge (not via rebase) and endure the extra commits and mangled history. Then if you're using Bitbucket, the UI provides special links to 'sync' out-of-date branches before merging; Exclusively using those links is much safer and easier than manually rebasing. /Warning

Best practice

  1. branch from master
  2. make changes
  3. when you're ready to merge back, first rebase onto master
  4. then merge to master in your working directory
  5. squash all your commits into a single commit
  6. push the single commit to master
UPDATE: If you delete your feature branches after merging (perhaps for audit reasons), it seems better not to squash but to have one additional commit (--no-ff) which represents the merge for ease of backing out, viewing the diff of a single feature, etc. while simultaneously keeping full commit history in master.
Branch names are of the format: xxx-123-foo-bar (where xxx is our JIRA project ID, 123 is the Jira story number, and 'foo bar' describes the story)
If anyone has cut a branch off your branch, be nice and tell them before you rebase.

Detailed instructions

Setup
git fetch
git checkout master
git pull origin master
Branch
git checkout -t -b my-branch
# make changes to code
# run tests
git commit
git push origin my-branch
Rebase
git fetch
git checkout master
git pull origin master
git checkout my-branch
git rebase master
# run tests
git push --force origin my-branch
Merge
git fetch
git checkout master
git pull origin master
    # should already be up-to-date, otherwise go back and rebase again
git merge my-branch
git push origin master
NOTE: Rebasing before merging to master is required to keeping all your commits together in a bunch,
and in the right place at the top of the commit log.
NOTE: push --force will change the commits on your branch that you had pushed previously.
Never push --force to master!

Rebasing onto master before merging

If you don't rebase before merging to master, the master commit log could look like this after merging (commits are shown in reverse chronological order):
  • 4pm commit, in master
  • [lots of other commits in master]
  • 3pm commit, from feature branch                   <-- a="" feature="" li="" new="" of="" part="">
  • 2pm commit, in master
  • [lots of other commits in master]
  • 1pm commit, from feature branch                   <-- a="" another="" feature="" li="" new="" of="" part="">
Someone looking at the top of the log would not realise that a feature has just been merged (and probably deployed).
And even if they did find out, they would not be able to easily pick out the commits belonging to the feature branch.
If you do rebase then you end up with this output, where the feature commits appear all together at the top:
  • 3pm commit, from feature branch                   <-- a="" feature="" li="" new="" of="" part="">
  • 1pm commit, from feature branch                   <-- a="" another="" feature="" li="" new="" of="" part="">
  • 4pm commit, in master
  • [lots of other commits in master]
  • 2pm commit, in master
  • [lots of other commits in master]
NOTE: The same useful log view is possible by passing the --topo-order option to git log, which displays the first example (not rebased) in the same order as the second example.

Squashing

You can optionally squash all your commits into a single commit or several large commits for ease of reading. This is quite controversial and there are good arguments for both sides.
The commit history showing that you tried several different approaches before settling on one, or made and fixed typos may be unnecessary noise. On the other hand, it may be useful to see the broad strokes that were taken to solve the problem. Another view is that all commits are valuable and don't need to be changed.

Consider squashing some of your commits which weren't intended for anyone to see, while keeping others to maintain the general workflow in the individual commit history.

To squash some or all of your commits:
git rebase -i $(git merge-base my-branch master)
  • Change the word 'pick' to 'squash' in order to combine that commit into the previous one
    • you may also change the order of the commits if it makes sense
  • save and exit the file

Project branch

AKA Testing branch/QA branch
After making changes and pushing them to a feature branch, we then merge that feature branch to a project/team branch. After the feature has been tested, the feature branch is merged to master.
A major benefit of this approach is that there's no big "final merge" at the end of an iteration where conflicts from all feature branches must be resolved at the same time. As soon as a feature is complete and tested, it is merged to master.
One drawback of this is that conflicts between features will need to be resolved once when merging to the project/testing branch, and then possibly resolved again when merging to master. We consider the benefits to outweigh this drawback.
There is no branching strategy that can completely prevent conflicts if two people are working on the same code simultaneously. Our solution is: Try to avoid conflicts as much as possible simply by not working on the same area of code at the same time as someone else.
If two people are working on the same code, they should resolve conflicts with each other as frequently as possible (e.g. every day or every couple of days), perhaps by using a merged feature branch. A side effect of this is that all their code must be released together.

Multiple feature branches

Where several stories need to be tested together before being finished. First create a feature branch for an umbrella story, and branch all features off there. Merge back to test them together, and finally merge the umbrella branch to master.
Do not cut feature branches from master as usual and merge them to the umbrella branch, as it then gets contaminated with unrelated commits.
If you some changes from master, first rebase the umbrella branch on master, let everyone know, and then rebase each feature branch on the umbrella branch.

Do not merge after a rebase

We've discovered that actually git doesn't hate us, we've just been using it wrong.
Say two people are working on a feature branch (that's been cut from master), they are happily pushing to and pulling from the branch. Then one of them rebases that branch back onto master. The others all pull the new version, and everything looks fine (except the number of commits might look like it's doubled). But everything isn't fine.
When you come to rebase that branch on master again, (perhaps just before you make the final merge back), you will encounter some weird conflicts - the branch will appear to conflict with its own commits.
The solution is: If the feature branch gets rebased, do not use 'git pull' (as that does a merge by default, and merges don't play nicely with rebases). Instead do 'git pull --rebase', and after that, everything will be groovy. It's best to 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
Another way to avoid this problem is to never 'pull': If you find you can't push your feature branch because someone else has made commits to it, then make a temporary branch to store your commits, then re-checkout the latest feature branch from origin (or do 'git reset --hard origin/branchname'), and then cherry-pick your commits back into it from the temporary branch.

General advice

Merge frequently

Merge your code upstream as often as possible, in order to resolve any conflicts early, while the code is still fresh in everyone's mind.

Rebase cautiously

If you're the only developer on a branch, rebase frequently.
If others are working on the same branch, or are working downstream, then you must agree a good time to rebase, i.e. after everyone has committed and is prepared to pull (if on same branch) or rebase (if downstream) and resolve any conflicts.

Document your policy

Write down the rules you plan to follow in order to minimise the pain of merge conflicts and other confusion. Make sure everyone understands the rules.

Communicate

Ensure developers working on the same branch, or downstream of others in the team are communicating enough to get advance warning of possible merge conflicts, so these can be resolved as early as possible, and prevent potential conflicts from all building up and causing pain/confusion at the end of the development cycle.

Avoid dependencies

The best way to avoid conflicts is to organise the work to reduce dependencies in the first place.

  • Horizontal slicing is one way to achieve this (e.g. one person works on the model, one on the view, and one on the controller).
  • Making small changes and deploying them quickly is another way.

There's more than one way to do it

If a set of stories/tasks requires distinct sections of code to be written for the same feature, consider cutting a main feature branch, and having each developer cut a sub-branch from that for their individual tasks. Individual developers could also use a sub-branch for wide-ranging experimental changes. Or if developers feel more comfortable committing to a single large feature branch and co-ordinating with each other, or if that just makes more sense for a particular feature, then do that instead.

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)

Enforcing breaks away from the screen

There's a need to interrupt oneself, to avoid spending too much time constantly staring at the screen/sitting down, etc. to prevent RSI, eye strain, back injury, etc.
  • RSIBreak - Looks promising, but falls prey to multi-monitor taskbar bug and is hidden behind taskbar, also greying out of screen doesn't work. And doesn't seem to always alert me. And "lock screen" button doesn't work. Nor does "run on startup". (Ubuntu 12.04).
  • Workrave - In some ways looks better than RSIBreak. Easier to configure/understand notification times, has numbers countdown instead of a clock, and suggests exercises to do in the breaks. But it doesn't match up small breaks with long breaks like RSIBreak does, so it often pops up an alert soon after I've just taken a break.