Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71c4b320aa |
221
.github/workflows/scripts/ghstack/check_permissions.js
vendored
Normal file
221
.github/workflows/scripts/ghstack/check_permissions.js
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env node
|
||||
// JS rewrite of https://github.com/Chillee/ghstack_land_example/blob/main/.github/workflows/scripts/ghstack-perm-check.py
|
||||
'use strict';
|
||||
|
||||
const {spawnSync} = require('child_process');
|
||||
const process = require('process');
|
||||
const {Octokit} = require('@octokit/rest');
|
||||
|
||||
const OWNER = 'facebook';
|
||||
const REPO = 'react';
|
||||
|
||||
async function must(cond, msg, octokit, issue_number) {
|
||||
if (!cond) {
|
||||
console.error(msg);
|
||||
try {
|
||||
await octokit.issues.createComment({
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
issue_number,
|
||||
body: `ghstack bot failed: ${msg}`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to post comment:', error);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
||||
if (!GITHUB_TOKEN) {
|
||||
console.error('GITHUB_TOKEN environment variable is not set.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const octokit = new Octokit({auth: GITHUB_TOKEN});
|
||||
const prNumber = parseInt(process.argv[2]);
|
||||
const headRef = process.argv[3];
|
||||
|
||||
console.log(headRef);
|
||||
await must(
|
||||
headRef && /^gh\/[A-Za-z0-9-]+\/[0-9]+\/head$/.test(headRef),
|
||||
'Not a ghstack PR',
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
|
||||
const origRef = headRef.replace('/head', '/orig');
|
||||
|
||||
console.log(':: Fetching newest main...');
|
||||
let result = spawnSync('git', ['fetch', 'origin', 'main'], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
await must(
|
||||
result.status === 0,
|
||||
"Can't fetch main",
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
|
||||
console.log(':: Fetching orig branch...');
|
||||
result = spawnSync('git', ['fetch', 'origin', origRef], {stdio: 'inherit'});
|
||||
await must(
|
||||
result.status === 0,
|
||||
"Can't fetch orig branch",
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
|
||||
result = spawnSync(
|
||||
'git',
|
||||
['log', 'FETCH_HEAD...$(git merge-base FETCH_HEAD origin/main)'],
|
||||
{shell: true}
|
||||
);
|
||||
const out = result.stdout.toString();
|
||||
await must(
|
||||
result.status === 0,
|
||||
'`git log` command failed!',
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
|
||||
const regex =
|
||||
/Pull Request resolved: https:\/\/github\.com\/.*?\/pull\/([0-9]+)/g;
|
||||
const prNumbers = [];
|
||||
let match;
|
||||
while ((match = regex.exec(out)) !== null) {
|
||||
prNumbers.push(parseInt(match[1], 10));
|
||||
}
|
||||
console.log(prNumbers);
|
||||
await must(
|
||||
prNumbers.length && prNumbers[0] === prNumber,
|
||||
'Extracted PR numbers not seems right!',
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
|
||||
for (const n of prNumbers) {
|
||||
process.stdout.write(`:: Checking PR status #${n}... `);
|
||||
|
||||
let prObj;
|
||||
try {
|
||||
const {data} = await octokit.pulls.get({
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
pull_number: n,
|
||||
});
|
||||
prObj = data;
|
||||
} catch (error) {
|
||||
await must(
|
||||
false,
|
||||
'Error Getting PR Object!',
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
}
|
||||
|
||||
let reviews;
|
||||
try {
|
||||
const {data} = await octokit.request(
|
||||
'GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews',
|
||||
{
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
pull_number: prNumber,
|
||||
headers: {
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
},
|
||||
}
|
||||
);
|
||||
reviews = data;
|
||||
} catch (error) {
|
||||
await must(
|
||||
false,
|
||||
'Error Getting PR Reviews!',
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
}
|
||||
|
||||
let approved = false;
|
||||
for (const review of reviews) {
|
||||
if (review.state === 'COMMENTED') continue;
|
||||
|
||||
await must(
|
||||
['APPROVED', 'DISMISSED'].includes(review.state),
|
||||
`@${review.user.login} has stamped PR #${n} \`${review.state}\`, please resolve it first!`,
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
if (review.state === 'APPROVED') {
|
||||
approved = true;
|
||||
}
|
||||
}
|
||||
await must(
|
||||
approved,
|
||||
`PR #${n} is not approved yet!`,
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
|
||||
let checkruns;
|
||||
try {
|
||||
const {data} = await octokit.checks.listForRef({
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
ref: prObj.head.sha,
|
||||
});
|
||||
checkruns = data;
|
||||
} catch (error) {
|
||||
await must(
|
||||
false,
|
||||
'Error getting check runs status!',
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
}
|
||||
|
||||
for (const cr of checkruns.check_runs) {
|
||||
const status = cr.conclusion ? cr.conclusion : cr.status;
|
||||
const name = cr.name;
|
||||
if (name === 'Copilot for PRs') continue;
|
||||
await must(
|
||||
['success', 'neutral'].includes(status),
|
||||
`PR #${n} check-run \`${name}\`'s status \`${status}\` is not success!`,
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
}
|
||||
console.log('SUCCESS!');
|
||||
}
|
||||
|
||||
console.log(':: All PRs are ready to be landed!');
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Unexpected error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
12
.github/workflows/scripts/ghstack/package.json
vendored
Normal file
12
.github/workflows/scripts/ghstack/package.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "ghstack-perm-check",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"check-permissions": "node ./check_permissions.js"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/rest": "^21.1.1"
|
||||
}
|
||||
}
|
||||
112
.github/workflows/scripts/ghstack/yarn.lock
vendored
Normal file
112
.github/workflows/scripts/ghstack/yarn.lock
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@octokit/auth-token@^5.0.0":
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-5.1.2.tgz#68a486714d7a7fd1df56cb9bc89a860a0de866de"
|
||||
integrity sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==
|
||||
|
||||
"@octokit/core@^6.1.4":
|
||||
version "6.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/core/-/core-6.1.4.tgz#f5ccf911cc95b1ce9daf6de425d1664392f867db"
|
||||
integrity sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==
|
||||
dependencies:
|
||||
"@octokit/auth-token" "^5.0.0"
|
||||
"@octokit/graphql" "^8.1.2"
|
||||
"@octokit/request" "^9.2.1"
|
||||
"@octokit/request-error" "^6.1.7"
|
||||
"@octokit/types" "^13.6.2"
|
||||
before-after-hook "^3.0.2"
|
||||
universal-user-agent "^7.0.0"
|
||||
|
||||
"@octokit/endpoint@^10.1.3":
|
||||
version "10.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-10.1.3.tgz#bfe8ff2ec213eb4216065e77654bfbba0fc6d4de"
|
||||
integrity sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==
|
||||
dependencies:
|
||||
"@octokit/types" "^13.6.2"
|
||||
universal-user-agent "^7.0.2"
|
||||
|
||||
"@octokit/graphql@^8.1.2":
|
||||
version "8.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-8.2.1.tgz#0cb83600e6b4009805acc1c56ae8e07e6c991b78"
|
||||
integrity sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==
|
||||
dependencies:
|
||||
"@octokit/request" "^9.2.2"
|
||||
"@octokit/types" "^13.8.0"
|
||||
universal-user-agent "^7.0.0"
|
||||
|
||||
"@octokit/openapi-types@^24.2.0":
|
||||
version "24.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-24.2.0.tgz#3d55c32eac0d38da1a7083a9c3b0cca77924f7d3"
|
||||
integrity sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==
|
||||
|
||||
"@octokit/plugin-paginate-rest@^11.4.2":
|
||||
version "11.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz#e5e9ff3530e867c3837fdbff94ce15a2468a1f37"
|
||||
integrity sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==
|
||||
dependencies:
|
||||
"@octokit/types" "^13.10.0"
|
||||
|
||||
"@octokit/plugin-request-log@^5.3.1":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz#ccb75d9705de769b2aa82bcd105cc96eb0c00f69"
|
||||
integrity sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods@^13.3.0":
|
||||
version "13.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.5.0.tgz#d8c8ca2123b305596c959a9134dfa8b0495b0ba6"
|
||||
integrity sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==
|
||||
dependencies:
|
||||
"@octokit/types" "^13.10.0"
|
||||
|
||||
"@octokit/request-error@^6.1.7":
|
||||
version "6.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-6.1.7.tgz#44fc598f5cdf4593e0e58b5155fe2e77230ff6da"
|
||||
integrity sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==
|
||||
dependencies:
|
||||
"@octokit/types" "^13.6.2"
|
||||
|
||||
"@octokit/request@^9.2.1", "@octokit/request@^9.2.2":
|
||||
version "9.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-9.2.2.tgz#754452ec4692d7fdc32438a14e028eba0e6b2c09"
|
||||
integrity sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==
|
||||
dependencies:
|
||||
"@octokit/endpoint" "^10.1.3"
|
||||
"@octokit/request-error" "^6.1.7"
|
||||
"@octokit/types" "^13.6.2"
|
||||
fast-content-type-parse "^2.0.0"
|
||||
universal-user-agent "^7.0.2"
|
||||
|
||||
"@octokit/rest@^21.1.1":
|
||||
version "21.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-21.1.1.tgz#7a70455ca451b1d253e5b706f35178ceefb74de2"
|
||||
integrity sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==
|
||||
dependencies:
|
||||
"@octokit/core" "^6.1.4"
|
||||
"@octokit/plugin-paginate-rest" "^11.4.2"
|
||||
"@octokit/plugin-request-log" "^5.3.1"
|
||||
"@octokit/plugin-rest-endpoint-methods" "^13.3.0"
|
||||
|
||||
"@octokit/types@^13.10.0", "@octokit/types@^13.6.2", "@octokit/types@^13.8.0":
|
||||
version "13.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.10.0.tgz#3e7c6b19c0236c270656e4ea666148c2b51fd1a3"
|
||||
integrity sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==
|
||||
dependencies:
|
||||
"@octokit/openapi-types" "^24.2.0"
|
||||
|
||||
before-after-hook@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-3.0.2.tgz#d5665a5fa8b62294a5aa0a499f933f4a1016195d"
|
||||
integrity sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==
|
||||
|
||||
fast-content-type-parse@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz#c236124534ee2cb427c8d8e5ba35a4856947847b"
|
||||
integrity sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==
|
||||
|
||||
universal-user-agent@^7.0.0, universal-user-agent@^7.0.2:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-7.0.2.tgz#52e7d0e9b3dc4df06cc33cb2b9fd79041a54827e"
|
||||
integrity sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==
|
||||
121
.github/workflows/shared_ghstack_land.yml
vendored
Normal file
121
.github/workflows/shared_ghstack_land.yml
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
name: (Shared) ghstack land
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
|
||||
|
||||
jobs:
|
||||
check_access:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is_member_or_collaborator: ${{ steps.check_access.outputs.result }}
|
||||
steps:
|
||||
- name: Check access
|
||||
id: check_access
|
||||
if: ${{ github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR' }}
|
||||
run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
check_maintainer:
|
||||
if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' }}
|
||||
needs: [check_access]
|
||||
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
|
||||
permissions:
|
||||
# Used by check_maintainer
|
||||
contents: read
|
||||
with:
|
||||
actor: ${{ github.event.comment.user.login }}
|
||||
|
||||
ghstack_land:
|
||||
if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/land') }}
|
||||
needs: [check_maintainer]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add reaction to comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const comment_id = "${{ github.event.comment.id }}"
|
||||
|
||||
await github.rest.reactions.createForCommitComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id,
|
||||
content: "rocket",
|
||||
});
|
||||
- name: Get PR details
|
||||
id: get-pr
|
||||
run: |
|
||||
PR_NUMBER=${{ github.event.issue.number }}
|
||||
echo "PR number is $PR_NUMBER"
|
||||
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
||||
|
||||
# Get PR details using GitHub API
|
||||
PR_DATA=$(curl -s \
|
||||
-H "Authorization: token ${{ github.token }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"${{ github.api_url }}/repos/${{ github.repository }}/pulls/$PR_NUMBER")
|
||||
|
||||
# Extract useful information
|
||||
PR_HEAD_REF=$(echo "$PR_DATA" | jq -r .head.ref)
|
||||
PR_HEAD_SHA=$(echo "$PR_DATA" | jq -r .head.sha)
|
||||
PR_URL="${{ github.server_url }}/${{ github.repository }}/pull/$PR_NUMBER"
|
||||
|
||||
echo "pr_branch=$PR_HEAD_REF" >> $GITHUB_OUTPUT
|
||||
echo "pr_sha=$PR_HEAD_SHA" >> $GITHUB_OUTPUT
|
||||
echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
|
||||
echo "pr_branch=$PR_HEAD_REF"
|
||||
echo "pr_sha=$PR_HEAD_SHA"
|
||||
echo "pr_url=$PR_URL"
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ghstack-pip-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install ghstack
|
||||
run: pip install requests ghstack
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: ghstack-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('.github/workflows/scripts/ghstack/yarn.lock') }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: .github/workflows/scripts/ghstack/yarn.lock
|
||||
- run: yarn install --cwd .github/workflows/scripts/ghstack --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Check Current CI Status
|
||||
run: |
|
||||
echo ${{ github.event.issue.number }}
|
||||
yarn --cwd .github/workflows/scripts/ghstack check-permissions ${{ github.event.issue.number }} ${{steps.get-pr.outputs.pr_branch}}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
- name: Land It!
|
||||
run: |
|
||||
git config --global user.email "facebook-github-bot@users.noreply.github.com"
|
||||
git config --global user.name "Facebook Community Bot"
|
||||
cat <<EOF > ~/.ghstackrc
|
||||
[ghstack]
|
||||
github_url = github.com
|
||||
github_oauth = $GITHUB_TOKEN
|
||||
github_username = facebook-github-bot
|
||||
remote_name = origin
|
||||
EOF
|
||||
ghstack land "${{ steps.get-pr.outputs.pr_url }}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
Reference in New Issue
Block a user