Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save TerrorJack/3d2d3188178bbf6da7e60c39aec1cf8e to your computer and use it in GitHub Desktop.

Select an option

Save TerrorJack/3d2d3188178bbf6da7e60c39aec1cf8e to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
set -euo pipefail
PROJECT_PATH="${PROJECT_PATH:-ghc/ghc}"
AUTHOR_USERNAME="${AUTHOR_USERNAME:-TerrorJack}"
ACCEPTED_ASSIGNEE_USERNAME="${ACCEPTED_ASSIGNEE_USERNAME:-marge-bot}"
GLAB_HOST="${GLAB_HOST:-gitlab.haskell.org}"
DELETE_REMOTE="${DELETE_REMOTE:-origin}"
require_command() {
local command_name=$1
if ! command -v "$command_name" >/dev/null 2>&1; then
printf 'error: required command not found: %s\n' "$command_name" >&2
exit 1
fi
}
urlencode() {
jq -nr --arg value "$1" '$value | @uri'
}
fetch_merge_requests() {
local query=$1
glab api "${glab_api_flags[@]}" \
"projects/${project_path}/merge_requests?${query}&per_page=100" \
--paginate \
--output ndjson
}
list_local_remote_branches() {
local remote_ref_prefix="refs/remotes/${DELETE_REMOTE}/"
git for-each-ref --format='%(refname)' "$remote_ref_prefix" \
| jq -R --arg prefix "$remote_ref_prefix" '
select(startswith($prefix))
| .[($prefix | length):]
| select(. != "HEAD")
' \
| jq -s 'unique | sort'
}
require_command glab
require_command git
require_command jq
local_remote_branches_file=$(mktemp)
trap 'rm -f "$local_remote_branches_file"' EXIT
project_path=$(urlencode "$PROJECT_PATH")
author_username=${AUTHOR_USERNAME#@}
accepted_assignee_username=${ACCEPTED_ASSIGNEE_USERNAME#@}
author_username_query=$(urlencode "$author_username")
accepted_assignee_username_query=$(urlencode "$accepted_assignee_username")
glab_api_flags=(--hostname "$GLAB_HOST")
accepted_merge_requests_json=$(
{
fetch_merge_requests \
"state=merged&author_username=${author_username_query}"
fetch_merge_requests \
"state=closed&author_username=${author_username_query}&assignee_username=${accepted_assignee_username_query}"
} | jq -s \
--arg accepted_assignee_username "$accepted_assignee_username" \
'
map(
select(
.state == "merged"
or (
.state == "closed"
and any(.assignees[]?; .username == $accepted_assignee_username)
)
)
)
| unique_by(.id)
| map(select(.source_branch != null and .source_branch != ""))
| sort_by(.iid)
'
)
accepted_merge_request_count=$(jq 'length' <<<"$accepted_merge_requests_json")
list_local_remote_branches >"$local_remote_branches_file"
same_project_accepted_merge_requests_json=$(
jq '
map(
select(
.source_project_id != null
and .target_project_id != null
and .source_project_id == .target_project_id
and .source_branch != null
and .source_branch != ""
and .source_branch != .target_branch
)
)
' <<<"$accepted_merge_requests_json"
)
same_project_accepted_merge_request_count=$(jq 'length' <<<"$same_project_accepted_merge_requests_json")
accepted_merge_requests_with_local_ref_json=$(
jq \
--slurpfile local_remote_branches "$local_remote_branches_file" \
'
map(
select(
.source_branch as $source_branch
| $local_remote_branches[0]
| index($source_branch)
)
)
| sort_by(.source_branch, .iid)
' <<<"$same_project_accepted_merge_requests_json"
)
accepted_merge_requests_with_local_ref_count=$(jq 'length' <<<"$accepted_merge_requests_with_local_ref_json")
delete_branches_json=$(
jq '
group_by(.source_branch)
| map({
source_branch: .[0].source_branch,
merge_requests: map({
iid,
title,
web_url,
reference: (.references.full // ("!" + (.iid | tostring))),
short_reference: (.references.short // ("!" + (.iid | tostring)))
})
})
| sort_by(.source_branch)
' <<<"$accepted_merge_requests_with_local_ref_json"
)
delete_branch_count=$(jq 'length' <<<"$delete_branches_json")
quoted_delete_remote=$(jq -nr --arg value "$DELETE_REMOTE" '$value | @sh')
cat <<SCRIPT
#!/usr/bin/env bash
set -euo pipefail
# Generated by $(basename "$0") on $(date -u +'%Y-%m-%dT%H:%M:%SZ').
# Accepted merge requests found: ${accepted_merge_request_count}
# Same-project accepted merge requests: ${same_project_accepted_merge_request_count}
# Same-project accepted merge requests with local refs/remotes/${DELETE_REMOTE}/ refs: ${accepted_merge_requests_with_local_ref_count}
# Unique remote branches emitted: ${delete_branch_count}
# Source query:
# host: ${GLAB_HOST}
# project: ${PROJECT_PATH}
# author: @${author_username}
# accepted when state=merged, or state=closed and assignee=@${accepted_assignee_username}
# emitted only when source_project_id == target_project_id
# emitted only when the branch exists locally under refs/remotes/${DELETE_REMOTE}/
default_remote=${quoted_delete_remote}
remote="\${REMOTE:-\$default_remote}"
delete_branch() {
local branch=\$1
local refs=\$2
local remote_ref="refs/remotes/\${remote}/\${branch}"
if git show-ref --verify --quiet "\$remote_ref"; then
printf 'Deleting %s for %s from %s\n' "\$branch" "\$refs" "\$remote" >&2
git push "\$remote" --delete "\$branch"
else
printf 'Skipping %s for %s: local remote ref not found: %s\n' "\$branch" "\$refs" "\$remote_ref" >&2
fi
}
SCRIPT
jq -r '
def clean_comment:
tostring | gsub("[\r\n]+"; " ");
.[]
| .merge_requests as $merge_requests
| ($merge_requests | map(.short_reference) | join(", ")) as $short_references
| ($merge_requests | map(.reference) | join(", ")) as $references
| "# " + $references
+ if ($merge_requests | length) == 1 then
": " + ($merge_requests[0].title | clean_comment)
else
": " + (($merge_requests | length) | tostring) + " accepted merge requests share this source branch"
end
+ "\n" + ($merge_requests | map("# " + .web_url) | join("\n"))
+ "\ndelete_branch "
+ (.source_branch | @sh)
+ " "
+ ($short_references | @sh)
+ "\n"
' <<<"$delete_branches_json"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment