Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bisect improvements #36

Merged
merged 2 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ $ yarn test
...
```

Run or debug locally
```bash
yarn build

GITHUB_WORKSPACE=/absolute/path/to/TypeScript \
GITHUB_TOKEN=$token \ # will comment without DRY!
DRY=1 \ # do not post results comment
ISSUE=50635 \ # optional
BISECT="good 4.7.3 bad main" \ # optional
node lib/_main.js
```

## To publish

Actions are run from GitHub repos so we will check-in the packed dist folder.
Expand Down
6 changes: 5 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ inputs:
description: 'What tag should be applied to the markdown code block?'
default: 'repro'

issue:
required: false
description: 'Limits analysis to a single issue'

bisect:
required: false
description: 'Request a git bisect against an issue that already has a repro. Value is the issue number.'
description: 'Runs a git bisect on an existing repro. Requires `issue` to be set. Value can be revision labels (e.g. `good v4.7.3 bad main`) or `true` to infer bisect range.'

runs:
using: 'node16'
Expand Down
86 changes: 51 additions & 35 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,19 +83,21 @@ const getContext = () => {
const workspace = process.env.GITHUB_WORKSPACE;
const label = (0, core_1.getInput)('label') || 'Has Repro';
const tag = (0, core_1.getInput)('code-tag') || 'repro';
const runIssue = (0, core_1.getInput)('issue') || process.env.ISSUE;
const bisectIssue = (0, core_1.getInput)('bisect') || process.env.BISECT_ISSUE;
const issue = (0, core_1.getInput)('issue') || process.env.ISSUE;
const bisect = (0, core_1.getInput)('bisect') || process.env.BISECT;
const owner = repo.split('/')[0];
const name = repo.split('/')[1];
const dryRun = !!process.env.DRY;
const ctx = {
token,
owner,
name,
label,
tag,
runIssue,
bisectIssue,
workspace
issue,
bisect,
workspace,
dryRun
};
return ctx;
};
Expand All @@ -120,8 +122,8 @@ async function getIssue(context, issue) {
exports.getIssue = getIssue;
async function getIssues(context) {
const octokit = (0, github_1.getOctokit)(context.token);
if (context.runIssue) {
return [await getIssue(context, parseInt(context.runIssue, 10))];
if (context.issue) {
return [await getIssue(context, parseInt(context.issue, 10))];
}
const req = issuesQuery(context.owner, context.name, context.label);
const initialIssues = (await octokit.graphql(req.query, Object.assign({}, req.vars)));
Expand Down Expand Up @@ -265,7 +267,8 @@ async function gitBisectTypeScript(context, issue) {
const requests = (0, getRequestsFromIssue_1.getRequestsFromIssue)(context)(issue);
const request = requests[requests.length - 1];
const resultComment = request && (0, getExistingComments_1.getResultCommentInfoForRequest)(issue.comments.nodes, request);
const bisectRevisions = getRevisionsFromComment(issue, request, context) ||
const bisectRevisions = getRevisionsFromContext(context, request) ||
getRevisionsFromComment(issue, request, context) ||
(resultComment && getRevisionsFromPreviousRun(resultComment, context));
if (!bisectRevisions)
return;
Expand Down Expand Up @@ -317,37 +320,43 @@ function getRevisionsFromPreviousRun(resultComment, context) {
const oldRef = `v${oldResult.label}`;
const newRef = newResult.label === 'Nightly' ? resultComment.info.typescriptSha : `v${newResult.label}`;
const oldMergeBase = (0, child_process_1.execSync)(`git merge-base ${oldRef} main`, { cwd: context.workspace, encoding: 'utf8' }).trim();
const newMergeBase = (0, child_process_1.execSync)(`git merge-base ${newRef} main`, { cwd: context.workspace, encoding: 'utf8' }).trim();
return {
oldRef: oldMergeBase,
newRef: newMergeBase,
newRef,
oldLabel: oldResult.label,
newLabel: newResult.label,
oldResult
};
}
}
const bisectCommentRegExp = /^@typescript-bot bisect (?:this )?(?:good|old) ([^\s]+) (?:bad|new) ([^\s]+)/;
const bisectCommentRegExp = /^(?:@typescript-bot bisect (?:this )?)?(?:good|old) ([^\s]+) (?:bad|new) ([^\s]+)/;
function getRevisionsFromComment(issue, request, context) {
for (let i = issue.comments.nodes.length - 1; i >= 0; i--) {
const comment = issue.comments.nodes[i];
const match = comment.body.match(bisectCommentRegExp);
if (match) {
const [, oldLabel, newLabel] = match;
const oldRef = (0, child_process_1.execSync)(`git merge-base ${oldLabel} main`, { cwd: context.workspace, encoding: 'utf8' }).trim();
const newRef = (0, child_process_1.execSync)(`git merge-base ${newLabel} main`, { cwd: context.workspace, encoding: 'utf8' }).trim();
(0, child_process_1.execSync)(`git checkout ${oldRef}`, { cwd: context.workspace });
const oldResult = buildAndRun(request, context);
return {
oldRef,
newRef,
oldLabel,
newLabel,
oldResult
};
}
const revs = tryGetRevisionsFromText(comment.body, request, context);
if (revs)
return revs;
}
}
function tryGetRevisionsFromText(text, request, context) {
const match = text.match(bisectCommentRegExp);
if (match) {
const [, oldLabel, newLabel] = match;
const oldRef = (0, child_process_1.execSync)(`git merge-base ${oldLabel} main`, { cwd: context.workspace, encoding: 'utf8' }).trim();
(0, child_process_1.execSync)(`git checkout ${oldRef}`, { cwd: context.workspace });
const oldResult = buildAndRun(request, context);
return {
oldRef,
newRef: newLabel,
oldLabel,
newLabel,
oldResult
};
}
}
function getRevisionsFromContext(context, request) {
return tryGetRevisionsFromText(context.bisect, request, context);
}
function buildAndRun(request, context) {
try {
// Try building without npm install for speed, it will work a fair amount of the time
Expand Down Expand Up @@ -474,12 +483,14 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.createCommentText = exports.updateIssue = exports.postBisectComment = exports.fixOrDeleteOldComments = void 0;
const getExistingComments_1 = __nccwpck_require__(2408);
const getTypeScriptNightlyVersion_1 = __nccwpck_require__(1371);
async function fixOrDeleteOldComments(issue, api) {
async function fixOrDeleteOldComments(issue, api, context) {
const outdatedComments = (0, getExistingComments_1.getAllTypeScriptBotComments)(issue.comments.nodes)
.filter(c => c.info.version !== 1)
.map(c => c.comment);
for (const comment of outdatedComments) {
await api.deleteComment(comment.id);
if (!context.dryRun) {
for (const comment of outdatedComments) {
await api.deleteComment(comment.id);
}
}
if (outdatedComments.length) {
return Object.assign(Object.assign({}, issue), { comments: { nodes: issue.comments.nodes.filter(c => !outdatedComments.includes(c)) } });
Expand Down Expand Up @@ -20052,11 +20063,14 @@ async function run() {
const ctx = (0, getContext_1.getContext)();
const api = (0, api_1.createAPI)(ctx);
console.log(`Context: ${JSON.stringify(ctx, null, ' ')}`);
if (ctx.bisectIssue) {
let issue = await (0, getIssues_1.getIssue)(ctx, parseInt(ctx.bisectIssue, 10));
issue = await (0, updatesIssue_1.fixOrDeleteOldComments)(issue, api);
if (ctx.bisect) {
if (!ctx.issue) {
throw new Error('Must provide an issue number to bisect');
}
let issue = await (0, getIssues_1.getIssue)(ctx, parseInt(ctx.issue, 10));
issue = await (0, updatesIssue_1.fixOrDeleteOldComments)(issue, api, ctx);
const result = await (0, gitBisectTypeScript_1.gitBisectTypeScript)(ctx, issue);
if (result) {
if (result && !ctx.dryRun) {
await (0, updatesIssue_1.postBisectComment)(issue, result, api);
}
return;
Expand All @@ -20068,11 +20082,13 @@ async function run() {
process.stdout.write('.');
if (issues.indexOf(issue) % 10)
console.log('');
issue = await (0, updatesIssue_1.fixOrDeleteOldComments)(issue, api);
issue = await (0, updatesIssue_1.fixOrDeleteOldComments)(issue, api, ctx);
const requests = (0, getRequestsFromIssue_1.getRequestsFromIssue)(ctx)(issue);
for (const request of requests) {
const results = (0, runTwoslashRequests_1.runTwoslashRequests)(issue, request);
await (0, updatesIssue_1.updateIssue)(request, issue, results, api);
if (!ctx.dryRun) {
await (0, updatesIssue_1.updateIssue)(request, issue, results, api);
}
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/__tests__/getRequestsFromIssue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ const testCtx: Context = {
token: '123456',
tag: 'repro',
workspace: '',
bisectIssue: undefined,
runIssue: undefined,
bisect: undefined,
issue: undefined,
dryRun: false,
}

const oneCodeBlock = ` comment blah
Expand Down
17 changes: 11 additions & 6 deletions src/_main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ async function run() {
const api = createAPI(ctx)
console.log(`Context: ${JSON.stringify(ctx, null, ' ')}`)

if (ctx.bisectIssue) {
let issue = await getIssue(ctx, parseInt(ctx.bisectIssue, 10))
issue = await fixOrDeleteOldComments(issue, api)
if (ctx.bisect) {
if (!ctx.issue) {
throw new Error('Must provide an issue number to bisect')
}
let issue = await getIssue(ctx, parseInt(ctx.issue, 10))
issue = await fixOrDeleteOldComments(issue, api, ctx)

const result = await gitBisectTypeScript(ctx, issue)
if (result) {
if (result && !ctx.dryRun) {
await postBisectComment(issue, result, api)
}
return
Expand All @@ -32,11 +35,13 @@ async function run() {
process.stdout.write('.')
if (issues.indexOf(issue) % 10) console.log('')

issue = await fixOrDeleteOldComments(issue, api)
issue = await fixOrDeleteOldComments(issue, api, ctx)
const requests = getRequestsFromIssue(ctx)(issue)
for (const request of requests) {
const results = runTwoslashRequests(issue, request)
await updateIssue(request, issue, results, api)
if (!ctx.dryRun) {
await updateIssue(request, issue, results, api)
}
}
}
}
Expand Down
26 changes: 19 additions & 7 deletions src/getContext.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
import {getInput} from '@actions/core'

export type Context = ReturnType<typeof getContext>
export interface Context {
token: string
owner: string
name: string
label: string
tag: string
issue: string | undefined
bisect: string | undefined
workspace: string
dryRun: boolean
}

export const getContext = () => {
const token = getInput('github-token') || process.env.GITHUB_TOKEN!
const repo = getInput('repo') || process.env.GITHUB_REPOSITORY || 'microsoft/TypeScript'
const workspace = process.env.GITHUB_WORKSPACE!
const label = getInput('label') || 'Has Repro'
const tag = getInput('code-tag') || 'repro'
const runIssue = getInput('issue') || process.env.ISSUE
const bisectIssue = getInput('bisect') || (process.env.BISECT_ISSUE as string | undefined)
const issue = getInput('issue') || process.env.ISSUE
const bisect = getInput('bisect') || (process.env.BISECT as string | undefined)
const owner = repo.split('/')[0]
const name = repo.split('/')[1]
const dryRun = !!process.env.DRY

const ctx = {
const ctx: Context = {
token,
owner,
name,
label,
tag,
runIssue,
bisectIssue,
workspace
issue,
bisect,
workspace,
dryRun
}

return ctx
Expand Down
4 changes: 2 additions & 2 deletions src/getIssues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export async function getIssue(context: Context, issue: number): Promise<Issue>

export async function getIssues(context: Context): Promise<Issue[]> {
const octokit = getOctokit(context.token)
if (context.runIssue) {
return [await getIssue(context, parseInt(context.runIssue, 10))]
if (context.issue) {
return [await getIssue(context, parseInt(context.issue, 10))]
}

const req = issuesQuery(context.owner, context.name, context.label)
Expand Down
46 changes: 29 additions & 17 deletions src/gitBisectTypeScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export async function gitBisectTypeScript(context: Context, issue: Issue): Promi
const request = requests[requests.length - 1]
const resultComment = request && getResultCommentInfoForRequest(issue.comments.nodes, request)
const bisectRevisions =
getRevisionsFromContext(context, request) ||
getRevisionsFromComment(issue, request, context) ||
(resultComment && getRevisionsFromPreviousRun(resultComment, context))
if (!bisectRevisions) return
Expand Down Expand Up @@ -85,43 +86,54 @@ function getRevisionsFromPreviousRun(
const oldRef = `v${oldResult.label}`
const newRef = newResult.label === 'Nightly' ? resultComment.info.typescriptSha : `v${newResult.label}`
const oldMergeBase = execSync(`git merge-base ${oldRef} main`, {cwd: context.workspace, encoding: 'utf8'}).trim()
const newMergeBase = execSync(`git merge-base ${newRef} main`, {cwd: context.workspace, encoding: 'utf8'}).trim()
return {
oldRef: oldMergeBase,
newRef: newMergeBase,
newRef,
oldLabel: oldResult.label,
newLabel: newResult.label,
oldResult
}
}
}

const bisectCommentRegExp = /^@typescript-bot bisect (?:this )?(?:good|old) ([^\s]+) (?:bad|new) ([^\s]+)/
const bisectCommentRegExp = /^(?:@typescript-bot bisect (?:this )?)?(?:good|old) ([^\s]+) (?:bad|new) ([^\s]+)/
function getRevisionsFromComment(
issue: Issue,
request: TwoslashRequest,
context: Context
): BisectRevisions | undefined {
for (let i = issue.comments.nodes.length - 1; i >= 0; i--) {
const comment = issue.comments.nodes[i]
const match = comment.body.match(bisectCommentRegExp)
if (match) {
const [, oldLabel, newLabel] = match
const oldRef = execSync(`git merge-base ${oldLabel} main`, {cwd: context.workspace, encoding: 'utf8'}).trim()
const newRef = execSync(`git merge-base ${newLabel} main`, {cwd: context.workspace, encoding: 'utf8'}).trim()
execSync(`git checkout ${oldRef}`, {cwd: context.workspace})
const oldResult = buildAndRun(request, context)
return {
oldRef,
newRef,
oldLabel,
newLabel,
oldResult
}
const revs = tryGetRevisionsFromText(comment.body, request, context)
if (revs) return revs
}
}

function tryGetRevisionsFromText(
text: string,
request: TwoslashRequest,
context: Context
): BisectRevisions | undefined {
const match = text.match(bisectCommentRegExp)
if (match) {
const [, oldLabel, newLabel] = match
const oldRef = execSync(`git merge-base ${oldLabel} main`, {cwd: context.workspace, encoding: 'utf8'}).trim()
execSync(`git checkout ${oldRef}`, {cwd: context.workspace})
const oldResult = buildAndRun(request, context)
return {
oldRef,
newRef: newLabel,
oldLabel,
newLabel,
oldResult
}
}
}

function getRevisionsFromContext(context: Context, request: TwoslashRequest): BisectRevisions | undefined {
return tryGetRevisionsFromText(context.bisect!, request, context)
}

function buildAndRun(request: TwoslashRequest, context: Context) {
try {
// Try building without npm install for speed, it will work a fair amount of the time
Expand Down