From 5cc7da6035e22660848c7960c9d18e6df1b7b0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20B=C3=B6nnemann?= Date: Sat, 22 Aug 2015 19:31:27 +0200 Subject: [PATCH] fix(commits): add helpful error when lastRelease not in history Closes #61, Closes #50 --- src/lib/commits.js | 69 +++++++++++++++++++++++++++++-------- test/mocks/child-process.js | 4 +++ test/specs/commits.js | 12 +++++-- test/specs/pre.js | 9 +++-- 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/src/lib/commits.js b/src/lib/commits.js index ac11d8d88c..d25c1c97f8 100644 --- a/src/lib/commits.js +++ b/src/lib/commits.js @@ -1,25 +1,64 @@ 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 range = (from ? from + '..' : '') + 'HEAD' - exec( - `git log -E --format=%H==SPLIT==%B==END== ${range}`, - (err, stdout) => { - if (err) return cb(err) + if (!from) return extract() - 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) => { - const data = raw.split('==SPLIT==') - return { - hash: data[0], - message: data[1] - } - })) + if (!inHistory) { + log.error('commits', +`The commit the last release of this package was derived from is no longer +in the direct history of the "${branch}" branch. +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] + } + })) + } + ) + } } diff --git a/test/mocks/child-process.js b/test/mocks/child-process.js index d539c49ad7..ff86eb0b34 100644 --- a/test/mocks/child-process.js +++ b/test/mocks/child-process.js @@ -5,6 +5,10 @@ const rawCommits = [ module.exports = { exec: (command, cb) => { + if (/contains/.test(command)) { + return cb(null, `whatever\nmaster\n`) + } + cb( null, /\.\.HEAD/.test(command) ? diff --git a/test/specs/commits.js b/test/specs/commits.js index d3b160c003..71bbc5d064 100644 --- a/test/specs/commits.js +++ b/test/specs/commits.js @@ -7,7 +7,7 @@ const commits = proxyquire('../../dist/lib/commits', { test('commits since last release', (t) => { t.test('get all commits', (tt) => { - commits({lastRelease: {}}, (err, commits) => { + commits({lastRelease: {}, branch: 'master'}, (err, commits) => { tt.error(err) tt.is(commits.length, 2, 'all commits') 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) => { - commits({lastRelease: {gitHead: 'hash'}}, (err, commits) => { + commits({lastRelease: {gitHead: 'hash'}, branch: 'master'}, (err, commits) => { tt.error(err) tt.is(commits.length, 1, 'specified commits') 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() }) diff --git a/test/specs/pre.js b/test/specs/pre.js index a1a6509d36..830cb0a675 100644 --- a/test/specs/pre.js +++ b/test/specs/pre.js @@ -3,7 +3,9 @@ const proxyquire = require('proxyquire') require('../mocks/registry') 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 = { @@ -14,14 +16,13 @@ const plugins = { verifyRelease: (release, cb) => cb(null, release), analyzeCommits: (commits, cb) => cb(null, 'major'), getLastRelease: ({ pkg }, cb) => { - cb(null, { version: versions[pkg.name] || null, gitHead: 'HEAD' }) + cb(null, {version: versions[pkg.name] || null, gitHead: 'HEAD'}) } } const npm = { registry: 'http://registry.npmjs.org/', tag: 'latest' - } test('full pre run', (t) => { @@ -29,6 +30,7 @@ test('full pre run', (t) => { tt.plan(3) pre({ + branch: 'master', npm, pkg: {name: 'available'}, plugins @@ -43,6 +45,7 @@ test('full pre run', (t) => { tt.plan(3) pre({ + branch: 'master', npm, pkg: {name: 'unavailable'}, plugins