From b729183b4af2818c713634746628f68d06e3a8bc Mon Sep 17 00:00:00 2001 From: Pierre Vanduynslager Date: Thu, 29 Nov 2018 19:55:19 -0500 Subject: [PATCH] fix: fetch all release branches on CI --- index.js | 4 +--- lib/branches/index.js | 17 +++++++++++------ lib/git.js | 31 ++++++++++++++++++++++++------- test/branches/branches.test.js | 8 ++++---- test/branches/expand.test.js | 12 ++++++++++-- test/git.test.js | 20 ++++++++++++++------ 6 files changed, 64 insertions(+), 28 deletions(-) diff --git a/index.js b/index.js index a9ccf47e5d..7d41184b5b 100644 --- a/index.js +++ b/index.js @@ -18,7 +18,7 @@ const {extractErrors, makeTag} = require('./lib/utils'); const getGitAuthUrl = require('./lib/get-git-auth-url'); const getBranches = require('./lib/branches'); const getLogger = require('./lib/get-logger'); -const {fetch, verifyAuth, isBranchUpToDate, getGitHead, tag, push} = require('./lib/git'); +const {verifyAuth, isBranchUpToDate, getGitHead, tag, push} = require('./lib/git'); const getError = require('./lib/get-error'); const {COMMIT_NAME, COMMIT_EMAIL} = require('./lib/definitions/constants'); @@ -53,8 +53,6 @@ async function run(context, plugins) { // Verify config await verify(context); - await fetch({cwd, env}); - context.branches = await getBranches(context); context.branch = context.branches.find(({name}) => name === ciBranch); diff --git a/lib/branches/index.js b/lib/branches/index.js index b039a3ee80..8f586f692b 100644 --- a/lib/branches/index.js +++ b/lib/branches/index.js @@ -3,20 +3,25 @@ const AggregateError = require('aggregate-error'); const pEachSeries = require('p-each-series'); const DEFINITIONS = require('../definitions/branches'); const getError = require('../get-error'); -const {verifyBranchName} = require('../git'); +const {fetch, verifyBranchName} = require('../git'); const expand = require('./expand'); const getTags = require('./get-tags'); const normalize = require('./normalize'); module.exports = async context => { - const branches = await getTags( + const {cwd, env} = context; + + const remoteBranches = await expand( context, - await expand( - context, - context.options.branches.map(branch => (isString(branch) || isRegExp(branch) ? {name: branch} : branch)) - ) + context.options.branches.map(branch => (isString(branch) || isRegExp(branch) ? {name: branch} : branch)) ); + await pEachSeries(remoteBranches, async ({name}) => { + await fetch(name, {cwd, env}); + }); + + const branches = await getTags(context, remoteBranches); + const errors = []; const branchesByType = Object.entries(DEFINITIONS).reduce( (branchesByType, [type, {filter}]) => ({[type]: branches.filter(filter), ...branchesByType}), diff --git a/lib/git.js b/lib/git.js index 62b285a5a0..63ec5112c3 100644 --- a/lib/git.js +++ b/lib/git.js @@ -1,4 +1,4 @@ -const {trimStart, matches, pick, memoize} = require('lodash'); +const {matches, pick, memoize} = require('lodash'); const gitLogParser = require('git-log-parser'); const getStream = require('get-stream'); const execa = require('execa'); @@ -63,9 +63,9 @@ async function getCommits(from, to, execaOpts) { * @throws {Error} If the `git` command fails. */ async function getBranches(execaOpts) { - return (await execa.stdout('git', ['branch', '--list', '--no-color'], execaOpts)) + return (await execa.stdout('git', ['ls-remote', '--heads', 'origin'], execaOpts)) .split('\n') - .map(branch => trimStart(branch, '*').trim()) + .map(branch => branch.match(/^.+refs\/heads\/(.+)$/)[1]) .filter(Boolean); } @@ -126,13 +126,30 @@ async function isRefExists(ref, execaOpts) { /** * Unshallow the git repository if necessary and fetch all the tags. * + * @param {String} branch The repository branch to fetch. * @param {Object} [execaOpts] Options to pass to `execa`. */ -async function fetch(execaOpts) { +async function fetch(branch, execaOpts) { + const isLocalExists = + (await execa('git', ['rev-parse', '--verify', branch], {...execaOpts, reject: false})).code === 0; + try { - await execa('git', ['fetch', '--unshallow', '--tags'], execaOpts); + await execa( + 'git', + [ + 'fetch', + '--unshallow', + '--tags', + ...(isLocalExists ? [] : ['origin', `+refs/heads/${branch}:refs/heads/${branch}`]), + ], + execaOpts + ); } catch (error) { - await execa('git', ['fetch', '--tags'], execaOpts); + await execa( + 'git', + ['fetch', '--tags', ...(isLocalExists ? [] : ['origin', `+refs/heads/${branch}:refs/heads/${branch}`])], + execaOpts + ); } } @@ -181,7 +198,7 @@ async function isGitRepo(execaOpts) { * Verify the write access authorization to remote repository with push dry-run. * * @param {String} repositoryUrl The remote repository URL. - * @param {String} branch The repositoru branch for which to verify write access. + * @param {String} branch The repository branch for which to verify write access. * @param {Object} [execaOpts] Options to pass to `execa`. * * @throws {Error} if not authorized to push. diff --git a/test/branches/branches.test.js b/test/branches/branches.test.js index 31c19c36b4..a8f4c3cbbe 100644 --- a/test/branches/branches.test.js +++ b/test/branches/branches.test.js @@ -22,7 +22,7 @@ test('Enforce ranges with branching release workflow', async t => { {name: 'beta', prerelease: true, tags: []}, {name: 'alpha', prerelease: true, tags: []}, ]; - const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches}); + const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []}); let result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range})); t.is(getBranch(result, '1.0.x').range, '>=1.0.0 <1.0.0', 'Cannot release on 1.0.x before a releasing on master'); @@ -145,7 +145,7 @@ test('Throw SemanticReleaseError for invalid configurations', async t => { {name: 'alpha', prerelease: 'alpha', tags: []}, {name: 'preview', prerelease: 'alpha', tags: []}, ]; - const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches}); + const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []}); const errors = [...(await t.throws(getBranches({options: {branches}})))]; t.is(errors[0].name, 'SemanticReleaseError'); @@ -172,7 +172,7 @@ test('Throw SemanticReleaseError for invalid configurations', async t => { test('Throw a SemanticReleaseError if there is duplicate branches', async t => { const branches = [{name: 'master', tags: []}, {name: 'master', tags: []}]; - const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches}); + const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []}); const errors = [...(await t.throws(getBranches({options: {branches}})))]; @@ -184,7 +184,7 @@ test('Throw a SemanticReleaseError if there is duplicate branches', async t => { test('Throw a SemanticReleaseError for each invalid branch name', async t => { const branches = [{name: '~master', tags: []}, {name: '^master', tags: []}]; - const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches}); + const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []}); const errors = [...(await t.throws(getBranches({options: {branches}})))]; diff --git a/test/branches/expand.test.js b/test/branches/expand.test.js index ec0f3853e2..889ac6ed77 100644 --- a/test/branches/expand.test.js +++ b/test/branches/expand.test.js @@ -1,24 +1,32 @@ import test from 'ava'; import expand from '../../lib/branches/expand'; -import {gitRepo, gitCommits, gitCheckout} from '../helpers/git-utils'; +import {gitRepo, gitCommits, gitCheckout, gitPush} from '../helpers/git-utils'; test('Expand branches defined with globs', async t => { - const {cwd} = await gitRepo(); + const {cwd, repositoryUrl} = await gitRepo(true); await gitCommits(['First'], {cwd}); + await gitPush(repositoryUrl, 'master', {cwd}); await gitCheckout('1.1.x', true, {cwd}); await gitCommits(['Second'], {cwd}); + await gitPush(repositoryUrl, '1.1.x', {cwd}); await gitCheckout('1.x.x', true, {cwd}); await gitCommits(['Third'], {cwd}); + await gitPush(repositoryUrl, '1.x.x', {cwd}); await gitCheckout('2.x', true, {cwd}); await gitCommits(['Fourth'], {cwd}); + await gitPush(repositoryUrl, '2.x', {cwd}); await gitCheckout('next', true, {cwd}); await gitCommits(['Fifth'], {cwd}); + await gitPush(repositoryUrl, 'next', {cwd}); await gitCheckout('pre/foo', true, {cwd}); await gitCommits(['Sixth'], {cwd}); + await gitPush(repositoryUrl, 'pre/foo', {cwd}); await gitCheckout('pre/bar', true, {cwd}); await gitCommits(['Seventh'], {cwd}); + await gitPush(repositoryUrl, 'pre/bar', {cwd}); await gitCheckout('beta', true, {cwd}); await gitCommits(['Eighth'], {cwd}); + await gitPush(repositoryUrl, 'beta', {cwd}); const branches = [ // Should match all maintenance type branches diff --git a/test/git.test.js b/test/git.test.js index 68c97645bb..82288b10a2 100644 --- a/test/git.test.js +++ b/test/git.test.js @@ -58,7 +58,7 @@ test('Unshallow and fetch repository', async t => { // Verify the shallow clone contains only one commit t.is((await gitGetCommits(undefined, {cwd})).length, 1); - await fetch({cwd}); + await fetch('master', {cwd}); // Verify the shallow clone contains all the commits t.is((await gitGetCommits(undefined, {cwd})).length, 2); @@ -66,10 +66,15 @@ test('Unshallow and fetch repository', async t => { test('Do not throw error when unshallow a complete repository', async t => { // Create a git repository, set the current working directory at the root of the repo - const {cwd} = await gitRepo(true); - // Add commits to the master branch + const {cwd, repositoryUrl} = await gitRepo(true); await gitCommits(['First'], {cwd}); - await t.notThrows(fetch({cwd})); + await gitPush(repositoryUrl, 'master', {cwd}); + await gitCheckout('second-branch', true, {cwd}); + await gitCommits(['Second'], {cwd}); + await gitPush(repositoryUrl, 'second-branch', {cwd}); + + await t.notThrows(fetch('master', {cwd})); + await t.notThrows(fetch('second-branch', {cwd})); }); test('Fetch all tags on a detached head repository', async t => { @@ -84,7 +89,7 @@ test('Fetch all tags on a detached head repository', async t => { await gitPush(repositoryUrl, 'master', {cwd}); cwd = await gitDetachedHead(repositoryUrl, commit.hash); - await fetch({cwd}); + await fetch('master', {cwd}); t.deepEqual((await getTags({cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0'].sort()); }); @@ -122,12 +127,15 @@ test('Verify if a branch exists', async t => { }); test('Get all branches', async t => { - const {cwd} = await gitRepo(); + const {cwd, repositoryUrl} = await gitRepo(true); await gitCommits(['First'], {cwd}); + await gitPush(repositoryUrl, 'master', {cwd}); await gitCheckout('second-branch', true, {cwd}); await gitCommits(['Second'], {cwd}); + await gitPush(repositoryUrl, 'second-branch', {cwd}); await gitCheckout('third-branch', true, {cwd}); await gitCommits(['Third'], {cwd}); + await gitPush(repositoryUrl, 'third-branch', {cwd}); t.deepEqual((await getBranches({cwd})).sort(), ['master', 'second-branch', 'third-branch'].sort()); });