How to Clean Up Git Branches: Scripts to Delete Old, Inactive, and Unused Branches

As software development projects grow, so does the number of Git branches. Before long, your repository can become cluttered with old, inactive branches that serve no purpose. These abandoned branches aren't just digital clutter—they can make repository management more difficult, slow down Git operations, and confuse new team members. In this comprehensive guide, I'll walk you through the process of cleaning up your Git repository by removing stale branches both locally and remotely. I'll provide ready-to-use scripts for various scenarios, with step-by-step explanations that even Git beginners can follow. Why You Should Clean Up Old Branches Before we dive into the technical details, let's understand why regular branch cleanup is important: Improved Performance: Fewer branches means faster Git operations, especially for commands like git fetch and git pull. Better Organization: A clean repository makes it easier to find relevant branches. Reduced Confusion: New team members won't be distracted by outdated branches. Lower Storage Requirements: While individual branches don't take up much space, they add up over time. Cleaner CI/CD Pipelines: Many CI systems run on all branches by default, so fewer branches means fewer unnecessary builds. Scenario 1: Deleting Inactive Branches with No Open PRs Our first scenario addresses a common need: removing branches that haven't been active in the past 30 days and don't have open pull requests associated with them. Here's a script that handles this task, with both local and remote branch cleanup: #!/bin/bash # Get current date in seconds since epoch current_date=$(date +%s) # Calculate date 30 days ago in seconds thirty_days_ago=$((current_date - 30*24*60*60)) # Remote repository name (usually "origin") remote="origin" # Get all branches except main/master branches=$(git branch | grep -v "^*\|main\|master" | sed 's/^[ \t]*//') for branch in $branches; do # Get last commit date for branch in seconds since epoch last_commit_date=$(git log -1 --format=%at "$branch") # Skip if the branch has activity in the last 30 days if [ "$last_commit_date" -gt "$thirty_days_ago" ]; then echo "Skipping $branch - has recent activity" continue fi # Check for open PRs (this example uses GitHub CLI) if command -v gh &> /dev/null; then pr_count=$(gh pr list --head "$branch" --state open --json number | jq length) if [ "$pr_count" -gt 0 ]; then echo "Skipping $branch - has $pr_count open PR(s)" continue fi else echo "GitHub CLI not installed, skipping PR check for $branch" read -p "Do you want to delete $branch anyway? (y/n) " confirm if [ "$confirm" != "y" ]; then continue fi fi # Delete the local branch echo "Deleting local branch: $branch" git branch -D "$branch" # Check if branch exists on remote if git ls-remote --heads $remote | grep -q "refs/heads/$branch"; then echo "Deleting remote branch: $branch from $remote" git push $remote --delete "$branch" else echo "No remote branch found for $branch" fi done If you don't have GitHub CLI installed, here's an alternative version that will ask for manual confirmation instead: #!/bin/bash # Get current date in seconds since epoch current_date=$(date +%s) # Calculate date 30 days ago in seconds thirty_days_ago=$((current_date - 30*24*60*60)) # Remote repository name (usually "origin") remote="origin" # Update remote references git fetch $remote --prune # Get all local branches except main/master branches=$(git branch | grep -v "^*\|main\|master" | sed 's/^[ \t]*//') for branch in $branches; do # Get last commit date for branch in seconds since epoch last_commit_date=$(git log -1 --format=%at "$branch") # Skip if the branch has activity in the last 30 days if [ "$last_commit_date" -gt "$thirty_days_ago" ]; then echo "Skipping $branch - has recent activity" continue fi # Manual check for PRs since we don't have GitHub CLI echo "Checking branch: $branch" echo "Please verify manually if there are any open PRs for this branch." read -p "Is it safe to delete $branch? (y/n) " confirm if [ "$confirm" != "y" ]; then continue fi # Delete the local branch echo "Deleting local branch: $branch" git branch -D "$branch" # Check if branch exists on remote if git ls-remote --heads $remote | grep -q "refs/heads/$branch"; then echo "Deleting remote branch: $branch from $remote" git push $remote --delete "$branch" else echo "No remote branch found for $branch" fi done Scenario 2: Keeping Only Specific Branches Sometimes you might want to take a more drastic approach and delete all branches except a few specific ones you want to keep. This is useful for repository cleanup after completing a major milestone or version release. Here's a script that keeps only the branches you specify and deletes everything else: #!/bin/bash # Li

Apr 11, 2025 - 04:13
 0
How to Clean Up Git Branches: Scripts to Delete Old, Inactive, and Unused Branches

As software development projects grow, so does the number of Git branches. Before long, your repository can become cluttered with old, inactive branches that serve no purpose. These abandoned branches aren't just digital clutter—they can make repository management more difficult, slow down Git operations, and confuse new team members.

In this comprehensive guide, I'll walk you through the process of cleaning up your Git repository by removing stale branches both locally and remotely. I'll provide ready-to-use scripts for various scenarios, with step-by-step explanations that even Git beginners can follow.

