Skip to content

Commit

Permalink
fix(parse): work with unknown "from" versions
Browse files Browse the repository at this point in the history
dependabot sometimes sends PRs without a "from" version number, this breaks the range detection
logic, the work around is to not fail if no "from" version is declared, and to fallback to manual
merging when version range comparision is required

Fixes #31
  • Loading branch information
Ahmad Nassri committed Oct 15, 2020
1 parent 94be032 commit 92c24d0
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 9 deletions.
23 changes: 19 additions & 4 deletions action/lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ export default function ({ title, labels = [], config = [], dependencies = {} })
const from = title.match(new RegExp('from v?' + regex.semver.source))?.groups
const to = title.match(new RegExp('to v?' + regex.semver.source))?.groups

if (!from || !to) {
if (!to) {
core.warning('failed to parse title: no recognizable versions')
return process.exit(0) // soft exit
}

// exit early
if (!semver.valid(from.version) || !semver.valid(to.version)) {
if (!semver.valid(to.version)) {
core.warning('failed to parse title: invalid semver')
return process.exit(0) // soft exit
}
Expand All @@ -78,13 +78,17 @@ export default function ({ title, labels = [], config = [], dependencies = {} })
}

// log
core.info(`from: ${from.version}`)
core.info(`from: ${from ? from.version : 'unknown'}`)
core.info(`to: ${to.version}`)
core.info(`dependency type: ${isProd ? 'production' : 'development'}`)
core.info(`security critical: ${isSecurity}`)

// analyze with semver
const versionChange = semver.diff(from.version, to.version)
let versionChange

if (from && from.version) {
versionChange = semver.diff(from.version, to.version)
}

// check all configuration variants to see if one matches
for (const { match: { dependency_name, dependency_type, update_type } } of config) {
Expand Down Expand Up @@ -119,6 +123,17 @@ export default function ({ title, labels = [], config = [], dependencies = {} })
// skip when config is for security update and PR is not security
if (type === 'security' && !isSecurity) continue

if (target === 'all') {
core.info(`${dependency_name || dependency_type}:${update_type} detected, will auto-merge`)
return true
}

// when there is no "from" version, there is no change detected
if (!versionChange) {
core.warning('no version range detected in PR title')
continue
}

// evaluate weight of detected change
if ((weight[target] || 0) >= (weight[versionChange] || 0)) {
// tell dependabot to merge
Expand Down
3 changes: 2 additions & 1 deletion action/test/parse/dep-name.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ tap.test('parse -> out of range', async assert => {
tap.test('parse -> edge cases', async assert => {
const titles = [
{ message: 'Update rake requirement from 10.4.0 to 13.0.0', name: 'rake' },
{ message: 'Bump actions/cache from v2.0.0 to v2.1.2', name: 'actions/cache' }
{ message: 'Bump actions/cache from v2.0.0 to v2.1.2', name: 'actions/cache' },
{ message: 'Update actions/setup-python requirement to v2.1.4', name: 'actions/setup-python' }
]

assert.plan(titles.length * 3)
Expand Down
90 changes: 86 additions & 4 deletions action/test/parse/match-target.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import fs from 'fs'
// module
import parse from '../../lib/parse.js'

function config (target) {
return [{ match: { dependency_type: 'all', update_type: `semver:${target}` } }]
function config (update_type) {
return [{ match: { dependency_type: 'all', update_type } }]
}

tap.test('title -> in range', async assert => {
Expand All @@ -20,7 +20,7 @@ tap.test('title -> in range', async assert => {

const options = {
title: 'chore(deps): bump api-problem from 6.1.2 to 6.1.4 in /path',
config: config('major')
config: config('semver:major')
}

assert.ok(parse(options))
Expand All @@ -36,6 +36,60 @@ tap.test('title -> in range', async assert => {
fs.existsSync.restore()
})

tap.test('title -> in range, no from', async assert => {
assert.plan(8)

sinon.stub(core, 'info')
// sinon.stub(core, 'warning')
sinon.stub(fs, 'existsSync').returns(false)

const options = {
title: 'Update actions/setup-python requirement to v2.1.4 in /path',
config: config('all')
}

assert.ok(parse(options))
assert.ok(core.info.called)
// assert.ok(core.warning.called)
assert.equal(core.info.getCall(0)?.firstArg, 'title: "Update actions/setup-python requirement to v2.1.4 in /path"')
assert.equal(core.info.getCall(1)?.firstArg, 'depName: actions/setup-python')
assert.equal(core.info.getCall(2)?.firstArg, 'from: unknown')
assert.equal(core.info.getCall(3)?.firstArg, 'to: 2.1.4')
assert.equal(core.info.getCall(6)?.firstArg, 'config: all:all')
assert.equal(core.info.getCall(7)?.firstArg, 'all:all detected, will auto-merge')
// assert.equal(core.warning.getCall(0)?.firstArg, 'no version range detected in PR title')

core.info.restore()
fs.existsSync.restore()
})

tap.test('title -> in range, no from', async assert => {
assert.plan(8)

sinon.stub(core, 'info')
// sinon.stub(core, 'warning')
sinon.stub(fs, 'existsSync').returns(false)

const options = {
title: 'Update actions/setup-python requirement to v2.1.4 in /path',
config: config('semver:all')
}

assert.ok(parse(options))
assert.ok(core.info.called)
// assert.ok(core.warning.called)
assert.equal(core.info.getCall(0)?.firstArg, 'title: "Update actions/setup-python requirement to v2.1.4 in /path"')
assert.equal(core.info.getCall(1)?.firstArg, 'depName: actions/setup-python')
assert.equal(core.info.getCall(2)?.firstArg, 'from: unknown')
assert.equal(core.info.getCall(3)?.firstArg, 'to: 2.1.4')
assert.equal(core.info.getCall(6)?.firstArg, 'config: all:semver:all')
assert.equal(core.info.getCall(7)?.firstArg, 'all:semver:all detected, will auto-merge')
// assert.equal(core.warning.getCall(0)?.firstArg, 'no version range detected in PR title')

core.info.restore()
fs.existsSync.restore()
})

tap.test('parse -> out of range', async assert => {
assert.plan(5)

Expand All @@ -44,7 +98,7 @@ tap.test('parse -> out of range', async assert => {

const options = {
title: 'chore(deps): bump api-problem from 6.1.2 to 7.0.0 in /path',
config: config('patch')
config: config('semver:patch')
}

assert.notOk(parse(options), false)
Expand All @@ -56,3 +110,31 @@ tap.test('parse -> out of range', async assert => {
core.info.restore()
fs.existsSync.restore()
})

tap.test('title -> out of range, no from', async assert => {
assert.plan(10)

sinon.stub(core, 'info')
sinon.stub(core, 'warning')
sinon.stub(fs, 'existsSync').returns(false)

const options = {
title: 'Update actions/setup-python requirement to v2.1.4 in /path',
config: config('semver:major')
}

assert.notOk(parse(options))
assert.ok(core.info.called)
assert.ok(core.warning.called)
assert.equal(core.info.getCall(0)?.firstArg, 'title: "Update actions/setup-python requirement to v2.1.4 in /path"')
assert.equal(core.info.getCall(1)?.firstArg, 'depName: actions/setup-python')
assert.equal(core.info.getCall(2)?.firstArg, 'from: unknown')
assert.equal(core.info.getCall(3)?.firstArg, 'to: 2.1.4')
assert.equal(core.info.getCall(6)?.firstArg, 'config: all:semver:major')
assert.equal(core.info.getCall(7)?.firstArg, 'manual merging required')
assert.equal(core.warning.getCall(0)?.firstArg, 'no version range detected in PR title')

core.info.restore()
core.warning.restore()
fs.existsSync.restore()
})

0 comments on commit 92c24d0

Please sign in to comment.