fix(commits): add helpful error when lastRelease not in history

Closes #61, Closes #50
This commit is contained in:
Stephan Bönnemann 2015-08-22 19:31:27 +02:00
parent a2d6db2ce5
commit 5cc7da6035
4 changed files with 74 additions and 20 deletions

View File

@ -1,25 +1,64 @@
const { exec } = require('child_process') const { exec } = require('child_process')
module.exports = function ({lastRelease}, cb) { const log = require('npmlog')
const SemanticReleaseError = require('@semantic-release/error')
module.exports = function ({lastRelease, branch}, cb) {
const from = lastRelease.gitHead const from = lastRelease.gitHead
const range = (from ? from + '..' : '') + 'HEAD' const range = (from ? from + '..' : '') + 'HEAD'
exec( if (!from) return extract()
`git log -E --format=%H==SPLIT==%B==END== ${range}`,
(err, stdout) => {
if (err) return cb(err)
cb(null, String(stdout).split('==END==\n') exec(`git branch --contains ${from}`, (err, stdout) => {
if (err) return cb(err)
let inHistory = false
.filter((raw) => !!raw.trim()) const branches = stdout.split('\n')
.map((result) => {
if (branch === result.replace('*', '').trim()) {
inHistory = true
return null
}
return result.trim()
})
.filter(branch => !!branch)
.map((raw) => { if (!inHistory) {
const data = raw.split('==SPLIT==') log.error('commits',
return { `The commit the last release of this package was derived from is no longer
hash: data[0], in the direct history of the "${branch}" branch.
message: data[1] This means semantic-release can not extract the commits between now and then.
} This is usually caused by force pushing or releasing from an unrelated branch.
})) You can recover from this error by publishing manually or restoring
the commit "${from}".` + (branches.length ?
`\nHere is a list of branches that still contain the commit in question: \n * ${branches.join('\n * ')}` :
''
))
return cb(new SemanticReleaseError('Commit not in history', 'ENOTINHISTORY'))
} }
)
extract()
})
function extract () {
exec(
`git log -E --format=%H==SPLIT==%B==END== ${range}`,
(err, stdout) => {
if (err) return cb(err)
cb(null, String(stdout).split('==END==\n')
.filter((raw) => !!raw.trim())
.map((raw) => {
const data = raw.split('==SPLIT==')
return {
hash: data[0],
message: data[1]
}
}))
}
)
}
} }

View File

@ -5,6 +5,10 @@ const rawCommits = [
module.exports = { module.exports = {
exec: (command, cb) => { exec: (command, cb) => {
if (/contains/.test(command)) {
return cb(null, `whatever\nmaster\n`)
}
cb( cb(
null, null,
/\.\.HEAD/.test(command) ? /\.\.HEAD/.test(command) ?

View File

@ -7,7 +7,7 @@ const commits = proxyquire('../../dist/lib/commits', {
test('commits since last release', (t) => { test('commits since last release', (t) => {
t.test('get all commits', (tt) => { t.test('get all commits', (tt) => {
commits({lastRelease: {}}, (err, commits) => { commits({lastRelease: {}, branch: 'master'}, (err, commits) => {
tt.error(err) tt.error(err)
tt.is(commits.length, 2, 'all commits') tt.is(commits.length, 2, 'all commits')
tt.is(commits[0].hash, 'hash-one', 'parsed hash') tt.is(commits[0].hash, 'hash-one', 'parsed hash')
@ -18,7 +18,7 @@ test('commits since last release', (t) => {
}) })
t.test('get commits since hash', (tt) => { t.test('get commits since hash', (tt) => {
commits({lastRelease: {gitHead: 'hash'}}, (err, commits) => { commits({lastRelease: {gitHead: 'hash'}, branch: 'master'}, (err, commits) => {
tt.error(err) tt.error(err)
tt.is(commits.length, 1, 'specified commits') tt.is(commits.length, 1, 'specified commits')
tt.is(commits[0].hash, 'hash-one', 'parsed hash') tt.is(commits[0].hash, 'hash-one', 'parsed hash')
@ -28,5 +28,13 @@ test('commits since last release', (t) => {
}) })
}) })
t.test('get commits since hash', (tt) => {
commits({lastRelease: {gitHead: 'notinhistory'}, branch: 'notmaster'}, (err, commits) => {
tt.ok(err)
tt.is(err.code, 'ENOTINHISTORY')
tt.end()
})
})
t.end() t.end()
}) })

View File

@ -3,7 +3,9 @@ const proxyquire = require('proxyquire')
require('../mocks/registry') require('../mocks/registry')
const pre = proxyquire('../../dist/pre', { const pre = proxyquire('../../dist/pre', {
'child_process': require('../mocks/child-process') './lib/commits': proxyquire('../../dist/lib/commits', {
'child_process': require('../mocks/child-process')
})
}) })
const versions = { const versions = {
@ -14,14 +16,13 @@ const plugins = {
verifyRelease: (release, cb) => cb(null, release), verifyRelease: (release, cb) => cb(null, release),
analyzeCommits: (commits, cb) => cb(null, 'major'), analyzeCommits: (commits, cb) => cb(null, 'major'),
getLastRelease: ({ pkg }, cb) => { getLastRelease: ({ pkg }, cb) => {
cb(null, { version: versions[pkg.name] || null, gitHead: 'HEAD' }) cb(null, {version: versions[pkg.name] || null, gitHead: 'HEAD'})
} }
} }
const npm = { const npm = {
registry: 'http://registry.npmjs.org/', registry: 'http://registry.npmjs.org/',
tag: 'latest' tag: 'latest'
} }
test('full pre run', (t) => { test('full pre run', (t) => {
@ -29,6 +30,7 @@ test('full pre run', (t) => {
tt.plan(3) tt.plan(3)
pre({ pre({
branch: 'master',
npm, npm,
pkg: {name: 'available'}, pkg: {name: 'available'},
plugins plugins
@ -43,6 +45,7 @@ test('full pre run', (t) => {
tt.plan(3) tt.plan(3)
pre({ pre({
branch: 'master',
npm, npm,
pkg: {name: 'unavailable'}, pkg: {name: 'unavailable'},
plugins plugins