mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-11 22:46:27 +00:00
refactor(git): Extract error handling to separate file (#13819)
This commit is contained in:
parent
f2b2f60f4d
commit
fbb0a01f15
2 changed files with 145 additions and 132 deletions
143
lib/util/git/error.ts
Normal file
143
lib/util/git/error.ts
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
import { CONFIG_VALIDATION } from '../../constants/error-messages';
|
||||||
|
import { logger } from '../../logger';
|
||||||
|
import { ExternalHostError } from '../../types/errors/external-host-error';
|
||||||
|
import { FileChange } from './types';
|
||||||
|
|
||||||
|
// istanbul ignore next
|
||||||
|
export function checkForPlatformFailure(err: Error): void {
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const externalHostFailureStrings = [
|
||||||
|
'remote: Invalid username or password',
|
||||||
|
'gnutls_handshake() failed',
|
||||||
|
'The requested URL returned error: 5',
|
||||||
|
'The remote end hung up unexpectedly',
|
||||||
|
'access denied or repository not exported',
|
||||||
|
'Could not write new index file',
|
||||||
|
'Failed to connect to',
|
||||||
|
'Connection timed out',
|
||||||
|
'malformed object name',
|
||||||
|
'Could not resolve host',
|
||||||
|
'early EOF',
|
||||||
|
'fatal: bad config', // .gitmodules problem
|
||||||
|
'expected flush after ref listing',
|
||||||
|
];
|
||||||
|
for (const errorStr of externalHostFailureStrings) {
|
||||||
|
if (err.message.includes(errorStr)) {
|
||||||
|
logger.debug({ err }, 'Converting git error to ExternalHostError');
|
||||||
|
throw new ExternalHostError(err, 'git');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const configErrorStrings = [
|
||||||
|
{
|
||||||
|
error: 'GitLab: Branch name does not follow the pattern',
|
||||||
|
message:
|
||||||
|
"Cannot push because branch name does not follow project's push rules",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
error: 'GitLab: Commit message does not follow the pattern',
|
||||||
|
message:
|
||||||
|
"Cannot push because commit message does not follow project's push rules",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
error: ' is not a member of team',
|
||||||
|
message:
|
||||||
|
'The `Restrict commits to existing GitLab users` rule is blocking Renovate push. Check the Renovate `gitAuthor` setting',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
error: 'TF401027:',
|
||||||
|
message:
|
||||||
|
'You need the Git `GenericContribute` permission to perform this action',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
error: 'matches more than one',
|
||||||
|
message:
|
||||||
|
"Renovate cannot push branches if there are tags with names the same as Renovate's branches. Please remove conflicting tag names or change Renovate's branchPrefix to avoid conflicts.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (const { error, message } of configErrorStrings) {
|
||||||
|
if (err.message.includes(error)) {
|
||||||
|
logger.debug({ err }, 'Converting git error to CONFIG_VALIDATION error');
|
||||||
|
const res = new Error(CONFIG_VALIDATION);
|
||||||
|
res.validationError = message;
|
||||||
|
res.validationMessage = err.message;
|
||||||
|
throw res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// istanbul ignore next
|
||||||
|
export function handleCommitError(
|
||||||
|
files: FileChange[],
|
||||||
|
branchName: string,
|
||||||
|
err: Error
|
||||||
|
): null {
|
||||||
|
checkForPlatformFailure(err);
|
||||||
|
if (err.message.includes(`'refs/heads/renovate' exists`)) {
|
||||||
|
const error = new Error(CONFIG_VALIDATION);
|
||||||
|
error.validationSource = 'None';
|
||||||
|
error.validationError = 'An existing branch is blocking Renovate';
|
||||||
|
error.validationMessage = `Renovate needs to create the branch "${branchName}" but is blocked from doing so because of an existing branch called "renovate". Please remove it so that Renovate can proceed.`;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
err.message.includes(
|
||||||
|
'refusing to allow a GitHub App to create or update workflow'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
logger.warn(
|
||||||
|
'App has not been granted permissions to update Workflows - aborting branch.'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(err.message.includes('remote rejected') || err.message.includes('403')) &&
|
||||||
|
files?.some((file) => file.path?.startsWith('.github/workflows/'))
|
||||||
|
) {
|
||||||
|
logger.debug({ err }, 'commitFiles error');
|
||||||
|
logger.info('Workflows update rejection - aborting branch.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (err.message.includes('protected branch hook declined')) {
|
||||||
|
const error = new Error(CONFIG_VALIDATION);
|
||||||
|
error.validationSource = branchName;
|
||||||
|
error.validationError = 'Renovate branch is protected';
|
||||||
|
error.validationMessage = `Renovate cannot push to its branch because branch protection has been enabled.`;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if (err.message.includes('can only push your own commits')) {
|
||||||
|
const error = new Error(CONFIG_VALIDATION);
|
||||||
|
error.validationSource = branchName;
|
||||||
|
error.validationError = 'Bitbucket committer error';
|
||||||
|
error.validationMessage = `Renovate has experienced the following error when attempting to push its branch to the server: "${String(
|
||||||
|
err.message
|
||||||
|
)}"`;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if (err.message.includes('remote: error: cannot lock ref')) {
|
||||||
|
logger.error({ err }, 'Error committing files.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (err.message.includes('[rejected] (stale info)')) {
|
||||||
|
logger.info(
|
||||||
|
'Branch update was rejected because local copy is not up-to-date.'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
err.message.includes('denying non-fast-forward') ||
|
||||||
|
err.message.includes('GH003: Sorry, force-pushing')
|
||||||
|
) {
|
||||||
|
logger.debug({ err }, 'Permission denied to update branch');
|
||||||
|
const error = new Error(CONFIG_VALIDATION);
|
||||||
|
error.validationSource = branchName;
|
||||||
|
error.validationError = 'Force push denied';
|
||||||
|
error.validationMessage = `Renovate is unable to update branch(es) due to force pushes being disallowed.`;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
logger.debug({ err }, 'Unknown error committing files');
|
||||||
|
// We don't know why this happened, so this will cause bubble up to a branch error
|
||||||
|
throw err;
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import { Limit, incLimitedValue } from '../../workers/global/limits';
|
||||||
import { regEx } from '../regex';
|
import { regEx } from '../regex';
|
||||||
import { parseGitAuthor } from './author';
|
import { parseGitAuthor } from './author';
|
||||||
import { getNoVerify, simpleGitConfig } from './config';
|
import { getNoVerify, simpleGitConfig } from './config';
|
||||||
|
import { checkForPlatformFailure, handleCommitError } from './error';
|
||||||
import { configSigningKey, writePrivateKey } from './private-key';
|
import { configSigningKey, writePrivateKey } from './private-key';
|
||||||
import type {
|
import type {
|
||||||
CommitFilesConfig,
|
CommitFilesConfig,
|
||||||
|
@ -42,71 +43,6 @@ export { setPrivateKey } from './private-key';
|
||||||
// TODO: fix upstream types https://github.com/steveukx/git-js/issues/704
|
// TODO: fix upstream types https://github.com/steveukx/git-js/issues/704
|
||||||
const ResetMode = (simpleGit.default as any).ResetMode as typeof _ResetMode;
|
const ResetMode = (simpleGit.default as any).ResetMode as typeof _ResetMode;
|
||||||
|
|
||||||
// istanbul ignore next
|
|
||||||
function checkForPlatformFailure(err: Error): void {
|
|
||||||
if (process.env.NODE_ENV === 'test') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const externalHostFailureStrings = [
|
|
||||||
'remote: Invalid username or password',
|
|
||||||
'gnutls_handshake() failed',
|
|
||||||
'The requested URL returned error: 5',
|
|
||||||
'The remote end hung up unexpectedly',
|
|
||||||
'access denied or repository not exported',
|
|
||||||
'Could not write new index file',
|
|
||||||
'Failed to connect to',
|
|
||||||
'Connection timed out',
|
|
||||||
'malformed object name',
|
|
||||||
'Could not resolve host',
|
|
||||||
'early EOF',
|
|
||||||
'fatal: bad config', // .gitmodules problem
|
|
||||||
'expected flush after ref listing',
|
|
||||||
];
|
|
||||||
for (const errorStr of externalHostFailureStrings) {
|
|
||||||
if (err.message.includes(errorStr)) {
|
|
||||||
logger.debug({ err }, 'Converting git error to ExternalHostError');
|
|
||||||
throw new ExternalHostError(err, 'git');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const configErrorStrings = [
|
|
||||||
{
|
|
||||||
error: 'GitLab: Branch name does not follow the pattern',
|
|
||||||
message:
|
|
||||||
"Cannot push because branch name does not follow project's push rules",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
error: 'GitLab: Commit message does not follow the pattern',
|
|
||||||
message:
|
|
||||||
"Cannot push because commit message does not follow project's push rules",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
error: ' is not a member of team',
|
|
||||||
message:
|
|
||||||
'The `Restrict commits to existing GitLab users` rule is blocking Renovate push. Check the Renovate `gitAuthor` setting',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
error: 'TF401027:',
|
|
||||||
message:
|
|
||||||
'You need the Git `GenericContribute` permission to perform this action',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
error: 'matches more than one',
|
|
||||||
message:
|
|
||||||
"Renovate cannot push branches if there are tags with names the same as Renovate's branches. Please remove conflicting tag names or change Renovate's branchPrefix to avoid conflicts.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
for (const { error, message } of configErrorStrings) {
|
|
||||||
if (err.message.includes(error)) {
|
|
||||||
logger.debug({ err }, 'Converting git error to CONFIG_VALIDATION error');
|
|
||||||
const res = new Error(CONFIG_VALIDATION);
|
|
||||||
res.validationError = message;
|
|
||||||
res.validationMessage = err.message;
|
|
||||||
throw res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function localName(branchName: string): string {
|
function localName(branchName: string): string {
|
||||||
return branchName.replace(regEx(/^origin\//), '');
|
return branchName.replace(regEx(/^origin\//), '');
|
||||||
}
|
}
|
||||||
|
@ -881,73 +817,7 @@ export async function commitFiles({
|
||||||
incLimitedValue(Limit.Commits);
|
incLimitedValue(Limit.Commits);
|
||||||
return commit;
|
return commit;
|
||||||
} catch (err) /* istanbul ignore next */ {
|
} catch (err) /* istanbul ignore next */ {
|
||||||
checkForPlatformFailure(err);
|
return handleCommitError(files, branchName, err);
|
||||||
if (err.message.includes(`'refs/heads/renovate' exists`)) {
|
|
||||||
const error = new Error(CONFIG_VALIDATION);
|
|
||||||
error.validationSource = 'None';
|
|
||||||
error.validationError = 'An existing branch is blocking Renovate';
|
|
||||||
error.validationMessage = `Renovate needs to create the branch "${branchName}" but is blocked from doing so because of an existing branch called "renovate". Please remove it so that Renovate can proceed.`;
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
err.message.includes(
|
|
||||||
'refusing to allow a GitHub App to create or update workflow'
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
logger.warn(
|
|
||||||
'App has not been granted permissions to update Workflows - aborting branch.'
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(err.message.includes('remote rejected') ||
|
|
||||||
err.message.includes('403')) &&
|
|
||||||
files?.some((file) => file.path?.startsWith('.github/workflows/'))
|
|
||||||
) {
|
|
||||||
logger.debug({ err }, 'commitFiles error');
|
|
||||||
logger.info('Workflows update rejection - aborting branch.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (err.message.includes('protected branch hook declined')) {
|
|
||||||
const error = new Error(CONFIG_VALIDATION);
|
|
||||||
error.validationSource = branchName;
|
|
||||||
error.validationError = 'Renovate branch is protected';
|
|
||||||
error.validationMessage = `Renovate cannot push to its branch because branch protection has been enabled.`;
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
if (err.message.includes('can only push your own commits')) {
|
|
||||||
const error = new Error(CONFIG_VALIDATION);
|
|
||||||
error.validationSource = branchName;
|
|
||||||
error.validationError = 'Bitbucket committer error';
|
|
||||||
error.validationMessage = `Renovate has experienced the following error when attempting to push its branch to the server: "${String(
|
|
||||||
err.message
|
|
||||||
)}"`;
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
if (err.message.includes('remote: error: cannot lock ref')) {
|
|
||||||
logger.error({ err }, 'Error committing files.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (err.message.includes('[rejected] (stale info)')) {
|
|
||||||
logger.info(
|
|
||||||
'Branch update was rejected because local copy is not up-to-date.'
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
err.message.includes('denying non-fast-forward') ||
|
|
||||||
err.message.includes('GH003: Sorry, force-pushing')
|
|
||||||
) {
|
|
||||||
logger.debug({ err }, 'Permission denied to update branch');
|
|
||||||
const error = new Error(CONFIG_VALIDATION);
|
|
||||||
error.validationSource = branchName;
|
|
||||||
error.validationError = 'Force push denied';
|
|
||||||
error.validationMessage = `Renovate is unable to update branch(es) due to force pushes being disallowed.`;
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
logger.debug({ err }, 'Unknown error committing files');
|
|
||||||
// We don't know why this happened, so this will cause bubble up to a branch error
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue