DEV Community

loading...

Spring cleaning Git branches

pavlovic265
A short bio...
・3 min read

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. 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.

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
Enter fullscreen mode Exit fullscreen mode

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 for this piece of code.

if [[ $EXCLUDE ]]; then
     SPLIT_EXCLUDE=$(echo $EXCLUDE | tr "," "|");
     EXCLUDE="^($EXCLUDE_DEFAULT|$SPLIT_EXCLUDE)$"
    else 
     EXCLUDE="^($EXCLUDE_DEFAULT)$"
    fi
Enter fullscreen mode Exit fullscreen mode

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.

if [[ $BRANCHES ]]; then
 SPLIT_BRANCHES=$(echo $BRANCHES | tr "," "|");
 DELETE_BRANCHES="^($SPLIT_BRANCHES)$"
fi
Enter fullscreen mode Exit fullscreen mode

Here I am checking if the variable BRANCHES is not empty. If not empty, replacing is done in the same way as EXCLUDE.

if [[ $PATTERN ]]; then
 DELETE_PATTERN="$PATTERN"
fi
Enter fullscreen mode Exit fullscreen mode

This one is here just for taking the value from the PATTERN.

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
Enter fullscreen mode Exit fullscreen mode

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.

if [[ $ALL ]]; then
 DELETE="$ALL"
fi
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)