diff --git a/src/lib/git.test.ts b/src/lib/git.test.ts index 15df6ffb..7ae7998b 100644 --- a/src/lib/git.test.ts +++ b/src/lib/git.test.ts @@ -313,7 +313,30 @@ describe('createBackportBranch', () => { backportBranch, }), ).rejects.toThrowErrorMatchingInlineSnapshot( - `"The branch "4.x" is invalid or doesn't exist"`, + `"The remote "'https://github.com/elastic/kibana.git'" is invalid or doesn't exist"`, + ); + }); + + it('should throw "is not a commit" error', async () => { + expect.assertions(1); + const err = new childProcess.SpawnError({ + code: 128, + cmdArgs: [], + stdout: '', + stderr: + "fatal: 'origin/remote-branch-name' is not a commit and a branch 'local-branch-name' cannot be created from it", + }); + + jest.spyOn(childProcess, 'spawnPromise').mockRejectedValueOnce(err); + await expect( + createBackportBranch({ + options, + sourceBranch, + targetBranch, + backportBranch, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"The branch "origin/remote-branch-name'" is invalid or doesn't exist"`, ); }); diff --git a/src/lib/git.ts b/src/lib/git.ts index d2268842..1cce84f9 100644 --- a/src/lib/git.ts +++ b/src/lib/git.ts @@ -504,9 +504,9 @@ export async function createBackportBranch({ }) { const spinner = ora(options.interactive, 'Pulling latest changes').start(); - try { - const cwd = getRepoPath(options); + const cwd = getRepoPath(options); + try { await spawnPromise('git', ['reset', '--hard'], cwd); await spawnPromise('git', ['clean', '-d', '--force'], cwd); @@ -534,35 +534,48 @@ export async function createBackportBranch({ try { await spawnPromise('git', ['branch', '-D', tmpBranchName], cwd); } catch (e) { - // + // swallow error } // fetch commits for source branch await fetchBranch(options, sourceBranch); - - spinner.succeed(); } catch (e) { spinner.fail(); if (e instanceof SpawnError) { - const isBranchInvalid = - e.context.stderr.toLowerCase().includes(`couldn't find remote ref`) || - e.context.stderr.toLowerCase().includes(`invalid refspec`) || - e.context.stderr - .toLowerCase() - .includes( - `is not a commit and a branch '${backportBranch}' cannot be created from it`, - ); - - if (isBranchInvalid) { + const invalidRemoteRef = e.context.stderr + .toLowerCase() + .match(/couldn't find remote ref (.*)/)?.[1]; + + const invalidCommit = e.context.stderr + .toLowerCase() + .match( + /'(.+) is not a commit and a branch .+ cannot be created from it/, + )?.[1]; + + const invalidBranch = invalidRemoteRef || invalidCommit; + + if (invalidBranch) { throw new BackportError( - `The branch "${targetBranch}" is invalid or doesn't exist`, + `The branch "${invalidBranch}" is invalid or doesn't exist`, + ); + } + + const invalidRefSpec = e.context.stderr + .toLowerCase() + .match(/invalid refspec (.*)/)?.[1]; + + if (invalidRefSpec) { + throw new BackportError( + `The remote "${invalidRefSpec}" is invalid or doesn't exist`, ); } } throw e; } + + spinner.succeed(); } export async function deleteBackportBranch({ diff --git a/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts b/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts index 8b2be735..a26dc30c 100644 --- a/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts +++ b/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts @@ -8,7 +8,7 @@ import { } from '../../../git'; import { logger } from '../../../logger'; import { - parseRemoteConfig, + parseRemoteConfigFile, swallowMissingConfigFileException, } from '../../../remoteConfig'; import { @@ -145,7 +145,7 @@ async function getRemoteConfigFileOptions( } logger.info('Remote config: Parsing.'); - return parseRemoteConfig(remoteConfig); + return parseRemoteConfigFile(remoteConfig); } function throwIfInsufficientPermissions( diff --git a/src/lib/remoteConfig.ts b/src/lib/remoteConfig.ts index a6aa0635..2defba82 100644 --- a/src/lib/remoteConfig.ts +++ b/src/lib/remoteConfig.ts @@ -1,6 +1,5 @@ import gql from 'graphql-tag'; -import { ConfigFileOptions } from '../entrypoint.api'; -import { withConfigMigrations } from '../options/config/readConfigFile'; +import { parseConfigFile } from '../options/config/readConfigFile'; import { GithubV4Exception } from './github/v4/apiRequestV4'; import { logger } from './logger'; @@ -40,11 +39,9 @@ export interface RemoteConfigHistory { }; } -export function parseRemoteConfig(remoteConfig: RemoteConfig) { +export function parseRemoteConfigFile(remoteConfig: RemoteConfig) { try { - return withConfigMigrations( - JSON.parse(remoteConfig.file.object.text), - ) as ConfigFileOptions; + return parseConfigFile(remoteConfig.file.object.text); } catch (e) { logger.info('Parsing remote config failed', e); return; diff --git a/src/lib/sourceCommit/parseSourceCommit.ts b/src/lib/sourceCommit/parseSourceCommit.ts index 84436551..cdd6de4b 100644 --- a/src/lib/sourceCommit/parseSourceCommit.ts +++ b/src/lib/sourceCommit/parseSourceCommit.ts @@ -2,9 +2,9 @@ import gql from 'graphql-tag'; import { differenceBy } from 'lodash'; import { ValidConfigOptions } from '../../options/options'; import { - parseRemoteConfig, RemoteConfigHistory, RemoteConfigHistoryFragment, + parseRemoteConfigFile, } from '../remoteConfig'; import { TargetPullRequest, @@ -281,6 +281,6 @@ function getSourceCommitBranchLabelMapping( sourcePullRequest?.mergeCommit.remoteConfigHistory.edges?.[0]?.remoteConfig; if (remoteConfig) { - return parseRemoteConfig(remoteConfig)?.branchLabelMapping; + return parseRemoteConfigFile(remoteConfig)?.branchLabelMapping; } } diff --git a/src/options/config/readConfigFile.ts b/src/options/config/readConfigFile.ts index ff032d3b..3d638158 100644 --- a/src/options/config/readConfigFile.ts +++ b/src/options/config/readConfigFile.ts @@ -8,10 +8,9 @@ export async function readConfigFile( filepath: string, ): Promise { const fileContents = await readFile(filepath, 'utf8'); - const configWithoutComments = stripJsonComments(fileContents); try { - return withConfigMigrations(JSON.parse(configWithoutComments)); + return parseConfigFile(fileContents); } catch (e) { throw new BackportError( `"${filepath}" contains invalid JSON:\n\n${fileContents}`, @@ -19,13 +18,11 @@ export async function readConfigFile( } } // ensure backwards compatability when config options are renamed -export function withConfigMigrations({ - upstream, - labels, - branches, - addOriginalReviewers, - ...config -}: ConfigFileOptions) { +export function parseConfigFile(fileContents: string): ConfigFileOptions { + const configWithoutComments = stripJsonComments(fileContents); + const { upstream, labels, branches, addOriginalReviewers, ...config } = + JSON.parse(configWithoutComments); + const { repoName, repoOwner } = parseUpstream(upstream, config); return excludeUndefined({