Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/backport.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Backport PR to branch
on:
issue_comment:
types: [created]

permissions:
contents: write
issues: write
pull-requests: write

jobs:
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: ./tools/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%

/cc %cc_users%

## Customer Impact

## Testing

- [ ] For any change that affects the release process, please work with a maintainer to come up with a plan to test this.

## Risk
2 changes: 1 addition & 1 deletion Analyzers.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
<ItemGroup>
<PackageReference Include="DotNetAnalyzers.DocumentationAnalyzers" Version="1.0.0-beta.59" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="7.0.0-preview1.22202.1" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="all" />
</ItemGroup>
</Project>
4 changes: 2 additions & 2 deletions src/Modules/PSGalleryModules.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

<ItemGroup>
<PackageReference Include="PowerShellGet" Version="2.2.5" />
<PackageReference Include="PackageManagement" Version="1.4.7" />
<PackageReference Include="PackageManagement" Version="1.4.7.1" />
<PackageReference Include="Microsoft.PowerShell.Archive" Version="1.2.5" />
<PackageReference Include="PSReadLine" Version="2.2.2" />
<PackageReference Include="PSReadLine" Version="2.2.3" />
<PackageReference Include="ThreadJob" Version="2.0.3" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion test/perf/dotnet-tools/Reporting/Reporting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="5.0.0-preview.5.20278.1" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.0-preview1" />
<PackageReference Include="MarkdownLog.NS20" Version="0.10.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="Perfolizer" Version="0.2.1" />
</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion test/tools/TestAlc/nested/Test.Isolated.Nested.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<ItemGroup>
<PackageReference Include="PowerShellStandard.Library" Version="5.1.0" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

</Project>
20 changes: 20 additions & 0 deletions tools/actions/backport/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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'
157 changes: 157 additions & 0 deletions tools/actions/backport/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// from https://github.com/dotnet/runtime/blob/main/eng/actions/backport/index.js

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 Microsoft team membership visibility is set to Public on https://github.com/orgs/microsoft/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 "[email protected]"`);

// 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();
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="System.Security.AccessControl" Version="6.0.2-mauipre.1.22102.15" />
<PackageReference Include="Microsoft.PowerShell.MarkdownRender" Version="7.2.0" />
</ItemGroup>
Expand Down