2022-05-07 12:54:51 +01:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
# Find alleged cherry-picks
|
|
|
|
|
2025-05-28 19:56:33 +02:00
|
|
|
set -euo pipefail
|
2022-05-07 12:54:51 +01:00
|
|
|
|
2025-05-28 21:16:01 +02:00
|
|
|
if [[ $# != "2" && $# != "3" ]] ; then
|
|
|
|
echo "usage: check-cherry-picks.sh base_rev head_rev [markdown_file]"
|
2022-05-07 12:54:51 +01:00
|
|
|
exit 2
|
|
|
|
fi
|
|
|
|
|
2025-05-28 21:16:01 +02:00
|
|
|
markdown_file="$(realpath ${3:-/dev/null})"
|
|
|
|
[ -v 3 ] && rm -f "$markdown_file"
|
|
|
|
|
2025-05-27 20:00:24 +02:00
|
|
|
# Make sure we are inside the nixpkgs repo, even when called from outside
|
|
|
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
|
|
|
|
2025-05-30 13:04:26 +02:00
|
|
|
PICKABLE_BRANCHES="master staging release-??.?? staging-??.?? haskell-updates python-updates staging-next staging-next-??.??"
|
2022-05-07 12:54:51 +01:00
|
|
|
problem=0
|
|
|
|
|
2025-05-28 12:48:30 +02:00
|
|
|
# Not everyone calls their remote "origin"
|
|
|
|
remote="$(git remote -v | grep -i 'NixOS/nixpkgs' | head -n1 | cut -f1 || true)"
|
|
|
|
|
2025-05-28 13:05:17 +02:00
|
|
|
commits="$(git rev-list --reverse "$1..$2")"
|
2025-05-27 19:59:28 +02:00
|
|
|
|
2025-05-28 20:03:54 +02:00
|
|
|
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]}$@"
|
2025-05-28 21:16:01 +02:00
|
|
|
|
|
|
|
# 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
|
2025-05-28 20:03:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
endgroup() {
|
|
|
|
if [ -v GITHUB_ACTIONS ] ; then
|
|
|
|
echo ::endgroup::
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2025-05-28 12:43:41 +02:00
|
|
|
while read -r new_commit_sha ; do
|
2025-05-28 19:56:33 +02:00
|
|
|
if [ -v GITHUB_ACTIONS ] ; then
|
2022-05-07 12:54:51 +01:00
|
|
|
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}" \
|
2025-05-28 13:05:17 +02:00
|
|
|
| grep -Eoi -m1 '[0-9a-f]{40}' || true
|
2022-05-07 12:54:51 +01:00
|
|
|
)
|
2025-05-28 12:43:41 +02:00
|
|
|
if [ -z "$original_commit_sha" ] ; then
|
2025-05-28 20:03:54 +02:00
|
|
|
endgroup
|
2025-05-28 21:16:01 +02:00
|
|
|
log warning "Couldn't locate original commit hash in message of $new_commit_sha."
|
2025-05-28 13:05:17 +02:00
|
|
|
problem=1
|
2022-05-07 12:54:51 +01:00
|
|
|
continue
|
|
|
|
fi
|
|
|
|
|
|
|
|
set -f # prevent pathname expansion of patterns
|
2025-05-28 12:48:30 +02:00
|
|
|
for pattern in $PICKABLE_BRANCHES ; do
|
2022-05-07 12:54:51 +01:00
|
|
|
set +f # re-enable pathname expansion
|
|
|
|
|
2025-05-28 15:31:14 +02:00
|
|
|
# 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)"
|
2025-05-28 12:39:32 +02:00
|
|
|
|
2022-05-07 12:54:51 +01:00
|
|
|
while read -r picked_branch ; do
|
|
|
|
if git merge-base --is-ancestor "$original_commit_sha" "$picked_branch" ; then
|
2025-05-28 14:56:08 +02:00
|
|
|
range_diff_common='git --no-pager range-diff
|
2022-05-07 12:54:51 +01:00
|
|
|
--no-notes
|
|
|
|
--creation-factor=100
|
|
|
|
'"$original_commit_sha~..$original_commit_sha"'
|
|
|
|
'"$new_commit_sha~..$new_commit_sha"'
|
|
|
|
'
|
|
|
|
|
2025-05-28 13:12:19 +02:00
|
|
|
if $range_diff_common --no-color 2> /dev/null | grep -E '^ {4}[+-]{2}' > /dev/null ; then
|
2025-05-28 20:03:54 +02:00
|
|
|
log success "$original_commit_sha present in branch $picked_branch"
|
|
|
|
endgroup
|
2025-05-28 21:16:01 +02:00
|
|
|
log warning "Difference between $new_commit_sha and original $original_commit_sha may warrant inspection."
|
2022-05-07 12:54:51 +01:00
|
|
|
|
2025-05-28 18:21:33 +02:00
|
|
|
# First line contains commit SHAs, which we already printed.
|
|
|
|
$range_diff_common --color | tail -n +2
|
2022-05-07 12:54:51 +01:00
|
|
|
|
2025-05-28 21:16:01 +02:00
|
|
|
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.
|
2025-05-31 10:56:29 +02:00
|
|
|
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
|
2025-05-28 21:16:01 +02:00
|
|
|
echo '> ```' >> $markdown_file
|
|
|
|
echo "> </details>" >> $markdown_file
|
|
|
|
|
2022-05-07 12:54:51 +01:00
|
|
|
problem=1
|
|
|
|
else
|
2025-05-28 20:03:54 +02:00
|
|
|
log success "$original_commit_sha present in branch $picked_branch"
|
|
|
|
log success "$original_commit_sha highly similar to $new_commit_sha"
|
2022-05-07 12:54:51 +01:00
|
|
|
$range_diff_common --color
|
2025-05-28 20:03:54 +02:00
|
|
|
endgroup
|
2022-05-07 12:54:51 +01:00
|
|
|
fi
|
|
|
|
|
|
|
|
# move on to next commit
|
|
|
|
continue 3
|
|
|
|
fi
|
2025-05-28 12:39:32 +02:00
|
|
|
done <<< "$branches"
|
2022-05-07 12:54:51 +01:00
|
|
|
done
|
|
|
|
|
2025-05-28 20:03:54 +02:00
|
|
|
endgroup
|
2025-05-28 21:16:01 +02:00
|
|
|
log error "$original_commit_sha given in $new_commit_sha not found in any pickable branch."
|
2022-05-07 12:54:51 +01:00
|
|
|
|
|
|
|
problem=1
|
2025-05-27 19:59:28 +02:00
|
|
|
done <<< "$commits"
|
2022-05-07 12:54:51 +01:00
|
|
|
|
|
|
|
exit $problem
|