1
1
// @ts -check
2
2
/// <reference lib="esnext.asynciterable" />
3
3
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 ;
5
8
6
9
/**
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).
8
15
*/
9
16
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
14
23
}
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` ;
16
28
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
-
28
29
// Forcibly cleanup workspace
29
30
runSequence ( [
30
31
[ "git" , [ "clean" , "-fdx" ] ] ,
31
32
[ "git" , [ "checkout" , "." ] ] ,
32
33
[ "git" , [ "checkout" , "master" ] ] ,
34
+ [ "git" , [ "remote" , "add" , "fork" , remoteUrl ] ] , // Add the remote fork
35
+ [ "git" , [ "fetch" , "origin" , "master:master" ] ] ,
33
36
] ) ;
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
56
60
}
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 } ` ) ;
58
71
}
59
- // Merge is good - apply a rebase and (force) push
60
- runSequence ( [
61
- [ "git" , [ "rebase" , "master" ] ] ,
62
- [ "git" , [ "push" , "-f" , "-u" , "origin" , branch ] ] ,
63
- ] ) ;
64
72
}
65
73
66
74
// Return to `master` and make a new `experimental` branch
@@ -71,17 +79,17 @@ async function main() {
71
79
] ) ;
72
80
73
81
// 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 ) {
75
83
// Find the merge base
76
84
const mergeBase = runSequence ( [
77
85
[ "git" , [ "merge-base" , branch , "experimental" ] ] ,
78
86
] ) ;
79
87
// Simulate the merge and abort if there are conflicts
80
88
const mergeTree = runSequence ( [
81
- [ "git" , [ "merge-tree" , mergeBase , branch , "experimental" ] ]
89
+ [ "git" , [ "merge-tree" , mergeBase . trim ( ) , branch , "experimental" ] ]
82
90
] ) ;
83
91
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` ) ;
85
93
}
86
94
// Merge (always producing a merge commit)
87
95
runSequence ( [
@@ -90,7 +98,7 @@ async function main() {
90
98
}
91
99
// Every branch merged OK, force push the replacement `experimental` branch
92
100
runSequence ( [
93
- [ "git" , [ "push" , "-f" , "-u" , "origin " , "experimental" ] ] ,
101
+ [ "git" , [ "push" , "-f" , "-u" , "fork " , "experimental" ] ] ,
94
102
] ) ;
95
103
}
96
104
0 commit comments