rebasing a git branch without pain

At work, we are asked to "squash" our commits once our pull-request is approved for merge. This makes the git log output much cleaner, since there are only two commits per feature (one for the work, and one merging that work).

If master hasn't been modified since you started work, this is painless. However, if it has, there's a chance you'll need to fix merge conflicts -- often multiple times on the same files, as git re-applies each commit. This is frustrating and unnecessary.

If you had nothing better to do, an easy "manual" way to do this would be:

  • check out a copy of the master branch
  • run git diff --name-only new_branch to see what was changed
  • for each file, check whether the file was added, modified, or deleted.
  • for each deleted file, delete from current repo
  • for each added or modified file, git checkout new_branch -- filename

Then just commit theses changes and you'll be all done.

Here's a script that does exactly that:

#!/usr/bin/env bash

current=$(git rev-parse --abbrev-ref HEAD)
older="$1"

if [ "$older" == "" ]; then
    echo "must pass in branch name to rebase from"
    exit
fi

git checkout $older || exit
git checkout -b rebase || exit

git diff --name-only $current | \
while read filename; do
    git checkout $current -- $filename || rm $filename
done

git status

echo "Done. WARNING: Review carefully, commit, and diff against $current"
echo "before deleting $current."

Here's an annotated version.

#!/usr/bin/env bash

# This line gets the currently-checked-out git branch.
current=$(git rev-parse --abbrev-ref HEAD)

# This is the first command-line argument, so you'd run this like so if you
# wanted to rebase from master:
# rebase master
older="$1"

# If they forget to pass in a branch from which to rebase:
if [ "$older" == "" ]; then
    echo "must pass in branch name to rebase from"
    exit
fi

# Check out the branch to rebase from. If it fails for some reason
# (branch doesn't exist, for example), it will quit.
git checkout $older || exit

# Check out a branch named "rebase" as a temporary branch. If that's not
# possible (branch already exists, for example), it will quit.
git checkout -b rebase || exit

# Get a list of files that differ.
git diff --name-only $current | \
while read filename; do
    # Check out the file. If the checkout fails (doesn't exist in the
    # $current branch, it was deleted in the newer branch, so delete
    # here. This will show error messages to the user while this script
    # is running, but that's okay.
    git checkout $current -- $filename || rm $filename
done

# Show the user the status of the uncommitted rebase branch.
git status

echo "Done. WARNING: Review carefully, commit, and diff against $current"
echo "before deleting $current."

Comments !

blogroll

social