When backporting a PR from master -> 25.05 -> 24.11 in a chain, the last cherry-pick will have two references to different commits in it. If there was conflict resolution in the first step, the diff will show up again in the last step. This can be fixed by comparing against the right hash - always the last one.
153 lines
5.6 KiB
Bash
Executable File
153 lines
5.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 "-------------------------------------------------"
|
|
|
|
# Using the last line with "cherry" + hash, because a chained backport
|
|
# can result in multiple of those lines. Only the last one counts.
|
|
original_commit_sha=$(
|
|
git rev-list --max-count=1 --format=format:%B "$new_commit_sha" \
|
|
| grep -Ei "cherry.*[0-9a-f]{40}" | tail -n1 \
|
|
| 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.
|
|
diff="$($range_diff_common | tail -n +2 | sed -Ee 's/^ {4}/> /g')"
|
|
# Also limit the output to 10k bytes (and remove the last, potentially incomplete line), because
|
|
# GitHub comments are limited in length. The value of 10k is arbitrary with the assumption, that
|
|
# after the range-diff becomes a certain size, a reviewer is better off reviewing the regular diff
|
|
# in GitHub's UI anyway, thus treating the commit as "new" and not cherry-picked.
|
|
# Note: This could still lead to a too lengthy comment with multiple commits touching the limit. We
|
|
# consider this too unlikely to happen, to deal with explicitly.
|
|
max_length=10000
|
|
if [ "${#diff}" -gt $max_length ]; then
|
|
printf -v diff "%s\n>\n> [...truncated...]" "$(echo "$diff" | head -c $max_length | head -n-1)"
|
|
fi
|
|
echo "$diff" >> $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
|