gup: A friendlier git pull --rebase
Posted (updated )
By now most git users would have heard about rebasing your local commits on top of the remote branch HEAD before you git push them rather than merging to prevent the proliferation of useless same branch merge commits like "Merge remote branch 'origin/topic' into topic".
If one is using git pull, the rebasing can be accomplished by using git pull --rebase instead. This essentially changes git pull from doing git fetch && git merge $TRACKING_BRANCH to git fetch && git rebase $TRACKING_BRANCH.
There's still the inconvenience of having to stash any uncommitted changes before a rebase. If you don't, you'll get messages like "refusing to pull with rebase: your working tree is not up-to-date". This results in a fetch, stash, rebase, pop dance which gets tiring. I think we can do better.
Update 2011-01-11: Another thing to watch out for when using git pull --rebase is merge commits. You cannot preserve merges when rebasing using git pull as it does not let you pass in the --preserve-merges option. This means you could end up losing valuable merge commits. Glen Maddern has a great post on Rebasing Merge Commits in Git over on the Envato Notes blog which covers this in more detail. The good news is that my gup script already handles rebasing merge commits by passing the -p (--preserve-merges) option to git rebase.
Update 2011-09-16: My gup function has had a number of tweaks since I first posted. I removed the quiet flag from git stash pop as late versions of git seem to silence the error when pop fails. The other significant change is that gup will now explicitly fast-forward if it can rather than rebasing. The rest of the changes are minor (e.g. refactoring for style).
Update 2011-09-16: I now prefer git-up when available as it has nicer output and it also has an option to show if one needs to bundle. I still use the gup function as it's handy on foreign systems where I don't have my normal Ruby setup and I like the command name better :).
function gup{# subshell for `set -e` and `trap`(set -e # fail immediately if there's a problem# use `git-up` if installedif type git-up > /dev/null 2>&1thenexec git-upfi# fetch upstream changesgit fetchBRANCH=$(git symbolic-ref -q HEAD)BRANCH=${BRANCH##refs/heads/}BRANCH=${BRANCH:-HEAD}if [ -z "$(git config branch.$BRANCH.remote)" -o -z "$(git config branch.$BRANCH.merge)" ]thenecho "\"$BRANCH\" is not a tracking branch." >&2exit 1fi# create a temp file for capturing command outputTEMPFILE="`mktemp -t gup.XXXXXX`"trap '{ rm -f "$TEMPFILE"; }' EXIT# if we're behind upstream, we need to updateif git status | grep "# Your branch" > "$TEMPFILE"then# extract tracking branch from messageUPSTREAM=$(cat "$TEMPFILE" | cut -d "'" -f 2)if [ -z "$UPSTREAM" ]thenecho Could not detect upstream branch >&2exit 1fi# can we fast-forward?CAN_FF=1grep -q "can be fast-forwarded" "$TEMPFILE" || CAN_FF=0# stash any uncommitted changesgit stash | tee "$TEMPFILE"[ "${PIPESTATUS[0]}" -eq 0 ] || exit 1# take note if anything was stashedHAVE_STASH=0grep -q "No local changes" "$TEMPFILE" || HAVE_STASH=1if [ "$CAN_FF" -ne 0 ]then# if nothing has changed locally, just fast foward.git merge --ff "$UPSTREAM"else# rebase our changes on top of upstream, but keep any mergesgit rebase -p "$UPSTREAM"fi# restore any stashed changesif [ "$HAVE_STASH" -ne 0 ]thengit stash popfifi)}
Throw the following into your shell's startup script. I keep the script in my dotfiles as it's much easier to bring it along to new machines. Alternatively you could remove the function wrapper and save it as a standalone script in ~/bin.
Once setup you can pull changes for the current tracking branch, rebase any unpushed commits on top of any new ones from upstream, all while preserving anything you have uncommitted (via git stash) with a single command: gup.