feat: Allow to recover from ENOTINHISTORY with a tag and handle detached head repo
- Tag sha will now be used also if there is a gitHead in last release and it's not in the history - Use `git merge-base` to determine if a commit is in history, allowing to use CI creating detached head repo - Mention recovery solution by creating a version tag in `ENOTINHISTORY` and `ENOGITHEAD` error messages - Do not mention branches containing missing commit in `ENOTINHISTORY` and `ENOGITHEAD` error messages as it's not available by default on most CI
This commit is contained in:
committed by
Gregor Martynus
parent
8e9d9f77f3
commit
580ad9c3d2
+172
-27
@@ -1,5 +1,14 @@
|
||||
import test from 'ava';
|
||||
import {gitRepo, gitCommits, gitCheckout, gitTagVersion, gitShallowClone, gitTags, gitLog} from './helpers/git-utils';
|
||||
import {
|
||||
gitRepo,
|
||||
gitCommits,
|
||||
gitCheckout,
|
||||
gitTagVersion,
|
||||
gitShallowClone,
|
||||
gitTags,
|
||||
gitLog,
|
||||
gitDetachedHead,
|
||||
} from './helpers/git-utils';
|
||||
import proxyquire from 'proxyquire';
|
||||
import {stub} from 'sinon';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
@@ -25,7 +34,7 @@ test.serial('Get all commits when there is no last release', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['fix: First fix', 'feat: Second feature']);
|
||||
const commits = await gitCommits(['First', 'Second']);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const result = await getCommits({lastRelease: {}, options: {branch: 'master'}});
|
||||
@@ -42,7 +51,7 @@ test.serial('Get all commits when there is no last release, including the ones n
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const repo = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['fix: First fix', 'feat: Second feature']);
|
||||
const commits = await gitCommits(['First', 'Second']);
|
||||
// Create a shallow clone with only 1 commit
|
||||
await gitShallowClone(repo);
|
||||
|
||||
@@ -64,9 +73,9 @@ test.serial('Get all commits since gitHead (from lastRelease)', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['fix: First fix', 'feat: Second feature', 'feat: Third feature']);
|
||||
const commits = await gitCommits(['First', 'Second', 'Third']);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
// Retrieve the commits with the commits module, since commit 'First'
|
||||
const result = await getCommits({
|
||||
lastRelease: {gitHead: commits[commits.length - 1].hash},
|
||||
options: {branch: 'master'},
|
||||
@@ -80,18 +89,38 @@ test.serial('Get all commits since gitHead (from lastRelease)', async t => {
|
||||
t.is(result[1].message, commits[1].message);
|
||||
});
|
||||
|
||||
test.serial('Get all commits since gitHead (from lastRelease) on a detached head repo', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const repo = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['First', 'Second', 'Third']);
|
||||
// Create a detached head repo at commit 'feat: Second'
|
||||
await gitDetachedHead(repo, commits[1].hash);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'First'
|
||||
const result = await getCommits({
|
||||
lastRelease: {gitHead: commits[commits.length - 1].hash},
|
||||
options: {branch: 'master'},
|
||||
});
|
||||
|
||||
// Verify the module retrieved only the commit 'feat: Second' (included in the detached and after 'fix: First')
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[0].message, commits[1].message);
|
||||
});
|
||||
|
||||
test.serial('Get all commits since gitHead (from tag) ', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
let commits = await gitCommits(['fix: First fix']);
|
||||
let commits = await gitCommits(['First']);
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
await gitTagVersion('1.0.0');
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['feat: Second feature', 'feat: Third feature'])).concat(commits);
|
||||
commits = (await gitCommits(['Second', 'Third'])).concat(commits);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const result = await getCommits({lastRelease: {version: `1.0.0`}, options: {branch: 'master'}});
|
||||
// Retrieve the commits with the commits module, since commit 'First' (associated with tag v1.0.0)
|
||||
const result = await getCommits({lastRelease: {version: '1.0.0'}, options: {branch: 'master'}});
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
@@ -101,18 +130,82 @@ test.serial('Get all commits since gitHead (from tag) ', async t => {
|
||||
t.is(result[1].message, commits[1].message);
|
||||
});
|
||||
|
||||
test.serial('Get all commits since gitHead (from tag) on a detached head repo', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const repo = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
let commits = await gitCommits(['First']);
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
await gitTagVersion('1.0.0');
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['Second', 'Third'])).concat(commits);
|
||||
// Create a detached head repo at commit 'feat: Second'
|
||||
await gitDetachedHead(repo, commits[1].hash);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'First' (associated with tag 1.0.0)
|
||||
const result = await getCommits({lastRelease: {version: '1.0.0'}, options: {branch: 'master'}});
|
||||
|
||||
// Verify the module retrieved only the commit 'feat: Second' (included in the detached and after 'fix: First')
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[0].message, commits[1].message);
|
||||
});
|
||||
|
||||
test.serial('Get all commits since gitHead (from tag formatted like v<version>) ', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
let commits = await gitCommits(['fix: First fix']);
|
||||
let commits = await gitCommits(['First']);
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['feat: Second feature', 'feat: Third feature'])).concat(commits);
|
||||
commits = (await gitCommits(['Second', 'Third'])).concat(commits);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const result = await getCommits({lastRelease: {version: `1.0.0`}, options: {branch: 'master'}});
|
||||
// Retrieve the commits with the commits module, since commit 'First' (associated with tag v1.0.0)
|
||||
const result = await getCommits({lastRelease: {version: '1.0.0'}, options: {branch: 'master'}});
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
t.is(result[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result[0].message, commits[0].message);
|
||||
t.is(result[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[1].message, commits[1].message);
|
||||
});
|
||||
|
||||
test.serial('Get commits when last release gitHead is missing but a tag match the version', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
let commits = await gitCommits(['First']);
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['Second', 'Third'])).concat(commits);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'First' (associated with tag v1.0.0)
|
||||
const result = await getCommits({lastRelease: {version: '1.0.0', gitHead: 'missing'}, options: {branch: 'master'}});
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
t.is(result[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result[0].message, commits[0].message);
|
||||
t.is(result[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[1].message, commits[1].message);
|
||||
});
|
||||
|
||||
test.serial('Get all commits since gitHead, when gitHead are mising from the shallow clone', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const repo = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['First', 'Second', 'Third']);
|
||||
// Create a shallow clone with only 1 commit and no tags
|
||||
await gitShallowClone(repo);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'First'
|
||||
const result = await getCommits({
|
||||
lastRelease: {version: '1.0.0', gitHead: commits[commits.length - 1].hash},
|
||||
options: {branch: 'master'},
|
||||
});
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
@@ -126,19 +219,19 @@ test.serial('Get all commits since gitHead from tag, when tags are mising from t
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const repo = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
let commits = await gitCommits(['fix: First fix']);
|
||||
let commits = await gitCommits(['First']);
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['feat: Second feature', 'feat: Third feature'])).concat(commits);
|
||||
commits = (await gitCommits(['Second', 'Third'])).concat(commits);
|
||||
// Create a shallow clone with only 1 commit and no tags
|
||||
await gitShallowClone(repo);
|
||||
|
||||
// Verify the shallow clone does not contains any tags
|
||||
t.is((await gitTags()).length, 0);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const result = await getCommits({lastRelease: {version: `1.0.0`}, options: {branch: 'master'}});
|
||||
// Retrieve the commits with the commits module, since commit 'First' (associated with tag v1.0.0)
|
||||
const result = await getCommits({lastRelease: {version: '1.0.0'}, options: {branch: 'master'}});
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
@@ -148,20 +241,20 @@ test.serial('Get all commits since gitHead from tag, when tags are mising from t
|
||||
t.is(result[1].message, commits[1].message);
|
||||
});
|
||||
|
||||
test.serial('Return empty array if there is no commits', async t => {
|
||||
test.serial('Return empty array if lastRelease.gitHead is the last commit', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['fix: First fix', 'feat: Second feature']);
|
||||
const commits = await gitCommits(['First', 'Second']);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
// Retrieve the commits with the commits module, since commit 'Second' (therefore none)
|
||||
const result = await getCommits({lastRelease: {gitHead: commits[0].hash}, options: {branch: 'master'}});
|
||||
|
||||
// Verify no commit is retrieved
|
||||
t.deepEqual(result, []);
|
||||
});
|
||||
|
||||
test.serial('Return empty array if lastRelease.gitHead is the last commit', async t => {
|
||||
test.serial('Return empty array if there is no commits', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
|
||||
@@ -176,7 +269,7 @@ test.serial('Throws ENOGITHEAD error if the gitHead of the last release cannot b
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['fix: First fix', 'feat: Second feature']);
|
||||
await gitCommits(['First', 'Second']);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const error = await t.throws(getCommits({lastRelease: {version: '1.0.0'}, options: {branch: 'master'}}));
|
||||
@@ -195,7 +288,7 @@ test.serial('Throws ENOTINHISTORY error if gitHead is not in history', async t =
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['fix: First fix', 'feat: Second feature']);
|
||||
await gitCommits(['First', 'Second']);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const error = await t.throws(getCommits({lastRelease: {gitHead: 'notinhistory'}, options: {branch: 'master'}}));
|
||||
@@ -218,8 +311,7 @@ test.serial('Throws ENOTINHISTORY error if gitHead is not in branch history but
|
||||
await gitCheckout('other-branch');
|
||||
// Add commits to the 'other-branch' branch
|
||||
const commitsBranch = await gitCommits(['Third', 'Fourth']);
|
||||
// Create the new branch 'another-branch' from 'other-branch'
|
||||
await gitCheckout('another-branch');
|
||||
await gitCheckout('master', false);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const error = await t.throws(
|
||||
@@ -233,6 +325,59 @@ test.serial('Throws ENOTINHISTORY error if gitHead is not in branch history but
|
||||
t.regex(errorLog.firstCall.args[1], /history of the "master" branch/);
|
||||
// Verify the log function has been called with a message mentionning the missing gitHead
|
||||
t.regex(errorLog.firstCall.args[1], new RegExp(`restoring the commit "${commitsBranch[0].hash}"`));
|
||||
// Verify the log function has been called with a message mentionning the branches that contains the gitHead
|
||||
t.regex(errorLog.firstCall.args[1], /\* another-branch\s+\* other-branch/);
|
||||
});
|
||||
|
||||
test.serial('Throws ENOTINHISTORY error if gitHead is not in detached head but present in other branch', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const repo = await gitRepo();
|
||||
// Add commit to the master branch
|
||||
await gitCommits(['First']);
|
||||
// Create the new branch 'other-branch' from master
|
||||
await gitCheckout('other-branch');
|
||||
// Add commits to the 'other-branch' branch
|
||||
const commitsBranch = await gitCommits(['Second', 'Third']);
|
||||
await gitCheckout('master', false);
|
||||
// Add new commit to master branch
|
||||
const commitsMaster = await gitCommits(['Fourth']);
|
||||
// Create a detached head repo at commit 'Fourth'
|
||||
await gitDetachedHead(repo, commitsMaster[0].hash);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'Second'
|
||||
const error = await t.throws(
|
||||
getCommits({lastRelease: {version: '1.0.1', gitHead: commitsBranch[0].hash}, options: {branch: 'master'}})
|
||||
);
|
||||
|
||||
// Verify error code and message
|
||||
t.is(error.code, 'ENOTINHISTORY');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
// Verify the log function has been called with a message mentionning the branch
|
||||
t.regex(errorLog.firstCall.args[1], /history of the "master" branch/);
|
||||
// Verify the log function has been called with a message mentionning the missing gitHead
|
||||
t.regex(errorLog.firstCall.args[1], new RegExp(`restoring the commit "${commitsBranch[0].hash}"`));
|
||||
});
|
||||
|
||||
test.serial('Throws ENOTINHISTORY error when a tag is not in branch history but present in others', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['First', 'Second']);
|
||||
// Create the new branch 'other-branch' from master
|
||||
await gitCheckout('other-branch');
|
||||
// Add commits to the 'other-branch' branch
|
||||
await gitCommits(['Third']);
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
const shaTag = await gitTagVersion('v1.0.0');
|
||||
await gitCheckout('master', false);
|
||||
// Add new commit to the master branch
|
||||
await gitCommits(['Forth']);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const error = await t.throws(getCommits({lastRelease: {version: '1.0.0'}, options: {branch: 'master'}}));
|
||||
// Verify error code and message
|
||||
t.is(error.code, 'ENOTINHISTORY');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
// Verify the log function has been called with a message mentionning the branch
|
||||
t.regex(errorLog.firstCall.args[1], /history of the "master" branch/);
|
||||
// Verify the log function has been called with a message mentionning the missing gitHead
|
||||
t.regex(errorLog.firstCall.args[1], new RegExp(`restoring the commit "${shaTag}"`));
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ export async function gitRepo() {
|
||||
*
|
||||
* @param {Array<string>} messages commit messages.
|
||||
*
|
||||
* @returns {Array<Commit>} commits the created commits, in reverse order (to match `git log` order).
|
||||
* @returns {Array<Commit>} The created commits, in reverse order (to match `git log` order).
|
||||
*/
|
||||
export async function gitCommits(messages) {
|
||||
return (await pMapSeries(messages, async msg => {
|
||||
@@ -45,6 +45,19 @@ export async function gitCommits(messages) {
|
||||
})).reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Amend a commit (rewriting the sha) on the current git repository.
|
||||
*
|
||||
* @param {string} messages commit message.
|
||||
*
|
||||
* @returns {Array<Commit>} the created commits.
|
||||
*/
|
||||
export async function gitAmmendCommit(msg) {
|
||||
const {stdout} = await execa('git', ['commit', '--amend', '-m', msg, '--allow-empty']);
|
||||
const [, branch, hash, message] = /^\[(\w+)\(?.*?\)?(\w+)\] (.+)(.|\s)+$/.exec(stdout);
|
||||
return {branch, hash, message};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkout a branch on the current git repository.
|
||||
*
|
||||
@@ -52,7 +65,7 @@ export async function gitCommits(messages) {
|
||||
* @param {boolean} create `true` to create the branche ans switch, `false` to only switch.
|
||||
*/
|
||||
export async function gitCheckout(branch, create = true) {
|
||||
await execa('git', ['checkout', create ? '-b' : null, branch]);
|
||||
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,9 +79,13 @@ export async function gitHead() {
|
||||
* Create a tag on the head commit in the current git repository.
|
||||
*
|
||||
* @param {string} tagName The tag name to create.
|
||||
* @param {string} [sha] The commit on which to create the tag. If undefined the tag is created on the last commit.
|
||||
*
|
||||
* @return {string} The commit sha of the created tag.
|
||||
*/
|
||||
export async function gitTagVersion(tagName) {
|
||||
await execa('git', ['tag', tagName]);
|
||||
export async function gitTagVersion(tagName, sha) {
|
||||
await execa('git', sha ? ['tag', '-f', tagName, sha] : ['tag', tagName]);
|
||||
return (await execa('git', ['rev-list', '-1', '--tags', tagName])).stdout;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,11 +110,29 @@ export async function gitLog() {
|
||||
* @param {number} [depth=1] The number of commit to clone.
|
||||
* @return {string} The path of the cloned repository.
|
||||
*/
|
||||
export async function gitShallowClone(origin, depth = 1) {
|
||||
export async function gitShallowClone(origin, branch = 'master', depth = 1) {
|
||||
const dir = tempy.directory();
|
||||
|
||||
process.chdir(dir);
|
||||
await execa('git', ['clone', '--no-hardlinks', '--no-tags', '--depth', depth, `file://${origin}`, dir]);
|
||||
await execa('git', ['clone', '--no-hardlinks', '--no-tags', '-b', branch, '--depth', depth, `file://${origin}`, dir]);
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a git repo with a detached head from another git repository and change the current working directory to the new repository root.
|
||||
*
|
||||
* @param {string} origin The path of the repository to clone.
|
||||
* @param {number} head A commit sha of the origin repo that will become the detached head of the new one.
|
||||
* @return {string} The path of the new repository.
|
||||
*/
|
||||
export async function gitDetachedHead(origin, head) {
|
||||
const dir = tempy.directory();
|
||||
|
||||
process.chdir(dir);
|
||||
await execa('git', ['init']);
|
||||
await execa('git', ['remote', 'add', 'origin', origin]);
|
||||
await execa('git', ['fetch']);
|
||||
await execa('git', ['checkout', head]);
|
||||
return dir;
|
||||
}
|
||||
|
||||
|
||||
+89
-17
@@ -1,9 +1,18 @@
|
||||
import test from 'ava';
|
||||
import {writeJson, readJson} from 'fs-extra';
|
||||
import {start, stop, uri} from './helpers/registry';
|
||||
import {gitRepo, gitCommits, gitHead, gitTagVersion, gitPackRefs} from './helpers/git-utils';
|
||||
import {gitRepo, gitCommits, gitHead, gitTagVersion, gitPackRefs, gitAmmendCommit} from './helpers/git-utils';
|
||||
import execa from 'execa';
|
||||
|
||||
// Environment variables used with cli
|
||||
const env = {
|
||||
CI: true,
|
||||
npm_config_registry: uri,
|
||||
GH_TOKEN: 'github_token',
|
||||
NPM_OLD_TOKEN: 'aW50ZWdyYXRpb246c3VjaHNlY3VyZQ==',
|
||||
NPM_EMAIL: 'integration@test.com',
|
||||
};
|
||||
|
||||
test.before(async t => {
|
||||
// Start the local NPM registry
|
||||
await start();
|
||||
@@ -25,14 +34,6 @@ test.after.always(async t => {
|
||||
});
|
||||
|
||||
test.serial('Release patch, minor and major versions', async t => {
|
||||
// Environment variables used with cli
|
||||
const env = {
|
||||
CI: true,
|
||||
npm_config_registry: uri,
|
||||
GH_TOKEN: 'github_token',
|
||||
NPM_OLD_TOKEN: 'aW50ZWdyYXRpb246c3VjaHNlY3VyZQ==',
|
||||
NPM_EMAIL: 'integration@test.com',
|
||||
};
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
t.log('Create git repository');
|
||||
await gitRepo();
|
||||
@@ -146,14 +147,6 @@ test.serial('Release patch, minor and major versions', async t => {
|
||||
});
|
||||
|
||||
test.serial('Release versions from a packed git repository, using tags to determine last release gitHead', async t => {
|
||||
// Environment variables used with cli
|
||||
const env = {
|
||||
CI: true,
|
||||
npm_config_registry: uri,
|
||||
GH_TOKEN: 'github_token',
|
||||
NPM_OLD_TOKEN: 'aW50ZWdyYXRpb246c3VjaHNlY3VyZQ==',
|
||||
NPM_EMAIL: 'integration@test.com',
|
||||
};
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
t.log('Create git repository');
|
||||
await gitRepo();
|
||||
@@ -230,3 +223,82 @@ test.serial('Exit with 1 in a plugin is not found', async t => {
|
||||
const {code} = await t.throws(execa(require.resolve('../bin/semantic-release'), ['pre'], {env}));
|
||||
t.is(code, 1);
|
||||
});
|
||||
|
||||
test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
t.log('Create git repository');
|
||||
await gitRepo();
|
||||
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', {
|
||||
name: 'test-module-4',
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: 'git+https://github.com/semantic-release/test-module-2'},
|
||||
release: {verifyConditions: require.resolve('../src/lib/plugin-noop')},
|
||||
});
|
||||
|
||||
/** Minor release **/
|
||||
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit']);
|
||||
t.log('$ semantic-release pre');
|
||||
let {stdout, stderr, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env});
|
||||
// Verify package.json has been updated
|
||||
t.is((await readJson('./package.json')).version, '1.0.0');
|
||||
t.log('$ npm publish');
|
||||
({stdout, code} = await execa('npm', ['publish'], {env}));
|
||||
// Verify output of npm publish
|
||||
t.regex(stdout, /test-module-4@1.0.0/);
|
||||
t.is(code, 0);
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
let [, version, releaseGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', 'test-module-4', 'version', 'gitHead'], {env})).stdout
|
||||
);
|
||||
const head = await gitHead();
|
||||
t.is(releaseGitHead, head);
|
||||
t.log(`+ released ${version}`);
|
||||
t.is(version, '1.0.0');
|
||||
// Create a tag version so the tag can be used later to determine the commit associated with the version
|
||||
await gitTagVersion('v1.0.0');
|
||||
t.log('Create git tag v1.0.0');
|
||||
|
||||
/** Rewrite sha of commit used for release **/
|
||||
|
||||
t.log('Amend release commit');
|
||||
const {hash} = await gitAmmendCommit('feat: Initial commit');
|
||||
|
||||
/** Patch release **/
|
||||
|
||||
t.log('Commit a fix');
|
||||
await gitCommits(['fix: bar']);
|
||||
t.log('$ semantic-release pre');
|
||||
({stderr, stdout, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env, reject: false}));
|
||||
|
||||
t.log('Fail with "ENOTINHISTORY" error');
|
||||
t.is(code, 1);
|
||||
t.regex(
|
||||
stderr,
|
||||
new RegExp(
|
||||
`You can recover from this error by restoring the commit "${head}" or by creating a tag for the version "1.0.0" on the commit corresponding to this release`
|
||||
)
|
||||
);
|
||||
|
||||
/** Create a tag to recover and redo release **/
|
||||
|
||||
t.log('Create git tag v1.0.0 to recover');
|
||||
await gitTagVersion('v1.0.0', hash);
|
||||
|
||||
t.log('$ semantic-release pre');
|
||||
({stderr, stdout, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env}));
|
||||
// Verify package.json has been updated
|
||||
t.is((await readJson('./package.json')).version, '1.0.1');
|
||||
t.log('$ npm publish');
|
||||
({stdout, code} = await execa('npm', ['publish'], {env}));
|
||||
// Verify output of npm publish
|
||||
t.regex(stdout, /test-module-4@1.0.1/);
|
||||
t.is(code, 0);
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
version = (await execa('npm', ['show', 'test-module-4', 'version'], {env})).stdout;
|
||||
t.is(version, '1.0.1');
|
||||
t.log(`+ released ${version}`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user