import { Octokit } from "@octokit/rest";
import { runSequence } from "./run-sequence.mjs";
const triggeredPR = process.env.SOURCE_ISSUE || process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER;
* This program should be invoked as `node ./scripts/update-experimental-branches <GithubAccessToken>`
* TODO: the following is racey - if two experiment-enlisted PRs trigger simultaneously and witness one another in an unupdated state, they'll both produce
* a new experimental branch, but each will be missing a change from the other. There's no _great_ way to fix this beyond setting the maximum concurrency
* of this task to 1 (so only one job is allowed to update experiments at a time).
*/
async function main() {
const gh = new Octokit({
auth: process.argv[2]
});
const prnums = (await gh.issues.listForRepo({
labels: "typescript@experimental",
sort: "created",
state: "open",
owner: "Microsoft",
repo: "TypeScript",
})).data.filter(i => !!i.pull_request).map(i => i.number);
if (triggeredPR && !prnums.some(n => n === +triggeredPR)) {
return;
}
console.log(`Performing experimental branch updating and merging for pull requests ${prnums.join(", ")}`);
const userName = process.env.GH_USERNAME;
const remoteUrl = `https://${process.argv[2]}@github.com/${userName}/TypeScript.git`;
runSequence([
["git", ["checkout", "."]],
["git", ["fetch", "-fu", "origin", "main:main"]],
["git", ["checkout", "main"]],
["git", ["remote", "add", "fork", remoteUrl]],
]);
for (const numRaw of prnums) {
const num = +numRaw;
if (num) {
const inputPR = await gh.pulls.get({ owner: "Microsoft", repo: "TypeScript", pull_number: num });
if (!inputPR.data.rebaseable) {
if (+(triggeredPR ?? 0) === num) {
await gh.issues.createComment({
owner: "Microsoft",
repo: "TypeScript",
issue_number: num,
body: `This PR is configured as an experiment, and currently has rebase conflicts with main - please rebase onto main and fix the conflicts.`
});
}
throw new Error(`Rebase conflict detected in PR ${num} with main`);
}
runSequence([
["git", ["fetch", "origin", `pull/${num}/head:${num}`]],
["git", ["checkout", `${num}`]],
["git", ["rebase", "main"]],
["git", ["push", "-f", "-u", "fork", `${num}`]],
]);
}
else {
throw new Error(`Invalid PR number: ${numRaw}`);
}
}
runSequence([
["git", ["checkout", "main"]],
["git", ["checkout", "-b", "experimental"]],
]);
for (const branchnum of prnums) {
const branch = "" + branchnum;
const mergeBase = runSequence([
["git", ["merge-base", branch, "experimental"]],
]);
const mergeTree = runSequence([
["git", ["merge-tree", mergeBase.trim(), branch, "experimental"]]
]);
if (mergeTree.indexOf(`===${"="}===`) >= 0) {
throw new Error(`Merge conflict detected involving PR ${branch} with other experiment`);
}
runSequence([
["git", ["merge", branch, "--no-ff"]],
]);
}
runSequence([
["git", ["push", "-f", "-u", "fork", "experimental"]],
]);
}
main().catch(e => (console.error(e), process.exitCode = 2));