Automating Git Branch Cleanup

Posted on Jul 27, 2013

I'm a huge fan of Git. Used properly, Git's branching system can be immensely powerful. In fact, it forms a core part of our engineering workflow at the office. By providing a healthy degree of isolation between different changes to the codebase, we can provide QA with a clean way to test various change sets in total isolation before we produce a release candidate.

This sounds rather nice until the realization that after we finally get a release candidate that we accept, somebody is left we the rather tedious job of removing all of the source branches from our repo. Well, that or ignoring them and watching the list balloon. Given the choice of doing nothing or manually performing this task time and time again has to be one form of Kobayashi Maru. Time for option three: automating the process.

Okay, that sounds all well and good, but where do we start? Well, a good place would seem to be getting a listing of all the remote branches that are in our repository.

git branch -r

From there, we can loop through them one at a a time so that we can check each of them.

for BRANCH in `git branch -r`
do
done

The names we get from git aren't exactly what we're wanting though: the names are all prefaced by our remote, in this case ‘origin/'. This can quickly be fixed by piping $BRANCH and the proper regex through sed. The result will be our branch name with the remote name scrubbed.

CLEAN_BRANCH=`echo "$BRANCH" | sed -e "s/origin\///"`

Once we have the branch name in a variable, we can loop through, passing the name of our branch to git branch --contains $BRANCH to get a listing of all the branches that it has been merged into. We can then pare down that list with grep to see if the current branch has been merged into our release branch.

git branch --contains $BRANCH | grep Release

Depending on the output of grep, we'll either have a result, in which case the branch has been fully merged into our release, or we will have no result at all, in which case no relevant merging has occurred. With this knowledge we can create a conditional using -n to check whether we had a result or not.

if [ -n "`git branch --contains $BRANCH | grep Release`" ]; then
fi

At this point we just have to tell bash what we'd like to do if the branch has been fully merged. Since our goal here is to remove the original remote source branch, we should do that here.

git push origin :$CLEAN_BRANCH

After combining this all together and adding a minimal amount of feedback with echo, we arrive at the final script:

for BRANCH in `git branch -r`
do
  CLEAN_BRANCH=`echo "$BRANCH" | sed -e "s/origin\///"`
  if [ -n "`git branch --contains $BRANCH | grep Release`" ]; then
    echo "DELETING $CLEAN_BRANCH"
    git push origin :$CLEAN_BRANCH
  else
    echo "KEEPING $CLEAN_BRANCH"
  fi
done

Now that we have the completed script, we can either run it at will or automate it with cron.

Edit

It was pointed out to me by Bryan Price that we're missing a condition needed to avoid eating the branch Release. In my use-case, Release is locked, so this was never an issue. Either way, this is mitigated by adding a grep to filter out of Release branch from the list of candidates.

TARGET="Release"
for BRANCH in `git branch -r | grep -v $TARGET`
do
  CLEAN_BRANCH=`echo "$BRANCH" | sed -e "s/origin\///"`
  if [ -n "`git branch --contains $BRANCH | grep $TARGET`" ]; then
    echo "DELETING $CLEAN_BRANCH"
    git push origin :$CLEAN_BRANCH
  else
    echo "KEEPING $CLEAN_BRANCH"
  fi
done