I've actually written a script to do the same thing myself. My tips:
Advice
I wouldn't use git branch
, its output is only meant to be human readable (in git parlance, it's part of git's porcelain). A better suited command is git for-each-ref
which outputs its data in a machine readable format (part of git's 'plumbing').
I'd also be more explicit about which branch you care about being merged into. I believe by default git branch --merged
will show you all branches already merged into the current branch. That works if you're on master
or whatever your main/trunk/integration branch is. But, if you happen to be on a different branch, you'll end up deleting a bunch of branches that you didn't mean to.
There's also no need to save the branch names to a file, just operate on them iteratively.
Make sure you're not deleting your main branch (it will show as merged into itself).
I also wouldn't just willy-nilly delete the branches without asking the user first.
I'd also use bash's ability to stop immediately when any command has an error.
Putting it all together
Stop the script immediately on any command failure:
set -e
To get the "main" branch of your repo you can do:
main_branch=$(git rev-parse --abbrev-ref HEAD)
To get the local branches that have been merged into your main branch:
git for-each-ref --merged "${main_branch}" --format '%(refname:short)' 'refs/heads/'
Now you'll want to iterate over that list. Git branch names are usually safe from oddball characters that might mess with shell parsing, so we could probably do:
for branch in $(git for-each-ref --merged "${main_branch}" --format '%(refname:short)' 'refs/heads/') ; do
# something here
done
I've found to be more explicit that we're dealing with one entry per line, and to take the entire line as the branch name no matter what characters we might find there, the following form is better:
while read branch ; do
# something here
done < <(git for-each-ref --merged "${main_branch}" --format '%(refname:short)' 'refs/heads/')
Now, what code do we put inside the loop? Well, first we'll want to make sure we don't delete the main branch:
test "${main_branch}" = "${branch}" && continue
Build the delete branch command
cmd="git branch -d '${branch}';"$'\n'
Ask the user about running the delete command (make sure we read from the terminal since we're inside the while loop which is reading from the git for-each-ref
command):
read -p "${cmd}Execute(y/N)? " run < /dev/tty
Only if the user responded with exactly 'y' do we run the command:
test "${run}" = "y" && eval "${cmd}"
The whole shebang
#!/usr/bin/env bash
set -e
main_branch=$(git rev-parse --abbrev-ref HEAD)
while read branch ; do
done < <(git for-each-ref --merged "${main_branch}" --format '%(refname:short)' 'refs/heads/')
test "${main_branch}" = "${branch}" ] && continue
cmd="git branch -d '${branch}';"$'\n'
read -p "${cmd}Execute(y/N)? " run < /dev/tty
test "${run}" = "y" && eval "${cmd}"
done < <(git for-each-ref --merged "${main_branch}" --format '%(refname:short)' 'refs/heads/')
Notes:
Now, if those local branches that you deleted had remote branches that should also be deleted, you'd want to do something like:
# Get the remote name of the main branch, probably just 'origin'
remote=$(git config "branch.${main_branch}.remote")
# Then inside the loop, you'd do something like this:
git push --delete "${remote}" "${branch}"
But it's a little more complicated because you'd want to first find out if this branch even has a remote set up, what the actual remote branch name is (could be different from the local), if its HEAD
is at (or an ancestor of) the local branch, etc.
sed
command will actually do anything useful: you've directed its input from the file, but its output will just be to standard output, so the file won't be edited. I think you need the-i
option to edit the file in place. \$\endgroup\$xargs
already reads in lines with leading/trailing whitespace removed making thatsed
redundant. A strange looking file that is - is it some kind of Magic number? \$\endgroup\$mktemp
. \$\endgroup\$