diff --git a/bin/semantic-release.js b/bin/semantic-release.js index 12dd2b56b6..37fcf85847 100755 --- a/bin/semantic-release.js +++ b/bin/semantic-release.js @@ -10,7 +10,7 @@ var execa = require('execa'); var findVersions = require('find-versions'); var pkg = require('../package.json'); -var MIN_GIT_VERSION = '2.0.0'; +var MIN_GIT_VERSION = '2.7.1'; if (!semver.satisfies(process.version, pkg.engines.node)) { console.error( diff --git a/docs/support/FAQ.md b/docs/support/FAQ.md index 4f7332143b..cb9d6278dd 100644 --- a/docs/support/FAQ.md +++ b/docs/support/FAQ.md @@ -238,6 +238,10 @@ In addition the [verify conditions step](../../README.md#release-steps) verifies See [Node version requirement](./node-version.md#node-version-requirement) for more details and solutions. +## Why does semantic-release require Git version >= 2.7.1? + +**semantic-release** uses Git CLI commands to read information about the repository such as branches, commit history and tags. Certain commands and options (such as [the `--merged` option of the `git tag` command](https://git-scm.com/docs/git-tag/2.7.0#git-tag---no-mergedltcommitgt) or bug fixes related to `git ls-files`) used by **semantic-release** are only available in Git version 2.7.1 and higher. + ## What is npx? [`npx`](https://www.npmjs.com/package/npx) – short for "npm exec" – is a CLI to find and execute npm binaries within the local `node_modules` folder or in the $PATH. If a binary can't be located npx will download the required package and execute it from its cache location. diff --git a/lib/branches/get-tags.js b/lib/branches/get-tags.js index d732d1a920..2f50a93845 100644 --- a/lib/branches/get-tags.js +++ b/lib/branches/get-tags.js @@ -2,7 +2,7 @@ const {template, escapeRegExp} = require('lodash'); const semver = require('semver'); const pReduce = require('p-reduce'); const debug = require('debug')('semantic-release:get-tags'); -const {getTags, isRefInHistory, getTagHead} = require('../../lib/git'); +const {getTags, getTagHead} = require('../../lib/git'); module.exports = async ({cwd, env, options: {tagFormat}}, branches) => { // Generate a regex to parse tags formatted with `tagFormat` @@ -10,25 +10,18 @@ module.exports = async ({cwd, env, options: {tagFormat}}, branches) => { // The `tagFormat` is compiled with space as the `version` as it's an invalid tag character, // so it's guaranteed to no be present in the `tagFormat`. const tagRegexp = `^${escapeRegExp(template(tagFormat)({version: ' '})).replace(' ', '(.[^@]+)@?(.+)?')}`; - const tags = (await getTags({cwd, env})) - .map(tag => { - const [, version, channel] = tag.match(tagRegexp) || []; - return {gitTag: tag, version, channel}; - }) - .filter(({version}) => version && semver.valid(semver.clean(version))); - - debug('found tags: %o', tags); return pReduce( branches, async (branches, branch) => { - const branchTags = await pReduce( - tags, - async (tags, {gitTag, ...rest}) => - (await isRefInHistory(gitTag, branch.name, {cwd, env})) - ? [...tags, {...rest, gitTag, gitHead: await getTagHead(gitTag, {cwd, env})}] - : tags, - [] + const branchTags = await Promise.all( + (await getTags(branch.name, {cwd, env})) + .map(tag => { + const [, version, channel] = tag.match(tagRegexp) || []; + return {gitTag: tag, version, channel}; + }) + .filter(({version}) => version && semver.valid(semver.clean(version))) + .map(async ({gitTag, ...rest}) => ({gitTag, gitHead: await getTagHead(gitTag, {cwd, env}), ...rest})) ); debug('found tags for branch %s: %o', branch.name, branchTags); diff --git a/lib/git.js b/lib/git.js index c776804c98..db8447e9c9 100644 --- a/lib/git.js +++ b/lib/git.js @@ -22,15 +22,16 @@ async function getTagHead(tagName, execaOpts) { } /** - * Get all the repository tags. + * Get all the tags for a given branch. * + * @param {String} branch The branch for which to retrieve the tags. * @param {Object} [execaOpts] Options to pass to `execa`. * * @return {Array} List of git tags. * @throws {Error} If the `git` command fails. */ -async function getTags(execaOpts) { - return (await execa('git', ['tag'], execaOpts)).stdout +async function getTags(branch, execaOpts) { + return (await execa('git', ['tag', '--merged', branch], execaOpts)).stdout .split('\n') .map(tag => tag.trim()) .filter(Boolean); diff --git a/test/branches/get-tags.test.js b/test/branches/get-tags.test.js index 78195dd733..0be5ae536d 100644 --- a/test/branches/get-tags.test.js +++ b/test/branches/get-tags.test.js @@ -109,15 +109,15 @@ test('Return branches with and empty tags array if no valid tag is found', async await gitCommits(['Third'], {cwd}); await gitTagVersion('v3.0', undefined, {cwd}); - const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}, {name: 'next'}]); + const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}]); - t.deepEqual(result, [{name: 'master', tags: []}, {name: 'next', tags: []}]); + t.deepEqual(result, [{name: 'master', tags: []}]); }); test('Return branches with and empty tags array if no valid tag is found in history of configured branches', async t => { const {cwd} = await gitRepo(); await gitCommits(['First'], {cwd}); - await gitCheckout('other-branch', true, {cwd}); + await gitCheckout('next', true, {cwd}); await gitCommits(['Second'], {cwd}); await gitTagVersion('v1.0.0', undefined, {cwd}); await gitTagVersion('v1.0.0@next', undefined, {cwd}); diff --git a/test/git.test.js b/test/git.test.js index 813844f3f6..34e5000d61 100644 --- a/test/git.test.js +++ b/test/git.test.js @@ -91,7 +91,7 @@ test('Fetch all tags on a detached head repository', async t => { await fetch(repositoryUrl, 'master', {cwd}); - t.deepEqual((await getTags({cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0'].sort()); + t.deepEqual((await getTags('master', {cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0'].sort()); }); test('Verify if the commit `sha` is in the direct history of the current branch', async t => { @@ -243,7 +243,7 @@ test('Return falsy for invalid tag names', async t => { test('Throws error if obtaining the tags fails', async t => { const cwd = tempy.directory(); - await t.throwsAsync(getTags({cwd})); + await t.throwsAsync(getTags('master', {cwd})); }); test('Return "true" if repository is up to date', async t => {