Spring cleaning Git branches 
Tags: git, bash
Published: 2021-08-01 15:57:32

---
title: Spring cleaning Git branches 
published: true
description: Script for deleting local git branches
tags: #git #bash
//cover_image: https://direct_url_to_image.jpg
---

When working on multiple repositories and branches, testing branches, backing them up, code reviewing, playing around branches, etc. I find myself in a situation where it pains me when the time for clean-up comes. 

In order to delete branches, I need to google the command. However, it might not always work as I want. For example, if I want some branches to be saved I need to update the command and restart the terminal, or if I want to save few additional branches with different naming convention I again need to update the command and restart the terminal... you get the point.

Additionally, I wanted to create a more permanent solution for all of the repositories. Here is the link to the repository: [remove-git-branch](https://github.com/pavlovic265/remove-git-branch). Let me try and explain what I did in the script the best I can.

My goal was to have a flexible script that does deletion based on arguments.

In the repository linked above you will find the script remove-git-branches.sh. 
```zsh
for i in "$@"; do
  case $i in
    -b=*|--branch=*)
      BRANCHES="${i#*=}"
      shift # past argument=value
      ;;
    -p=*|--pattern=*)
      PATTERN="${i#*=}"
      shift # past argument=value
      ;;
    -a|--all)
      ALL=".*"
      shift # past argument=value
      ;;
    -e=*|--exclude=*)
      EXCLUDE="${i#*=}"
      shift # past argument=value
      ;;
    -c|--check)
      CHECK="true"
      shift # past argument=value
      ;;
    -f|--force)
      FORCE="true"
      shift # past argument=value
      ;;
    -h|--help)
    echo "usage: remove-git-branch  [-b=<pattern> | --branch=<pattern>] [-p=<pattern> | --pattern=<pattern>]
                [-a | --all] [-h | --help]"
      echo "-b (--branch) - Branches to remove separated by ','. It has priority over pattern."
      echo "-p (--pattern) - Regex pattern to define witch branches should be removed."
      echo "-a (--all) - Remove all branches, expect master, **main, and current."
      echo "-e (--exclude) - Branches to exclude from deleting."
      echo "-c (--check) Print branches that will be deleted. But DOES NOT delete them."
      echo "-f (--force) Force delete branches, even if it has unmerged changes."
      echo "-h (--help) - Available flags."
      exit 1;
      ;;
    *)
      # unknown option
      ;;
  esac
done
```
One of the first things you will notice is a for-in loop. This loop will collect all the arguments when a script is called. You can thank [Bruno Bronosky stackoverflow answer](https://stackoverflow.com/a/14203146/7028003) for this piece of code. 

```zsh
if [[ $EXCLUDE ]]; then
	 SPLIT_EXCLUDE=$(echo $EXCLUDE | tr "," "|");
	 EXCLUDE="^($EXCLUDE_DEFAULT|$SPLIT_EXCLUDE)$"
	else 
	 EXCLUDE="^($EXCLUDE_DEFAULT)$"
	fi
```
EXCLUDE, as the name suggests, excludes branches from deleting. By default, it always excludes master, main and current branches which is represented by EXCLUDE_DEFAULT.

```zsh
if [[ $BRANCHES ]]; then
 SPLIT_BRANCHES=$(echo $BRANCHES | tr "," "|");
 DELETE_BRANCHES="^($SPLIT_BRANCHES)$"
fi
```
Here I am checking if the variable BRANCHES is not empty. If not empty, replacing is done in the same way as EXCLUDE.

```zsh
if [[ $PATTERN ]]; then
 DELETE_PATTERN="$PATTERN"
fi
```
This one is here just for taking the value from the PATTERN.

```zsh
if [[ $DELETE_PATTERN ]] && [[ $DELETE_BRANCHES ]]; then
 DELETE="(($DELETE_BRANCHES)|($DELETE_PATTERN))"
elif [[ $DELETE_PATTERN ]]; then
 DELETE=$DELETE_PATTERN
elif [[ $DELETE_BRANCHES ]]; then
 DELETE=$DELETE_BRANCHES
fi
```
There are three ways of deleting: by passing branches, by passing pattern, or combined. The first IF statement would merge branch and pattern meaning we passed both of them, the second one would pass only pattern, and the third one would pass only branches.

```zsh
if [[ $ALL ]]; then
 DELETE="$ALL"
fi
```	 
Next IF statement is ALL. It removes all the local branches we have. Again, the master, the main, and the current branch will not be deleted.


```zsh
if [[ $CHECK ]]; then
	 git branch | awk '{gsub(/^[ \t]+| [ \t]+$/,""); print $0 }' | egrep -v "$EXCLUDE" | egrep "$DELETE"
	elif [[ $FORCE ]]; then
	 git branch | awk '{gsub(/^[ \t]+| [ \t]+$/,""); print $0 }' | egrep -v "$EXCLUDE" | egrep "$DELETE" | xargs git branch -D
	else
	 git branch | awk '{gsub(/^[ \t]+| [ \t]+$/,""); print $0 }' | egrep -v "$EXCLUDE" | egrep "$DELETE" | xargs git branch -d
	fi
```
The last block of code is where the command runs. There are few things that are happening.
1. `git branch` gets a list of all branches
2. `awk '{gsub(/^[ \t]+| [ \t]+$/,""); print $0 }'` removes empty spaces on both sides of the branch
3.  `egrep -v "$EXCLUDE"` prints all the branches that **DO NOT** match pattern, `-v` flag does that.
4. `egrep "$DELETE"` prints all the branches that **DO** match pattern.
5. - If we pass the check flag (`$CHECK`), deletion will not happen but  the branch name that was supposed to be deleted will be printed, it can be thought of as a sanity check. 
   - If we pass the force flag (`$FORCE`), deletion will happen but it will additionally delete a branch that was not merged.
   - The last ELSE statement is a "safe" option that prevents deletion of the branch if it has unmerged changes.


## References
- [ryanstutorials bash-if-statements](https://ryanstutorials.net/bash-scripting-tutorial/bash-if-statements.php)
- [geeksforgeeks awk-command-unixlinux-examples](https://www.geeksforgeeks.org/awk-command-unixlinux-examples/)
- [geeksforgeeks egrep-command-in-linux-with-examples](https://www.geeksforgeeks.org/egrep-command-in-linux-with-examples/#:~:text=egrep%20is%20a%20pattern%20searching,lines%20that%20match%20the%20pattern.)