Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit aed8237

Browse filesBrowse files
mmarchiniBethGriggs
authored andcommitted
build: implement a Commit Queue in Actions
This is a (still experimental) implementation of a Commit Queue on GitHub Actions, using labels and the scheduler event to land Pull Requests. It uses `node-core-utils` to validate Pull Requests and to prepare the commit message, and then it uses a GitHub personal token to push changes back to the repository. If the Queue fails to land a Pull Request, that PR will be removed from the queue and the `node-core-utils` output will be pasted in the Pull Request. An overview of the implementation is provided in doc/guides/commit-queue.md, as well as current limitations. Ref: https://github.com/mmarchini-oss/automated-merge-test Ref: nodejs/build#2201 PR-URL: #34112 Refs: https://github.com/mmarchini-oss/automated-merge-test Refs: nodejs/build#2201 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Denys Otrishko <shishugi@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Shelley Vohr <codebytere@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Richard Lau <riclau@uk.ibm.com>
1 parent 8403118 commit aed8237
Copy full SHA for aed8237

File tree

Expand file treeCollapse file tree

4 files changed

+285
-6
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+285
-6
lines changed
Open diff view settings
Collapse file

‎.github/workflows/auto-start-ci.yml‎

Copy file name to clipboardExpand all lines: .github/workflows/auto-start-ci.yml
+4-6Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ name: Auto Start CI
33

44
on:
55
schedule:
6-
# `schedule` event is used instead of `pull_request` because when a
7-
# `pull_requesst` event is triggered on a PR from a fork, GITHUB_TOKEN will
8-
# be read-only, and the Action won't have access to any other repository
9-
# secrets, which it needs to access Jenkins API. Runs every five minutes
10-
# (fastest the scheduler can run). Five minutes is optimistic, it can take
11-
# longer to run.
6+
# Runs every five minutes (fastest the scheduler can run). Five minutes is
7+
# optimistic, it can take longer to run.
8+
# To understand why `schedule` is used instead of other events, refer to
9+
# ./doc/guides/commit-queue.md
1210
- cron: "*/5 * * * *"
1311

1412
jobs:
Collapse file

‎.github/workflows/commit-queue.yml‎

Copy file name to clipboard
+81Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
# This action requires the following secrets to be set on the repository:
3+
# GH_USER_NAME: GitHub user whose Jenkins and GitHub token are defined below
4+
# GH_USER_TOKEN: GitHub user token, to be used by ncu and to push changes
5+
# JENKINS_TOKEN: Jenkins token, to be used to check CI status
6+
7+
name: Commit Queue
8+
9+
on:
10+
# `schedule` event is used instead of `pull_request` because when a
11+
# `pull_request` event is triggered on a PR from a fork, GITHUB_TOKEN will
12+
# be read-only, and the Action won't have access to any other repository
13+
# secrets, which it needs to access Jenkins API.
14+
schedule:
15+
- cron: "*/5 * * * *"
16+
17+
jobs:
18+
commitQueue:
19+
if: github.repository == 'nodejs/node'
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v2
23+
with:
24+
# A personal token is required because pushing with GITHUB_TOKEN will
25+
# prevent commits from running CI after they land on master. It needs
26+
# to be set here because `checkout` configures GitHub authentication
27+
# for push as well.
28+
token: ${{ secrets.GH_USER_TOKEN }}
29+
30+
# Install dependencies
31+
- name: Install Node.js
32+
uses: actions/setup-node@v2-beta
33+
with:
34+
node-version: '12'
35+
- name: Install dependencies
36+
run: |
37+
sudo apt-get install jq -y
38+
# TODO(mmarchini): install from npm after next ncu release is out
39+
npm install -g 'https://github.com/mmarchini/node-core-utils#commit-queue-branch'
40+
# npm install -g node-core-utils
41+
42+
- name: Set variables
43+
run: |
44+
echo "::set-env name=REPOSITORY::$(echo ${{ github.repository }} | cut -d/ -f2)"
45+
echo "::set-env name=OWNER::${{ github.repository_owner }}"
46+
47+
- name: Get Pull Requests
48+
uses: octokit/graphql-action@v2.x
49+
id: get_mergable_pull_requests
50+
with:
51+
query: |
52+
query release($owner:String!,$repo:String!, $base_ref:String!) {
53+
repository(owner:$owner, name:$repo) {
54+
pullRequests(baseRefName: $base_ref, labels: ["commit-queue"], states: OPEN, last: 100) {
55+
nodes {
56+
number
57+
}
58+
}
59+
}
60+
}
61+
owner: ${{ env.OWNER }}
62+
repo: ${{ env.REPOSITORY }}
63+
# Commit queue is only enabled for the default branch on the repository
64+
# TODO(mmarchini): get the default branch programmatically instead of
65+
# assuming `master`
66+
base_ref: "master"
67+
env:
68+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69+
70+
- name: Configure node-core-utils
71+
run: |
72+
ncu-config set branch master
73+
ncu-config set upstream origin
74+
ncu-config set username "${{ secrets.GH_USER_NAME }}"
75+
ncu-config set token "${{ secrets.GH_USER_TOKEN }}"
76+
ncu-config set jenkins_token "${{ secrets.JENKINS_TOKEN }}"
77+
ncu-config set repo "${{ env.REPOSITORY }}"
78+
ncu-config set owner "${{ env.OWNER }}"
79+
80+
- name: Start the commit queue
81+
run: ./tools/actions/commit-queue.sh ${{ env.OWNER }} ${{ env.REPOSITORY }} ${{ secrets.GITHUB_TOKEN }} $(echo '${{ steps.get_mergable_pull_requests.outputs.data }}' | jq '.repository.pullRequests.nodes | map(.number) | .[]')
Collapse file

