Skip to content

Commit 9da0524

Browse files
committed
Rewire experimental update script to handle PR triggers
1 parent 49c44f6 commit 9da0524

File tree

1 file changed

+58
-50
lines changed

1 file changed

+58
-50
lines changed

scripts/update-experimental-branches.js

+58-50
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,74 @@
11
// @ts-check
22
/// <reference lib="esnext.asynciterable" />
33
const Octokit = require("@octokit/rest");
4-
const {runSequence} = require("./run-sequence");
4+
const { runSequence } = require("./run-sequence");
5+
6+
// The first is used by bot-based kickoffs, the second by automatic triggers
7+
const triggeredPR = process.env.SOURCE_ISSUE || process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER;
58

69
/**
7-
* This program should be invoked as `node ./scripts/update-experimental-branches <GithubAccessToken> <Branch1> [Branch2] [...]`
10+
* This program should be invoked as `node ./scripts/update-experimental-branches <GithubAccessToken> <PR1> [PR2] [...]`
11+
* The order PR numbers are passed controls the order in which they are merged together.
12+
* TODO: the following is racey - if two experiment-enlisted PRs trigger simultaneously and witness one another in an unupdated state, they'll both produce
13+
* 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
14+
* of this task to 1 (so only one job is allowed to update experiments at a time).
815
*/
916
async function main() {
10-
const branchesRaw = process.argv[3];
11-
const branches = process.argv.slice(3);
12-
if (!branches.length) {
13-
throw new Error(`No experimental branches, aborting...`);
17+
const prnums = process.argv.slice(3);
18+
if (!prnums.length) {
19+
return; // No enlisted PRs, nothing to update
20+
}
21+
if (!prnums.some(n => n === triggeredPR)) {
22+
return; // Only have work to do for enlisted PRs
1423
}
15-
console.log(`Performing experimental branch updating and merging for branches ${branchesRaw}`);
24+
console.log(`Performing experimental branch updating and merging for pull requests ${prnums.join(", ")}`);
25+
26+
const userName = process.env.GH_USERNAME;
27+
const remoteUrl = `https://${process.argv[2]}@github.com/${userName}/TypeScript.git`;
1628

17-
const gh = new Octokit();
18-
gh.authenticate({
19-
type: "token",
20-
token: process.argv[2]
21-
});
22-
23-
// Fetch all relevant refs
24-
runSequence([
25-
["git", ["fetch", "origin", "master:master", ...branches.map(b => `${b}:${b}`)]]
26-
])
27-
2829
// Forcibly cleanup workspace
2930
runSequence([
3031
["git", ["clean", "-fdx"]],
3132
["git", ["checkout", "."]],
3233
["git", ["checkout", "master"]],
34+
["git", ["remote", "add", "fork", remoteUrl]], // Add the remote fork
35+
["git", ["fetch", "origin", "master:master"]],
3336
]);
34-
35-
// Update branches
36-
for (const branch of branches) {
37-
// Checkout, then get the merge base
38-
const mergeBase = runSequence([
39-
["git", ["checkout", branch]],
40-
["git", ["merge-base", branch, "master"]],
41-
]);
42-
// Simulate the merge and abort if there are conflicts
43-
const mergeTree = runSequence([
44-
["git", ["merge-tree", mergeBase, branch, "master"]]
45-
]);
46-
if (mergeTree.indexOf(`===${"="}===`)) { // 7 equals is the center of the merge conflict marker
47-
const res = await gh.pulls.list({owner: "Microsoft", repo: "TypeScript", base: branch});
48-
if (res && res.data && res.data[0]) {
49-
const pr = res.data[0];
50-
await gh.issues.createComment({
51-
owner: "Microsoft",
52-
repo: "TypeScript",
53-
number: pr.number,
54-
body: `This PR is configured as an experiment, and currently has merge conflicts with master - please rebase onto master and fix the conflicts.`
55-
});
37+
38+
const gh = new Octokit();
39+
gh.authenticate({
40+
type: "token",
41+
token: process.argv[2]
42+
});
43+
for (const numRaw of prnums) {
44+
const num = +numRaw;
45+
if (num) {
46+
// PR number rather than branch name - lookup info
47+
const inputPR = await gh.pulls.get({ owner: "Microsoft", repo: "TypeScript", number: num });
48+
// GH calculates the rebaseable-ness of a PR into its target, so we can just use that here
49+
if (!inputPR.data.rebaseable) {
50+
if (+triggeredPR === num) {
51+
await gh.issues.createComment({
52+
owner: "Microsoft",
53+
repo: "TypeScript",
54+
number: num,
55+
body: `This PR is configured as an experiment, and currently has merge conflicts with master - please rebase onto master and fix the conflicts.`
56+
});
57+
throw new Error(`Merge conflict detected in PR ${num} with master`);
58+
}
59+
return; // A PR is currently in conflict, give up
5660
}
57-
throw new Error(`Merge conflict detected on branch ${branch} with master`);
61+
runSequence([
62+
["git", ["fetch", "origin", `pull/${num}/head:${num}`]],
63+
["git", ["checkout", `${num}`]],
64+
["git", ["rebase", "master"]],
65+
["git", ["push", "-f", "-u", "fork", `${num}`]], // Keep a rebased copy of this branch in our fork
66+
]);
67+
68+
}
69+
else {
70+
throw new Error(`Invalid PR number: ${numRaw}`);
5871
}
59-
// Merge is good - apply a rebase and (force) push
60-
runSequence([
61-
["git", ["rebase", "master"]],
62-
["git", ["push", "-f", "-u", "origin", branch]],
63-
]);
6472
}
6573

6674
// Return to `master` and make a new `experimental` branch
@@ -71,17 +79,17 @@ async function main() {
7179
]);
7280

7381
// Merge each branch into `experimental` (which, if there is a conflict, we now know is from inter-experiment conflict)
74-
for (const branch of branches) {
82+
for (const branch of prnums) {
7583
// Find the merge base
7684
const mergeBase = runSequence([
7785
["git", ["merge-base", branch, "experimental"]],
7886
]);
7987
// Simulate the merge and abort if there are conflicts
8088
const mergeTree = runSequence([
81-
["git", ["merge-tree", mergeBase, branch, "experimental"]]
89+
["git", ["merge-tree", mergeBase.trim(), branch, "experimental"]]
8290
]);
8391
if (mergeTree.indexOf(`===${"="}===`)) { // 7 equals is the center of the merge conflict marker
84-
throw new Error(`Merge conflict detected on branch ${branch} with other experiment`);
92+
throw new Error(`Merge conflict detected involving PR ${branch} with other experiment`);
8593
}
8694
// Merge (always producing a merge commit)
8795
runSequence([
@@ -90,7 +98,7 @@ async function main() {
9098
}
9199
// Every branch merged OK, force push the replacement `experimental` branch
92100
runSequence([
93-
["git", ["push", "-f", "-u", "origin", "experimental"]],
101+
["git", ["push", "-f", "-u", "fork", "experimental"]],
94102
]);
95103
}
96104

0 commit comments

Comments
 (0)