perf: use git tag --merge <branch> to filter tags present in a branch history
				
					
				
			BREAKING CHANGE: Git CLI version 2.7.1 or higher is now required The `--merge` option of the `git tag` command has been added in Git version 2.7.1 and is now used by semantic-release
This commit is contained in:
		
							parent
							
								
									844e0b07e0
								
							
						
					
					
						commit
						cffe9a8d33
					
				| @ -10,7 +10,7 @@ var execa = require('execa'); | |||||||
| var findVersions = require('find-versions'); | var findVersions = require('find-versions'); | ||||||
| var pkg = require('../package.json'); | 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)) { | if (!semver.satisfies(process.version, pkg.engines.node)) { | ||||||
|   console.error( |   console.error( | ||||||
|  | |||||||
| @ -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. | 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? | ## 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. | [`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. | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ const {template, escapeRegExp} = require('lodash'); | |||||||
| const semver = require('semver'); | const semver = require('semver'); | ||||||
| const pReduce = require('p-reduce'); | const pReduce = require('p-reduce'); | ||||||
| const debug = require('debug')('semantic-release:get-tags'); | 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) => { | module.exports = async ({cwd, env, options: {tagFormat}}, branches) => { | ||||||
|   // Generate a regex to parse tags formatted with `tagFormat`
 |   // 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,
 |   // 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`.
 |   // so it's guaranteed to no be present in the `tagFormat`.
 | ||||||
|   const tagRegexp = `^${escapeRegExp(template(tagFormat)({version: ' '})).replace(' ', '(.[^@]+)@?(.+)?')}`; |   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( |   return pReduce( | ||||||
|     branches, |     branches, | ||||||
|     async (branches, branch) => { |     async (branches, branch) => { | ||||||
|       const branchTags = await pReduce( |       const branchTags = await Promise.all( | ||||||
|         tags, |         (await getTags(branch.name, {cwd, env})) | ||||||
|         async (tags, {gitTag, ...rest}) => |           .map(tag => { | ||||||
|           (await isRefInHistory(gitTag, branch.name, {cwd, env})) |             const [, version, channel] = tag.match(tagRegexp) || []; | ||||||
|             ? [...tags, {...rest, gitTag, gitHead: await getTagHead(gitTag, {cwd, env})}] |             return {gitTag: tag, version, channel}; | ||||||
|             : tags, |           }) | ||||||
|         [] |           .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); |       debug('found tags for branch %s: %o', branch.name, branchTags); | ||||||
|  | |||||||
| @ -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`. |  * @param {Object} [execaOpts] Options to pass to `execa`. | ||||||
|  * |  * | ||||||
|  * @return {Array<String>} List of git tags. |  * @return {Array<String>} List of git tags. | ||||||
|  * @throws {Error} If the `git` command fails. |  * @throws {Error} If the `git` command fails. | ||||||
|  */ |  */ | ||||||
| async function getTags(execaOpts) { | async function getTags(branch, execaOpts) { | ||||||
|   return (await execa('git', ['tag'], execaOpts)).stdout |   return (await execa('git', ['tag', '--merged', branch], execaOpts)).stdout | ||||||
|     .split('\n') |     .split('\n') | ||||||
|     .map(tag => tag.trim()) |     .map(tag => tag.trim()) | ||||||
|     .filter(Boolean); |     .filter(Boolean); | ||||||
|  | |||||||
| @ -109,15 +109,15 @@ test('Return branches with and empty tags array if no valid tag is found', async | |||||||
|   await gitCommits(['Third'], {cwd}); |   await gitCommits(['Third'], {cwd}); | ||||||
|   await gitTagVersion('v3.0', undefined, {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 => { | 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(); |   const {cwd} = await gitRepo(); | ||||||
|   await gitCommits(['First'], {cwd}); |   await gitCommits(['First'], {cwd}); | ||||||
|   await gitCheckout('other-branch', true, {cwd}); |   await gitCheckout('next', true, {cwd}); | ||||||
|   await gitCommits(['Second'], {cwd}); |   await gitCommits(['Second'], {cwd}); | ||||||
|   await gitTagVersion('v1.0.0', undefined, {cwd}); |   await gitTagVersion('v1.0.0', undefined, {cwd}); | ||||||
|   await gitTagVersion('v1.0.0@next', undefined, {cwd}); |   await gitTagVersion('v1.0.0@next', undefined, {cwd}); | ||||||
|  | |||||||
| @ -91,7 +91,7 @@ test('Fetch all tags on a detached head repository', async t => { | |||||||
| 
 | 
 | ||||||
|   await fetch(repositoryUrl, 'master', {cwd}); |   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 => { | 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 => { | test('Throws error if obtaining the tags fails', async t => { | ||||||
|   const cwd = tempy.directory(); |   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 => { | test('Return "true" if repository is up to date', async t => { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user