Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scripts: new attempt-backport script for PRs #90

Merged
merged 6 commits into from
Nov 16, 2016
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
scripts: new attempt-backport script for PRs
Refs: #77
PR-URL: #90
Fishrock123 committed Nov 16, 2016
commit 4a5183fc2bb5c42f3a4731bac04df18b12071e88
1 change: 1 addition & 0 deletions lib/node-repo.js
Original file line number Diff line number Diff line change
@@ -93,6 +93,7 @@ function itemsInCommon (arr1, arr2) {
return arr1.filter((item) => arr2.indexOf(item) !== -1)
}

exports.updatePrWithLabels = updatePrWithLabels
exports.resolveLabelsThenUpdatePr = deferredResolveLabelsThenUpdatePr

// exposed for testability
205 changes: 205 additions & 0 deletions scripts/attempt-backport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
'use strict'

const child_process = require('child_process')
const debug = require('debug')('attempt-backport')
const request = require('request')
const updatePrWithLabels = require('../lib/node-repo').updatePrWithLabels

const enabledRepos = ['node']
const queue = []
let inProgress = false

module.exports = function (app) {
if (!global._node_repo_dir) return

app.on('pull_request.opened', handlePrUpdate)
// Pull Request updates
app.on('pull_request.synchronize', handlePrUpdate)

function handlePrUpdate (event, owner, repo) {
if (!~enabledRepos.indexOf(repo)) return

const prId = event.number
const options = { owner, repo, prId, logger: event.logger }

debug(`/${owner}/${repo}/pull/${prId} sync`)
queueAttemptBackport(options, 7, false)
queueAttemptBackport(options, 6, true)
queueAttemptBackport(options, 4, true)

if (!inProgress) processNextBackport()
}

// to trigger polling manually
app.get('/pr/:owner/:repo/:id', (req, res) => {
const owner = req.params.owner
const repo = req.params.repo
const prId = parseInt(req.params.id, 10)
const options = { owner, repo, prId, logger: req.log }

if (~enabledRepos.indexOf(repo)) {
queueAttemptBackport(options, 7, false)
queueAttemptBackport(options, 6, true)
queueAttemptBackport(options, 4, true)
}

if (!inProgress) processNextBackport()

res.end()
})
}

function processNextBackport() {
const item = queue.shift()
if (!item) return

if (typeof item !== 'function') {
debug(`item was not a function! - queue size: ${queue.length}`)
return
} else if (inProgress) {
debug(`was still in progress! - queue size: ${queue.length}`)
return
}
item()
}

function queueAttemptBackport(options, version, isLTS) {
queue.push(function() {
debug(`processing a new backport to v${version}`)
attemptBackport(options, version, isLTS, processNextBackport)
})
}

function attemptBackport(options, version, isLTS, cb) {
// Start
gitAmAbort()

function wrapCP(cmd, args, opts, callback) {
let exited = false

if (arguments.length === 3) {
callback = opts
opts = {}
}

opts.cwd = global._node_repo_dir

const cp = child_process.spawn(cmd, args, opts)
const argsString = [cmd, ...args].join(' ')

cp.on('error', function(err) {
debug(`child_process err: ${err}`)
if (!exited) onError()
})
cp.on('exit', function(code) {
exited = true
if (!cb) {
debug(`error before exit, code: ${code}, on '${argsString}'`)
return
} else if (code > 0) {
debug(`exit code > 0: ${code}, on '${argsString}'`)
onError()
return
}
callback()
})
// Useful when debugging.
//
// cp.stdout.on('data', (data) => {
// console.log(data.toString())
// })
// cp.stderr.on('data', (data) => {
// console.log(data.toString())
// })

return cp
}

function onError() {
if (!cb) return
const _cb = cb
setImmediate(() => {
debug(`backport to ${version} failed`)
if (!isLTS) updatePrWithLabels(options, [`dont-land-on-v${version}.x`])
setImmediate(() => {
inProgress = false
_cb()
})
})
cb = null
}

function gitAmAbort() {
// TODO(Fishrock123): this should probably just merge into wrapCP
let exited = false
debug(`aborting any previous backport attempt...`)

const cp = child_process.spawn('git', ['am', '--abort'], { cwd: global._node_repo_dir })
const argsString = 'git am --abort'

cp.on('error', function(err) {
debug(`child_process err: ${err}`)
if (!exited) onError()
})
cp.on('exit', function() {
exited = true
if (!cb) {
debug(`error before exit, code: ${code}, on '${argsString}'`)
return
}
gitRemoteUpdate()
})
}

function gitRemoteUpdate() {
debug(`updating git remotes...`)
wrapCP('git', ['remote', 'update', '-p'], gitCheckout)
}

function gitCheckout() {
debug(`checking out upstream/v${version}.x-staging...`)
wrapCP('git', ['checkout', `upstream/v${version}.x-staging`], gitReset)
}

function gitReset() {
debug(`resetting upstream/v${version}.x-staging...`)
wrapCP('git', ['reset', `upstream/v${version}.x-staging`, '--hard'], fetchDiff)
}

function fetchDiff() {
debug(`fetching diff from pr ${options.prId}...`)

const url = `https://patch-diff.githubusercontent.com/raw/${options.owner}/${options.repo}/pull/${options.prId}.patch`

const req = request(url)

req.on('error', function(err) {
debug(`request err: ${err}`)
return onError()
})
req.on('response', function(response) {
if (response.statusCode !== 200) {
debug(`request non-200 status: ${response.statusCode}`)
return onError()
}
})

gitAttemptBackport(req)
}

function gitAttemptBackport(req) {
debug(`attempting a backport to v${version}...`)
const cp = wrapCP('git', ['am'], { stdio: 'pipe' }, function done() {
// Success!
if (isLTS) updatePrWithLabels(options, [`lts-watch-v${version}.x`])

setImmediate(() => {
debug(`backport to v${version} successful`)
inProgress = false
cb()
})
})

req.pipe(cp.stdin)
}
}
15 changes: 15 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
'use strict'

const child_process = require('child_process')

if (process.env.NODE_REPO_DIR) {
const fs = require('fs')
global._node_repo_dir = fs.realpathSync(process.env.NODE_REPO_DIR)
const out = child_process.spawnSync('git', ['status'], { cwd: global._node_repo_dir })

if (out.status !== 0) {
logger.info(out.stdout)
logger.error(out.stderr)
logger.error('Bad NODE_REPO_DIR. Backport patch testing disabled.')
global._node_repo_dir = false
}
}

const app = require('./app')
const logger = require('./lib/logger')