Why You Should Clean Up Old Branches

Before we dive into the technical details, let's understand why regular branch cleanup is important:

  1. Improved Performance: Fewer branches means faster Git operations, especially for commands like git fetch and git pull.
  2. Better Organization: A clean repository makes it easier to find relevant branches.
  3. Reduced Confusion: New team members won't be distracted by outdated branches.
  4. Lower Storage Requirements: While individual branches don't take up much space, they add up over time.
  5. Cleaner CI/CD Pipelines: Many CI systems run on all branches by default, so fewer branches means fewer unnecessary builds.

Scenario 1: Deleting Inactive Branches with No Open PRs

Our first scenario addresses a common need: removing branches that haven't been active in the past 30 days and don't have open pull requests associated with them.

Here's a script that handles this task, with both local and remote branch cleanup:

#!/bin/bash

# Get current date in seconds since epoch
current_date=$(date +%s)

# Calculate date 30 days ago in seconds
thirty_days_ago=$((current_date - 30*24*60*60))

# Remote repository name (usually "origin")
remote="origin"

# Get all branches except main/master
branches=$(git branch | grep -v "^*\|main\|master" | sed 's/^[ \t]*//')

for branch in $branches; do
  # Get last commit date for branch in seconds since epoch
  last_commit_date=$(git log -1 --format=%at "$branch")

  # Skip if the branch has activity in the last 30 days
  if [ "$last_commit_date" -gt "$thirty_days_ago" ]; then
    echo "Skipping $branch - has recent activity"
    continue
  fi

  # Check for open PRs (this example uses GitHub CLI)
  if command -v gh &> /dev/null; then
    pr_count=$(gh pr list --head "$branch" --state open --json number | jq length)

    if [ "$pr_count" -gt 0 ]; then
      echo "Skipping $branch - has $pr_count open PR(s)"
      continue
    fi
  else
    echo "GitHub CLI not installed, skipping PR check for $branch"
    read -p "Do you want to delete $branch anyway? (y/n) " confirm
    if [ "$confirm" != "y" ]; then
      continue
    fi
  fi

  # Delete the local branch
  echo "Deleting local branch: $branch"
  git branch -D "$branch"

  # Check if branch exists on remote
  if git ls-remote --heads $remote | grep -q "refs/heads/$branch"; then
    echo "Deleting remote branch: $branch from $remote"
    git push $remote --delete "$branch"
  else
    echo "No remote branch found for $branch"
  fi
done

If you don't have GitHub CLI installed, here's an alternative version that will ask for manual confirmation instead:

#!/bin/bash

# Get current date in seconds since epoch
current_date=$(date +%s)

# Calculate date 30 days ago in seconds
thirty_days_ago=$((current_date - 30*24*60*60))

# Remote repository name (usually "origin")
remote="origin"

# Update remote references
git fetch $remote --prune

# Get all local branches except main/master
branches=$(git branch | grep -v "^*\|main\|master" | sed 's/^[ \t]*//')

for branch in $branches; do
  # Get last commit date for branch in seconds since epoch
  last_commit_date=$(git log -1 --format=%at "$branch")

  # Skip if the branch has activity in the last 30 days
  if [ "$last_commit_date" -gt "$thirty_days_ago" ]; then
    echo "Skipping $branch - has recent activity"
    continue
  fi

  # Manual check for PRs since we don't have GitHub CLI
  echo "Checking branch: $branch"
  echo "Please verify manually if there are any open PRs for this branch."
  read -p "Is it safe to delete $branch? (y/n) " confirm
  if [ "$confirm" != "y" ]; then
    continue
  fi

  # Delete the local branch
  echo "Deleting local branch: $branch"
  git branch -D "$branch"

  # Check if branch exists on remote
  if git ls-remote --heads $remote | grep -q "refs/heads/$branch"; then
    echo "Deleting remote branch: $branch from $remote"
    git push $remote --delete "$branch"
  else
    echo "No remote branch found for $branch"
  fi
done

Scenario 2: Keeping Only Specific Branches

Sometimes you might want to take a more drastic approach and delete all branches except a few specific ones you want to keep. This is useful for repository cleanup after completing a major milestone or version release.

Here's a script that keeps only the branches you specify and deletes everything else:

#!/bin/bash

# List of branches to keep
protected_branches=("master" "develop" "feature-x" "release-1.0" "hotfix-login")

# Remote repository name (usually "origin")
remote="origin"

# Fetch latest information from remote
echo "Fetching latest information from remote..."
git fetch --all --prune

# Get all local branches
echo "Finding branches to delete..."
all_local_branches=$(git branch | sed 's/^[ *]*//')

