Use backport workflow from arcade repo (#77844)
authorAlexander Köplinger <alex.koeplinger@outlook.com>
Fri, 4 Nov 2022 09:11:54 +0000 (10:11 +0100)
committerGitHub <noreply@github.com>
Fri, 4 Nov 2022 09:11:54 +0000 (10:11 +0100)
* Use backport workflow from arcade repo

The arcade repo now includes the backport workflow as a reusable workflow so we can reference it from there and cleanup code duplication.

* Preserve custom PR description template

.github/workflows/backport.yml
eng/actions/backport/action.yml [deleted file]
eng/actions/backport/index.js [deleted file]

index 5f6fe98..ac4a8bd 100644 (file)
@@ -3,81 +3,28 @@ on:
   issue_comment:
     types: [created]
   schedule:
-    # once a day at 13:00 UTC
+    # once a day at 13:00 UTC to cleanup old runs
     - cron: '0 13 * * *'
 
 permissions:
   contents: write
   issues: write
   pull-requests: write
+  actions: write
 
 jobs:
-  cleanup_old_runs:
-    if: github.event.schedule == '0 13 * * *'
-    runs-on: ubuntu-20.04
-    permissions:
-      actions: write
-    env:
-      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-    steps:
-    - name: Delete old workflow runs
-      run: |
-        _UrlPath="/repos/$GITHUB_REPOSITORY/actions/workflows"
-        _CurrentWorkflowID="$(gh api -X GET "$_UrlPath" | jq '.workflows[] | select(.name == '\""$GITHUB_WORKFLOW"\"') | .id')"
-
-        # delete workitems which are 'completed'. (other candidate values of status field are: 'queued' and 'in_progress')
-
-        gh api -X GET "$_UrlPath/$_CurrentWorkflowID/runs" --paginate \
-          | jq '.workflow_runs[] | select(.status == "completed") | .id' \
-          | xargs -I{} gh api -X DELETE "/repos/$GITHUB_REPOSITORY/actions/runs"/{}
-
   backport:
-    if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/backport to')
-    runs-on: ubuntu-20.04
-    steps:
-    - name: Extract backport target branch
-      uses: actions/github-script@v3
-      id: target-branch-extractor
-      with:
-        result-encoding: string
-        script: |
-          if (context.eventName !== "issue_comment") throw "Error: This action only works on issue_comment events.";
-
-          // extract the target branch name from the trigger phrase containing these characters: a-z, A-Z, digits, forward slash, dot, hyphen, underscore
-          const regex = /^\/backport to ([a-zA-Z\d\/\.\-\_]+)/;
-          target_branch = regex.exec(context.payload.comment.body);
-          if (target_branch == null) throw "Error: No backport branch found in the trigger phrase.";
-
-          return target_branch[1];
-    - name: Post backport started comment to pull request
-      uses: actions/github-script@v3
-      with:
-        script: |
-          const backport_start_body = `Started backporting to ${{ steps.target-branch-extractor.outputs.result }}: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`;
-          await github.issues.createComment({
-            issue_number: context.issue.number,
-            owner: context.repo.owner,
-            repo: context.repo.repo,
-            body: backport_start_body
-          });
-    - name: Checkout repo
-      uses: actions/checkout@v2
-      with:
-        fetch-depth: 0
-    - name: Run backport
-      uses: ./eng/actions/backport
-      with:
-        target_branch: ${{ steps.target-branch-extractor.outputs.result }}
-        auth_token: ${{ secrets.GITHUB_TOKEN }}
-        pr_description_template: |
-          Backport of #%source_pr_number% to %target_branch%
+    uses: dotnet/arcade/.github/workflows/backport-base.yml@main
+    with:
+      pr_description_template: |
+        Backport of #%source_pr_number% to %target_branch%
 
-          /cc %cc_users%
+        /cc %cc_users%
 
-          ## Customer Impact
+        ## Customer Impact
 
-          ## Testing
+        ## Testing
 
-          ## Risk
+        ## Risk
 
-          IMPORTANT: Is this backport for a servicing release? If so and this change touches code that ships in a NuGet package, please make certain that you have added any necessary [package authoring](https://github.com/dotnet/runtime/blob/main/docs/project/library-servicing.md) and gotten it explicitly reviewed.
+        IMPORTANT: Is this backport for a servicing release? If so and this change touches code that ships in a NuGet package, please make certain that you have added any necessary [package authoring](https://github.com/dotnet/runtime/blob/main/docs/project/library-servicing.md) and gotten it explicitly reviewed.
diff --git a/eng/actions/backport/action.yml b/eng/actions/backport/action.yml
deleted file mode 100644 (file)
index e596f1d..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-name: 'PR Backporter'
-description: 'Backports a pull request to a branch using the "/backport to <branch>" comment'
-inputs:
-  target_branch:
-    description: 'Backport target branch.'
-  auth_token:
-    description: 'The token used to authenticate to GitHub.'
-  pr_title_template:
-      description: 'The template used for the PR title. Special placeholder tokens that will be replaced with a value: %target_branch%, %source_pr_title%, %source_pr_number%, %cc_users%.'
-      default: '[%target_branch%] %source_pr_title%'
-  pr_description_template:
-    description: 'The template used for the PR description. Special placeholder tokens that will be replaced with a value: %target_branch%, %source_pr_title%, %source_pr_number%, %cc_users%.'
-    default: |
-      Backport of #%source_pr_number% to %target_branch%
-
-      /cc %cc_users%
-
-runs:
-  using: 'node12'
-  main: 'index.js'
diff --git a/eng/actions/backport/index.js b/eng/actions/backport/index.js
deleted file mode 100644 (file)
index 1b4f227..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-function BackportException(message, postToGitHub = true) {
-  this.message = message;
-  this.postToGitHub = postToGitHub;
-}
-
-async function run() {
-  const util = require("util");
-  const jsExec = util.promisify(require("child_process").exec);
-
-  console.log("Installing npm dependencies");
-  const { stdout, stderr } = await jsExec("npm install @actions/core @actions/github @actions/exec");
-  console.log("npm-install stderr:\n\n" + stderr);
-  console.log("npm-install stdout:\n\n" + stdout);
-  console.log("Finished installing npm dependencies");
-
-  const core = require("@actions/core");
-  const github = require("@actions/github");
-  const exec = require("@actions/exec");
-
-  const repo_owner = github.context.payload.repository.owner.login;
-  const repo_name = github.context.payload.repository.name;
-  const pr_number = github.context.payload.issue.number;
-  const comment_user = github.context.payload.comment.user.login;
-
-  let octokit = github.getOctokit(core.getInput("auth_token", { required: true }));
-  let target_branch = core.getInput("target_branch", { required: true });
-
-  try {
-    // verify the comment user is a repo collaborator
-    try {
-      await octokit.rest.repos.checkCollaborator({
-        owner: repo_owner,
-        repo: repo_name,
-        username: comment_user
-      });
-      console.log(`Verified ${comment_user} is a repo collaborator.`);
-    } catch (error) {
-      console.log(error);
-      throw new BackportException(`Error: @${comment_user} is not a repo collaborator, backporting is not allowed. If you're a collaborator please make sure your ${repo_owner} team membership visibility is set to Public on https://github.com/orgs/${repo_owner}/people?query=${comment_user}`);
-    }
-
-    try { await exec.exec(`git ls-remote --exit-code --heads origin ${target_branch}`) } catch { throw new BackportException(`Error: The specified backport target branch ${target_branch} wasn't found in the repo.`); }
-    console.log(`Backport target branch: ${target_branch}`);
-
-    console.log("Applying backport patch");
-
-    await exec.exec(`git checkout ${target_branch}`);
-    await exec.exec(`git clean -xdff`);
-
-    // configure git
-    await exec.exec(`git config user.name "github-actions"`);
-    await exec.exec(`git config user.email "github-actions@github.com"`);
-
-    // create temporary backport branch
-    const temp_branch = `backport/pr-${pr_number}-to-${target_branch}`;
-    await exec.exec(`git checkout -b ${temp_branch}`);
-
-    // skip opening PR if the branch already exists on the origin remote since that means it was opened
-    // by an earlier backport and force pushing to the branch updates the existing PR
-    let should_open_pull_request = true;
-    try {
-      await exec.exec(`git ls-remote --exit-code --heads origin ${temp_branch}`);
-      should_open_pull_request = false;
-    } catch { }
-
-    // download and apply patch
-    await exec.exec(`curl -sSL "${github.context.payload.issue.pull_request.patch_url}" --output changes.patch`);
-
-    const git_am_command = "git am --3way --ignore-whitespace --keep-non-patch changes.patch";
-    let git_am_output = `$ ${git_am_command}\n\n`;
-    let git_am_failed = false;
-    try {
-      await exec.exec(git_am_command, [], {
-        listeners: {
-          stdout: function stdout(data) { git_am_output += data; },
-          stderr: function stderr(data) { git_am_output += data; }
-        }
-      });
-    } catch (error) {
-      git_am_output += error;
-      git_am_failed = true;
-    }
-
-    if (git_am_failed) {
-      const git_am_failed_body = `@${github.context.payload.comment.user.login} backporting to ${target_branch} failed, the patch most likely resulted in conflicts:\n\n\`\`\`shell\n${git_am_output}\n\`\`\`\n\nPlease backport manually!`;
-      await octokit.rest.issues.createComment({
-        owner: repo_owner,
-        repo: repo_name,
-        issue_number: pr_number,
-        body: git_am_failed_body
-      });
-      throw new BackportException("Error: git am failed, most likely due to a merge conflict.", false);
-    }
-    else {
-      // push the temp branch to the repository
-      await exec.exec(`git push --force --set-upstream origin HEAD:${temp_branch}`);
-    }
-
-    if (!should_open_pull_request) {
-      console.log("Backport temp branch already exists, skipping opening a PR.");
-      return;
-    }
-
-    // prepate the GitHub PR details
-    let backport_pr_title = core.getInput("pr_title_template");
-    let backport_pr_description = core.getInput("pr_description_template");
-
-    // get users to cc (append PR author if different from user who issued the backport command)
-    let cc_users = `@${comment_user}`;
-    if (comment_user != github.context.payload.issue.user.login) cc_users += ` @${github.context.payload.issue.user.login}`;
-
-    // replace the special placeholder tokens with values
-    backport_pr_title = backport_pr_title
-      .replace(/%target_branch%/g, target_branch)
-      .replace(/%source_pr_title%/g, github.context.payload.issue.title)
-      .replace(/%source_pr_number%/g, github.context.payload.issue.number)
-      .replace(/%cc_users%/g, cc_users);
-
-    backport_pr_description = backport_pr_description
-      .replace(/%target_branch%/g, target_branch)
-      .replace(/%source_pr_title%/g, github.context.payload.issue.title)
-      .replace(/%source_pr_number%/g, github.context.payload.issue.number)
-      .replace(/%cc_users%/g, cc_users);
-
-    // open the GitHub PR
-    await octokit.rest.pulls.create({
-      owner: repo_owner,
-      repo: repo_name,
-      title: backport_pr_title,
-      body: backport_pr_description,
-      head: temp_branch,
-      base: target_branch
-    });
-
-    console.log("Successfully opened the GitHub PR.");
-  } catch (error) {
-
-    core.setFailed(error);
-
-    if (error.postToGitHub === undefined || error.postToGitHub == true) {
-      // post failure to GitHub comment
-      const unknown_error_body = `@${comment_user} an error occurred while backporting to ${target_branch}, please check the run log for details!\n\n${error.message}`;
-      await octokit.rest.issues.createComment({
-        owner: repo_owner,
-        repo: repo_name,
-        issue_number: pr_number,
-        body: unknown_error_body
-      });
-    }
-  }
-}
-
-run();