8000 fix: verify the local branch is up to date with the remote one · semantic-release/semantic-release@d15905c · GitHub
[go: up one dir, main page]

Skip to content

Commit d15905c

Browse files
committed
fix: verify the local branch is up to date with the remote one
1 parent a11da0d commit d15905c

File tree

5 files changed

+106
-3
lines changed

5 files changed

+106
-3
lines changed

index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const getLastRelease = require('./lib/get-last-release');
1313
const {extractErrors} = require('./lib/utils');
1414
const getGitAuthUrl = require('./lib/get-git-auth-url');
1515
const logger = require('./lib/logger');
16-
const {unshallow, verifyAuth, gitHead: getGitHead, tag, push} = require('./lib/git');
16+
const {unshallow, verifyAuth, isBranchUpToDate, gitHead: getGitHead, tag, push} = require('./lib/git');
1717
const getError = require('./lib/get-error');
1818

1919
marked.setOptions({renderer: new TerminalRenderer()});
@@ -47,6 +47,15 @@ async function run(options, plugins) {
4747
await verify(options);
4848

4949
options.repositoryUrl = await getGitAuthUrl(options);
50+
51< 8000 span class="diff-text-marker">+
if (!await isBranchUpToDate(options.branch)) {
52+
logger.log(
53+
"The local branch %s is behind the remote one, therefore a new version won't be published.",
54+
options.branch
55+
);
56+
return false;
57+
}
58+
5059
if (!await verifyAuth(options.repositoryUrl, options.branch)) {
5160
throw getError('EGITNOPERMISSION', {options});
5261
}

lib/git.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,17 @@ async function verifyTagName(tagName) {
131131
}
132132
}
133133

