# Background

My team develops in a git monorepo. User stories are broken into very small discrete sub-tasks and merged directly into master, rather than into a feature branch for the story.

### Topic branches

Our topic branch names are all of the form issue/<developer username>/<subtask ticket>.

Those are pushed to origin and merged via a code-reviewed pull-request process.

### Release branches

We also maintain long-lived release branches named release/sprint/<number> used for hot-patching.

Release branches are never re-merged to master. The individual fixes, if relevant in the current release, are re-implemented in another branch or applied as a patch. There are few enough of them to manage manually.

# Work

This works well for us, but it means we have hundreds of topic branches hanging around as developers often forget to delete them as they go.

I wrote a simple bash script to

1. Sync up with origin (we have only one),
2. Figure out which branches have been merged into master (or, optionally, a different branch, so far nobody has used that),
3. Delete locally-merged branches,
4. Optionally run the gc

The verbose prompting at every stage is intentional as reassurance.

# Concerns

Primarily, is this "safe", meaning:

1. Have I used an appropriate method to list branches?
2. Have I used an appropriate method for deleting branches?
3. Have I accounted for all possible pathways that could result in destruction of a local branch?
4. Have I adequately guarded against destruction of a local branch not merged into master?
5. Should I verify that topic branches have been merged into origin's master and not the local master, in the event that a user mistakenly merges locally and loses their branch?

# Code

#!/bin/bash
MERGED_INTO=${1:-"master"} statistics() { echo echo "Remaining local branches:" git branch echo echo "These local branches are not merged into${MERGED_INTO}:"
git branch --no-merged ${MERGED_INTO} echo echo "Size of local repo:$(git count-objects)"

git status
}

error () {
echo "WARNING: Something went wrong. Aborting..."
if [[ ${1} ]]; then echo "The previous command exited with${?}"; fi

echo "This is the current state of your working copy:"
git status
}

confirm () {
read -r -p "Are you sure? [y/N] " response
case ${response} in [yY][eE][sS]|[yY]) true ;; *) echo "Ok, aborting..." echo false ;; esac } check_on_main_branch() { local current_branch; current_branch=$(git symbolic-ref -q --short HEAD)
if [ "${current_branch}" != "${MERGED_INTO}" ]; then
echo "You should be on '${MERGED_INTO}' for this operation. I can switch branches for you." confirm && git checkout "${MERGED_INTO}" || error $? fi } delete_local_merged_branches() { git branch --merged${MERGED_INTO} | egrep -v "(^\*|${MERGED_INTO})" | xargs git branch --delete } # Step 1 echo "Fetching remote branch state" git fetch --all # Step 2 echo "The following local branches have been merged into${MERGED_INTO} and will be deleted locally:"
git branch --merged ${MERGED_INTO} | egrep -v "(^\*|${MERGED_INTO})"
confirm && delete_local_merged_branches

# Step 3
echo "Would you like to run the garbage collector?"
confirm && \

### Exit code of a function

Although this works:

confirm () {
read -r -p "Are you sure? [y/N] " response