diff --git a/.github/workflows/check-status.yml b/.github/workflows/check-status.yml
new file mode 100644
index 000000000..0e9b9db95
--- /dev/null
+++ b/.github/workflows/check-status.yml
@@ -0,0 +1,180 @@
+name: check-status
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
+
+on:
+ pull_request:
+ branches:
+ - develop
+ - master
+ - hotfix-*
+ - release-*
+
+jobs:
+ check-status:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Check workflow statuses and display token usage
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ echo "current rest api rate usage:"
+ curl -s -H "Accept: application/vnd.github+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/rate_limit | jq .rate
+ echo ""
+ echo ""
+ echo "current graphql rate usage:"
+ curl -s -H "Accept: application/vnd.github+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/rate_limit | jq .resources.graphql
+ echo ""
+ echo ""
+
+ - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PR_NUMBER: ${{ github.event.number }}
+ with:
+ script: |
+ await exec.exec("sleep 20s");
+
+ for (let i = 0; i < 120; i++) {
+ const failure = [];
+ const cancelled = [];
+ const pending = [];
+
+ const result = await github.rest.checks.listSuitesForRef({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ ref: "${{ github.head_ref }}"
+ });
+ result.data.check_suites.forEach(({ app: { slug }, conclusion, id}) => {
+ if (slug === 'github-actions') {
+ if (conclusion === 'failure' || conclusion === 'cancelled') {
+ failure.push(id);
+ } else if (conclusion === null) {
+ pending.push(id);
+ }
+ console.log(`check suite ${id} => ${conclusion === null ? 'pending' : conclusion}`);
+ }
+ });
+
+ if (pending.length === 0) {
+ core.setFailed("Cannot get pull request check status");
+ return;
+ }
+
+ if (failure.length > 0) {
+ let failureMessage = '';
+ const failedCheckRuns = [];
+ for await (const suite_id of failure) {
+ const resultCheckRuns = await github.rest.checks.listForSuite({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ check_suite_id: suite_id
+ });
+
+ resultCheckRuns.data.check_runs.forEach(({ conclusion, name, html_url }) => {
+ if (conclusion === 'failure' || conclusion === 'cancelled') {
+ failedCheckRuns.push(`${name} (${conclusion})`);
+ }
+ });
+ }
+
+ core.summary.addRaw(`${failedCheckRuns.length} job(s) failed:`, true)
+ core.summary.addList(failedCheckRuns);
+ core.summary.write()
+
+ if (failedCheckRuns.length > 0) {
+ core.setFailed(`${failedCheckRuns.length} job(s) failed`);
+ return;
+ }
+ }
+
+ if (pending.length === 1) {
+ core.info("All workflows are ok");
+ return;
+ }
+
+ core.info(`${pending.length} workflows in progress`);
+
+ await exec.exec("sleep 30s");
+ }
+
+ core.setFailed("Timeout: some jobs are still in progress");
+
+ get-environment:
+ if: |
+ contains(fromJSON('["pull_request", "pull_request_target"]') , github.event_name) &&
+ (startsWith(github.base_ref, 'release-') || startsWith(github.base_ref, 'hotfix-'))
+ uses: ./.github/workflows/get-environment.yml
+
+ check-cherry-pick:
+ needs: [get-environment, check-status]
+ runs-on: ubuntu-24.04
+ if: |
+ contains(fromJSON('["pull_request", "pull_request_target"]') , github.event_name) &&
+ needs.get-environment.outputs.target_stability == 'testing' &&
+ ! contains(needs.get-environment.outputs.labels, 'skip-cherry-pick')
+
+ steps:
+ - name: Check if the PR is a cherry-pick from dev branch
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
+ env:
+ LINKED_DEV_BRANCH: develop
+ with:
+ script: |
+ let linkedPrs = [];
+ let errorMessage = `This pull request is not a cherry-pick from ${process.env.LINKED_DEV_BRANCH} or has no reference to a pull request which has been merged on ${process.env.LINKED_DEV_BRANCH}\n`;
+
+ try {
+ const pull = await github.rest.pulls.get({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.issue.number
+ });
+
+ const { title, body } = pull.data;
+
+ [title, body].forEach((text) => {
+ const linkedPrMatches = text.matchAll(/(?:#|\/pull\/)(\d+)/g);
+ if (linkedPrMatches) {
+ [...linkedPrMatches].forEach((match) => {
+ linkedPrs.push(Number(match[1]));
+ });
+ }
+ });
+
+ // remove duplicates
+ linkedPrs = [...new Set(linkedPrs)];
+ console.log(`Linked pull requests found in PR title and body: ${linkedPrs.join(', ')}`);
+ } catch (e) {
+ throw new Error(`Failed to get information of pull request #${context.issue.number}: ${e}`);
+ }
+
+ for await (const prNumber of linkedPrs) {
+ try {
+ const pull = await github.rest.pulls.get({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: prNumber
+ });
+
+ if (pull.data.base.ref === process.env.LINKED_DEV_BRANCH) {
+ if (pull.data.state === 'closed' && pull.data.merged === true) {
+ console.log(`This pull request is a cherry-pick from pull request #${prNumber} on ${process.env.LINKED_DEV_BRANCH}`);
+ return;
+ } else {
+ errorMessage += `This pull request seems to be a cherry-pick from pull request #${prNumber} on ${process.env.LINKED_DEV_BRANCH} but it is not merged yet\n`;
+ }
+ } else {
+ errorMessage += `Pull request #${prNumber} is linked to ${pull.data.base.ref} instead of ${process.env.LINKED_DEV_BRANCH}\n`;
+ }
+ } catch (e) {
+ errorMessage += `Failed to get information on pull request #${prNumber}: ${e}\n`;
+ }
+ }
+
+ errorMessage += `\nIf you are sure this PR does not need to be a cherry-pick from ${process.env.LINKED_DEV_BRANCH} or must be merged urgently, `;
+ errorMessage += `open the pull request on ${process.env.LINKED_DEV_BRANCH} and add label "skip-cherry-pick" to the PR and re-run all jobs of workflow check-status\n`;
+
+ throw new Error(errorMessage);
diff --git a/.github/workflows/get-environment.yml b/.github/workflows/get-environment.yml
index 14c1eba61..e97a2d0a3 100644
--- a/.github/workflows/get-environment.yml
+++ b/.github/workflows/get-environment.yml
@@ -26,6 +26,9 @@ on:
skip_workflow:
description: "if the current workflow should be skipped"
value: ${{ jobs.get-environment.outputs.skip_workflow }}
+ labels:
+ description: "list of labels on the PR"
+ value: ${{ jobs.get-environment.outputs.labels }}
jobs:
get-environment:
@@ -38,6 +41,7 @@ jobs:
release_type: ${{ steps.get_release_type.outputs.release_type }}
is_targeting_feature_branch: ${{ steps.get_stability.outputs.is_targeting_feature_branch }}
skip_workflow: ${{ steps.skip_workflow.outputs.result }}
+ labels: ${{ steps.has_skip_label.outputs.labels }}
steps:
- name: Check if PR has skip label
@@ -46,14 +50,17 @@ jobs:
with:
script: |
let hasSkipLabel = false;
+ let labels = [];
+
if (${{ contains(fromJSON('["pull_request", "pull_request_target"]') , github.event_name) }} === true) {
try {
- const labels = await github.rest.issues.listLabelsOnIssue({
+ const fetchedLabels = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
- labels.data.forEach(({ name }) => {
+ fetchedLabels.data.forEach(({ name }) => {
+ labels.push(name);
if (name === '${{ format('skip-workflow-{0}', github.workflow) }}') {
hasSkipLabel = true;
}
@@ -62,6 +69,9 @@ jobs:
core.warning(`failed to list labels: ${e}`);
}
}
+
+ core.setOutput('labels', labels);
+
return hasSkipLabel;
- name: Checkout sources (current branch)
@@ -276,7 +286,8 @@ jobs:
['release_type', '${{ steps.get_release_type.outputs.release_type || 'not defined because this is not a release' }}'],
['is_targeting_feature_branch', '${{ steps.get_stability.outputs.is_targeting_feature_branch }}'],
['target_stability', '${{ steps.get_stability.outputs.target_stability || 'not defined because current run is not triggered by pull request event' }}'],
- ['skip_workflow', '${{ steps.skip_workflow.outputs.result }}']
+ ['skip_workflow', '${{ steps.skip_workflow.outputs.result }}'],
+ ['labels', '${{ steps.has_skip_label.outputs.labels }}'],
];
core.summary
.addHeading(`${context.workflow} environment outputs`)