nixpkgs/ci/check-cherry-picks.sh
Wolfgang Walther 515b174c42
workflows/check-cherry-picks: post review comments
Instead of failing the job, the workflow will now post review comments
as "Request Changes". This makes the feedback more readily visible and
avoids having to merge despite a failing CI job. It is also a
pre-requisite to enable required status checks / required workflows in
the future.

Committers are asked to confirm the differences by explicitly dismissing
the generated review. After dismissal, the related review comment will
automatically be marked as "resolved".

The comments only report warnings and errors. Reviews are automatically
dismissed when they have been addressed by the author and no problems
remain. If problems remain, existing, still pending, review comments
will be updated. If the same problems had already been dismissed
earlier, no new review comment will be created either.
2025-06-01 09:35:47 +02:00

139 lines
4.6 KiB
Bash
Executable file

#!/usr/bin/env bash
# Find alleged cherry-picks
set -euo pipefail
if [[ $# != "2" && $# != "3" ]] ; then
echo "usage: check-cherry-picks.sh base_rev head_rev [markdown_file]"
exit 2
fi
markdown_file="$(realpath ${3:-/dev/null})"
[ -v 3 ] && rm -f "$markdown_file"
# Make sure we are inside the nixpkgs repo, even when called from outside
cd "$(dirname "${BASH_SOURCE[0]}")"
PICKABLE_BRANCHES="master staging release-??.?? staging-??.?? haskell-updates python-updates staging-next staging-next-??.??"
problem=0
# Not everyone calls their remote "origin"
remote="$(git remote -v | grep -i 'NixOS/nixpkgs' | head -n1 | cut -f1 || true)"
commits="$(git rev-list --reverse "$1..$2")"
log() {
type="$1"
shift 1
local -A prefix
prefix[success]=" ✔ "
if [ -v GITHUB_ACTIONS ]; then
prefix[warning]="::warning::"
prefix[error]="::error::"
else
prefix[warning]=" ⚠ "
prefix[error]=" ✘ "
fi
echo "${prefix[$type]}$@"
# Only logging errors and warnings, which allows comparing the markdown file
# between pushes to the PR. Even if a new, proper cherry-pick, commit is added
# it won't change the markdown file's content and thus not trigger another comment.
if [ "$type" != "success" ]; then
local -A alert
alert[warning]="WARNING"
alert[error]="CAUTION"
echo >> $markdown_file
echo "> [!${alert[$type]}]" >> $markdown_file
echo "> $@" >> $markdown_file
fi
}
endgroup() {
if [ -v GITHUB_ACTIONS ] ; then
echo ::endgroup::
fi
}
while read -r new_commit_sha ; do
if [ -v GITHUB_ACTIONS ] ; then
echo "::group::Commit $new_commit_sha"
else
echo "================================================="
fi
git rev-list --max-count=1 --format=medium "$new_commit_sha"
echo "-------------------------------------------------"
original_commit_sha=$(
git rev-list --max-count=1 --format=format:%B "$new_commit_sha" \
| grep -Ei -m1 "cherry.*[0-9a-f]{40}" \
| grep -Eoi -m1 '[0-9a-f]{40}' || true
)
if [ -z "$original_commit_sha" ] ; then
endgroup
log warning "Couldn't locate original commit hash in message of $new_commit_sha."
problem=1
continue
fi
set -f # prevent pathname expansion of patterns
for pattern in $PICKABLE_BRANCHES ; do
set +f # re-enable pathname expansion
# Reverse sorting by refname and taking one match only means we can only backport
# from unstable and the latest stable. That makes sense, because even right after
# branch-off, when we have two supported stable branches, we only ever want to cherry-pick
# **to** the older one, but never **from** it.
# This makes the job significantly faster in the case when commits can't be found,
# because it doesn't need to iterate through 20+ branches, which all need to be fetched.
branches="$(git for-each-ref --sort=-refname --format="%(refname)" \
"refs/remotes/${remote:-origin}/$pattern" | head -n1)"
while read -r picked_branch ; do
if git merge-base --is-ancestor "$original_commit_sha" "$picked_branch" ; then
range_diff_common='git --no-pager range-diff
--no-notes
--creation-factor=100
'"$original_commit_sha~..$original_commit_sha"'
'"$new_commit_sha~..$new_commit_sha"'
'
if $range_diff_common --no-color 2> /dev/null | grep -E '^ {4}[+-]{2}' > /dev/null ; then
log success "$original_commit_sha present in branch $picked_branch"
endgroup
log warning "Difference between $new_commit_sha and original $original_commit_sha may warrant inspection."
# First line contains commit SHAs, which we already printed.
$range_diff_common --color | tail -n +2
echo -e "> <details><summary>Show diff</summary>\n>" >> $markdown_file
echo '> ```diff' >> $markdown_file
# The output of `git range-diff` is indented with 4 spaces, which we need to match with the
# code blocks indent to get proper syntax highlighting on GitHub.
$range_diff_common | tail -n +2 | sed -Ee 's/^ {4}/> /g' >> $markdown_file
echo '> ```' >> $markdown_file
echo "> </details>" >> $markdown_file
problem=1
else
log success "$original_commit_sha present in branch $picked_branch"
log success "$original_commit_sha highly similar to $new_commit_sha"
$range_diff_common --color
endgroup
fi
# move on to next commit
continue 3
fi
done <<< "$branches"
done
endgroup
log error "$original_commit_sha given in $new_commit_sha not found in any pickable branch."
problem=1
done <<< "$commits"
exit $problem