Sep 9, 2024

Local Git Branch Cleanup

A Handy Script to Tidy Up Your Local Repository

Tom Krueger
by Tom Krueger
Local Git Branch Cleanup

When working on a development team, it’s easy to accumulate a lot of branches, especially if you’re creating a new branch for each requirement and using pull requests for code reviews. Over time, my local machine ends up with branches that I’ve lost track of, especially those that have already been merged and deleted from the remote repository. It can quickly become overwhelming to manage.

To keep things tidy, I created a simple script called CleanBranches.sh. I use this script to clean up local branches that no longer exist on the remote server. I keep the file in my root directory, so I can run it on any of my repositories whenever things start to pile up.

Why Clean Up Git Branches?

  • Over time, local branches can accumulate when working on different features or bugs.
  • When a remote branch is deleted (after a pull request is merged, for example), the corresponding local branch may still hang around, leading to a cluttered environment.
  • Cleaning up these branches manually can be time-consuming, and this script automates that task.

CleanBranches.sh Script

#!/bin/bash

# Usage sh ~/cleanBranches.sh

echo Cleaning Branches...

echo
echo Local Branches (Before Deleting).
git branch

# Delete local branches that had remote but no longer does.
echo
echo Deleting Local Branches where remote no longer exists...
git fetch -p && for branch in $(git for-each-ref --format '%(refname) %(upstream:track)' refs/heads | awk '$2 == "[gone]" {sub("refs/heads/", "", $1); print $1}'); do git branch -D $branch; done

echo
echo Local Branches (Remaining After Delete).
git branch

Example Output:

Cleaning Branches...  

Local Branches (Before Deleting). 
 TOM-415-Delete-Large-Calendar-Import-Fix
 TOM-417-Name-Calendar-Imports
 TOM-465-Fix-Email-Connect-Issue
 TOM-476-Refresh-Calendar-Import
* main

Deleting Local Branches where remote no longer exists... 
From github.com:YourOrg/YourRepo
- [deleted]         (none)     -> origin/TOM-415-Delete-Large-Calendar-Import-Fix 
- [deleted]         (none)     -> origin/TOM-417-Name-Calendar-Imports
Deleted branch TOM-415-Delete-Large-Calendar-Import-Fix (was 8e85d28).
Deleted branch TOM-417-Name-Calendar-Imports (was 01713cv).

Local Branches (Remaining After Delete).
 TOM-465-Fix-Email-Connect-Issue
 TOM-476-Refresh-Calendar-Import
* main

Usage

First checkout the main branch and then run it. The reason to checkout main first is that the script won't delete the branch if it is active locally.

git checkout main
sh ~/cleanBranches.sh

Breaking Down The Delete Command

(If you really want to know the details carry on)

git fetch -p && \
for branch in $(
  git for-each-ref --format '%(refname) %(upstream:track)' refs/heads |
  awk '$2 == "[gone]" {sub("refs/heads/", "", $1); print $1}'
); do
 
  git branch -D $branch; 

done

git fetch -p
This command fetches the latest changes from the remote and prunes any branches that no longer exist on the remote.

  • -p (or --prune): This option removes any remote-tracking references that no longer exist on the remote. If a branch has been deleted on the remote repository, using git fetch -p will automatically remove the corresponding reference from your local repository. (Note: It doesn't delete the branch).

Example Output:

From github.com:YourOrg/YourRepo
 - [deleted]         (none)     -> origin/test

for branch in $(...):
This loop iterates over each branch that matches the criteria in the git for-each-ref command.

git for-each-ref --format '%(refname) %(upstream:track)' refs/heads
Lists all the local branches and their tracking status with respect to the upstream branches (remote branches)

--format '%(refname) %(upstream:track)': This specifies the output format:

  • %(refname): The full name of the reference, such as refs/heads/branch-name (which is the local branch name).
  • %(upstream:track): This shows [gone] if the remote branch no longer exists.

refs/heads: This limits the command to show only the local branches (those found under refs/heads/ in Git).

Example Output:

refs/heads/TOM-417-Name-Calendar-Imports
refs/heads/TOM-465-Fix-Email-Connect-Issue
refs/heads/TOM-476-Refresh-Calendar-Import
refs/heads/main
refs/heads/test [gone]

awk '$2 == "[gone]" sub("refs/heads/", "", $1); print $1'
* writing note. Left curly braces off as it wrecked this editor

  • $2 == "[gone]":
    This condition checks if the second field ($2) in each line is equal to "[gone]".

  • Only lines where this condition is true are processed further (i.e., branches where the remote branch has been deleted).

  • sub("refs/heads/", "", $1):
    A substitution function that operates on the first field ($1), which contains the full reference name, such as refs/heads/branch-name.

    This command removes the refs/heads/ prefix from the first field ($1), leaving just the branch name (e.g., branch-name).

  • print $1:
    After the substitution, print $1 outputs the modified first field, which is the cleaned-up branch name (without the refs/heads/ prefix).

    These branch names are passed to the for loop as variable $branch.

git branch -D $branch;
Deletes the local branch.

In Short

Put the script in your root directory and run it from any of your repo folders to tidy up your local branches quickly. Enjoy!

Continue Reading