134+
/**
135+
* Verify the local branch is up to date with the remote one.
136+
*
13 8000 7+
* @param {String} branch The repository branch for which to verify status.
138+
*
139+
* @return {Boolean} `true` is the HEAD of the current local branch is the same as the HEAD of the remote branch, `false` otherwise.
140+
*/
141+
async function isBranchUpToDate(branch) {
142+
return isRefInHistory(await execa.stdout('git', ['rev-parse', `${branch}@{u}`]));
143+
}
144+
134145
module.exports = {
135146
gitTagHead,
136147
gitTags,
@@ -143,4 +154,5 @@ module.exports = {
143154
tag,
144155
push,
145156
verifyTagName,
157+
isBranchUpToDate,
146158
};

test/git.test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
gitTags,
1212
isGitRepo,
1313
verifyTagName,
14+
isBranchUpToDate,
1415
} from '../lib/git';
1516
import {
1617
gitRepo,
@@ -22,6 +23,8 @@ import {
2223
gitAddConfig,
2324
gitCommitTag,
2425
gitRemoteTagHead,
26+
push as pushUtil,
27+
reset,
2528
} from './helpers/git-utils';
2629

2730
// Save the current working diretory
@@ -183,3 +186,33 @@ test.serial('Throws error if obtaining the tags fails', async t => {
183186

184187
await t.throws(gitTags());
185188
});
189+
190+
test.serial('Return "true" if repository is up to date', async t => {
191+
await gitRepo(true);
192+
await gitCommits(['First']);
193+
await pushUtil();
194+
195+
t.true(await isBranchUpToDate('master'));
196+
});
197+
198+
test.serial('Return falsy if repository is not up to date', async t => {
199+
await gitRepo(true);
200+
await gitCommits(['First']);
201+
await gitCommits(['Second']);
202+
await pushUtil();
203+
204+
t.true(await isBranchUpToDate('master'));
205+
206+
await reset();
207+
208+
t.falsy(await isBranchUpToDate('master'));
209+
});
210+
211+
test.serial('Return "true" if local repository is ahead', async t => {
212+
await gitRepo(true);
213+
await gitCommits(['First']);
214+
await pushUtil();
215+
await gitCommits(['Second']);
216+
217+
t.true(await isBranchUpToDate('master'));
218+
});

test/helpers/git-utils.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export async function gitGetCommits(from) {
9898
* Checkout a branch on the current git repository.
9999
*
100100
* @param {String} branch Branch name.
101+
* @param {Boolean} create `true` to create the branche ans switch, `false` to only switch.
101102
*/
102103
export async function gitCheckout(branch, create = true) {
103104
await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch]);
@@ -208,6 +209,15 @@ export async function gitCommitTag(gitHead) {
208209
* @param {String} branch The branch to push.
209210
* @throws {Error} if the push failed.
210211
*/
211-
export async function push(origin, branch) {
212+
export async function push(origin = 'origin', branch = 'master') {
212213
await execa('git', ['push', '--tags', origin, `HEAD:${branch}`]);
213214
}
215+
216+
/**
217+
* Reset repository to a commit.
218+
*
219+
* @param {String} [commit='HEAD~1'] Commit reference to reset the repo to.
220+
*/
221+
export async function reset(commit = 'HEAD~1') {
222+
await execa('git', ['reset', commit]);
223+
}

test/index.test.js

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
gitRemoteTagHead,
1515
push,
1616
gitShallowClone,
17+
reset,
1718
} from './helpers/git-utils';
1819

1920
// Save the current process.env
@@ -57,6 +58,7 @@ test.serial('Plugins are called with expected values', async t => {
5758
await gitTagVersion('v1.0.0');
5859
// Add new commits to the master branch
5960
commits = (await gitCommits(['Second'])).concat(commits);
61+
await push();
6062

6163
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
6264
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 => {
162164
await gitCommits(['First']);
163165
await gitTagVersion('test-1.0.0');
164166
await gitCommits(['Second']);
167+
await push();
165168

166169
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'test-2.0.0'};
167170
const notes = 'Release notes';
@@ -198,6 +201,7 @@ test.serial('Use new gitHead, and recreate release notes if a prepare plugin cre
198201
await gitTagVersion('v1.0.0');
199202
// Add new commits to the master branch
200203
commits = (await gitCommits(['Second'])).concat(commits);
204+
await push();
201205

202206
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
203207
const notes = 'Release notes';
@@ -257,6 +261,7 @@ test.serial('Call all "success" plugins even if one errors out', async t => {
257261
await gitTagVersion('v1.0.0');
258262
// Add new commits to the master branch
259263
await gitCommits(['Second']);
264+
await push();
260265

261266
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
262267
const notes = 'Release notes';
@@ -304,6 +309,7 @@ test.serial('Log all "verifyConditions" errors', async t => {
304309
const repositoryUrl = await gitRepo(true);
305310
// Add commits to the master branch
306311
await gitCommits(['First']);
312+
await push();
307313

308314
const error1 = new Error('error 1');
309315
const error2 = new SemanticReleaseError('error 2', 'ERR2');
@@ -346,6 +352,7 @@ test.serial('Log all "verifyRelease" errors', async t => {
346352
await gitTagVersion('v1.0.0');
347353
// Add new commits to the master branch
348354
await gitCommits(['Second']);
355+
await push();
349356

350357
const error1 = new SemanticReleaseError('error 1', 'ERR1');
351358
const error2 = new SemanticReleaseError('error 2', 'ERR2');
@@ -382,6 +389,7 @@ test.serial('Dry-run skips publish and success', async t => {
382389
await gitTagVersion('v1.0.0');
383390
// Add new commits to the master branch
384391
await gitCommits(['Second']);
392+
await push();
385393

386394
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
387395
const notes = 'Release notes';
@@ -430,6 +438,7 @@ test.serial('Dry-run skips fail', async t => {
430438
await gitTagVersion('v1.0.0');
431439
// Add new commits to the master branch
432440
await gitCommits(['Second']);
441+
await push();
433442

434443
const error1 = new SemanticReleaseError('error 1', 'ERR1');
435444
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
464473
await gitTagVersion('v1.0.0');
465474
// Add new commits to the master branch
466475
await gitCommits(['Second']);
476+
await push();
467477

468478
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
469479
const notes = 'Release notes';
@@ -513,6 +523,7 @@ test.serial('Allow local releases with "noCi" option', async t => {
513523
await gitTagVersion('v1.0.0');
514524
// Add new commits to the master branch
515525
await gitCommits(['Second']);
526+
await push();
516527

517528
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
518529
const notes = 'Release notes';
@@ -566,6 +577,7 @@ test.serial('Accept "undefined" value returned by the "generateNotes" plugins',
566577
await gitTagVersion('v1.0.0');
567578
// Add new commits to the master branch
568579
commits = (await gitCommits(['Second'])).concat(commits);
580+
await push();
569581

570582
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
571583
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 => {
623635
);
624636
});
625637

638+
test.serial('Returns falsy value if triggered on an outdated clone', async t => {
639+
// Create a git repository, set the current working directory at the root of the repo
640+
const repositoryUrl = await gitRepo(true);
641+
// Add commits to the master branch
642+
await gitCommits(['First']);
643+
await gitCommits(['Second']);
644+
await push();
645+
await reset();
646+
647+
const semanticRelease = proxyquire('..', {
648+
'./lib/logger': t.context.logger,
649+
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
650+
});
651+
652+
t.falsy(await semanticRelease({repositoryUrl}));
653+
t.deepEqual(t.context.log.args[t.context.log.args.length - 1], [
654+
"The local branch %s is behind the remote one, therefore a new version won't be published.",
655+
'master',
656+
]);
657+
});
658+
626659
test.serial('Returns falsy value if not running from the configured branch', async t => {
627660
// Create a git repository, set the current working directory at the root of the repo
628661
const repositoryUrl = await gitRepo(true);
@@ -656,6 +689,7 @@ test.serial('Returns falsy value if there is no relevant changes', async t => {
656689
const repositoryUrl = await gitRepo(true);
657690
// Add commits to the master branch
658691
await gitCommits(['First']);
692+
await push();
659693

660694
const analyzeCommits = stub().resolves();
661695
const verifyRelease = stub().resolves();
@@ -685,7 +719,10 @@ test.serial('Returns falsy value if there is no relevant changes', async t => {
685719
t.is(verifyRelease.callCount, 0);
686720
t.is(generateNotes.callCount, 0);
687721
t.is(publish.callCount, 0);
688-
t.is(t.context.log.args[7][0], 'There are no relevant changes, so no new version is released.');
722+
t.is(
723+
t.context.log.args[t.context.log.args.length - 1][0],
724+
'There are no relevant changes, so no new version is released.'
725+
);
689726
});
690727

691728
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
702739
'Test commit\n\n commit body\n[skip release]',
703740
'Test commit\n\n commit body\n[release skip]',
704741
]);
742+
await push();
705743
const analyzeCommits = stub().resolves();
706744
const config = {branch: 'master', repositoryUrl, globalOpt: 'global'};
707745
const options = {
@@ -826,6 +864,7 @@ test.serial('Throw an Error if plugin returns an unexpected value', async t => {
826864
await gitTagVersion('v1.0.0');
827865
// Add new commits to the master branch
828866
await gitCommits(['Second']);
867+
await push();
829868

830869
const verifyConditions = stub().resolves();
831870
const analyzeCommits = stub().resolves('string');

0 commit comments

Comments
 (0)
0