fix: verify the local branch is up to date with the remote one
This commit is contained in:
parent
a11da0d5e3
commit
d15905c0d5
11
index.js
11
index.js
@ -13,7 +13,7 @@ const getLastRelease = require('./lib/get-last-release');
|
|||||||
const {extractErrors} = require('./lib/utils');
|
const {extractErrors} = require('./lib/utils');
|
||||||
const getGitAuthUrl = require('./lib/get-git-auth-url');
|
const getGitAuthUrl = require('./lib/get-git-auth-url');
|
||||||
const logger = require('./lib/logger');
|
const logger = require('./lib/logger');
|
||||||
const {unshallow, verifyAuth, gitHead: getGitHead, tag, push} = require('./lib/git');
|
const {unshallow, verifyAuth, isBranchUpToDate, gitHead: getGitHead, tag, push} = require('./lib/git');
|
||||||
const getError = require('./lib/get-error');
|
const getError = require('./lib/get-error');
|
||||||
|
|
||||||
marked.setOptions({renderer: new TerminalRenderer()});
|
marked.setOptions({renderer: new TerminalRenderer()});
|
||||||
@ -47,6 +47,15 @@ async function run(options, plugins) {
|
|||||||
await verify(options);
|
await verify(options);
|
||||||
|
|
||||||
options.repositoryUrl = await getGitAuthUrl(options);
|
options.repositoryUrl = await getGitAuthUrl(options);
|
||||||
|
|
||||||
|
if (!await isBranchUpToDate(options.branch)) {
|
||||||
|
logger.log(
|
||||||
|
"The local branch %s is behind the remote one, therefore a new version won't be published.",
|
||||||
|
options.branch
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!await verifyAuth(options.repositoryUrl, options.branch)) {
|
if (!await verifyAuth(options.repositoryUrl, options.branch)) {
|
||||||
throw getError('EGITNOPERMISSION', {options});
|
throw getError('EGITNOPERMISSION', {options});
|
||||||
}
|
}
|
||||||
|
12
lib/git.js
12
lib/git.js
@ -131,6 +131,17 @@ async function verifyTagName(tagName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the local branch is up to date with the remote one.
|
||||||
|
*
|
||||||
|
* @param {String} branch The repository branch for which to verify status.
|
||||||
|
*
|
||||||
|
* @return {Boolean} `true` is the HEAD of the current local branch is the same as the HEAD of the remote branch, `false` otherwise.
|
||||||
|
*/
|
||||||
|
async function isBranchUpToDate(branch) {
|
||||||
|
return isRefInHistory(await execa.stdout('git', ['rev-parse', `${branch}@{u}`]));
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
gitTagHead,
|
gitTagHead,
|
||||||
gitTags,
|
gitTags,
|
||||||
@ -143,4 +154,5 @@ module.exports = {
|
|||||||
tag,
|
tag,
|
||||||
push,
|
push,
|
||||||
verifyTagName,
|
verifyTagName,
|
||||||
|
isBranchUpToDate,
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
gitTags,
|
gitTags,
|
||||||
isGitRepo,
|
isGitRepo,
|
||||||
verifyTagName,
|
verifyTagName,
|
||||||
|
isBranchUpToDate,
|
||||||
} from '../lib/git';
|
} from '../lib/git';
|
||||||
import {
|
import {
|
||||||
gitRepo,
|
gitRepo,
|
||||||
@ -22,6 +23,8 @@ import {
|
|||||||
gitAddConfig,
|
gitAddConfig,
|
||||||
gitCommitTag,
|
gitCommitTag,
|
||||||
gitRemoteTagHead,
|
gitRemoteTagHead,
|
||||||
|
push as pushUtil,
|
||||||
|
reset,
|
||||||
} from './helpers/git-utils';
|
} from './helpers/git-utils';
|
||||||
|
|
||||||
// Save the current working diretory
|
// Save the current working diretory
|
||||||
@ -183,3 +186,33 @@ test.serial('Throws error if obtaining the tags fails', async t => {
|
|||||||
|
|
||||||
await t.throws(gitTags());
|
await t.throws(gitTags());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.serial('Return "true" if repository is up to date', async t => {
|
||||||
|
await gitRepo(true);
|
||||||
|
await gitCommits(['First']);
|
||||||
|
await pushUtil();
|
||||||
|
|
||||||
|
t.true(await isBranchUpToDate('master'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test.serial('Return falsy if repository is not up to date', async t => {
|
||||||
|
await gitRepo(true);
|
||||||
|
await gitCommits(['First']);
|
||||||
|
await gitCommits(['Second']);
|
||||||
|
await pushUtil();
|
||||||
|
|
||||||
|
t.true(await isBranchUpToDate('master'));
|
||||||
|
|
||||||
|
await reset();
|
||||||
|
|
||||||
|
t.falsy(await isBranchUpToDate('master'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test.serial('Return "true" if local repository is ahead', async t => {
|
||||||
|
await gitRepo(true);
|
||||||
|
await gitCommits(['First']);
|
||||||
|
await pushUtil();
|
||||||
|
await gitCommits(['Second']);
|
||||||
|
|
||||||
|
t.true(await isBranchUpToDate('master'));
|
||||||
|
});
|
||||||
|
@ -98,6 +98,7 @@ export async function gitGetCommits(from) {
|
|||||||
* Checkout a branch on the current git repository.
|
* Checkout a branch on the current git repository.
|
||||||
*
|
*
|
||||||
* @param {String} branch Branch name.
|
* @param {String} branch Branch name.
|
||||||
|
* @param {Boolean} create `true` to create the branche ans switch, `false` to only switch.
|
||||||
*/
|
*/
|
||||||
export async function gitCheckout(branch, create = true) {
|
export async function gitCheckout(branch, create = true) {
|
||||||
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch]);
|
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch]);
|
||||||
@ -208,6 +209,15 @@ export async function gitCommitTag(gitHead) {
|
|||||||
* @param {String} branch The branch to push.
|
* @param {String} branch The branch to push.
|
||||||
* @throws {Error} if the push failed.
|
* @throws {Error} if the push failed.
|
||||||
*/
|
*/
|
||||||
export async function push(origin, branch) {
|
export async function push(origin = 'origin', branch = 'master') {
|
||||||
await execa('git', ['push', '--tags', origin, `HEAD:${branch}`]);
|
await execa('git', ['push', '--tags', origin, `HEAD:${branch}`]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset repository to a commit.
|
||||||
|
*
|
||||||
|
* @param {String} [commit='HEAD~1'] Commit reference to reset the repo to.
|
||||||
|
*/
|
||||||
|
export async function reset(commit = 'HEAD~1') {
|
||||||
|
await execa('git', ['reset', commit]);
|
||||||
|
}
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
gitRemoteTagHead,
|
gitRemoteTagHead,
|
||||||
push,
|
push,
|
||||||
gitShallowClone,
|
gitShallowClone,
|
||||||
|
reset,
|
||||||
} from './helpers/git-utils';
|
} from './helpers/git-utils';
|
||||||
|
|
||||||
// Save the current process.env
|
// Save the current process.env
|
||||||
@ -57,6 +58,7 @@ test.serial('Plugins are called with expected values', async t => {
|
|||||||
await gitTagVersion('v1.0.0');
|
await gitTagVersion('v1.0.0');
|
||||||
// Add new commits to the master branch
|
// Add new commits to the master branch
|
||||||
commits = (await gitCommits(['Second'])).concat(commits);
|
commits = (await gitCommits(['Second'])).concat(commits);
|
||||||
|
await push();
|
||||||
|
|
||||||
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
|
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
|
||||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||||
@ -162,6 +164,7 @@ test.serial('Use custom tag format', async t => {
|
|||||||
await gitCommits(['First']);
|
await gitCommits(['First']);
|
||||||
await gitTagVersion('test-1.0.0');
|
await gitTagVersion('test-1.0.0');
|
||||||
await gitCommits(['Second']);
|
await gitCommits(['Second']);
|
||||||
|
await push();
|
||||||
|
|
||||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'test-2.0.0'};
|
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'test-2.0.0'};
|
||||||
const notes = 'Release notes';
|
const notes = 'Release notes';
|
||||||
@ -198,6 +201,7 @@ test.serial('Use new gitHead, and recreate release notes if a prepare plugin cre
|
|||||||
await gitTagVersion('v1.0.0');
|
await gitTagVersion('v1.0.0');
|
||||||
// Add new commits to the master branch
|
// Add new commits to the master branch
|
||||||
commits = (await gitCommits(['Second'])).concat(commits);
|
commits = (await gitCommits(['Second'])).concat(commits);
|
||||||
|
await push();
|
||||||
|
|
||||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||||
const notes = 'Release notes';
|
const notes = 'Release notes';
|
||||||
@ -257,6 +261,7 @@ test.serial('Call all "success" plugins even if one errors out', async t => {
|
|||||||
await gitTagVersion('v1.0.0');
|
await gitTagVersion('v1.0.0');
|
||||||
// Add new commits to the master branch
|
// Add new commits to the master branch
|
||||||
await gitCommits(['Second']);
|
await gitCommits(['Second']);
|
||||||
|
await push();
|
||||||
|
|
||||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||||
const notes = 'Release notes';
|
const notes = 'Release notes';
|
||||||
@ -304,6 +309,7 @@ test.serial('Log all "verifyConditions" errors', async t => {
|
|||||||
const repositoryUrl = await gitRepo(true);
|
const repositoryUrl = await gitRepo(true);
|
||||||
// Add commits to the master branch
|
// Add commits to the master branch
|
||||||
await gitCommits(['First']);
|
await gitCommits(['First']);
|
||||||
|
await push();
|
||||||
|
|
||||||
const error1 = new Error('error 1');
|
const error1 = new Error('error 1');
|
||||||
const error2 = new SemanticReleaseError('error 2', 'ERR2');
|
const error2 = new SemanticReleaseError('error 2', 'ERR2');
|
||||||
@ -346,6 +352,7 @@ test.serial('Log all "verifyRelease" errors', async t => {
|
|||||||
await gitTagVersion('v1.0.0');
|
await gitTagVersion('v1.0.0');
|
||||||
// Add new commits to the master branch
|
// Add new commits to the master branch
|
||||||
await gitCommits(['Second']);
|
await gitCommits(['Second']);
|
||||||
|
await push();
|
||||||
|
|
||||||
const error1 = new SemanticReleaseError('error 1', 'ERR1');
|
const error1 = new SemanticReleaseError('error 1', 'ERR1');
|
||||||
const error2 = new SemanticReleaseError('error 2', 'ERR2');
|
const error2 = new SemanticReleaseError('error 2', 'ERR2');
|
||||||
@ -382,6 +389,7 @@ test.serial('Dry-run skips publish and success', async t => {
|
|||||||
await gitTagVersion('v1.0.0');
|
await gitTagVersion('v1.0.0');
|
||||||
// Add new commits to the master branch
|
// Add new commits to the master branch
|
||||||
await gitCommits(['Second']);
|
await gitCommits(['Second']);
|
||||||
|
await push();
|
||||||
|
|
||||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||||
const notes = 'Release notes';
|
const notes = 'Release notes';
|
||||||
@ -430,6 +438,7 @@ test.serial('Dry-run skips fail', async t => {
|
|||||||
await gitTagVersion('v1.0.0');
|
await gitTagVersion('v1.0.0');
|
||||||
// Add new commits to the master branch
|
// Add new commits to the master branch
|
||||||
await gitCommits(['Second']);
|
await gitCommits(['Second']);
|
||||||
|
await push();
|
||||||
|
|
||||||
const error1 = new SemanticReleaseError('error 1', 'ERR1');
|
const error1 = new SemanticReleaseError('error 1', 'ERR1');
|
||||||
const error2 = new SemanticReleaseError('error 2', 'ERR2');
|
const error2 = new SemanticReleaseError('error 2', 'ERR2');
|
||||||
@ -464,6 +473,7 @@ test.serial('Force a dry-run if not on a CI and "noCi" is not explicitly set', a
|
|||||||
await gitTagVersion('v1.0.0');
|
await gitTagVersion('v1.0.0');
|
||||||
// Add new commits to the master branch
|
// Add new commits to the master branch
|
||||||
await gitCommits(['Second']);
|
await gitCommits(['Second']);
|
||||||
|
await push();
|
||||||
|
|
||||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||||
const notes = 'Release notes';
|
const notes = 'Release notes';
|
||||||
@ -513,6 +523,7 @@ test.serial('Allow local releases with "noCi" option', async t => {
|
|||||||
await gitTagVersion('v1.0.0');
|
await gitTagVersion('v1.0.0');
|
||||||
// Add new commits to the master branch
|
// Add new commits to the master branch
|
||||||
await gitCommits(['Second']);
|
await gitCommits(['Second']);
|
||||||
|
await push();
|
||||||
|
|
||||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||||
const notes = 'Release notes';
|
const notes = 'Release notes';
|
||||||
@ -566,6 +577,7 @@ test.serial('Accept "undefined" value returned by the "generateNotes" plugins',
|
|||||||
await gitTagVersion('v1.0.0');
|
await gitTagVersion('v1.0.0');
|
||||||
// Add new commits to the master branch
|
// Add new commits to the master branch
|
||||||
commits = (await gitCommits(['Second'])).concat(commits);
|
commits = (await gitCommits(['Second'])).concat(commits);
|
||||||
|
await push();
|
||||||
|
|
||||||
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
|
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
|
||||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||||
@ -623,6 +635,27 @@ test.serial('Returns falsy value if triggered by a PR', async t => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.serial('Returns falsy value if triggered on an outdated clone', async t => {
|
||||||
|
// Create a git repository, set the current working directory at the root of the repo
|
||||||
|
const repositoryUrl = await gitRepo(true);
|
||||||
|
// Add commits to the master branch
|
||||||
|
await gitCommits(['First']);
|
||||||
|
await gitCommits(['Second']);
|
||||||
|
await push();
|
||||||
|
await reset();
|
||||||
|
|
||||||
|
const semanticRelease = proxyquire('..', {
|
||||||
|
'./lib/logger': t.context.logger,
|
||||||
|
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||||
|
});
|
||||||
|
|
||||||
|
t.falsy(await semanticRelease({repositoryUrl}));
|
||||||
|
t.deepEqual(t.context.log.args[t.context.log.args.length - 1], [
|
||||||
|
"The local branch %s is behind the remote one, therefore a new version won't be published.",
|
||||||
|
'master',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test.serial('Returns falsy value if not running from the configured branch', async t => {
|
test.serial('Returns falsy value if not running from the configured branch', async t => {
|
||||||
// Create a git repository, set the current working directory at the root of the repo
|
// Create a git repository, set the current working directory at the root of the repo
|
||||||
const repositoryUrl = await gitRepo(true);
|
const repositoryUrl = await gitRepo(true);
|
||||||
@ -656,6 +689,7 @@ test.serial('Returns falsy value if there is no relevant changes', async t => {
|
|||||||
const repositoryUrl = await gitRepo(true);
|
const repositoryUrl = await gitRepo(true);
|
||||||
// Add commits to the master branch
|
// Add commits to the master branch
|
||||||
await gitCommits(['First']);
|
await gitCommits(['First']);
|
||||||
|
await push();
|
||||||
|
|
||||||
const analyzeCommits = stub().resolves();
|
const analyzeCommits = stub().resolves();
|
||||||
const verifyRelease = stub().resolves();
|
const verifyRelease = stub().resolves();
|
||||||
@ -685,7 +719,10 @@ test.serial('Returns falsy value if there is no relevant changes', async t => {
|
|||||||
t.is(verifyRelease.callCount, 0);
|
t.is(verifyRelease.callCount, 0);
|
||||||
t.is(generateNotes.callCount, 0);
|
t.is(generateNotes.callCount, 0);
|
||||||
t.is(publish.callCount, 0);
|
t.is(publish.callCount, 0);
|
||||||
t.is(t.context.log.args[7][0], 'There are no relevant changes, so no new version is released.');
|
t.is(
|
||||||
|
t.context.log.args[t.context.log.args.length - 1][0],
|
||||||
|
'There are no relevant changes, so no new version is released.'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial('Exclude commits with [skip release] or [release skip] from analysis', async t => {
|
test.serial('Exclude commits with [skip release] or [release skip] from analysis', async t => {
|
||||||
@ -702,6 +739,7 @@ test.serial('Exclude commits with [skip release] or [release skip] from analysis
|
|||||||
'Test commit\n\n commit body\n[skip release]',
|
'Test commit\n\n commit body\n[skip release]',
|
||||||
'Test commit\n\n commit body\n[release skip]',
|
'Test commit\n\n commit body\n[release skip]',
|
||||||
]);
|
]);
|
||||||
|
await push();
|
||||||
const analyzeCommits = stub().resolves();
|
const analyzeCommits = stub().resolves();
|
||||||
const config = {branch: 'master', repositoryUrl, globalOpt: 'global'};
|
const config = {branch: 'master', repositoryUrl, globalOpt: 'global'};
|
||||||
const options = {
|
const options = {
|
||||||
@ -826,6 +864,7 @@ test.serial('Throw an Error if plugin returns an unexpected value', async t => {
|
|||||||
await gitTagVersion('v1.0.0');
|
await gitTagVersion('v1.0.0');
|
||||||
// Add new commits to the master branch
|
// Add new commits to the master branch
|
||||||
await gitCommits(['Second']);
|
await gitCommits(['Second']);
|
||||||
|
await push();
|
||||||
|
|
||||||
const verifyConditions = stub().resolves();
|
const verifyConditions = stub().resolves();
|
||||||
const analyzeCommits = stub().resolves('string');
|
const analyzeCommits = stub().resolves('string');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user