Skip to content

Instantly share code, notes, and snippets.

@GMNGeoffrey
Last active April 21, 2025 16:19
Show Gist options
  • Save GMNGeoffrey/38d27939cb751ab066df59927f807f37 to your computer and use it in GitHub Desktop.
Save GMNGeoffrey/38d27939cb751ab066df59927f807f37 to your computer and use it in GitHub Desktop.
#!/bin/bash
set -euo pipefail
# Delete local branches whose head commit is the same as that of a PR that has
# been merged.
DRY_RUN=0
DEBUG=0
function usage() {
echo "usage: git gone [-n|--dry-run|--dry_run] [--debug]" >&2
exit 1
}
while (( "$#" )); do
case "$1" in
-n|--dry-run)
DRY_RUN=1
shift 1
;;
--debug)
DEBUG=1
shift 1
;;
*)
echo "Unsupported argument $1" >&2
usage
;;
esac
done
if (( DEBUG == 1 )); then
set -x
fi
function main() {
readarray -t branches < <(git for-each-ref --format='%(refname:short)' refs/heads/)
local -a gh_branch_filter=("${branches[@]/#/head:}")
# We filter to only PRs with head branches matching our local branches,
# except that the gh CLI uses the search endpoint here, which does a prefix
# match for branch names
# (https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests#search-by-branch-name),
# so we have to then filter client side with jq (using the `--head` flag
# only allows a single input, so that doesn't work either). Finally, we
# group by the branch name, since multiple PRs could've used the same branch
# name and break out the json arrays for each branch name into separate
# entries in a bash array.
readarray -t json_prs_by_branch < <(\
gh pr list \
--limit 1000 \
--state merged \
--author "@me" \
--search "${gh_branch_filter[*]}" \
--json 'headRefOid' --json 'headRefName' --json 'number' --json 'url' \
| jq -c '. | map(select([.headRefName] | inside($ARGS.positional))) | group_by(.headRefName)[]' \
--args "${branches[@]}"
)
if (( ${#json_prs_by_branch[@]} == 0 )); then
echo "Found no matching PRs for local branches"
fi
for json_branch_prs in "${json_prs_by_branch[@]}"; do
branch="$(jq -r '.[0].headRefName' <<< "${json_branch_prs}")"
local_head_commit="$(git rev-parse "${branch}")"
merged=0
readarray -t json_prs < <(jq -c 'sort_by(.number) | .[]' <<< "${json_branch_prs}")
for json_pr in "${json_prs[@]}"; do
pr_head_commit="$(jq -r '.headRefOid' <<< "${json_pr}")"
pr_number="$(jq -r '.number' <<< "${json_pr}")"
pr_url="$(jq -r '.url' <<< "${json_pr}")"
if [[ "${pr_head_commit}" == "${local_head_commit}" ]]; then
merged=1
break
fi
done
if (( merged==1 )); then
# No newline so the git output is on the same line
echo -n "Deleting branch '${branch}' for merged PR #${pr_number} (${pr_url}): "
if (( DRY_RUN == 0 )); then
git branch -D ${branch}
else
# Since we didn't do a newline above, we need to add one if we don't actually run the git command above
echo ''
fi
else
echo "Not deleting '${branch}'. No matching PRs have a matching head commit ${local_head_commit}. Found matching PRs:"
for json_pr in "${json_prs[@]}"; do
pr_head_commit="$(jq -r '.headRefOid' <<< "${json_pr}")"
pr_number="$(jq -r '.number' <<< "${json_pr}")"
pr_url="$(jq -r '.url' <<< "${json_pr}")"
echo " #${pr_number} (${pr_url}): ${pr_head_commit}"
done
fi
done
}
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment