blob: abba55a39ad22125b2e36ebc61f080f9ad08def0 [file] [log] [blame]
#!/usr/bin/env bash
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
set -eEuo pipefail
trap 'exit 1' ERR
declare DEBUG
if [ "${DEBUG}" = 'true' ] ; then
set -x
fi
declare GITHUB_TOKEN
declare -a GITHUB_AUTH
GITHUB_AUTH=('--header' "Authorization: token ${GITHUB_TOKEN}")
declare GITHUB_API_URL="https://api.github.com"
# the user_id of the buildbot that leaves the comments we want to hide.
declare BUILD_BOT_USER_ID="${BUILD_BOT_USER_ID:-49303554}"
declare REPO="${REPO:-apache/hbase}"
declare JOB_NAME="${JOB_NAME:-HBase-PreCommit-GitHub-PR}"
JOB_NAME="$(echo "${JOB_NAME}" | cut -d/ -f1)"
declare CURL="${CURL:-curl}"
function fetch_comments {
local pr="$1"
local comments_file
local page_file
local headers_file
local -a curl_args
local page=1
local next_url
comments_file="$(mktemp "comments_${pr}" 2>/dev/null || mktemp -t "comments_${pr}.XXXXXXXXXX")" || \
{ >&2 echo 'cannot create temp file'; exit 1 ;}
page_file="$(mktemp "page_${pr}" 2>/dev/null || mktemp -t "page_${pr}.XXXXXXXXXX")" || \
{ >&2 echo 'cannot create temp file'; exit 1 ;}
headers_file="$(mktemp "headers_${pr}" 2>/dev/null || mktemp -t "headers_${pr}.XXXXXXXXXX")" || \
{ >&2 echo 'cannot create temp file'; exit 1 ;}
# cleanup temp files on error
trap 'rm -f "${page_file}" "${headers_file}"; exit 1' ERR
next_url="${GITHUB_API_URL}/repos/${REPO}/issues/${pr}/comments?per_page=100"
# start with empty JSON array
echo '[]' > "${comments_file}"
while [ -n "${next_url}" ] ; do
curl_args=(
--fail
--max-time 30
"${GITHUB_AUTH[@]}"
--header 'Accept: application/vnd.github+json'
--header 'X-GitHub-Api-Version: 2022-11-28'
--dump-header "${headers_file}"
--request GET
--url "${next_url}"
)
if [ "${DEBUG}" = true ] ; then
curl_args+=(--verbose)
>&2 echo "Fetching page ${page}: ${next_url}"
else
curl_args+=(--silent)
fi
if ! "${CURL}" "${curl_args[@]}" > "${page_file}"; then
>&2 echo "Failed to fetch page ${page}: ${next_url}"
rm -f "${page_file}" "${headers_file}"
exit 1
fi
if [ "${DEBUG}" = 'true' ] ; then
>&2 echo "Page ${page} returned $(jq length "${page_file}") comments"
fi
# merge this page into the accumulated results
if ! jq -s '.[0] + .[1]' "${comments_file}" "${page_file}" > "${comments_file}.tmp"; then
>&2 echo "Failed to merge comments from page ${page}"
rm -f "${page_file}" "${headers_file}" "${comments_file}.tmp"
exit 1
fi
mv "${comments_file}.tmp" "${comments_file}"
# check for next page in Link header
# Link header format: <url>; rel="next", <url>; rel="last"
# Extract URL associated with rel="next" regardless of position
next_url=""
if grep -qi '^link:' "${headers_file}" ; then
next_url=$(grep -i '^link:' "${headers_file}" | tr ',' '\n' | grep 'rel="next"' | sed -n 's/.*<\([^>]*\)>.*/\1/p' || true)
fi
page=$((page + 1))
done
rm -f "${page_file}" "${headers_file}"
trap - ERR
if [ "${DEBUG}" = 'true' ] ; then
>&2 echo "Total comments fetched: $(jq length "${comments_file}")"
>&2 cat "${comments_file}"
fi
echo "${comments_file}"
}
function hide_old_comment {
local pr="$1"
local comment_id="$2"
local -a curl_args
local query
read -r -d '' query << EOF || :
mutation {
minimizeComment(input: { subjectId: \"$comment_id\", classifier: OUTDATED }) {
minimizedComment {
isMinimized,
minimizedReason
}
}
}
EOF
# remove newlines and whitespace rather than escaping them
query="${query//[$'\r\n\t ']/}"
curl_args=(
--fail
"${GITHUB_AUTH[@]}"
--header 'Accept: application/vnd.github+json'
--header 'Content-Type: application/json'
--header 'X-GitHub-Api-Version: 2022-11-28'
--request POST
--url "${GITHUB_API_URL}/graphql"
)
if [ "${DEBUG}" = true ] ; then
curl_args+=(--verbose)
else
curl_args+=(--silent)
fi
"${CURL}" "${curl_args[@]}" --data "{ \"query\": \"${query}\" }"
}
function identify_most_recent_build_number {
local pr="$1"
local comments_file="$2"
local jq_filter
local url_pattern="${JOB_NAME}/job/PR-${pr}/(?<buildnum>[0-9]+)/"
# GitHub Actions URLs don't have /job/ in them
if [[ "${JOB_NAME}" == *"GH-Actions"* ]]; then
url_pattern="${JOB_NAME}/PR-${pr}/(?<buildnum>[0-9]+)/"
fi
read -r -d '' jq_filter << EOF || :
.[] \
| select(.user.id == ${BUILD_BOT_USER_ID}) \
| .body \
| capture("${url_pattern}") \
| .buildnum
EOF
jq -r "${jq_filter}" "${comments_file}" \
| sort -nu \
| tail -n1
}
function identify_old_comment_ids {
local pr="$1"
local comments_file="$2"
local most_recent_build_number="$3"
local jq_filter
local url_pattern="${JOB_NAME}/job/PR-${pr}/(?<buildnum>[0-9]+)/"
# GitHub Actions URLs don't have /job/ in them
if [[ "${JOB_NAME}" == *"GH-Actions"* ]]; then
url_pattern="${JOB_NAME}/PR-${pr}/(?<buildnum>[0-9]+)/"
fi
read -r -d '' jq_filter << EOF || :
.[] \
| select(.user.id == ${BUILD_BOT_USER_ID}) \
| { node_id, buildnum: (.body | capture("${url_pattern}") | .buildnum | tonumber) } \
| select(.buildnum < (${most_recent_build_number} | tonumber)) \
| .node_id
EOF
jq -r "${jq_filter}" "${comments_file}"
}
function main {
local pr="$1"
local comments_file
local most_recent_build_number
comments_file="$(fetch_comments "$pr")"
most_recent_build_number="$(identify_most_recent_build_number "${pr}" "${comments_file}")"
for comment_id in $(identify_old_comment_ids "${pr}" "${comments_file}" "${most_recent_build_number}") ; do
hide_old_comment "${pr}" "${comment_id}"
sleep 1
done
}
main "$@"