‎doc/guides/commit-queue.md‎

Copy file name to clipboard
+115Lines changed: 115 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Commit Queue
2+
3+
> Stability: 1 - Experimental
4+
5+
*tl;dr: You can land Pull Requests by adding the `commit-queue` label to it.*
6+
7+
Commit Queue is an experimental feature for the project which simplifies the
8+
landing process by automating it via GitHub Actions. With it, Collaborators are
9+
able to land Pull Requests by adding the `commit-queue` label to a PR. All
10+
checks will run via node-core-utils, and if the Pull Request is ready to land,
11+
the Action will rebase it and push to master.
12+
13+
This document gives an overview on how the Commit Queue works, as well as
14+
implementation details, reasoning for design choices, and current limitations.
15+
16+
## Overview
17+
18+
From a high-level, the Commit Queue works as follow:
19+
20+
1. Collaborators will add `commit-queue` label to Pull Reuqests ready to land
21+
2. Every five minutes the queue will do the following for each Pull Request
22+
with the label:
23+
1. Check if the PR also has a `request-ci` label (if it has, skip this PR
24+
since it's pending a CI run)
25+
2. Check if the last Jenkins CI is finished running (if it is not, skip this
26+
PR)
27+
3. Remove the `commit-queue` label
28+
4. Run `git node land <pr>`
29+
5. If it fails:
30+
1. Abort `git node land` session
31+
2. Add `commit-queue-failed` label to the PR
32+
3. Leave a comment on the PR with the output from `git node land`
33+
4. Skip next steps, go to next PR in the queue
34+
6. If it succeeds:
35+
1. Push the changes to nodejs/node
36+
2. Leave a comment on the PR with `Landed in ...`
37+
3. Close the PR
38+
4. Go to next PR in the queue
39+
40+
## Current Limitations
41+
42+
The Commit Queue feature is still in early stages, and as such it might not
43+
work for more complex Pull Requests. These are the currently known limitations
44+
of the commit queue:
45+
46+
1. The Pull Request must have only one commit
47+
2. A CI must've ran and succeeded since the last change on the PR
48+
3. A Collaborator must have approved the PR since the last change
49+
4. Only Jenkins CI is checked (Actions, V8 CI and CITGM are ignored)
50+
51+
## Implementation
52+
53+
The [action](/.github/workflows/commit_queue.yml) will run on scheduler
54+
events every five minutes. Five minutes is the smallest number accepted by
55+
the scheduler. The scheduler is not guaranteed to run every five minutes, it
56+
might take longer between runs.
57+
58+
Using the scheduler is preferrable over using pull_request_target for two
59+
reasons:
60+
61+
1. if two Commit Queue Actions execution overlap, there's a high-risk that
62+
the last one to finish will fail because the local branch will be out of
63+
sync with the remote after the first Action pushes. `issue_comment` event
64+
has the same limitation.
65+
2. `pull_request_target` will only run if the Action exists on the base commit
66+
of a pull request, and it will run the Action version present on that
67+
commit, meaning we wouldn't be able to use it for already opened PRs
68+
without rebasing them first.
69+
70+
`node-core-utils` is configured with a personal token and
71+
a Jenkins token from
72+
[@nodejs-github-bot](https://github.com/nodejs/github-bot).
73+
`octokit/graphql-action` is used to fetch all Pull Requests with the
74+
`commit-queue` label. The output is a JSON payload, so `jq` is used to turn
75+
that into a list of PR ids we can pass as arguments to
76+
[`commit-queue.sh`](./tools/actions/commit-queue.sh).
77+
78+
> The personal token only needs permission for public repositories and to read
79+
> profiles, we can use the GITHUB_TOKEN for write operations. Jenkins token is
80+
> required to check CI status.
81+
82+
`commit-queue.sh` receives the following positional arguments:
83+
84+
1. The repository owner
85+
2. The repository name
86+
3. The Action GITHUB_TOKEN
87+
4. Every positional argument starting at this one will be a Pull Reuqest ID of
88+
a Pull Request with commit-queue set.
89+
90+
The script will iterate over the pull requests. `ncu-ci` is used to check if
91+
the last CI is still pending, and calls to the GitHub API are used to check if
92+
the PR is waiting for CI to start (`request-ci` label). The PR is skipped if CI
93+
is pending. No other CI validation is done here since `git node land` will fail
94+
if the last CI failed.
95+
96+
The script removes the `commit-queue` label. It then runs `git node land`,
97+
forwarding stdout and stderr to a file. If any errors happens,
98+
`git node land --abort` is run, and then a `commit-queue-failed` label is added
99+
to the PR, as well as a comment with the output of `git node land`.
100+
101+
If no errors happen during `git node land`, the script will use the
102+
`GITHUB_TOKEN` to push the changes to `master`, and then will leave a
103+
`Landed in ...` comment in the PR, and then will close it. Iteration continues
104+
until all PRs have done the steps above.
105+
106+
## Reverting Broken Commits
107+
108+
Reverting broken commits is done manually by Collaborators, just like when
109+
commits are landed manually via `git node land`. An easy way to revert is a
110+
good feature for the project, but is not explicitly required for the Commit
111+
Queue to work because the Action lands PRs just like collaborators do today. If
112+
once we start using the Commit Queue we notice that the number of required
113+
reverts increases drastically, we can pause the queue until a Revert Queue is
114+
implemented, but until then we can enable the Commit Queue and then work on a
115+
Revert Queue as a follow up.
Collapse file

‎tools/actions/commit-queue.sh‎

Copy file name to clipboard
+85Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/bin/bash
2+
3+
set -xe
4+
5+
OWNER=$1
6+
REPOSITORY=$2
7+
GITHUB_TOKEN=$3
8+
shift 3
9+
10+
API_URL=https://api.github.com
11+
COMMIT_QUEUE_LABEL='commit-queue'
12+
COMMIT_QUEUE_FAILED_LABEL='commit-queue-failed'
13+
14+
function issueUrl() {
15+
echo "$API_URL/repos/${OWNER}/${REPOSITORY}/issues/${1}"
16+
}
17+
18+
function labelsUrl() {
19+
echo "$(issueUrl "${1}")/labels"
20+
}
21+
22+
function commentsUrl() {
23+
echo "$(issueUrl "${1}")/comments"
24+
}
25+
26+
function gitHubCurl() {
27+
url=$1
28+
method=$2
29+
shift 2
30+
31+
curl -fsL --request "$method" \
32+
--url "$url" \
33+
--header "authorization: Bearer ${GITHUB_TOKEN}" \
34+
--header 'content-type: application/json' "$@"
35+
}
36+
37+
38+
# TODO(mmarchini): should this be set with whoever added the label for each PR?
39+
git config --local user.email "github-bot@iojs.org"
40+
git config --local user.name "Node.js GitHub Bot"
41+
42+
for pr in "$@"; do
43+
# Skip PR if CI was requested
44+
if gitHubCurl "$(labelsUrl "$pr")" GET | jq -e 'map(.name) | index("request-ci")'; then
45+
echo "pr ${pr} skipped, waiting for CI to start"
46+
continue
47+
fi
48+
49+
# Skip PR if CI is still running
50+
if ncu-ci url "https://github.com/${OWNER}/${REPOSITORY}/pull/${pr}" 2>&1 | grep "^Result *PENDING"; then
51+
echo "pr ${pr} skipped, CI still running"
52+
continue
53+
fi
54+
55+
# Delete the commit queue label
56+
gitHubCurl "$(labelsUrl "$pr")"/"$COMMIT_QUEUE_LABEL" DELETE
57+
58+
git node land --yes "$pr" >output 2>&1 || echo "Failed to land #${pr}"
59+
# cat here otherwise we'll be supressing the output of git node land
60+
cat output
61+
62+
# TODO(mmarchini): workaround for ncu not returning the expected status code,
63+
# if the "Landed in..." message was not on the output we assume land failed
64+
if ! tail -n 10 output | grep '. Post "Landed in .*/pull/'"${pr}"; then
65+
gitHubCurl "$(labelsUrl "$pr")" POST --data '{"labels": ["'"${COMMIT_QUEUE_FAILED_LABEL}"'"]}'
66+
67+
jq -n --arg content "<details><summary>Commit Queue failed</summary><pre>$(cat output)</pre></details>" '{body: $content}' > output.json
68+
cat output.json
69+
70+
gitHubCurl "$(commentsUrl "$pr")" POST --data @output.json
71+
72+
rm output output.json
73+
# If `git node land --abort` fails, we're in unknown state. Better to stop
74+
# the script here, current PR was removed from the queue so it shouldn't
75+
# interfer again in the future
76+
git node land --abort --yes
77+
else
78+
rm output
79+
git push origin master
80+
81+
gitHubCurl "$(commentsUrl "$pr")" POST --data '{"body": "Landed in '"$(git rev-parse HEAD)"'"}'
82+
83+
gitHubCurl "$(issueUrl "$pr")" PATCH --data '{"state": "closed"}'
84+
fi
85+
done

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.