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