mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-12 02:28:28 -06:00
Experimental management scripts (#31067)
* Add configure-experimental * Add script for synchronizing branches with master and creating an experimental branch with the result of merging those
This commit is contained in:
parent
885d4d63c8
commit
bb1ac81bb1
@ -1,16 +1,7 @@
|
||||
/// <reference lib="esnext.asynciterable" />
|
||||
// Must reference esnext.asynciterable lib, since octokit uses AsyncIterable internally
|
||||
import cp = require("child_process");
|
||||
import Octokit = require("@octokit/rest");
|
||||
|
||||
const opts = { timeout: 100_000, shell: true, stdio: "inherit" }
|
||||
function runSequence(tasks: [string, string[]][]) {
|
||||
for (const task of tasks) {
|
||||
console.log(`${task[0]} ${task[1].join(" ")}`);
|
||||
const result = cp.spawnSync(task[0], task[1], opts);
|
||||
if (result.status !== 0) throw new Error(`${task[0]} ${task[1].join(" ")} failed: ${result.stderr && result.stderr.toString()}`);
|
||||
}
|
||||
}
|
||||
import {runSequence} from "./run-sequence";
|
||||
|
||||
function padNum(number: number) {
|
||||
const str = "" + number;
|
||||
|
||||
19
scripts/run-sequence.js
Normal file
19
scripts/run-sequence.js
Normal file
@ -0,0 +1,19 @@
|
||||
// @ts-check
|
||||
const cp = require("child_process");
|
||||
/**
|
||||
*
|
||||
* @param {[string, string[]][]} tasks
|
||||
* @param {cp.SpawnSyncOptions} opts
|
||||
*/
|
||||
function runSequence(tasks, opts = { timeout: 100000, shell: true, stdio: "inherit" }) {
|
||||
let lastResult;
|
||||
for (const task of tasks) {
|
||||
console.log(`${task[0]} ${task[1].join(" ")}`);
|
||||
const result = cp.spawnSync(task[0], task[1], opts);
|
||||
if (result.status !== 0) throw new Error(`${task[0]} ${task[1].join(" ")} failed: ${result.stderr && result.stderr.toString()}`);
|
||||
lastResult = result;
|
||||
}
|
||||
return lastResult && lastResult.stdout && lastResult.stdout.toString();
|
||||
}
|
||||
|
||||
exports.runSequence = runSequence;
|
||||
97
scripts/update-experimental-branches.js
Normal file
97
scripts/update-experimental-branches.js
Normal file
@ -0,0 +1,97 @@
|
||||
// @ts-check
|
||||
/// <reference lib="esnext.asynciterable" />
|
||||
const Octokit = require("@octokit/rest");
|
||||
const {runSequence} = require("./run-sequence");
|
||||
|
||||
/**
|
||||
* This program should be invoked as `node ./scripts/update-experimental-branches <GithubAccessToken> <Branch1> [Branch2] [...]`
|
||||
*/
|
||||
async function main() {
|
||||
const branchesRaw = process.argv[3];
|
||||
const branches = process.argv.slice(3);
|
||||
if (!branches.length) {
|
||||
throw new Error(`No experimental branches, aborting...`);
|
||||
}
|
||||
console.log(`Performing experimental branch updating and merging for branches ${branchesRaw}`);
|
||||
|
||||
const gh = new Octokit();
|
||||
gh.authenticate({
|
||||
type: "token",
|
||||
token: process.argv[2]
|
||||
});
|
||||
|
||||
// Fetch all relevant refs
|
||||
runSequence([
|
||||
["git", ["fetch", "origin", "master:master", ...branches.map(b => `${b}:${b}`)]]
|
||||
])
|
||||
|
||||
// Forcibly cleanup workspace
|
||||
runSequence([
|
||||
["git", ["clean", "-fdx"]],
|
||||
["git", ["checkout", "."]],
|
||||
["git", ["checkout", "master"]],
|
||||
]);
|
||||
|
||||
// Update branches
|
||||
for (const branch of branches) {
|
||||
// Checkout, then get the merge base
|
||||
const mergeBase = runSequence([
|
||||
["git", ["checkout", branch]],
|
||||
["git", ["merge-base", branch, "master"]],
|
||||
]);
|
||||
// Simulate the merge and abort if there are conflicts
|
||||
const mergeTree = runSequence([
|
||||
["git", ["merge-tree", mergeBase, branch, "master"]]
|
||||
]);
|
||||
if (mergeTree.indexOf(`===${"="}===`)) { // 7 equals is the center of the merge conflict marker
|
||||
const res = await gh.pulls.list({owner: "Microsoft", repo: "TypeScript", base: branch});
|
||||
if (res && res.data && res.data[0]) {
|
||||
const pr = res.data[0];
|
||||
await gh.issues.createComment({
|
||||
owner: "Microsoft",
|
||||
repo: "TypeScript",
|
||||
number: pr.number,
|
||||
body: `This PR is configured as an experiment, and currently has merge conflicts with master - please rebase onto master and fix the conflicts.`
|
||||
});
|
||||
}
|
||||
throw new Error(`Merge conflict detected on branch ${branch} with master`);
|
||||
}
|
||||
// Merge is good - apply a rebase and (force) push
|
||||
runSequence([
|
||||
["git", ["rebase", "master"]],
|
||||
["git", ["push", "-f", "-u", "origin", branch]],
|
||||
]);
|
||||
}
|
||||
|
||||
// Return to `master` and make a new `experimental` branch
|
||||
runSequence([
|
||||
["git", ["checkout", "master"]],
|
||||
["git", ["branch", "-D", "experimental"]],
|
||||
["git", ["checkout", "-b", "experimental"]],
|
||||
]);
|
||||
|
||||
// Merge each branch into `experimental` (which, if there is a conflict, we now know is from inter-experiment conflict)
|
||||
for (const branch of branches) {
|
||||
// Find the merge base
|
||||
const mergeBase = runSequence([
|
||||
["git", ["merge-base", branch, "experimental"]],
|
||||
]);
|
||||
// Simulate the merge and abort if there are conflicts
|
||||
const mergeTree = runSequence([
|
||||
["git", ["merge-tree", mergeBase, branch, "experimental"]]
|
||||
]);
|
||||
if (mergeTree.indexOf(`===${"="}===`)) { // 7 equals is the center of the merge conflict marker
|
||||
throw new Error(`Merge conflict detected on branch ${branch} with other experiment`);
|
||||
}
|
||||
// Merge (always producing a merge commit)
|
||||
runSequence([
|
||||
["git", ["merge", branch, "--no-ff"]],
|
||||
]);
|
||||
}
|
||||
// Every branch merged OK, force push the replacement `experimental` branch
|
||||
runSequence([
|
||||
["git", ["push", "-f", "-u", "origin", "experimental"]],
|
||||
]);
|
||||
}
|
||||
|
||||
main().catch(e => (console.error(e), process.exitCode = 2));
|
||||
Loading…
x
Reference in New Issue
Block a user