Simple merge queue (#22992)
This commit is contained in:
124
.github/actions-scripts/update-merge-queue-branch.js
vendored
Normal file
124
.github/actions-scripts/update-merge-queue-branch.js
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { getOctokit } from '@actions/github'
|
||||
const token = process.env.GITHUB_TOKEN
|
||||
const github = getOctokit(token)
|
||||
|
||||
// Mergeable status documentation here:
|
||||
// https://docs.github.com/en/graphql/reference/enums#mergestatestatus
|
||||
// https://docs.github.com/en/graphql/reference/enums#mergeablestate
|
||||
|
||||
/*
|
||||
This script gets a list of automerge-enabled PRs and sorts them
|
||||
by priority. The PRs with the skip-to-front-of-merge-queue label
|
||||
are prioritized first. The rest of the PRs are sorted by the date
|
||||
they were updated. This is basically a FIFO queue, while allowing
|
||||
writers the ability to skip the line when high-priority ships are
|
||||
needed but a freeze isn't necessary.
|
||||
*/
|
||||
|
||||
main()
|
||||
|
||||
async function main() {
|
||||
// Get a list of open PRs and order them from oldest to newest
|
||||
const query = `query ($first: Int, $after: String, $firstLabels: Int) {
|
||||
organization(login: "github") {
|
||||
repository(name: "docs-internal") {
|
||||
pullRequests(first: $first, after: $after, states: OPEN, orderBy: {field: UPDATED_AT, direction: ASC}) {
|
||||
edges{
|
||||
node {
|
||||
number
|
||||
url
|
||||
updatedAt
|
||||
mergeable
|
||||
mergeStateStatus
|
||||
autoMergeRequest {
|
||||
enabledBy {
|
||||
login
|
||||
}
|
||||
enabledAt
|
||||
}
|
||||
labels (first:$firstLabels){
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
const queryVariables = {
|
||||
first: 100,
|
||||
after: null, // when pagination in null it will get first page
|
||||
firstLabels: 100,
|
||||
headers: {
|
||||
// required for the mergeStateStatus enum
|
||||
accept: 'application/vnd.github.merge-info-preview+json',
|
||||
},
|
||||
}
|
||||
let hasNextPage = true
|
||||
const autoMergeEnabledPRs = []
|
||||
|
||||
// we need to get all the paginated results in the case that
|
||||
// there are more than 100 PRs
|
||||
while (hasNextPage) {
|
||||
const graph = await github.graphql(query, queryVariables)
|
||||
const dataRoot = graph.organization.repository.pullRequests
|
||||
const pullRequests = dataRoot.edges
|
||||
// update pagination variables
|
||||
hasNextPage = dataRoot.pageInfo.hasNextPage
|
||||
// the endCursor is the start cursor for the next page
|
||||
queryVariables.after = dataRoot.pageInfo.endCursor
|
||||
|
||||
const filteredPrs = pullRequests
|
||||
// this simplifies the format received from the graphql query to
|
||||
// remove the unnecessary nested objects
|
||||
.map((pr) => {
|
||||
// make the labels object just an array of the label names
|
||||
const labelArray = pr.node.labels.nodes.map((label) => label.name)
|
||||
pr.node.labels = labelArray
|
||||
// return the pr object and ✂️ the node property
|
||||
return pr.node
|
||||
})
|
||||
.filter((pr) => pr.autoMergeRequest !== null)
|
||||
.filter((pr) => pr.mergeable === 'MERGEABLE')
|
||||
// filter out prs that don't have a calculated mergeable state yet
|
||||
.filter((pr) => pr.mergeStateStatus !== 'UNKNOWN')
|
||||
// filter out prs that still need a review, have merge conflicts,
|
||||
// or have failing ci tests
|
||||
.filter((pr) => pr.mergeStateStatus !== 'BLOCKED')
|
||||
// **NOTE**: In the future we may want to send slack message to initiators
|
||||
// of PRs with the following merge states because these can happen after
|
||||
// a PR is green and the automerge is enabled
|
||||
.filter((pr) => pr.mergeStateStatus !== 'DIRTY')
|
||||
.filter((pr) => pr.mergeStateStatus !== 'UNSTABLE')
|
||||
|
||||
autoMergeEnabledPRs.push(...filteredPrs)
|
||||
}
|
||||
|
||||
// Get the list of prs with the skip label so they can
|
||||
// be put at the beginning of the list
|
||||
const prioritizedPrList = autoMergeEnabledPRs
|
||||
.filter((pr) => pr.labels.includes('skip-to-front-of-merge-queue'))
|
||||
.concat(autoMergeEnabledPRs.filter((pr) => !pr.labels.includes('skip-to-front-of-merge-queue')))
|
||||
|
||||
const nextInQueue = prioritizedPrList.shift()
|
||||
// Update the branch for the next PR in the merge queue
|
||||
github.rest.pulls.updateBranch({
|
||||
owner: 'github',
|
||||
repo: 'docs-internal',
|
||||
pull_number: parseInt(nextInQueue.number),
|
||||
})
|
||||
|
||||
console.log(`⏱ Total PRs in the merge queue: ${prioritizedPrList.length + 1}`)
|
||||
console.log(`🚂 Updated branch for PR #${JSON.stringify(nextInQueue, null, 2)}`)
|
||||
console.log(`🚏 Next up in the queue: `)
|
||||
console.log(JSON.stringify(prioritizedPrList, null, 2))
|
||||
}
|
||||
1
.github/allowed-actions.js
vendored
1
.github/allowed-actions.js
vendored
@@ -17,7 +17,6 @@ export default [
|
||||
'cschleiden/actions-linter@caffd707beda4fc6083926a3dff48444bc7c24aa', // uses github-actions-parser v0.23.0
|
||||
'dawidd6/action-delete-branch@47743101a121ad657031e6704086271ca81b1911', // v3.0.2
|
||||
'dawidd6/action-download-artifact@af92a8455a59214b7b932932f2662fdefbd78126', // v2.15.0
|
||||
'docker://chinthakagodawita/autoupdate-action:v1',
|
||||
'dorny/paths-filter@eb75a1edc117d3756a18ef89958ee59f9500ba58',
|
||||
'trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b', // v1.2.4
|
||||
'github/codeql-action/analyze@v1',
|
||||
|
||||
31
.github/workflows/autoupdate-branch.yml
vendored
31
.github/workflows/autoupdate-branch.yml
vendored
@@ -1,20 +1,22 @@
|
||||
name: Autoupdate branch
|
||||
|
||||
# **What it does**: Any pull requests with automerge will get main branch updates.
|
||||
# **Why we have it**: So we don't have to watch pull requests and click update branch 100x.
|
||||
# **What it does**: The next pull request in the merge queue will get its
|
||||
# branch updated with main. Only updating one branch ensures that pull requests
|
||||
# in the queue are merged sequentially.
|
||||
# **Why we have it**: So we don't have to watch pull requests and click
|
||||
# update branch 1000x.
|
||||
# **Who does it impact**: Our health.
|
||||
|
||||
#
|
||||
# This workflow checks all open PRs targeting `main` as their base branch and
|
||||
# will attempt to update them if they have automerge.
|
||||
# It is triggered when a `push` event occurs ON the `main` branch (e.g. a PR
|
||||
# was merged or a force-push was done).
|
||||
# The merge queue consists of any pull requests with automerge enabled and
|
||||
# are mergeable. There is a label that can be used to skip to the front of
|
||||
# the queue (`skip-to-front-of-merge-queue`).
|
||||
#
|
||||
# It should work on all PRs created from source branches within the repo itself
|
||||
# but is unlikely to work for PRs created from forked repos.
|
||||
# This workflow is triggered when a `push` event occurs ON the `main` branch
|
||||
# (e.g. a PR was merged or a force-push was done).
|
||||
#
|
||||
# It is still worthwhile to leave it enabled for the `docs` open source repo as
|
||||
# it should at least be running on `repo-sync` branch PRs.
|
||||
# This workflow runs on all PRs created from source branches within the
|
||||
# public and private docs repos but is won't work for PRs created from
|
||||
# forked repos.
|
||||
#
|
||||
|
||||
on:
|
||||
@@ -28,8 +30,7 @@ jobs:
|
||||
name: autoupdate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: docker://chinthakagodawita/autoupdate-action:v1
|
||||
- name: Update next PR in queue
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }}
|
||||
PR_FILTER: auto_merge
|
||||
MERGE_MSG: 'Autoupdate branch'
|
||||
GITHUB_TOKEN: ${{ secrets.DOCUBOT_REPO_PAT }}
|
||||
run: node .github/actions-scripts/update-merge-queue-branch.js
|
||||
|
||||
Reference in New Issue
Block a user