I've already blogged about a rebasing git workflow, but I wanted to be more concise (it's still long!) and perhaps more useful.
Eventually you’ll discover the Easter egg in Git: all meaningful operations can be expressed in terms of the rebase command. Once you figure that out it all makes sense. I thought the joke would be obvious: rebase, freebase, as in what was Linus smoking? -- Linus Torvalds
Here's some shortcuts I've added to my
.gitconfig, and I'll use them throughout this post:
[color] ui = true [alias] r = rebase f = fetch ff = merge --ff-only co = checkout c = commit a = add p = push s = status b = branch h = log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short # for even more convenience op = push origin uf = fetch upstream uffm = merge --ff-only upstream/master [remote "upstream"] fetch = +refs/heads/*:refs/remotes/upstream/* fetch = +refs/pull/*/head:refs/remotes/upstream/pr/*
Keep up to date with upstream, and backup your local copy of master (which is always just a mirror of
upstream/master from the last update). You do this to your master branch as follow:
git f upstream git ff upstream/master git p origin master
Actually I've a shell alias
gum which has this wrapped in git stash and apply; that is, it stores your work (without you needing to commit), while it updates local master then puts you back on the branch you were previously working on and restores your work:
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD); git co master; git f upstream; git p origin master; git ff upstream/master; git co $OLD; unset CURRENT_BRANCH
rebase your current work
Once you've updated master, you'll want to rebase your current work. You going to want to commit before doing this, or if you'd prefer stash, rebase, stash apply:
git r master
Once you've updated master, you can start, or get back to, work:
- NEVER commit to master locally (at least try not to, see below)
Work on a new branch off of master, give it a nice descriptive name (not like
git co -b feature_A
Or rebase what you've previously been doing (to minimise pain of stepping this code again later), you should either commit what you've been doing first, or stash, rebase and stash apply:
git a -A git c -m 'add feature A' git r master git stash git r master git stash apply
You may get merge conflicts here, but you'd have got them anyway if you'd tried to merge. The more frequently you rebase the less painful this process is (as you're only having to walk over the most recent code).
oh no, I've committed to master
No problem (don't push though!), first we'll make a "back up" of master (including your latest commits):
git co -b feature_b
Finish off whatever it is you were doing, and leave the working directory clean (i.e. commit everything), and make sure it has the work you've just been doing. Now you can go ahead and fix up your copy of master (remembering it should just that, a copy of master).
As, your commits are safely in the new branch (seriously, double check this), so we can just back up a few commits and then update from upstream:
git co master git reset --hard HEAD~1 # if more than one commit, replace with a larger number git ff upstream/master
If you'd pushed to origin, you're going to have to force push next time.
pushing to origin
With the rebasing model you're going to have to force push occasionally (each time you rebase):
git f origin feature_A git f origin feature_A --force # if you've done a rebase
But, NEVER force push to upstream/master (this is a recipe for disaster), although if other people are following this model the red flag will be raised the next time they update master and fail to fast forward...
Git history is NOT supposed to be a meandering record of your thoughts and misadventures, it should be a block of work to implement a new feature or fix a bug. If it's taken 20 commits to implement a feature and you've been going back and forth on the same lines... resolving merge conflicts is going to be hell (for you and other developers).
Squish it down to several commits (or ideally one!) using "interactive" rebase:
If you're new to squishing I recommend taking a backup branch:
git co -b feature_A_backup git co feature_A
git h # check the history, and count how many commits to squish git r -i HEAD~4 # replace 4 with number to squish
In the interactive buffer (make sure at least the first commit is left as pick) and replace
s (squish), and save. Now you have another chance to clean up the commit messages for others who might be reading them.
checking out pull request locally
With the above git config you can play around / test pull requests locally very easily. If the oull request was number 1234:
git co pr/1234
If you want to make changes to it, you can create a new branch off of it locally:
git co -b more_on_pr_1234