Skip to content

Commit 663b410

Browse files
committed
scripts: new attempt-backport script for PRs
1 parent 9e348cb commit 663b410

File tree

3 files changed

+221
-0
lines changed

3 files changed

+221
-0
lines changed

lib/node-repo.js

+1
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,5 @@ function updatePrWithLabels (options, labels) {
4545
})
4646
}
4747

48+
exports.updatePrWithLabels = updatePrWithLabels
4849
exports.resolveLabelsThenUpdatePr = deferredResolveLabelsThenUpdatePr

scripts/attempt-backport.js

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
'use strict'
2+
3+
const child_process = require('child_process')
4+
const debug = require('debug')('attempt-backport')
5+
const request = require('request')
6+
const updatePrWithLabels = require('../lib/node-repo').updatePrWithLabels
7+
8+
const enabledRepos = ['node']
9+
const queue = []
10+
let inProgress = false
11+
12+
module.exports = function (app) {
13+
if (!global._node_repo_dir) return
14+
15+
app.on('pull_request.opened', handlePrUpdate)
16+
// Pull Request updates
17+
app.on('pull_request.synchronize', handlePrUpdate)
18+
19+
function handlePrUpdate (event, owner, repo) {
20+
if (!~enabledRepos.indexOf(repo)) return
21+
22+
const prId = event.number
23+
const options = { owner, repo, prId, logger: event.logger }
24+
25+
debug(`/${owner}/${repo}/pull/${prId} sync`)
26+
queueAttemptBackport(options, 7, false)
27+
queueAttemptBackport(options, 6, true)
28+
queueAttemptBackport(options, 4, true)
29+
30+
if (!inProgress) processNextBackport()
31+
}
32+
33+
// to trigger polling manually
34+
app.get('/pr/:owner/:repo/:id', (req, res) => {
35+
const owner = req.params.owner
36+
const repo = req.params.repo
37+
const prId = parseInt(req.params.id, 10)
38+
const options = { owner, repo, prId, logger: req.log }
39+
40+
if (~enabledRepos.indexOf(repo)) {
41+
queueAttemptBackport(options, 7, false)
42+
queueAttemptBackport(options, 6, true)
43+
queueAttemptBackport(options, 4, true)
44+
}
45+
46+
if (!inProgress) processNextBackport()
47+
48+
res.end()
49+
})
50+
}
51+
52+
function processNextBackport() {
53+
const item = queue.shift()
54+
if (!item) return
55+
56+
if (typeof item !== 'function') {
57+
debug(`item was not a function! - queue size: ${queue.length}`)
58+
return
59+
} else if (inProgress) {
60+
debug(`was still in progress! - queue size: ${queue.length}`)
61+
return
62+
}
63+
item()
64+
}
65+
66+
function queueAttemptBackport(options, version, isLTS) {
67+
queue.push(function() {
68+
debug(`processing a new backport to v${version}`)
69+
attemptBackport(options, version, isLTS, processNextBackport)
70+
})
71+
}
72+
73+
function attemptBackport(options, version, isLTS, cb) {
74+
// Start
75+
gitAmAbort()
76+
77+
function wrapCP(cmd, args, opts, callback) {
78+
let exited = false
79+
80+
if (arguments.length === 3) {
81+
callback = opts
82+
opts = {}
83+
}
84+
85+
opts.cwd = global._node_repo_dir
86+
87+
const cp = child_process.spawn(cmd, args, opts)
88+
const argsString = [cmd, ...args].join(' ')
89+
90+
cp.on('error', function(err) {
91+
debug(`child_process err: ${err}`)
92+
if (!exited) onError()
93+
})
94+
cp.on('exit', function(code) {
95+
exited = true
96+
if (!cb) {
97+
debug(`error before exit, code: ${code}, on '${argsString}'`)
98+
return
99+
} else if (code > 0) {
100+
debug(`exit code > 0: ${code}, on '${argsString}'`)
101+
onError()
102+
return
103+
}
104+
callback()
105+
})
106+
// Useful when debugging.
107+
//
108+
// cp.stdout.on('data', (data) => {
109+
// console.log(data.toString())
110+
// })
111+
// cp.stderr.on('data', (data) => {
112+
// console.log(data.toString())
113+
// })
114+
115+
return cp
116+
}
117+
118+
function onError() {
119+
if (!cb) return
120+
const _cb = cb
121+
setImmediate(() => {
122+
debug(`backport to ${version} failed`)
123+
if (!isLTS) updatePrWithLabels(options, [`dont-land-on-v${version}.x`])
124+
setImmediate(() => {
125+
inProgress = false
126+
_cb()
127+
})
128+
})
129+
cb = null
130+
}
131+
132+
function gitAmAbort() {
133+
// TODO(Fishrock123): this should probably just merge into wrapCP
134+
let exited = false
135+
debug(`aborting any previous backport attempt...`)
136+
137+
const cp = child_process.spawn('git', ['am', '--abort'], { cwd: global._node_repo_dir })
138+
const argsString = 'git am --abort'
139+
140+
cp.on('error', function(err) {
141+
debug(`child_process err: ${err}`)
142+
if (!exited) onError()
143+
})
144+
cp.on('exit', function() {
145+
exited = true
146+
if (!cb) {
147+
debug(`error before exit, code: ${code}, on '${argsString}'`)
148+
return
149+
}
150+
gitRemoteUpdate()
151+
})
152+
}
153+
154+
function gitRemoteUpdate() {
155+
debug(`updating git remotes...`)
156+
wrapCP('git', ['remote', 'update', '-p'], gitCheckout)
157+
}
158+
159+
function gitCheckout() {
160+
debug(`checking out upstream/v${version}.x-staging...`)
161+
wrapCP('git', ['checkout', `upstream/v${version}.x-staging`], gitReset)
162+
}
163+
164+
function gitReset() {
165+
debug(`resetting upstream/v${version}.x-staging...`)
166+
wrapCP('git', ['reset', `upstream/v${version}.x-staging`, '--hard'], fetchDiff)
167+
}
168+
169+
function fetchDiff() {
170+
debug(`fetching diff from pr ${options.prId}...`)
171+
172+
const url = `https://patch-diff.githubusercontent.com/raw/${options.owner}/${options.repo}/pull/${options.prId}.patch`
173+
174+
const req = request(url)
175+
176+
req.on('error', function(err) {
177+
debug(`request err: ${err}`)
178+
return onError()
179+
})
180+
req.on('response', function(response) {
181+
if (response.statusCode !== 200) {
182+
debug(`request non-200 status: ${response.statusCode}`)
183+
return onError()
184+
}
185+
})
186+
187+
gitAttemptBackport(req)
188+
}
189+
190+
function gitAttemptBackport(req) {
191+
debug(`attempting a backport to v${version}...`)
192+
const cp = wrapCP('git', ['am'], { stdio: 'pipe' }, function done() {
193+
// Success!
194+
if (isLTS) updatePrWithLabels(options, [`lts-watch-v${version}.x`])
195+
196+
setImmediate(() => {
197+
debug(`backport to v${version} successful`)
198+
inProgress = false
199+
cb()
200+
})
201+
})
202+
203+
req.pipe(cp.stdin)
204+
}
205+
}

server.js

+15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
'use strict'
22

3+
const child_process = require('child_process')
4+
5+
if (process.env.NODE_REPO_DIR) {
6+
const fs = require('fs')
7+
global._node_repo_dir = fs.realpathSync(process.env.NODE_REPO_DIR)
8+
const out = child_process.spawnSync('git', ['status'], { cwd: global._node_repo_dir })
9+
10+
if (out.status !== 0) {
11+
logger.info(out.stdout)
12+
logger.error(out.stderr)
13+
logger.error('Bad NODE_REPO_DIR. Backport patch testing disabled.')
14+
global._node_repo_dir = false
15+
}
16+
}
17+
318
const app = require('./app')
419
const logger = require('./lib/logger')
520

0 commit comments

Comments
 (0)