evecube: enable connector before starting charging #1810
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Backport Command | |
| # Triggered by a maintainer commenting `/backport [branch]` on a merged pull | |
| # request. Cherry-picks the merged commit onto the target branch and opens a | |
| # pull request against it, so a fix can ship as a bugfix release without | |
| # pulling in everything that landed on master since. | |
| # | |
| # Without an argument the branch of the next bugfix release is used, e.g. | |
| # `release/0.313.1`. It is created at the newest tag of that line on first use. | |
| # | |
| # Pushing and opening the pull request use RELEASE_DEPLOY_TOKEN so its checks | |
| # start without approval. GITHUB_TOKEN would create them in a pending state. | |
| on: | |
| issue_comment: | |
| types: [created] | |
| permissions: | |
| contents: read | |
| jobs: | |
| backport: | |
| name: Backport | |
| # on any PR comment starting with /backport, only from maintainers | |
| if: | | |
| github.event.issue.pull_request && | |
| startsWith(github.event.comment.body, '/backport') && | |
| contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) | |
| runs-on: depot-ubuntu-24.04-arm | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: React to comment | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| await github.rest.reactions.createForIssueComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: context.payload.comment.id, | |
| content: 'eyes', | |
| }); | |
| - name: Resolve target branch | |
| id: pr | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const { owner, repo } = context.repo; | |
| // reported back as a comment, so the reason is visible on the pull request | |
| const fail = (message) => { | |
| core.setOutput('error', message); | |
| core.setFailed(message); | |
| }; | |
| // the command is usually posted right before hitting merge, so give the | |
| // pull request time to land instead of failing on the race | |
| const deadline = Date.now() + 5 * 60 * 1000; | |
| let pr; | |
| for (;;) { | |
| try { | |
| ({ data: pr } = await github.rest.pulls.get({ | |
| owner, | |
| repo, | |
| pull_number: context.payload.issue.number, | |
| })); | |
| // a closed pull request will never merge, no point in waiting it out | |
| if (pr.merged || pr.state === 'closed') { | |
| break; | |
| } | |
| } catch (err) { | |
| // a blip while polling must not fail the command, but a request that | |
| // is still failing when the time is up is a real error | |
| if (Date.now() >= deadline) { | |
| throw err; | |
| } | |
| } | |
| if (Date.now() >= deadline) { | |
| break; | |
| } | |
| await new Promise((resolve) => setTimeout(resolve, 15000)); | |
| } | |
| if (!pr.merged) { | |
| fail(`pull request #${pr.number} is not merged`); | |
| return; | |
| } | |
| if (!pr.labels.some((l) => l.name === 'bug')) { | |
| fail(`pull request #${pr.number} is not labelled \`bug\`, only bugfixes are backported`); | |
| return; | |
| } | |
| // same marker the changelog uses to group breaking changes | |
| if (/\(BC\)/i.test(pr.title)) { | |
| fail(`pull request #${pr.number} is a breaking change, it must not go into a bugfix release`); | |
| return; | |
| } | |
| const compare = (a, b) => { | |
| const [x, y] = [a, b].map((v) => v.split('.').map(Number)); | |
| return x[0] - y[0] || x[1] - y[1] || x[2] - y[2]; | |
| }; | |
| const tags = await github.paginate(github.rest.repos.listTags, { owner, repo, per_page: 100 }); | |
| const releases = tags.map((t) => t.name).filter((n) => /^\d+\.\d+\.\d+$/.test(n)).sort(compare); | |
| let target = context.payload.comment.body.trim().split(/\s+/)[1]; | |
| if (target) { | |
| // the target ends up in a checkout ref, keep it to plain branch names | |
| if (!/^[\w.\-\/]+$/.test(target) || target.includes('..')) { | |
| fail('usage: /backport [branch]'); | |
| return; | |
| } | |
| } else { | |
| // named after the bugfix release the branch will produce | |
| const [major, minor, patch] = releases[releases.length - 1].split('.').map(Number); | |
| target = `release/${major}.${minor}.${patch + 1}`; | |
| } | |
| // branch off the newest tag of the release line on first backport | |
| try { | |
| await github.rest.repos.getBranch({ owner, repo, branch: target }); | |
| } catch (err) { | |
| if (err.status !== 404) throw err; | |
| const line = target.replace(/^release\//, '').split('.').slice(0, 2).join('.'); | |
| const base = releases.filter((n) => n.startsWith(`${line}.`)).pop(); | |
| if (!base) { | |
| fail(`branch \`${target}\` does not exist and no release matches it`); | |
| return; | |
| } | |
| const { data: commit } = await github.rest.repos.getCommit({ owner, repo, ref: base }); | |
| await github.rest.git.createRef({ owner, repo, ref: `refs/heads/${target}`, sha: commit.sha }); | |
| core.notice(`created ${target} at ${base}`); | |
| } | |
| core.setOutput('target', target); | |
| core.setOutput('branch', `backport/${pr.number}-${target}`); | |
| core.setOutput('sha', pr.merge_commit_sha); | |
| core.setOutput('title', pr.title); | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ steps.pr.outputs.target }} | |
| # persisted for the push below | |
| token: ${{ secrets.RELEASE_DEPLOY_TOKEN }} | |
| - name: Cherry-pick | |
| env: | |
| BRANCH: ${{ steps.pr.outputs.branch }} | |
| SHA: ${{ steps.pr.outputs.sha }} | |
| ACTOR: ${{ github.event.comment.user.login }} | |
| ACTOR_ID: ${{ github.event.comment.user.id }} | |
| run: | | |
| # the cherry-pick keeps the original author, commit it as the requester | |
| git config user.name "$ACTOR" | |
| git config user.email "$ACTOR_ID+$ACTOR@users.noreply.github.com" | |
| git switch -c "$BRANCH" | |
| # -m 1 picks the first-parent diff of a merge commit; squashed pull | |
| # requests are ordinary commits and must not get the flag | |
| mainline="" | |
| if git rev-parse --quiet --verify "$SHA^2" >/dev/null; then | |
| mainline="-m 1" | |
| fi | |
| git cherry-pick $mainline "$SHA" | |
| git push origin "$BRANCH" | |
| - name: Create pull request | |
| id: create | |
| env: | |
| GH_TOKEN: ${{ secrets.RELEASE_DEPLOY_TOKEN }} | |
| BRANCH: ${{ steps.pr.outputs.branch }} | |
| TARGET: ${{ steps.pr.outputs.target }} | |
| TITLE: ${{ steps.pr.outputs.title }} | |
| ACTOR: ${{ github.event.comment.user.login }} | |
| run: | | |
| # the pull request is authored by the deploy token, so assign the | |
| # requester. the body stays empty, squashing it would carry the text | |
| # into the release branch as the commit description. | |
| # the title stays untouched so the changelog filters and groups still | |
| # see the original prefix, the backport label identifies the pull request | |
| url=$(gh pr create \ | |
| --base "$TARGET" \ | |
| --head "$BRANCH" \ | |
| --title "$TITLE" \ | |
| --label backport \ | |
| --assignee "$ACTOR" \ | |
| --body "") | |
| echo "url=$url" >> "$GITHUB_OUTPUT" | |
| - name: Comment result | |
| if: always() | |
| uses: actions/github-script@v8 | |
| env: | |
| TARGET: ${{ steps.pr.outputs.target }} | |
| URL: ${{ steps.create.outputs.url }} | |
| ERROR: ${{ steps.pr.outputs.error }} | |
| with: | |
| script: | | |
| const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; | |
| const { TARGET, URL, ERROR } = process.env; | |
| const body = URL | |
| ? `β Opened backport pull request on \`${TARGET}\`: ${URL}` | |
| : ERROR | |
| ? `β ${ERROR}` | |
| : `β Backport failed, most likely a cherry-pick conflict. See the [run logs](${runUrl}).`; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.payload.issue.number, | |
| body, | |
| }); | |
| - name: Resolve command comment | |
| if: always() | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| // collapse the /backport comment as resolved, the result was reported | |
| // back as a comment either way | |
| await github.graphql( | |
| `mutation($id:ID!){minimizeComment(input:{subjectId:$id,classifier:RESOLVED}){minimizedComment{isMinimized}}}`, | |
| { id: context.payload.comment.node_id } | |
| ); |