11// @ts -check
22/// <reference lib="esnext.asynciterable" />
33const 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 */
916async 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