# Delete local branches
for branch in $all_local_branches; do
  # Check if branch is in protected list
  if [[ ! " ${protected_branches[@]} " =~ " ${branch} " ]]; then
    echo "Deleting local branch: $branch"
    git branch -D "$branch" || echo "Failed to delete local branch: $branch"
  else
    echo "Keeping protected local branch: $branch"
  fi
done

# Get all remote branches
all_remote_branches=$(git branch -r | grep -v HEAD | sed "s|$remote/||" | tr -d ' ')

# Delete remote branches
for branch in $all_remote_branches; do
  # Check if branch is in protected list
  if [[ ! " ${protected_branches[@]} " =~ " ${branch} " ]]; then
    echo "Deleting remote branch: $branch"
    git push $remote --delete "$branch" || echo "Failed to delete remote branch: $branch"
  else
    echo "Keeping protected remote branch: $branch"
  fi
done

echo "Branch cleanup complete!"

Simply replace the protected_branches array with the names of the branches you want to keep. Everything else will be deleted both locally and remotely.

How to Execute the Cleanup Scripts

To use any of these scripts, follow these simple steps:

  1. Save the script to a file with a .sh extension, for example cleanup-inactive-branches.sh or keep-only-selected-branches.sh.

  2. Make the script executable:

   chmod +x cleanup-inactive-branches.sh
  1. Navigate to your Git repository:
   cd /path/to/your/repository
  1. Run the script:
   ./cleanup-inactive-branches.sh

The script will process each branch according to the criteria defined and provide output about which branches are being kept or deleted.

Understanding the Code

Let's break down the key components of these scripts so you understand what they're doing:

For the Inactive Branches Script:

  1. Date Calculation: The script calculates the current date and the date 30 days ago in Unix timestamp format (seconds since January 1, 1970).

  2. Branch Listing: It retrieves all branches except the currently checked-out branch and standard branches like main or master.

  3. Activity Check: For each branch, it gets the timestamp of the last commit and compares it to the 30-day threshold.

  4. PR Check: It uses GitHub CLI (if available) to check if there are open pull requests for each branch.

  5. Branch Deletion: For branches that pass all criteria (inactive and no open PRs), it deletes them both locally and remotely.

For the Keep-Only-Selected Script:

  1. Protected Branches: You define an array of branch names that should be kept.

  2. Branch Listing: The script gets all local and remote branches.

  3. Comparison Logic: It checks each branch against the protected list.

  4. Branch Deletion: Any branch not in the protected list is deleted, both locally and remotely.

Safety Precautions

Working with branch deletion can be risky, so here are some important safety measures:

  1. Backup First: Always create a backup of your repository before running these scripts, especially in production environments. A simple way is to clone your repository to a temporary location:
   git clone https://github.com/your-username/your-repo.git backup-repo
  1. Dry Run Mode: Consider adding a "dry run" mode to your scripts by commenting out the actual deletion commands and just printing what would be deleted:
   # echo "Would delete local branch: $branch"
   # git branch -D "$branch"
  1. Check Current Branch: Make sure you're not deleting the branch you're currently on, which would cause errors.

  2. Permission Check: Ensure you have the necessary permissions to delete remote branches before running the script.

  3. Team Communication: If you're working in a team, communicate your intentions before performing mass branch deletion.

Automating Branch Cleanup

To maintain a clean repository long-term, consider automating the cleanup process:

Using Git Hooks

You can set up a Git hook to run your cleanup script at regular intervals:

  1. Create a pre-push hook:
   touch .git/hooks/pre-push
   chmod +x .git/hooks/pre-push
  1. Add your cleanup script to the hook:
   # .git/hooks/pre-push
   #!/bin/bash

   # Run branch cleanup
   /path/to/your/cleanup-inactive-branches.sh

Using CI/CD Pipelines

Many CI/CD systems like GitHub Actions, GitLab CI, or Jenkins can be configured to run maintenance tasks like branch cleanup on a schedule.

Here's a simple GitHub Actions workflow that runs branch cleanup monthly:

name: Branch Cleanup

on:
  schedule:
    - cron: '0 0 1 * *'  # Runs at midnight on the first day of each month

jobs:
  cleanup:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Setup Git
        run: |
          git config user.name "GitHub Actions"
          git config user.email "actions@github.com"

      - name: Run cleanup script
        run: |
          chmod +x .github/scripts/cleanup-inactive-branches.sh
          .github/scripts/cleanup-inactive-branches.sh

Conclusion

Regular Git branch cleanup is an essential part of maintaining a healthy and efficient development workflow. The scripts provided in this guide give you powerful tools to automate this process according to your specific needs.

Remember that these scripts are destructive by nature—they're designed to delete branches. Always use them with caution, ensure you have proper backups, and communicate with your team before performing large-scale cleanups.

By incorporating these practices into your development workflow, you'll enjoy a cleaner, more organized Git repository that's easier to manage and navigate for everyone on your team.

Do you have any other Git maintenance scripts you find helpful? Share them in the comments below!