зеркало из https://github.com/electron/trop.git
chore: lint with prettier (#155)
This commit is contained in:
Родитель
517da59a45
Коммит
8bdd43fd97
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf"
|
||||
}
|
|
@ -9,7 +9,8 @@
|
|||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "probot run ./lib/index.js",
|
||||
"lint": "tslint --project '.'",
|
||||
"prettier:write": "prettier --write '**/*.ts'",
|
||||
"lint": "prettier --check '**/*.ts'",
|
||||
"test": "jest --testPathIgnorePatterns=/working/ --testPathIgnorePatterns=/node_modules/",
|
||||
"postinstall": "tsc"
|
||||
},
|
||||
|
@ -34,6 +35,7 @@
|
|||
"@types/node-fetch": "^2.5.4",
|
||||
"@types/sinon": "^5.0.5",
|
||||
"jest": "^23.6.0",
|
||||
"prettier": "^2.0.5",
|
||||
"sinon": "^7.5.0",
|
||||
"smee-client": "^1.0.1",
|
||||
"ts-jest": "^23.10.4",
|
||||
|
|
|
@ -3,7 +3,9 @@ import * as commands from '../src/constants';
|
|||
describe('commands', () => {
|
||||
it('should all be unique', () => {
|
||||
const commandsRecord: Record<string, string> = commands as any;
|
||||
const allCommands = Object.keys(commandsRecord).map(key => commandsRecord[key]).sort();
|
||||
const allCommands = Object.keys(commandsRecord)
|
||||
.map((key) => commandsRecord[key])
|
||||
.sort();
|
||||
const uniqueCommands = Array.from(new Set(allCommands)).sort();
|
||||
expect(allCommands).toStrictEqual(uniqueCommands);
|
||||
});
|
||||
|
|
|
@ -2,7 +2,10 @@ jest.mock('request');
|
|||
import { Application } from 'probot';
|
||||
|
||||
import * as utils from '../src/utils';
|
||||
import { backportToBranch, backportToLabel } from '../src/operations/backport-to-location';
|
||||
import {
|
||||
backportToBranch,
|
||||
backportToLabel,
|
||||
} from '../src/operations/backport-to-location';
|
||||
import { updateManualBackport } from '../src/operations/update-manual-backport';
|
||||
import { ProbotHandler } from '../src/index';
|
||||
|
||||
|
@ -37,64 +40,76 @@ describe('trop', () => {
|
|||
|
||||
github = {
|
||||
repos: {
|
||||
getContents: jest.fn().mockReturnValue(Promise.resolve({
|
||||
data: { content: Buffer.from('watchedProject:\n name: Radar\nauthorizedUsers:\n - codebytere').toString('base64') },
|
||||
})),
|
||||
getContents: jest.fn().mockReturnValue(
|
||||
Promise.resolve({
|
||||
data: {
|
||||
content: Buffer.from(
|
||||
'watchedProject:\n name: Radar\nauthorizedUsers:\n - codebytere',
|
||||
).toString('base64'),
|
||||
},
|
||||
}),
|
||||
),
|
||||
getBranch: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
listBranches: jest.fn().mockReturnValue(Promise.resolve({
|
||||
data: [
|
||||
{ name: '8-x-y' },
|
||||
{ name: '7-1-x' },
|
||||
],
|
||||
})),
|
||||
listBranches: jest.fn().mockReturnValue(
|
||||
Promise.resolve({
|
||||
data: [{ name: '8-x-y' }, { name: '7-1-x' }],
|
||||
}),
|
||||
),
|
||||
},
|
||||
git: {
|
||||
deleteRef: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
},
|
||||
pulls: {
|
||||
get: jest.fn().mockReturnValue(Promise.resolve({
|
||||
data: {
|
||||
merged: true,
|
||||
base: {
|
||||
repo: {
|
||||
name: 'test',
|
||||
owner: {
|
||||
login: 'codebytere',
|
||||
get: jest.fn().mockReturnValue(
|
||||
Promise.resolve({
|
||||
data: {
|
||||
merged: true,
|
||||
base: {
|
||||
repo: {
|
||||
name: 'test',
|
||||
owner: {
|
||||
login: 'codebytere',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
head: {
|
||||
sha: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
|
||||
},
|
||||
labels: [
|
||||
{
|
||||
url: 'my_cool_url',
|
||||
name: 'target/X-X-X',
|
||||
color: 'fc2929',
|
||||
head: {
|
||||
sha: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
|
||||
},
|
||||
],
|
||||
},
|
||||
})),
|
||||
labels: [
|
||||
{
|
||||
url: 'my_cool_url',
|
||||
name: 'target/X-X-X',
|
||||
color: 'fc2929',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
),
|
||||
},
|
||||
issues: {
|
||||
addLabels: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
removeLabel: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
createLabel: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
createComment: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
listLabelsOnIssue: jest.fn().mockReturnValue(Promise.resolve({
|
||||
data: [
|
||||
{
|
||||
id: 208045946,
|
||||
url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug',
|
||||
name: 'bug',
|
||||
description: 'Something isn\'t working',
|
||||
color: 'f29513',
|
||||
},
|
||||
],
|
||||
})),
|
||||
listLabelsOnIssue: jest.fn().mockReturnValue(
|
||||
Promise.resolve({
|
||||
data: [
|
||||
{
|
||||
id: 208045946,
|
||||
url:
|
||||
'https://api.github.com/repos/octocat/Hello-World/labels/bug',
|
||||
name: 'bug',
|
||||
description: "Something isn't working",
|
||||
color: 'f29513',
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
},
|
||||
checks: {
|
||||
listForRef: jest.fn().mockReturnValue(Promise.resolve({ data: { check_runs: [] } })),
|
||||
listForRef: jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve({ data: { check_runs: [] } })),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -119,7 +134,9 @@ describe('trop', () => {
|
|||
});
|
||||
|
||||
it('does not trigger the backport on comment if the PR is not merged', async () => {
|
||||
github.pulls.get = jest.fn().mockReturnValue(Promise.resolve({ data: { merged: false } }));
|
||||
github.pulls.get = jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve({ data: { merged: false } }));
|
||||
|
||||
await robot.receive(issueCommentBackportCreatedEvent);
|
||||
|
||||
|
@ -145,7 +162,9 @@ describe('trop', () => {
|
|||
});
|
||||
|
||||
it('does not trigger the backport on comment to a targeted branch if the branch does not exist', async () => {
|
||||
github.repos.getBranch = jest.fn().mockReturnValue(Promise.reject(new Error('404')));
|
||||
github.repos.getBranch = jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.reject(new Error('404')));
|
||||
await robot.receive(issueCommentBackportToCreatedEvent);
|
||||
|
||||
expect(github.pulls.get).toHaveBeenCalled();
|
||||
|
|
|
@ -25,19 +25,23 @@ describe('runner', () => {
|
|||
|
||||
describe('initRepo()', () => {
|
||||
it('should clone a github repository', async () => {
|
||||
const dir = saveDir(await initRepo({
|
||||
slug: 'electron/trop',
|
||||
accessToken: '',
|
||||
}));
|
||||
const dir = saveDir(
|
||||
await initRepo({
|
||||
slug: 'electron/trop',
|
||||
accessToken: '',
|
||||
}),
|
||||
);
|
||||
expect(await fs.pathExists(dir)).toBe(true);
|
||||
expect(await fs.pathExists(path.resolve(dir, '.git'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should fail if the github repository does not exist', async () => {
|
||||
await expect(initRepo({
|
||||
slug: 'electron/this-is-not-trop',
|
||||
accessToken: '',
|
||||
})).rejects.toBeTruthy();
|
||||
await expect(
|
||||
initRepo({
|
||||
slug: 'electron/this-is-not-trop',
|
||||
accessToken: '',
|
||||
}),
|
||||
).rejects.toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -59,29 +63,40 @@ describe('runner', () => {
|
|||
it('should set new remotes correctly', async () => {
|
||||
await setupRemotes({
|
||||
dir,
|
||||
remotes: [{
|
||||
name: 'origin',
|
||||
value: 'https://github.com/electron/clerk.git',
|
||||
}, {
|
||||
name: 'secondary',
|
||||
value: 'https://github.com/electron/trop.git',
|
||||
}],
|
||||
remotes: [
|
||||
{
|
||||
name: 'origin',
|
||||
value: 'https://github.com/electron/clerk.git',
|
||||
},
|
||||
{
|
||||
name: 'secondary',
|
||||
value: 'https://github.com/electron/trop.git',
|
||||
},
|
||||
],
|
||||
});
|
||||
const git = simpleGit(dir);
|
||||
const remotes = await git.raw(['remote', '-v']);
|
||||
const parsedRemotes = remotes.trim()
|
||||
.replace(/ +/g, ' ').replace(/\t/g, ' ')
|
||||
.replace(/ \(fetch\)/g, '').replace(/ \(push\)/g, '')
|
||||
.split(/\r?\n/g).map(line => line.trim().split(' '));
|
||||
const parsedRemotes = remotes
|
||||
.trim()
|
||||
.replace(/ +/g, ' ')
|
||||
.replace(/\t/g, ' ')
|
||||
.replace(/ \(fetch\)/g, '')
|
||||
.replace(/ \(push\)/g, '')
|
||||
.split(/\r?\n/g)
|
||||
.map((line) => line.trim().split(' '));
|
||||
|
||||
expect(parsedRemotes.length).toBe(4);
|
||||
for (const remote of parsedRemotes) {
|
||||
expect(remote.length).toBe(2);
|
||||
expect(['origin', 'secondary']).toContain(remote[0]);
|
||||
if (remote[0] === 'origin') {
|
||||
expect(remote[1].endsWith('github.com/electron/clerk.git')).toBeTruthy();
|
||||
expect(
|
||||
remote[1].endsWith('github.com/electron/clerk.git'),
|
||||
).toBeTruthy();
|
||||
} else {
|
||||
expect(remote[1].endsWith('github.com/electron/trop.git')).toBeTruthy();
|
||||
expect(
|
||||
remote[1].endsWith('github.com/electron/trop.git'),
|
||||
).toBeTruthy();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -11,7 +11,11 @@ const waitForEvent = (emitter: EventEmitter, event: string) => {
|
|||
});
|
||||
};
|
||||
|
||||
const delayedEvent = async (emitter: EventEmitter, event: string, fn: () => Promise<void>) => {
|
||||
const delayedEvent = async (
|
||||
emitter: EventEmitter,
|
||||
event: string,
|
||||
fn: () => Promise<void>,
|
||||
) => {
|
||||
const waiter = waitForEvent(emitter, event);
|
||||
await fn();
|
||||
await waiter;
|
||||
|
@ -22,7 +26,12 @@ const fakeTask = (name: string) => {
|
|||
name,
|
||||
taskRunner: sinon.stub().returns(Promise.resolve()),
|
||||
errorHandler: sinon.stub().returns(Promise.resolve()),
|
||||
args: () => [name, namedArgs.taskRunner, namedArgs.errorHandler] as [string, () => Promise<void>, () => Promise<void>],
|
||||
args: () =>
|
||||
[name, namedArgs.taskRunner, namedArgs.errorHandler] as [
|
||||
string,
|
||||
() => Promise<void>,
|
||||
() => Promise<void>,
|
||||
],
|
||||
};
|
||||
return namedArgs;
|
||||
};
|
||||
|
@ -74,7 +83,7 @@ describe('ExecutionQueue', () => {
|
|||
expect(task2.taskRunner.callCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should run the next task if the current task fails and it\'s error handler fails', async () => {
|
||||
it("should run the next task if the current task fails and it's error handler fails", async () => {
|
||||
const q = new ExecutionQueue();
|
||||
|
||||
const task = fakeTask('test');
|
||||
|
|
40
src/Queue.ts
40
src/Queue.ts
|
@ -16,7 +16,11 @@ export class ExecutionQueue extends EventEmitter {
|
|||
super();
|
||||
}
|
||||
|
||||
public enterQueue = (identifier: string, fn: Executor, errorFn: ErrorExecutor) => {
|
||||
public enterQueue = (
|
||||
identifier: string,
|
||||
fn: Executor,
|
||||
errorFn: ErrorExecutor,
|
||||
) => {
|
||||
if (this.activeIdents.has(identifier)) return;
|
||||
|
||||
this.activeIdents.add(identifier);
|
||||
|
@ -26,24 +30,30 @@ export class ExecutionQueue extends EventEmitter {
|
|||
} else {
|
||||
this.run([identifier, fn, errorFn]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private run = (fns: [string, Executor, ErrorExecutor]) => {
|
||||
this.active += 1;
|
||||
fns[1]().then(() => this.runNext(fns[0])).catch((err: any) => {
|
||||
if (!process.env.SPEC_RUNNING) {
|
||||
console.error(err);
|
||||
}
|
||||
fns[2](err)
|
||||
.catch((e) => {
|
||||
if (!process.env.SPEC_RUNNING) console.error(e);
|
||||
})
|
||||
.then(() => this.runNext(fns[0]));
|
||||
});
|
||||
}
|
||||
fns[1]()
|
||||
.then(() => this.runNext(fns[0]))
|
||||
.catch((err: any) => {
|
||||
if (!process.env.SPEC_RUNNING) {
|
||||
console.error(err);
|
||||
}
|
||||
fns[2](err)
|
||||
.catch((e) => {
|
||||
if (!process.env.SPEC_RUNNING) console.error(e);
|
||||
})
|
||||
.then(() => this.runNext(fns[0]));
|
||||
});
|
||||
};
|
||||
|
||||
private runNext = (lastIdent: string) => {
|
||||
log('runNext', LogLevel.INFO, `Running queue item with identifier ${lastIdent}`);
|
||||
log(
|
||||
'runNext',
|
||||
LogLevel.INFO,
|
||||
`Running queue item with identifier ${lastIdent}`,
|
||||
);
|
||||
|
||||
this.activeIdents.delete(lastIdent);
|
||||
this.active -= 1;
|
||||
|
@ -52,7 +62,7 @@ export class ExecutionQueue extends EventEmitter {
|
|||
} else {
|
||||
this.emit('empty');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default new ExecutionQueue();
|
||||
|
|
|
@ -2,4 +2,5 @@ export const CHECK_PREFIX = 'Backportable? - ';
|
|||
|
||||
export const NUM_SUPPORTED_VERSIONS = 4;
|
||||
|
||||
export const SKIP_CHECK_LABEL = process.env.SKIP_CHECK_LABEL || 'backport-check-skip';
|
||||
export const SKIP_CHECK_LABEL =
|
||||
process.env.SKIP_CHECK_LABEL || 'backport-check-skip';
|
||||
|
|
403
src/index.ts
403
src/index.ts
|
@ -6,8 +6,14 @@ import { TropConfig } from './interfaces';
|
|||
import { CHECK_PREFIX, SKIP_CHECK_LABEL } from './constants';
|
||||
import { getEnvVar } from './utils/env-util';
|
||||
import { PRChange, PRStatus, BackportPurpose, CheckRunStatus } from './enums';
|
||||
import { ChecksListForRefResponseCheckRunsItem, PullsGetResponse } from '@octokit/rest';
|
||||
import { backportToLabel, backportToBranch } from './operations/backport-to-location';
|
||||
import {
|
||||
ChecksListForRefResponseCheckRunsItem,
|
||||
PullsGetResponse,
|
||||
} from '@octokit/rest';
|
||||
import {
|
||||
backportToLabel,
|
||||
backportToBranch,
|
||||
} from './operations/backport-to-location';
|
||||
import { updateManualBackport } from './operations/update-manual-backport';
|
||||
import { getSupportedBranches, getBackportPattern } from './utils/branch-util';
|
||||
import { updateBackportValidityCheck } from './utils/checks-util';
|
||||
|
@ -15,7 +21,9 @@ import { updateBackportValidityCheck } from './utils/checks-util';
|
|||
const probotHandler = async (robot: Application) => {
|
||||
const labelMergedPRs = async (context: Context, pr: PullsGetResponse) => {
|
||||
for (const label of pr.labels) {
|
||||
const targetBranch = label.name.match(/^(\d)+-(?:(?:[0-9]+-x$)|(?:x+-y$))$/);
|
||||
const targetBranch = label.name.match(
|
||||
/^(\d)+-(?:(?:[0-9]+-x$)|(?:x+-y$))$/,
|
||||
);
|
||||
if (targetBranch && targetBranch[0]) {
|
||||
await labelMergedPR(context, pr, label.name);
|
||||
}
|
||||
|
@ -30,49 +38,57 @@ const probotHandler = async (robot: Application) => {
|
|||
};
|
||||
|
||||
const runCheck = async (context: Context, pr: PullsGetResponse) => {
|
||||
const allChecks = await context.github.checks.listForRef(context.repo({
|
||||
ref: pr.head.sha,
|
||||
per_page: 100,
|
||||
}));
|
||||
const checkRuns = allChecks.data.check_runs.filter(run => run.name.startsWith(CHECK_PREFIX));
|
||||
const allChecks = await context.github.checks.listForRef(
|
||||
context.repo({
|
||||
ref: pr.head.sha,
|
||||
per_page: 100,
|
||||
}),
|
||||
);
|
||||
const checkRuns = allChecks.data.check_runs.filter((run) =>
|
||||
run.name.startsWith(CHECK_PREFIX),
|
||||
);
|
||||
|
||||
for (const label of pr.labels) {
|
||||
if (!label.name.startsWith(PRStatus.TARGET)) continue;
|
||||
const targetBranch = labelToTargetBranch(label, PRStatus.TARGET);
|
||||
const runName = `${CHECK_PREFIX}${targetBranch}`;
|
||||
const existing = checkRuns.find(run => run.name === runName);
|
||||
const existing = checkRuns.find((run) => run.name === runName);
|
||||
if (existing) {
|
||||
if (existing.conclusion !== 'neutral') continue;
|
||||
|
||||
await context.github.checks.update(context.repo({
|
||||
name: existing.name,
|
||||
check_run_id: existing.id,
|
||||
status: 'queued' as 'queued',
|
||||
}));
|
||||
await context.github.checks.update(
|
||||
context.repo({
|
||||
name: existing.name,
|
||||
check_run_id: existing.id,
|
||||
status: 'queued' as 'queued',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
await context.github.checks.create(context.repo({
|
||||
name: runName,
|
||||
head_sha: pr.head.sha,
|
||||
status: 'queued' as 'queued',
|
||||
details_url: 'https://github.com/electron/trop',
|
||||
}));
|
||||
await context.github.checks.create(
|
||||
context.repo({
|
||||
name: runName,
|
||||
head_sha: pr.head.sha,
|
||||
status: 'queued' as 'queued',
|
||||
details_url: 'https://github.com/electron/trop',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await backportImpl(
|
||||
robot,
|
||||
context,
|
||||
targetBranch,
|
||||
BackportPurpose.Check,
|
||||
);
|
||||
await backportImpl(robot, context, targetBranch, BackportPurpose.Check);
|
||||
}
|
||||
|
||||
for (const checkRun of checkRuns) {
|
||||
if (!pr.labels.find(
|
||||
label => label.name === `${PRStatus.TARGET}${checkRun.name.replace(CHECK_PREFIX, '')}`,
|
||||
)) {
|
||||
if (
|
||||
!pr.labels.find(
|
||||
(label) =>
|
||||
label.name ===
|
||||
`${PRStatus.TARGET}${checkRun.name.replace(CHECK_PREFIX, '')}`,
|
||||
)
|
||||
) {
|
||||
await updateBackportValidityCheck(context, checkRun, {
|
||||
title: 'Cancelled',
|
||||
summary: 'This trop check was cancelled and can be ignored as this \
|
||||
summary:
|
||||
'This trop check was cancelled and can be ignored as this \
|
||||
PR is no longer targeting this branch for a backport',
|
||||
conclusion: CheckRunStatus.NEUTRAL,
|
||||
});
|
||||
|
@ -95,9 +111,11 @@ const probotHandler = async (robot: Application) => {
|
|||
const backportPattern = getBackportPattern();
|
||||
// Check if this PR is a manual backport of another PR.
|
||||
let match: RegExpExecArray | null;
|
||||
while (match = backportPattern.exec(pr.body)) {
|
||||
while ((match = backportPattern.exec(pr.body))) {
|
||||
// This might be the first or second capture group depending on if it's a link or not.
|
||||
backportNumbers.push(match[1] ? parseInt(match[1], 10) : parseInt(match[2], 10));
|
||||
backportNumbers.push(
|
||||
match[1] ? parseInt(match[1], 10) : parseInt(match[2], 10),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,39 +139,55 @@ const probotHandler = async (robot: Application) => {
|
|||
const pr = context.payload.pull_request;
|
||||
|
||||
// Only check for manual backports when a new PR is opened or if the PR body is edited.
|
||||
if (oldPRNumbers.length > 0 && ['opened', 'edited'].includes(context.payload.action)) {
|
||||
if (
|
||||
oldPRNumbers.length > 0 &&
|
||||
['opened', 'edited'].includes(context.payload.action)
|
||||
) {
|
||||
for (const oldPRNumber of oldPRNumbers) {
|
||||
robot.log(`Updating original backport at ${oldPRNumber} for ${pr.number}`);
|
||||
robot.log(
|
||||
`Updating original backport at ${oldPRNumber} for ${pr.number}`,
|
||||
);
|
||||
await updateManualBackport(context, PRChange.OPEN, oldPRNumber);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the PR is going to master, if it's not check if it's correctly
|
||||
// tagged as a backport of a PR that has already been merged into master.
|
||||
const { data: allChecks } = await context.github.checks.listForRef(context.repo({
|
||||
ref: pr.head.sha,
|
||||
per_page: 100,
|
||||
}));
|
||||
let checkRun = allChecks.check_runs.find(run => run.name === VALID_BACKPORT_CHECK_NAME);
|
||||
const { data: allChecks } = await context.github.checks.listForRef(
|
||||
context.repo({
|
||||
ref: pr.head.sha,
|
||||
per_page: 100,
|
||||
}),
|
||||
);
|
||||
let checkRun = allChecks.check_runs.find(
|
||||
(run) => run.name === VALID_BACKPORT_CHECK_NAME,
|
||||
);
|
||||
|
||||
if (pr.base.ref !== 'master') {
|
||||
if (!checkRun) {
|
||||
robot.log(`Queueing new check run for #${pr.number}`);
|
||||
checkRun = (await context.github.checks.create(context.repo({
|
||||
name: VALID_BACKPORT_CHECK_NAME,
|
||||
head_sha: pr.head.sha,
|
||||
status: 'queued' as 'queued',
|
||||
details_url: 'https://github.com/electron/trop',
|
||||
}))).data as any as ChecksListForRefResponseCheckRunsItem;
|
||||
checkRun = ((
|
||||
await context.github.checks.create(
|
||||
context.repo({
|
||||
name: VALID_BACKPORT_CHECK_NAME,
|
||||
head_sha: pr.head.sha,
|
||||
status: 'queued' as 'queued',
|
||||
details_url: 'https://github.com/electron/trop',
|
||||
}),
|
||||
)
|
||||
).data as any) as ChecksListForRefResponseCheckRunsItem;
|
||||
}
|
||||
|
||||
// If a branch is targeting something that isn't master it might not be a backport;
|
||||
// allow for a label to skip backport validity check for these branches.
|
||||
if (await labelExistsOnPR(context, pr.number, SKIP_CHECK_LABEL)) {
|
||||
robot.log(`#${pr.number} is labeled with ${SKIP_CHECK_LABEL} - skipping backport validation check`);
|
||||
robot.log(
|
||||
`#${pr.number} is labeled with ${SKIP_CHECK_LABEL} - skipping backport validation check`,
|
||||
);
|
||||
await updateBackportValidityCheck(context, checkRun, {
|
||||
title: 'Backport Check Skipped',
|
||||
summary: 'This PR is not a backport - skip backport validation check',
|
||||
summary:
|
||||
'This PR is not a backport - skip backport validation check',
|
||||
conclusion: CheckRunStatus.NEUTRAL,
|
||||
});
|
||||
return;
|
||||
|
@ -171,45 +205,63 @@ const probotHandler = async (robot: Application) => {
|
|||
// There are several types of PRs which might not target master yet which are
|
||||
// inherently valid; e.g roller-bot PRs. Check for and allow those here.
|
||||
if (oldPRNumbers.length === 0) {
|
||||
robot.log(`#${pr.number} does not have backport numbers - checking fast track status`);
|
||||
robot.log(
|
||||
`#${pr.number} does not have backport numbers - checking fast track status`,
|
||||
);
|
||||
if (
|
||||
!FASTTRACK_PREFIXES.some(pre => pr.title.startsWith(pre)) &&
|
||||
!FASTTRACK_USERS.some(user => pr.user.login === user) &&
|
||||
!FASTTRACK_LABELS.some(label => pr.labels.some((prLabel: any) => prLabel.name === label))
|
||||
!FASTTRACK_PREFIXES.some((pre) => pr.title.startsWith(pre)) &&
|
||||
!FASTTRACK_USERS.some((user) => pr.user.login === user) &&
|
||||
!FASTTRACK_LABELS.some((label) =>
|
||||
pr.labels.some((prLabel: any) => prLabel.name === label),
|
||||
)
|
||||
) {
|
||||
robot.log(`#${pr.number} is not a fast track PR - marking check run as failed`);
|
||||
robot.log(
|
||||
`#${pr.number} is not a fast track PR - marking check run as failed`,
|
||||
);
|
||||
await updateBackportValidityCheck(context, checkRun, {
|
||||
title: 'Invalid Backport',
|
||||
summary: 'This PR is targeting a branch that is not master but is missing a "Backport of #{N}" declaration. \
|
||||
summary:
|
||||
'This PR is targeting a branch that is not master but is missing a "Backport of #{N}" declaration. \
|
||||
Check out the trop documentation linked below for more information.',
|
||||
conclusion: CheckRunStatus.FAILURE,
|
||||
});
|
||||
} else {
|
||||
robot.log(`#${pr.number} is a fast track PR - marking check run as succeeded`);
|
||||
robot.log(
|
||||
`#${pr.number} is a fast track PR - marking check run as succeeded`,
|
||||
);
|
||||
await updateBackportValidityCheck(context, checkRun, {
|
||||
title: 'Valid Backport',
|
||||
summary: 'This PR is targeting a branch that is not master but a designated fast-track backport which does \
|
||||
summary:
|
||||
'This PR is targeting a branch that is not master but a designated fast-track backport which does \
|
||||
not require a manual backport number.',
|
||||
conclusion: CheckRunStatus.SUCCESS,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
robot.log(`#${pr.number} has backport numbers - checking their validity now`);
|
||||
robot.log(
|
||||
`#${pr.number} has backport numbers - checking their validity now`,
|
||||
);
|
||||
const supported = await getSupportedBranches(context);
|
||||
|
||||
for (const oldPRNumber of oldPRNumbers) {
|
||||
robot.log(`Checking validity of #${oldPRNumber}`);
|
||||
const oldPR = (await context.github.pulls.get(context.repo({
|
||||
pull_number: oldPRNumber,
|
||||
}))).data;
|
||||
const oldPR = (
|
||||
await context.github.pulls.get(
|
||||
context.repo({
|
||||
pull_number: oldPRNumber,
|
||||
}),
|
||||
)
|
||||
).data;
|
||||
|
||||
// The current PR is only valid if the PR it is backporting
|
||||
// was merged to master or to a supported release branch.
|
||||
if (!['master', ...supported].includes(oldPR.base.ref)) {
|
||||
const cause = 'the PR that it is backporting was not targeting the master branch.';
|
||||
const cause =
|
||||
'the PR that it is backporting was not targeting the master branch.';
|
||||
failureMap.set(oldPRNumber, cause);
|
||||
} else if (!oldPR.merged) {
|
||||
const cause = 'the PR that this is backporting has not been merged yet.';
|
||||
const cause =
|
||||
'the PR that this is backporting has not been merged yet.';
|
||||
failureMap.set(oldPRNumber, cause);
|
||||
}
|
||||
}
|
||||
|
@ -217,10 +269,18 @@ const probotHandler = async (robot: Application) => {
|
|||
|
||||
for (const oldPRNumber of oldPRNumbers) {
|
||||
if (failureMap.has(oldPRNumber)) {
|
||||
robot.log(`#${pr.number} is targeting a branch that is not master - ${failureMap.get(oldPRNumber)}`);
|
||||
robot.log(
|
||||
`#${
|
||||
pr.number
|
||||
} is targeting a branch that is not master - ${failureMap.get(
|
||||
oldPRNumber,
|
||||
)}`,
|
||||
);
|
||||
await updateBackportValidityCheck(context, checkRun, {
|
||||
title: 'Invalid Backport',
|
||||
summary: `This PR is targeting a branch that is not master but ${failureMap.get(oldPRNumber)}`,
|
||||
summary: `This PR is targeting a branch that is not master but ${failureMap.get(
|
||||
oldPRNumber,
|
||||
)}`,
|
||||
conclusion: CheckRunStatus.FAILURE,
|
||||
});
|
||||
} else {
|
||||
|
@ -235,7 +295,9 @@ const probotHandler = async (robot: Application) => {
|
|||
} else if (checkRun) {
|
||||
// If we're somehow targeting master and have a check run,
|
||||
// we mark this check as cancelled.
|
||||
robot.log(`#${pr.number} is targeting 'master' and is not a backport - marking as cancelled`);
|
||||
robot.log(
|
||||
`#${pr.number} is targeting 'master' and is not a backport - marking as cancelled`,
|
||||
);
|
||||
await updateBackportValidityCheck(context, checkRun, {
|
||||
title: 'Cancelled',
|
||||
summary: 'This PR is targeting `master` and is not a backport',
|
||||
|
@ -245,7 +307,10 @@ const probotHandler = async (robot: Application) => {
|
|||
|
||||
// Only run the backportable checks on "opened" and "synchronize"
|
||||
// an "edited" change can not impact backportability.
|
||||
if (context.payload.action === 'edited' || context.payload.action === 'synchronize') {
|
||||
if (
|
||||
context.payload.action === 'edited' ||
|
||||
context.payload.action === 'synchronize'
|
||||
) {
|
||||
maybeRunCheck(context);
|
||||
}
|
||||
},
|
||||
|
@ -276,12 +341,16 @@ const probotHandler = async (robot: Application) => {
|
|||
|
||||
robot.log(`Deleting base branch: ${pr.base.ref}`);
|
||||
try {
|
||||
await context.github.git.deleteRef(context.repo({ ref: pr.base.ref }));
|
||||
await context.github.git.deleteRef(
|
||||
context.repo({ ref: pr.base.ref }),
|
||||
);
|
||||
} catch (e) {
|
||||
robot.log('Failed to delete base branch: ', e);
|
||||
}
|
||||
} else {
|
||||
robot.log(`Backporting #${pr.number} to all branches specified by labels`);
|
||||
robot.log(
|
||||
`Backporting #${pr.number} to all branches specified by labels`,
|
||||
);
|
||||
backportAllLabels(context, pr);
|
||||
}
|
||||
}
|
||||
|
@ -292,13 +361,15 @@ const probotHandler = async (robot: Application) => {
|
|||
// Manually trigger backporting process on trigger comment phrase.
|
||||
robot.on('issue_comment.created', async (context: Context) => {
|
||||
const payload = context.payload;
|
||||
const config = await context.config<TropConfig>('config.yml') as TropConfig;
|
||||
const config = (await context.config<TropConfig>(
|
||||
'config.yml',
|
||||
)) as TropConfig;
|
||||
if (!config || !Array.isArray(config.authorizedUsers)) {
|
||||
robot.log('missing or invalid config', config);
|
||||
return;
|
||||
}
|
||||
|
||||
const isPullRequest = (issue: { number: number, html_url: string }) =>
|
||||
const isPullRequest = (issue: { number: number; html_url: string }) =>
|
||||
issue.html_url.endsWith(`/pull/${issue.number}`);
|
||||
|
||||
if (!isPullRequest(payload.issue)) return;
|
||||
|
@ -307,95 +378,127 @@ const probotHandler = async (robot: Application) => {
|
|||
if (!cmd.startsWith(TROP_COMMAND_PREFIX)) return;
|
||||
|
||||
if (!config.authorizedUsers.includes(payload.comment.user.login)) {
|
||||
robot.log(`@${payload.comment.user.login} is not authorized to run PR backports - stopping`);
|
||||
await context.github.issues.createComment(context.repo({
|
||||
issue_number: payload.issue.number,
|
||||
body: `@${payload.comment.user.login} is not authorized to run PR backports.`,
|
||||
}));
|
||||
robot.log(
|
||||
`@${payload.comment.user.login} is not authorized to run PR backports - stopping`,
|
||||
);
|
||||
await context.github.issues.createComment(
|
||||
context.repo({
|
||||
issue_number: payload.issue.number,
|
||||
body: `@${payload.comment.user.login} is not authorized to run PR backports.`,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const actualCmd = cmd.substr(TROP_COMMAND_PREFIX.length);
|
||||
|
||||
const actions = [{
|
||||
name: 'backport sanity checker',
|
||||
command: /^run backport/,
|
||||
execute: async () => {
|
||||
const pr = (await context.github.pulls.get(
|
||||
context.repo({ pull_number: payload.issue.number }))
|
||||
).data;
|
||||
if (!pr.merged) {
|
||||
await context.github.issues.createComment(context.repo({
|
||||
issue_number: payload.issue.number,
|
||||
body: 'This PR has not been merged yet, and cannot be backported.',
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}, {
|
||||
name: 'backport automatically',
|
||||
command: /^run backport$/,
|
||||
execute: async () => {
|
||||
const pr = (await context.github.pulls.get(
|
||||
context.repo({ pull_number: payload.issue.number }))
|
||||
).data as any;
|
||||
await context.github.issues.createComment(context.repo({
|
||||
body: 'The backport process for this PR has been manually initiated, here we go! :D',
|
||||
issue_number: payload.issue.number,
|
||||
}));
|
||||
backportAllLabels(context, pr);
|
||||
return true;
|
||||
},
|
||||
}, {
|
||||
name: 'backport to branch',
|
||||
command: /^run backport-to ([^\s:]+)/,
|
||||
execute: async (targetBranches: string) => {
|
||||
const branches = targetBranches.split(',');
|
||||
for (const branch of branches) {
|
||||
robot.log(`Initiatating backport to ${branch} from 'backport-to' comment`);
|
||||
|
||||
if (!(branch.trim())) continue;
|
||||
const pr = (await context.github.pulls.get(
|
||||
context.repo({ pull_number: payload.issue.number }))
|
||||
const actions = [
|
||||
{
|
||||
name: 'backport sanity checker',
|
||||
command: /^run backport/,
|
||||
execute: async () => {
|
||||
const pr = (
|
||||
await context.github.pulls.get(
|
||||
context.repo({ pull_number: payload.issue.number }),
|
||||
)
|
||||
).data;
|
||||
|
||||
try {
|
||||
(await context.github.repos.getBranch(context.repo({ branch })));
|
||||
} catch (err) {
|
||||
await context.github.issues.createComment(context.repo({
|
||||
body: `The branch you provided "${branch}" does not appear to exist :cry:`,
|
||||
issue_number: payload.issue.number,
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Optionally disallow backports to EOL branches
|
||||
const noEOLSupport = getEnvVar('NO_EOL_SUPPORT', '');
|
||||
if (noEOLSupport) {
|
||||
const supported = await getSupportedBranches(context);
|
||||
if (!supported.includes(branch)) {
|
||||
robot.log(`${branch} is no longer supported - no backport will be initiated`);
|
||||
await context.github.issues.createComment(context.repo({
|
||||
body: `${branch} is no longer supported - no backport will be initiated.`,
|
||||
if (!pr.merged) {
|
||||
await context.github.issues.createComment(
|
||||
context.repo({
|
||||
issue_number: payload.issue.number,
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
body:
|
||||
'This PR has not been merged yet, and cannot be backported.',
|
||||
}),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
robot.log(`Initiating manual backport process for #${payload.issue.number} to ${branch}`);
|
||||
await context.github.issues.createComment(context.repo({
|
||||
body: `The backport process for this PR has been manually initiated -
|
||||
sending your commits to "${branch}"!`,
|
||||
issue_number: payload.issue.number,
|
||||
}));
|
||||
context.payload.pull_request = context.payload.pull_request || pr;
|
||||
backportToBranch(robot, context, branch);
|
||||
}
|
||||
return true;
|
||||
return true;
|
||||
},
|
||||
},
|
||||
}];
|
||||
{
|
||||
name: 'backport automatically',
|
||||
command: /^run backport$/,
|
||||
execute: async () => {
|
||||
const pr = (
|
||||
await context.github.pulls.get(
|
||||
context.repo({ pull_number: payload.issue.number }),
|
||||
)
|
||||
).data as any;
|
||||
await context.github.issues.createComment(
|
||||
context.repo({
|
||||
body:
|
||||
'The backport process for this PR has been manually initiated, here we go! :D',
|
||||
issue_number: payload.issue.number,
|
||||
}),
|
||||
);
|
||||
backportAllLabels(context, pr);
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backport to branch',
|
||||
command: /^run backport-to ([^\s:]+)/,
|
||||
execute: async (targetBranches: string) => {
|
||||
const branches = targetBranches.split(',');
|
||||
for (const branch of branches) {
|
||||
robot.log(
|
||||
`Initiatating backport to ${branch} from 'backport-to' comment`,
|
||||
);
|
||||
|
||||
if (!branch.trim()) continue;
|
||||
const pr = (
|
||||
await context.github.pulls.get(
|
||||
context.repo({ pull_number: payload.issue.number }),
|
||||
)
|
||||
).data;
|
||||
|
||||
try {
|
||||
await context.github.repos.getBranch(context.repo({ branch }));
|
||||
} catch (err) {
|
||||
await context.github.issues.createComment(
|
||||
context.repo({
|
||||
body: `The branch you provided "${branch}" does not appear to exist :cry:`,
|
||||
issue_number: payload.issue.number,
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Optionally disallow backports to EOL branches
|
||||
const noEOLSupport = getEnvVar('NO_EOL_SUPPORT', '');
|
||||
if (noEOLSupport) {
|
||||
const supported = await getSupportedBranches(context);
|
||||
if (!supported.includes(branch)) {
|
||||
robot.log(
|
||||
`${branch} is no longer supported - no backport will be initiated`,
|
||||
);
|
||||
await context.github.issues.createComment(
|
||||
context.repo({
|
||||
body: `${branch} is no longer supported - no backport will be initiated.`,
|
||||
issue_number: payload.issue.number,
|
||||
}),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
robot.log(
|
||||
`Initiating manual backport process for #${payload.issue.number} to ${branch}`,
|
||||
);
|
||||
await context.github.issues.createComment(
|
||||
context.repo({
|
||||
body: `The backport process for this PR has been manually initiated -
|
||||
sending your commits to "${branch}"!`,
|
||||
issue_number: payload.issue.number,
|
||||
}),
|
||||
);
|
||||
context.payload.pull_request = context.payload.pull_request || pr;
|
||||
backportToBranch(robot, context, branch);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const action of actions) {
|
||||
const match = actualCmd.match(action.command);
|
||||
|
@ -404,7 +507,7 @@ sending your commits to "${branch}"!`,
|
|||
robot.log(`running action: ${action.name} for comment`);
|
||||
|
||||
// @ts-ignore (false positive on next line arg count)
|
||||
if (!await action.execute(...match.slice(1))) {
|
||||
if (!(await action.execute(...match.slice(1)))) {
|
||||
robot.log(`${action.name} failed, stopping responder chain`);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ export interface TropConfig {
|
|||
export interface RemotesOptions {
|
||||
dir: string;
|
||||
remotes: {
|
||||
name: string,
|
||||
value: string,
|
||||
name: string;
|
||||
value: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
|
|
|
@ -15,14 +15,21 @@ import { LogLevel } from '../enums';
|
|||
* @returns {Object} - an object containing the repo initialization directory
|
||||
*/
|
||||
export const backportCommitsToBranch = async (options: BackportOptions) => {
|
||||
log('backportCommitsToBranch', LogLevel.INFO, `Backporting ${options.patches.length} commits to ${options.targetBranch}`);
|
||||
log(
|
||||
'backportCommitsToBranch',
|
||||
LogLevel.INFO,
|
||||
`Backporting ${options.patches.length} commits to ${options.targetBranch}`,
|
||||
);
|
||||
|
||||
const git = simpleGit(options.dir);
|
||||
|
||||
// Create branch to cherry-pick the commits to.
|
||||
await git.checkout(`target_repo/${options.targetBranch}`);
|
||||
await git.pull('target_repo', options.targetBranch);
|
||||
await git.checkoutBranch(options.tempBranch, `target_repo/${options.targetBranch}`);
|
||||
await git.checkoutBranch(
|
||||
options.tempBranch,
|
||||
`target_repo/${options.targetBranch}`,
|
||||
);
|
||||
|
||||
// Cherry pick the commits to be backported.
|
||||
const patchPath = `${options.dir}.patch`;
|
||||
|
|
|
@ -17,16 +17,28 @@ export const backportToLabel = async (
|
|||
context: Context,
|
||||
label: PullsGetResponseLabelsItem,
|
||||
) => {
|
||||
log('backportToLabel', LogLevel.INFO, `Executing backport to branch from label ${label}`);
|
||||
log(
|
||||
'backportToLabel',
|
||||
LogLevel.INFO,
|
||||
`Executing backport to branch from label ${label}`,
|
||||
);
|
||||
|
||||
if (!label.name.startsWith(PRStatus.TARGET)) {
|
||||
log('backportToLabel', LogLevel.ERROR, `Label '${label.name}' does not begin with '${PRStatus.TARGET}'`);
|
||||
log(
|
||||
'backportToLabel',
|
||||
LogLevel.ERROR,
|
||||
`Label '${label.name}' does not begin with '${PRStatus.TARGET}'`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const targetBranch = labelUtils.labelToTargetBranch(label, PRStatus.TARGET);
|
||||
if (!targetBranch) {
|
||||
log('backportToLabel', LogLevel.WARN, 'No target branch specified - aborting backport process');
|
||||
log(
|
||||
'backportToLabel',
|
||||
LogLevel.WARN,
|
||||
'No target branch specified - aborting backport process',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -54,7 +66,11 @@ export const backportToBranch = async (
|
|||
context: Context,
|
||||
targetBranch: string,
|
||||
) => {
|
||||
log('backportToLabel', LogLevel.INFO, `Executing backport to branch '${targetBranch}'`);
|
||||
log(
|
||||
'backportToLabel',
|
||||
LogLevel.INFO,
|
||||
`Executing backport to branch '${targetBranch}'`,
|
||||
);
|
||||
|
||||
const labelToRemove = undefined;
|
||||
const labelToAdd = PRStatus.IN_FLIGHT + targetBranch;
|
||||
|
|
|
@ -21,15 +21,25 @@ export const updateManualBackport = async (
|
|||
let labelToRemove;
|
||||
let labelToAdd;
|
||||
|
||||
log('updateManualBackport', LogLevel.INFO, `Updating backport of ${oldPRNumber} to ${pr.base.ref}`);
|
||||
log(
|
||||
'updateManualBackport',
|
||||
LogLevel.INFO,
|
||||
`Updating backport of ${oldPRNumber} to ${pr.base.ref}`,
|
||||
);
|
||||
|
||||
if (type === PRChange.OPEN) {
|
||||
log('updateManualBackport', LogLevel.INFO, `New manual backport opened at #${pr.number}`);
|
||||
log(
|
||||
'updateManualBackport',
|
||||
LogLevel.INFO,
|
||||
`New manual backport opened at #${pr.number}`,
|
||||
);
|
||||
|
||||
labelToAdd = PRStatus.IN_FLIGHT + pr.base.ref;
|
||||
labelToRemove = PRStatus.NEEDS_MANUAL + pr.base.ref;
|
||||
|
||||
if (!await labelUtils.labelExistsOnPR(context, oldPRNumber, labelToRemove)) {
|
||||
if (
|
||||
!(await labelUtils.labelExistsOnPR(context, oldPRNumber, labelToRemove))
|
||||
) {
|
||||
labelToRemove = PRStatus.TARGET + pr.base.ref;
|
||||
}
|
||||
|
||||
|
@ -39,23 +49,33 @@ please check out #${pr.number}`;
|
|||
|
||||
// TODO(codebytere): Once probot updates to @octokit/rest@16 we can use .paginate to
|
||||
// get all the comments properly, for now 100 should do
|
||||
const { data: existingComments } = await context.github.issues.listComments(context.repo({
|
||||
issue_number: oldPRNumber,
|
||||
per_page: 100,
|
||||
}));
|
||||
const { data: existingComments } = await context.github.issues.listComments(
|
||||
context.repo({
|
||||
issue_number: oldPRNumber,
|
||||
per_page: 100,
|
||||
}),
|
||||
);
|
||||
|
||||
// We should only comment if there is not a previous existing comment
|
||||
const shouldComment = !existingComments.some(comment => comment.body === commentBody);
|
||||
const shouldComment = !existingComments.some(
|
||||
(comment) => comment.body === commentBody,
|
||||
);
|
||||
|
||||
if (shouldComment) {
|
||||
// Comment on the original PR with the manual backport link
|
||||
await context.github.issues.createComment(context.repo({
|
||||
issue_number: oldPRNumber,
|
||||
body: commentBody,
|
||||
}));
|
||||
await context.github.issues.createComment(
|
||||
context.repo({
|
||||
issue_number: oldPRNumber,
|
||||
body: commentBody,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log('updateManualBackport', LogLevel.INFO, `Backport of ${oldPRNumber} at #${pr.number} merged to ${pr.base.ref}`);
|
||||
log(
|
||||
'updateManualBackport',
|
||||
LogLevel.INFO,
|
||||
`Backport of ${oldPRNumber} at #${pr.number} merged to ${pr.base.ref}`,
|
||||
);
|
||||
|
||||
labelToRemove = PRStatus.IN_FLIGHT + pr.base.ref;
|
||||
labelToAdd = PRStatus.MERGED + pr.base.ref;
|
||||
|
|
283
src/utils.ts
283
src/utils.ts
|
@ -27,15 +27,25 @@ import { log } from './utils/log-util';
|
|||
const makeQueue: IQueue = require('queue');
|
||||
const { parse: parseDiff } = require('what-the-diff');
|
||||
|
||||
export const labelMergedPR = async (context: Context, pr: PullsGetResponse, targetBranch: String) => {
|
||||
log('labelMergedPR', LogLevel.INFO, `Labeling original PRs for PR at #${pr.number}`);
|
||||
export const labelMergedPR = async (
|
||||
context: Context,
|
||||
pr: PullsGetResponse,
|
||||
targetBranch: String,
|
||||
) => {
|
||||
log(
|
||||
'labelMergedPR',
|
||||
LogLevel.INFO,
|
||||
`Labeling original PRs for PR at #${pr.number}`,
|
||||
);
|
||||
|
||||
const backportNumbers: number[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
const backportPattern = getBackportPattern();
|
||||
while (match = backportPattern.exec(pr.body)) {
|
||||
while ((match = backportPattern.exec(pr.body))) {
|
||||
// This might be the first or second capture group depending on if it's a link or not.
|
||||
backportNumbers.push(match[1] ? parseInt(match[1], 10) : parseInt(match[2], 10));
|
||||
backportNumbers.push(
|
||||
match[1] ? parseInt(match[1], 10) : parseInt(match[2], 10),
|
||||
);
|
||||
}
|
||||
|
||||
for (const prNumber of backportNumbers) {
|
||||
|
@ -48,10 +58,16 @@ export const labelMergedPR = async (context: Context, pr: PullsGetResponse, targ
|
|||
};
|
||||
|
||||
const checkUserHasWriteAccess = async (context: Context, user: string) => {
|
||||
log('checkUserHasWriteAccess', LogLevel.INFO, `Checking whether ${user} has write access`);
|
||||
log(
|
||||
'checkUserHasWriteAccess',
|
||||
LogLevel.INFO,
|
||||
`Checking whether ${user} has write access`,
|
||||
);
|
||||
|
||||
const params = context.repo({ username: user });
|
||||
const { data: userInfo } = await context.github.repos.getCollaboratorPermissionLevel(params);
|
||||
const {
|
||||
data: userInfo,
|
||||
} = await context.github.repos.getCollaboratorPermissionLevel(params);
|
||||
|
||||
// Possible values for the permission key: 'admin', 'write', 'read', 'none'.
|
||||
// In order for the user's review to count, they must be at least 'write'.
|
||||
|
@ -59,13 +75,20 @@ const checkUserHasWriteAccess = async (context: Context, user: string) => {
|
|||
};
|
||||
|
||||
const createBackportComment = (pr: PullsGetResponse) => {
|
||||
log('createBackportComment', LogLevel.INFO, `Creating backport comment for #${pr.number}`);
|
||||
log(
|
||||
'createBackportComment',
|
||||
LogLevel.INFO,
|
||||
`Creating backport comment for #${pr.number}`,
|
||||
);
|
||||
|
||||
let body = `Backport of #${pr.number}\n\nSee that PR for details.`;
|
||||
|
||||
const onelineMatch = pr.body.match(/(?:(?:\r?\n)|^)notes: (.+?)(?:(?:\r?\n)|$)/gi);
|
||||
const multilineMatch =
|
||||
pr.body.match(/(?:(?:\r?\n)Notes:(?:\r?\n)((?:\*.+(?:(?:\r?\n)|$))+))/gi);
|
||||
const onelineMatch = pr.body.match(
|
||||
/(?:(?:\r?\n)|^)notes: (.+?)(?:(?:\r?\n)|$)/gi,
|
||||
);
|
||||
const multilineMatch = pr.body.match(
|
||||
/(?:(?:\r?\n)Notes:(?:\r?\n)((?:\*.+(?:(?:\r?\n)|$))+))/gi,
|
||||
);
|
||||
|
||||
// attach release notes to backport PR body
|
||||
if (onelineMatch && onelineMatch[0]) {
|
||||
|
@ -79,23 +102,30 @@ const createBackportComment = (pr: PullsGetResponse) => {
|
|||
return body;
|
||||
};
|
||||
|
||||
export const backportImpl = async (robot: Application,
|
||||
context: Context,
|
||||
targetBranch: string,
|
||||
purpose: BackportPurpose,
|
||||
labelToRemove?: string,
|
||||
labelToAdd?: string) => {
|
||||
|
||||
export const backportImpl = async (
|
||||
robot: Application,
|
||||
context: Context,
|
||||
targetBranch: string,
|
||||
purpose: BackportPurpose,
|
||||
labelToRemove?: string,
|
||||
labelToAdd?: string,
|
||||
) => {
|
||||
// Optionally disallow backports to EOL branches
|
||||
const noEOLSupport = getEnvVar('NO_EOL_SUPPORT', '');
|
||||
if (noEOLSupport) {
|
||||
const supported = await getSupportedBranches(context);
|
||||
if (!['master', ...supported].includes(targetBranch)) {
|
||||
log('backportImpl', LogLevel.WARN, `${targetBranch} is no longer supported - no backport will be initiated.`);
|
||||
await context.github.issues.createComment(context.repo({
|
||||
body: `${targetBranch} is no longer supported - no backport will be initiated.`,
|
||||
issue_number: context.payload.issue.number,
|
||||
}));
|
||||
log(
|
||||
'backportImpl',
|
||||
LogLevel.WARN,
|
||||
`${targetBranch} is no longer supported - no backport will be initiated.`,
|
||||
);
|
||||
await context.github.issues.createComment(
|
||||
context.repo({
|
||||
body: `${targetBranch} is no longer supported - no backport will be initiated.`,
|
||||
issue_number: context.payload.issue.number,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -106,14 +136,18 @@ export const backportImpl = async (robot: Application,
|
|||
log('backportImpl', LogLevel.INFO, `Queuing ${bp} for "${slug}"`);
|
||||
|
||||
const getCheckRun = async () => {
|
||||
const allChecks = await context.github.checks.listForRef(context.repo({
|
||||
ref: context.payload.pull_request.head.sha,
|
||||
per_page: 100,
|
||||
}));
|
||||
const allChecks = await context.github.checks.listForRef(
|
||||
context.repo({
|
||||
ref: context.payload.pull_request.head.sha,
|
||||
per_page: 100,
|
||||
}),
|
||||
);
|
||||
|
||||
return allChecks.data.check_runs.find((run: ChecksListForRefResponseCheckRunsItem) => {
|
||||
return run.name === `${CHECK_PREFIX}${targetBranch}`;
|
||||
});
|
||||
return allChecks.data.check_runs.find(
|
||||
(run: ChecksListForRefResponseCheckRunsItem) => {
|
||||
return run.name === `${CHECK_PREFIX}${targetBranch}`;
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
let createdDir: string | null = null;
|
||||
|
@ -125,11 +159,13 @@ export const backportImpl = async (robot: Application,
|
|||
if (purpose === BackportPurpose.Check) {
|
||||
const checkRun = await getCheckRun();
|
||||
if (checkRun) {
|
||||
await context.github.checks.update(context.repo({
|
||||
check_run_id: checkRun.id,
|
||||
name: checkRun.name,
|
||||
status: 'in_progress' as 'in_progress',
|
||||
}));
|
||||
await context.github.checks.update(
|
||||
context.repo({
|
||||
check_run_id: checkRun.id,
|
||||
name: checkRun.name,
|
||||
status: 'in_progress' as 'in_progress',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,38 +184,63 @@ export const backportImpl = async (robot: Application,
|
|||
const targetRepoRemote = `https://x-access-token:${repoAccessToken}@github.com/${slug}.git`;
|
||||
await setupRemotes({
|
||||
dir,
|
||||
remotes: [{
|
||||
name: 'target_repo',
|
||||
value: targetRepoRemote,
|
||||
}],
|
||||
remotes: [
|
||||
{
|
||||
name: 'target_repo',
|
||||
value: targetRepoRemote,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Get list of commits.
|
||||
log('backportImpl', LogLevel.INFO, `Getting rev list from: ${pr.base.sha}..${pr.head.sha}`);
|
||||
const commits = (await context.github.pulls.listCommits(context.repo({
|
||||
pull_number: pr.number,
|
||||
}))).data.map((commit: PullsListCommitsResponseItem) => commit.sha!);
|
||||
log(
|
||||
'backportImpl',
|
||||
LogLevel.INFO,
|
||||
`Getting rev list from: ${pr.base.sha}..${pr.head.sha}`,
|
||||
);
|
||||
const commits = (
|
||||
await context.github.pulls.listCommits(
|
||||
context.repo({
|
||||
pull_number: pr.number,
|
||||
}),
|
||||
)
|
||||
).data.map((commit: PullsListCommitsResponseItem) => commit.sha!);
|
||||
|
||||
// No commits == WTF
|
||||
if (commits.length === 0) {
|
||||
log('backportImpl', LogLevel.INFO, 'Found no commits to backport - aborting backport process');
|
||||
log(
|
||||
'backportImpl',
|
||||
LogLevel.INFO,
|
||||
'Found no commits to backport - aborting backport process',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Over 240 commits is probably the limit from GitHub so let's not bother.
|
||||
if (commits.length >= 240) {
|
||||
log('backportImpl', LogLevel.ERROR, `Too many commits (${commits.length})...backport will not be performed.`);
|
||||
await context.github.issues.createComment(context.repo({
|
||||
issue_number: pr.number,
|
||||
body: 'This PR has exceeded the automatic backport commit limit \
|
||||
log(
|
||||
'backportImpl',
|
||||
LogLevel.ERROR,
|
||||
`Too many commits (${commits.length})...backport will not be performed.`,
|
||||
);
|
||||
await context.github.issues.createComment(
|
||||
context.repo({
|
||||
issue_number: pr.number,
|
||||
body:
|
||||
'This PR has exceeded the automatic backport commit limit \
|
||||
and must be performed manually.',
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
log('backportImpl', LogLevel.INFO, `Found ${commits.length} commits to backport - requesting details now.`);
|
||||
const patches: string[] = (new Array(commits.length)).fill('');
|
||||
log(
|
||||
'backportImpl',
|
||||
LogLevel.INFO,
|
||||
`Found ${commits.length} commits to backport - requesting details now.`,
|
||||
);
|
||||
const patches: string[] = new Array(commits.length).fill('');
|
||||
const q = makeQueue({
|
||||
concurrency: 5,
|
||||
});
|
||||
|
@ -195,20 +256,29 @@ export const backportImpl = async (robot: Application,
|
|||
},
|
||||
});
|
||||
patches[i] = await patchBody.text();
|
||||
log('backportImpl', LogLevel.INFO, `Got patch (${i + 1}/${commits.length})`);
|
||||
log(
|
||||
'backportImpl',
|
||||
LogLevel.INFO,
|
||||
`Got patch (${i + 1}/${commits.length})`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
await new Promise(r => q.start(r));
|
||||
await new Promise((r) => q.start(r));
|
||||
log('backportImpl', LogLevel.INFO, 'Got all commit info');
|
||||
|
||||
// Create temporary branch name.
|
||||
const sanitizedTitle = pr.title
|
||||
.replace(/\*/g, 'x').toLowerCase()
|
||||
.replace(/\*/g, 'x')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_]+/g, '-');
|
||||
const tempBranch = `trop/${targetBranch}-bp-${sanitizedTitle}-${Date.now()}`;
|
||||
|
||||
log('backportImpl', LogLevel.INFO, `Checking out target: "target_repo/${targetBranch}" to temp: "${tempBranch}"`);
|
||||
log(
|
||||
'backportImpl',
|
||||
LogLevel.INFO,
|
||||
`Checking out target: "target_repo/${targetBranch}" to temp: "${tempBranch}"`,
|
||||
);
|
||||
log('backportImpl', LogLevel.INFO, 'Will start backporting now');
|
||||
|
||||
await backportCommitsToBranch({
|
||||
|
@ -221,33 +291,43 @@ export const backportImpl = async (robot: Application,
|
|||
shouldPush: purpose === BackportPurpose.ExecuteBackport,
|
||||
});
|
||||
|
||||
log('backportImpl', LogLevel.INFO, 'Cherry pick success - pushed up to target_repo');
|
||||
log(
|
||||
'backportImpl',
|
||||
LogLevel.INFO,
|
||||
'Cherry pick success - pushed up to target_repo',
|
||||
);
|
||||
|
||||
if (purpose === BackportPurpose.ExecuteBackport) {
|
||||
log('backportImpl', LogLevel.INFO, 'Creating Pull Request');
|
||||
const { data: newPr } = (await context.github.pulls.create(context.repo({
|
||||
head: `${tempBranch}`,
|
||||
base: targetBranch,
|
||||
title: pr.title,
|
||||
body: createBackportComment(pr),
|
||||
maintainer_can_modify: false,
|
||||
})));
|
||||
const { data: newPr } = await context.github.pulls.create(
|
||||
context.repo({
|
||||
head: `${tempBranch}`,
|
||||
base: targetBranch,
|
||||
title: pr.title,
|
||||
body: createBackportComment(pr),
|
||||
maintainer_can_modify: false,
|
||||
}),
|
||||
);
|
||||
|
||||
// If user has sufficient permissions (i.e has write access)
|
||||
// request their review on the automatically backported pull request
|
||||
if (await checkUserHasWriteAccess(context, pr.user.login)) {
|
||||
await context.github.pulls.createReviewRequest(context.repo({
|
||||
pull_number: newPr.number,
|
||||
reviewers: [pr.user.login],
|
||||
}));
|
||||
await context.github.pulls.createReviewRequest(
|
||||
context.repo({
|
||||
pull_number: newPr.number,
|
||||
reviewers: [pr.user.login],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
log('backportImpl', LogLevel.INFO, 'Adding breadcrumb comment');
|
||||
await context.github.issues.createComment(context.repo({
|
||||
issue_number: pr.number,
|
||||
body: `I have automatically backported this PR to "${targetBranch}", \
|
||||
await context.github.issues.createComment(
|
||||
context.repo({
|
||||
issue_number: pr.number,
|
||||
body: `I have automatically backported this PR to "${targetBranch}", \
|
||||
please check out #${newPr.number}`,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
if (labelToRemove) {
|
||||
await labelUtils.removeLabel(context, pr.number, labelToRemove);
|
||||
|
@ -257,7 +337,10 @@ export const backportImpl = async (robot: Application,
|
|||
await labelUtils.addLabel(context, pr.number, [labelToAdd]);
|
||||
}
|
||||
|
||||
await labelUtils.addLabel(context, newPr.number!, ['backport', `${targetBranch}`]);
|
||||
await labelUtils.addLabel(context, newPr.number!, [
|
||||
'backport',
|
||||
`${targetBranch}`,
|
||||
]);
|
||||
|
||||
log('backportImpl', LogLevel.INFO, 'Backport process complete');
|
||||
}
|
||||
|
@ -265,16 +348,18 @@ export const backportImpl = async (robot: Application,
|
|||
if (purpose === BackportPurpose.Check) {
|
||||
const checkRun = await getCheckRun();
|
||||
if (checkRun) {
|
||||
context.github.checks.update(context.repo({
|
||||
check_run_id: checkRun.id,
|
||||
name: checkRun.name,
|
||||
conclusion: 'success' as 'success',
|
||||
completed_at: (new Date()).toISOString(),
|
||||
output: {
|
||||
title: 'Clean Backport',
|
||||
summary: `This PR was checked and can be backported to "${targetBranch}" cleanly.`,
|
||||
},
|
||||
}));
|
||||
context.github.checks.update(
|
||||
context.repo({
|
||||
check_run_id: checkRun.id,
|
||||
name: checkRun.name,
|
||||
conclusion: 'success' as 'success',
|
||||
completed_at: new Date().toISOString(),
|
||||
output: {
|
||||
title: 'Clean Backport',
|
||||
summary: `This PR was checked and can be backported to "${targetBranch}" cleanly.`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,17 +378,27 @@ export const backportImpl = async (robot: Application,
|
|||
for (const file of diff) {
|
||||
if (file.binary) continue;
|
||||
|
||||
for (const hunk of (file.hunks || [])) {
|
||||
const startOffset = hunk.lines.findIndex((line: string) => line.includes('<<<<<<<'));
|
||||
const endOffset = hunk.lines.findIndex((line: string) => line.includes('=======')) - 2;
|
||||
const finalOffset = hunk.lines.findIndex((line: string) => line.includes('>>>>>>>'));
|
||||
for (const hunk of file.hunks || []) {
|
||||
const startOffset = hunk.lines.findIndex((line: string) =>
|
||||
line.includes('<<<<<<<'),
|
||||
);
|
||||
const endOffset =
|
||||
hunk.lines.findIndex((line: string) => line.includes('=======')) -
|
||||
2;
|
||||
const finalOffset = hunk.lines.findIndex((line: string) =>
|
||||
line.includes('>>>>>>>'),
|
||||
);
|
||||
annotations.push({
|
||||
path: file.filePath,
|
||||
start_line: hunk.theirStartLine + Math.max(0, startOffset),
|
||||
end_line: hunk.theirStartLine + Math.max(0, endOffset),
|
||||
annotation_level: 'failure',
|
||||
message: 'Patch Conflict',
|
||||
raw_details: hunk.lines.filter((_: any, i: number) => i >= startOffset && i <= finalOffset).join('\n'),
|
||||
raw_details: hunk.lines
|
||||
.filter(
|
||||
(_: any, i: number) => i >= startOffset && i <= finalOffset,
|
||||
)
|
||||
.join('\n'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -313,11 +408,13 @@ export const backportImpl = async (robot: Application,
|
|||
|
||||
const pr = context.payload.pull_request;
|
||||
if (purpose === BackportPurpose.ExecuteBackport) {
|
||||
await context.github.issues.createComment(context.repo({
|
||||
issue_number: pr.number,
|
||||
body: `I was unable to backport this PR to "${targetBranch}" cleanly;
|
||||
await context.github.issues.createComment(
|
||||
context.repo({
|
||||
issue_number: pr.number,
|
||||
body: `I was unable to backport this PR to "${targetBranch}" cleanly;
|
||||
you will need to perform this backport manually.`,
|
||||
}) as any);
|
||||
}) as any,
|
||||
);
|
||||
|
||||
const labelToRemove = PRStatus.TARGET + targetBranch;
|
||||
await labelUtils.removeLabel(context, pr.number, labelToRemove);
|
||||
|
@ -334,11 +431,13 @@ export const backportImpl = async (robot: Application,
|
|||
check_run_id: checkRun.id,
|
||||
name: checkRun.name,
|
||||
conclusion: 'neutral' as 'neutral',
|
||||
completed_at: (new Date()).toISOString(),
|
||||
completed_at: new Date().toISOString(),
|
||||
output: {
|
||||
title: 'Backport Failed',
|
||||
summary: `This PR was checked and could not be automatically backported to "${targetBranch}" cleanly`,
|
||||
text: diff ? `Failed Diff:\n\n${mdSep}diff\n${rawDiff}\n${mdSep}` : undefined,
|
||||
text: diff
|
||||
? `Failed Diff:\n\n${mdSep}diff\n${rawDiff}\n${mdSep}`
|
||||
: undefined,
|
||||
annotations: annotations ? annotations : undefined,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -11,32 +11,49 @@ import { LogLevel } from '../enums';
|
|||
* @param {Context} context - the context of the event that was triggered
|
||||
* @returns {Promise<string[]>} - an array of currently supported branches in x-y-z format
|
||||
*/
|
||||
export async function getSupportedBranches(context: Context): Promise<string[]> {
|
||||
log('getSupportedBranches', LogLevel.INFO, 'Fetching supported branches for this repository');
|
||||
export async function getSupportedBranches(
|
||||
context: Context,
|
||||
): Promise<string[]> {
|
||||
log(
|
||||
'getSupportedBranches',
|
||||
LogLevel.INFO,
|
||||
'Fetching supported branches for this repository',
|
||||
);
|
||||
|
||||
const SUPPORTED_BRANCH_ENV_PATTERN = getEnvVar('SUPPORTED_BRANCH_PATTERN', '^(\d)+-(?:(?:[0-9]+-x$)|(?:x+-y$))$');
|
||||
const SUPPORTED_BRANCH_ENV_PATTERN = getEnvVar(
|
||||
'SUPPORTED_BRANCH_PATTERN',
|
||||
'^(d)+-(?:(?:[0-9]+-x$)|(?:x+-y$))$',
|
||||
);
|
||||
const SUPPORTED_BRANCH_PATTERN = new RegExp(SUPPORTED_BRANCH_ENV_PATTERN);
|
||||
|
||||
const { data: branches } = await context.github.repos.listBranches(context.repo({
|
||||
protected: true,
|
||||
}));
|
||||
const { data: branches } = await context.github.repos.listBranches(
|
||||
context.repo({
|
||||
protected: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const releaseBranches = branches.filter(branch => branch.name.match(SUPPORTED_BRANCH_PATTERN));
|
||||
const releaseBranches = branches.filter((branch) =>
|
||||
branch.name.match(SUPPORTED_BRANCH_PATTERN),
|
||||
);
|
||||
const filtered: Record<string, string> = {};
|
||||
releaseBranches.sort((a, b) => {
|
||||
const aParts = a.name.split('-');
|
||||
const bParts = b.name.split('-');
|
||||
for (let i = 0; i < aParts.length; i += 1) {
|
||||
if (aParts[i] === bParts[i]) continue;
|
||||
return parseInt(aParts[i], 10) - parseInt(bParts[i], 10);
|
||||
}
|
||||
return 0;
|
||||
}).forEach((branch) => {
|
||||
return (filtered[branch.name.split('-')[0]] = branch.name);
|
||||
});
|
||||
releaseBranches
|
||||
.sort((a, b) => {
|
||||
const aParts = a.name.split('-');
|
||||
const bParts = b.name.split('-');
|
||||
for (let i = 0; i < aParts.length; i += 1) {
|
||||
if (aParts[i] === bParts[i]) continue;
|
||||
return parseInt(aParts[i], 10) - parseInt(bParts[i], 10);
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
.forEach((branch) => {
|
||||
return (filtered[branch.name.split('-')[0]] = branch.name);
|
||||
});
|
||||
|
||||
const values = Object.values(filtered);
|
||||
return values.sort((a, b) => parseInt(a, 10) - parseInt(b, 10)).slice(-NUM_SUPPORTED_VERSIONS);
|
||||
return values
|
||||
.sort((a, b) => parseInt(a, 10) - parseInt(b, 10))
|
||||
.slice(-NUM_SUPPORTED_VERSIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,20 +6,23 @@ export async function updateBackportValidityCheck(
|
|||
context: Context,
|
||||
checkRun: ChecksListForRefResponseCheckRunsItem,
|
||||
statusItems: {
|
||||
conclusion: CheckRunStatus,
|
||||
title: string
|
||||
summary: string,
|
||||
conclusion: CheckRunStatus;
|
||||
title: string;
|
||||
summary: string;
|
||||
},
|
||||
) {
|
||||
await context.github.checks.update(context.repo({
|
||||
check_run_id: checkRun.id,
|
||||
name: checkRun.name,
|
||||
conclusion: statusItems.conclusion as CheckRunStatus,
|
||||
completed_at: (new Date()).toISOString(),
|
||||
details_url: 'https://github.com/electron/trop/blob/master/docs/manual-backports.md',
|
||||
output: {
|
||||
title: statusItems.title,
|
||||
summary: statusItems.summary,
|
||||
},
|
||||
}));
|
||||
await context.github.checks.update(
|
||||
context.repo({
|
||||
check_run_id: checkRun.id,
|
||||
name: checkRun.name,
|
||||
conclusion: statusItems.conclusion as CheckRunStatus,
|
||||
completed_at: new Date().toISOString(),
|
||||
details_url:
|
||||
'https://github.com/electron/trop/blob/master/docs/manual-backports.md',
|
||||
output: {
|
||||
title: statusItems.title,
|
||||
summary: statusItems.summary,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,11 @@ export function getEnvVar(envVar: string, defaultValue?: string): string {
|
|||
|
||||
const value = process.env[envVar] || defaultValue;
|
||||
if (!value && value !== '') {
|
||||
log('getEnvVar', LogLevel.ERROR, `Missing environment variable '${envVar}'`);
|
||||
log(
|
||||
'getEnvVar',
|
||||
LogLevel.ERROR,
|
||||
`Missing environment variable '${envVar}'`,
|
||||
);
|
||||
throw new Error(`Missing environment variable '${envVar}'`);
|
||||
}
|
||||
return value;
|
||||
|
|
|
@ -3,39 +3,68 @@ import { PullsGetResponseLabelsItem } from '@octokit/rest';
|
|||
import { log } from './log-util';
|
||||
import { LogLevel } from '../enums';
|
||||
|
||||
export const addLabel = async (context: Context, prNumber: number, labelsToAdd: string[]) => {
|
||||
export const addLabel = async (
|
||||
context: Context,
|
||||
prNumber: number,
|
||||
labelsToAdd: string[],
|
||||
) => {
|
||||
log('addLabel', LogLevel.INFO, `Adding ${labelsToAdd} to PR #${prNumber}`);
|
||||
|
||||
return context.github.issues.addLabels(context.repo({
|
||||
issue_number: prNumber,
|
||||
labels: labelsToAdd,
|
||||
}));
|
||||
return context.github.issues.addLabels(
|
||||
context.repo({
|
||||
issue_number: prNumber,
|
||||
labels: labelsToAdd,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const removeLabel = async (context: Context, prNumber: number, labelToRemove: string) => {
|
||||
log('removeLabel', LogLevel.INFO, `Removing ${labelToRemove} from PR #${prNumber}`);
|
||||
export const removeLabel = async (
|
||||
context: Context,
|
||||
prNumber: number,
|
||||
labelToRemove: string,
|
||||
) => {
|
||||
log(
|
||||
'removeLabel',
|
||||
LogLevel.INFO,
|
||||
`Removing ${labelToRemove} from PR #${prNumber}`,
|
||||
);
|
||||
|
||||
// If the issue does not have the label, don't try remove it
|
||||
if (!await labelExistsOnPR(context, prNumber, labelToRemove)) return;
|
||||
if (!(await labelExistsOnPR(context, prNumber, labelToRemove))) return;
|
||||
|
||||
return context.github.issues.removeLabel(context.repo({
|
||||
issue_number: prNumber,
|
||||
name: labelToRemove,
|
||||
}));
|
||||
return context.github.issues.removeLabel(
|
||||
context.repo({
|
||||
issue_number: prNumber,
|
||||
name: labelToRemove,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const labelToTargetBranch = (label: PullsGetResponseLabelsItem, prefix: string) => {
|
||||
export const labelToTargetBranch = (
|
||||
label: PullsGetResponseLabelsItem,
|
||||
prefix: string,
|
||||
) => {
|
||||
return label.name.replace(prefix, '');
|
||||
};
|
||||
|
||||
export const labelExistsOnPR = async (context: Context, prNumber: number, labelName: string) => {
|
||||
log('labelExistsOnPR', LogLevel.INFO, `Checking if ${labelName} exists on #${prNumber}`);
|
||||
export const labelExistsOnPR = async (
|
||||
context: Context,
|
||||
prNumber: number,
|
||||
labelName: string,
|
||||
) => {
|
||||
log(
|
||||
'labelExistsOnPR',
|
||||
LogLevel.INFO,
|
||||
`Checking if ${labelName} exists on #${prNumber}`,
|
||||
);
|
||||
|
||||
const labels = await context.github.issues.listLabelsOnIssue(context.repo({
|
||||
issue_number: prNumber,
|
||||
per_page: 100,
|
||||
page: 1,
|
||||
}));
|
||||
const labels = await context.github.issues.listLabelsOnIssue(
|
||||
context.repo({
|
||||
issue_number: prNumber,
|
||||
per_page: 100,
|
||||
page: 1,
|
||||
}),
|
||||
);
|
||||
|
||||
return labels.data.some(label => label.name === labelName);
|
||||
return labels.data.some((label) => label.name === labelName);
|
||||
};
|
||||
|
|
|
@ -7,7 +7,11 @@ import { LogLevel } from '../enums';
|
|||
* @param {LogLevel }logLevel - the severity level of the log
|
||||
* @param {any[]} message - the message to write to console
|
||||
*/
|
||||
export const log = (functionName: string, logLevel: LogLevel, ...message: any[]) => {
|
||||
export const log = (
|
||||
functionName: string,
|
||||
logLevel: LogLevel,
|
||||
...message: any[]
|
||||
) => {
|
||||
const output = `${functionName}: ${message}`;
|
||||
|
||||
if (logLevel === LogLevel.INFO) {
|
||||
|
|
|
@ -9,7 +9,10 @@ import { LogLevel } from '../enums';
|
|||
* @param {Context} context - the context of the event that was triggered
|
||||
* @returns {Promise<string>} - a string representing a GitHub App installation token
|
||||
*/
|
||||
export const getRepoToken = async (robot: Application, context: Context): Promise<string> => {
|
||||
export const getRepoToken = async (
|
||||
robot: Application,
|
||||
context: Context,
|
||||
): Promise<string> => {
|
||||
log('getRepoToken', LogLevel.INFO, 'Creating GitHub App token');
|
||||
|
||||
const hub = await robot.auth();
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"extends": "tslint-config-airbnb",
|
||||
"rules": {
|
||||
"import-name": false,
|
||||
"max-line-length": [true, 140],
|
||||
"prefer-array-literal": false
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче