Skip to content

Instantly share code, notes, and snippets.

@lamchau
Created June 23, 2021 07:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lamchau/77b7cc9d9f5308ea9f47fa1c299ca24c to your computer and use it in GitHub Desktop.
Save lamchau/77b7cc9d9f5308ea9f47fa1c299ca24c to your computer and use it in GitHub Desktop.
Prune remote stale and merged branches that are >= 1 year old
#!/usr/bin/env bash
# used to execute branch deletes in parallel, shows progress bar
if ! [ -x "$(command -v parallel)" ]; then
echo "ERROR: Missing command 'parallel'"
exit 1
fi
if ! [ -x "$(command -v git)" ]; then
echo "ERROR: Missing command 'git'"
exit 1
fi
repo_url="$1"
if [ -z "$repo_url" ] || [ "$repo_url" == "-h" ] || [ "$repo_url" == "--help" ]; then
cat << __EOF__
Usage: $(basename "$0") repo_url
Clones a bare git repository in order to fetch all remote branches. Then
deletes anything >= 1 year (relative time). Can be modified for absolute dates
swapping out \`\$format_relative\` -> \`\$format_absolute\`, and updating the
\`grep -v\` pattern.
To finalize, you must run git push --mirror to delete the branches upstream.
__EOF__
exit 1
fi
repo_name="$(basename "$repo_url")"
target_dir="${repo_name}-mirror"
if [ ! -d "$target_dir" ]; then
# clone a bare directory with all remote branches, which allows for
# a "dry run" committing to deletes (e.g. git push --mirror)
git clone "$repo_url" "$target_dir" --mirror
fi
if [ ! -d "$target_dir" ]; then
echo "Failed to clone: $repo_url"
exit 1
fi
pushd "$target_dir" > /dev/null || exit 1
# merged branches
git branch --merged |
cut -c3- |
grep --invert-match --extended-regexp 'master|deployable|HEAD' |
parallel --jobs 16 --bar git branch --delete --force {}
# shellcheck disable=SC2034
format_absolute='%(committerdate:format:%Y-%m-%d)|%(refname:short)'
# stale branches (edge case for 12 months != 1 year)
format_relative='%(committerdate:relative)|%(refname:short)'
git for-each-ref --format="$format_relative" --sort=committerdate refs/heads |
awk 'BEGIN { FS = "|" }; { printf "%-25s%s\n", $1, $2 }' |
grep --extended-regexp "(\d+) year" |
cut -c26- |
parallel --jobs 16 --bar git branch --delete --force {}
cat << __EOF__
To complete purging of stale branches, run
cd $target_dir
git push --mirror
Note: Errors may occur due if the target repository if Github but these can safely
be ignored
Excerpt from https://github.com/rtyley/bfg-repo-cleaner/issues/36#issuecomment-37877829:
The refs beginning 'refs/pull' are synthetic read-only refs created by
GitHub - you can't update (and therefore 'clean') them, because they
reflect branches that may well actually come from other repositories - ones
that submitted pull-requests to you.
So, while you've pushed all your real refs, the pull requests don't get
updated. There's no real way to fix them with your existing repo with the
GitHub admin tools. The severity of an issue depends on whether you were
trying to remove any 'private' data:
- Private data: the pull requests still have that private data in their
history - they will remain as a way for other people to retrieve that
data. The only ways to recover from this are to a) delete the repo using
the GitHub admin tools, and then recreate it, or b) contact GitHub
support and ask them to delete the PRs for you (they're pretty good about
it)
- Nothing Private: the pull requests still reference your old history - if
any of them are still open, you don't want to merge them into your new
fresh history! Get the submitters to rebase them on your new fresh
history before merging.
__EOF__
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment