-
Notifications
You must be signed in to change notification settings - Fork 6
157 lines (136 loc) · 7.13 KB
/
Copy pathspring-cherry-pick.yml
File metadata and controls
157 lines (136 loc) · 7.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
name: Automatically cherry-pick commit
on:
workflow_call:
inputs:
autoCherryPickToken:
description: 'The sub-string in the commit message to determine that commit has to be cherry-picked'
required: false
default: Auto-cherry-pick to
type: string
secrets:
GH_ACTIONS_REPO_TOKEN:
required: true
NOTIFICATIONS_CHAT_WEBHOOK_URL:
required: false
env:
GITHUB_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
NOTIFICATIONS_CHAT_WEBHOOK_URL: ${{ secrets.NOTIFICATIONS_CHAT_WEBHOOK_URL }}
jobs:
cherry-pick:
runs-on: ubuntu-latest
# Skip entirely on force-pushes (e.g. a rebase): GitHub's push payload lists the
# rewritten commits as if they were new, which would replay cherry-picks that were
# already performed against the pre-rebase commits.
if: ${{ !github.event.forced && contains(join(github.event.commits.*.message, ' '), inputs.autoCherryPickToken) }}
steps:
# A re-run replays the same commits from github.event.commits against whatever the target
# branches look like at re-run time. The patch-id check below makes that a no-op for branches
# already cherry-picked into in a prior attempt, so there is nothing left for a re-run to
# recover: a branch that failed with a genuine conflict needs a manual fix pushed by hand.
# Cancel instead of wasting a run on a retry that can't succeed differently.
- name: Prevent re-run
if: github.run_attempt != '1'
run: |
gh run cancel ${{ github.run_id }} -R ${{ github.repository }}
echo "::warning title=Re-run is not supported::A failed cherry-pick needs a manual fix, not a re-run. Resolve and push it by hand instead"
# Block until the cancellation takes effect, so no subsequent step is performed
gh run watch ${{ github.run_id }} -R ${{ github.repository }}
- uses: actions/checkout@v7
with:
token: ${{ env.GITHUB_TOKEN }}
show-progress: false
fetch-depth: 0
- name: Cherry-pick to branches in commit message
env:
CHERRY_PICK_TOKEN: ${{ inputs.autoCherryPickToken }}
run: |
git config --global user.name 'Spring Builds'
git config --global user.email 'builds@springframework.org'
# A push can carry several commits; process every commit that carries the token,
# not just github.event.head_commit (the last commit of the push).
# Re-runs are cancelled (see the 'Prevent re-run' step), so a conflict here has no
# retry to fall back on: record it and keep going through the remaining commits/branches
# instead of aborting the whole loop, then fail the job at the end if anything failed.
failed=''
# Cached per branch across all commits/branches in this push, so a maintenance line
# mentioned by several commits only costs one existence check and one lock lookup.
declare -A branchExists
declare -A branchLocked
while IFS= read -r commit
do
sha=$(echo "$commit" | jq -r '.id')
message=$(echo "$commit" | jq -r '.message')
branches=$(echo "$message" | grep "$CHERRY_PICK_TOKEN" | grep -o -E "([0-9]+\.[0-9]+(\.[0-9]+)?\.x(-internal)?)")
for branch in $branches
do
if [ "$branch" != '${{ github.ref_name }}' ]
then
if [ -z "${branchExists[$branch]+x}" ]
then
if git ls-remote --exit-code --heads origin "$branch" > /dev/null
then
branchExists[$branch]=true
else
branchExists[$branch]=false
fi
fi
# The commit message may mention a branch which does not exist (yet or different repository):
# a typo, or a maintenance line which has not been cut. Skip it with a warning instead of failing the whole run,
# so the remaining, existing branches are still cherry-picked into.
if [ "${branchExists[$branch]}" = false ]
then
echo "::warning title=Skip cherry-pick::No '$branch' branch in ${{ github.repository }}. The commit $sha is not cherry-picked there"
continue
fi
if [ -z "${branchLocked[$branch]+x}" ]
then
if [ "$(gh api "repos/${{ github.repository }}/branches/$branch/protection" --jq '.lock_branch.enabled // false' 2>/dev/null)" = true ]
then
branchLocked[$branch]=true
else
branchLocked[$branch]=false
fi
fi
# A locked branch is a maintenance line for which support has ended: it is read-only,
# so checkout/cherry-pick/push would only fail. Skip it up front instead of wasting the
# attempt (and instead of a push failure going unnoticed, since only cherry-pick failures
# below are tracked in $failed).
if [ "${branchLocked[$branch]}" = true ]
then
echo "::warning title=Skip cherry-pick::Branch '$branch' in ${{ github.repository }} is locked (read-only, out of support). The commit $sha is not cherry-picked there"
continue
fi
git checkout "$branch"
# A rebase + force-push gives the same logical change a new SHA, so an equivalent
# patch may already be on the target branch from a run before the rebase. Compare by
# patch-id (not SHA) and skip re-picking it.
if git cherry "$branch" "$sha" "$sha^" | grep -q '^-'
then
echo "::notice title=Skip cherry-pick::$sha (or an equivalent patch) is already in branch $branch"
continue
fi
if git cherry-pick "$sha" -x
then
echo "::notice title=Commit cherry-picked::$sha to branch $branch"
else
git cherry-pick --abort || true
echo "::error title=Cannot cherry-pick::$sha to branch $branch. Manual procedure required"
failed="$failed $sha->$branch"
continue
fi
branchCommitMessage=$(git log -1 --pretty=%B | grep -v "$CHERRY_PICK_TOKEN")
git commit --amend -o -m "$branchCommitMessage"
git push origin "$branch"
fi
done
done < <(jq -c --arg token "$CHERRY_PICK_TOKEN" '.commits[] | select(.message | contains($token))' "$GITHUB_EVENT_PATH")
if [ -n "$failed" ]
then
echo "::error title=Cherry-pick failures::Manual procedure required for:$failed"
exit 1
fi
- name: Notify Failure in Chat
if: ${{ failure() && env.NOTIFICATIONS_CHAT_WEBHOOK_URL }}
uses: spring-io/spring-github-workflows/.github/actions/spring-failure-to-gchat@main
with:
gchat-webhook-url: ${{ env.NOTIFICATIONS_CHAT_WEBHOOK_URL }}