mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-01 10:09:50 +00:00
4afe14fb19
* refactor: emojify pr body notes Signed-off-by: Adam Setch <adam.setch@outlook.com> * refactor: emojify pr body notes Signed-off-by: Adam Setch <adam.setch@outlook.com> * refactor: emojify pr body notes Signed-off-by: Adam Setch <adam.setch@outlook.com> --------- Signed-off-by: Adam Setch <adam.setch@outlook.com>
657 lines
22 KiB
TypeScript
657 lines
22 KiB
TypeScript
import {
|
|
isArray,
|
|
isNonEmptyArray,
|
|
isNumber,
|
|
isUndefined,
|
|
} from '@sindresorhus/is';
|
|
import { codeBlock } from 'common-tags';
|
|
import { GlobalConfig } from '../../../../config/global.ts';
|
|
import type { RenovateConfig } from '../../../../config/types.ts';
|
|
import {
|
|
PLATFORM_INTEGRATION_UNAUTHORIZED,
|
|
PLATFORM_RATE_LIMIT_EXCEEDED,
|
|
REPOSITORY_CHANGED,
|
|
} from '../../../../constants/error-messages.ts';
|
|
import { pkg } from '../../../../expose.ts';
|
|
import { logger } from '../../../../logger/index.ts';
|
|
import { ensureComment } from '../../../../modules/platform/comment.ts';
|
|
import type {
|
|
PlatformPrOptions,
|
|
Pr,
|
|
PrDebugData,
|
|
UpdatePrConfig,
|
|
} from '../../../../modules/platform/index.ts';
|
|
import { platform } from '../../../../modules/platform/index.ts';
|
|
import {
|
|
getPrBodyStruct,
|
|
hashBody,
|
|
} from '../../../../modules/platform/pr-body.ts';
|
|
import { scm } from '../../../../modules/platform/scm.ts';
|
|
import { ExternalHostError } from '../../../../types/errors/external-host-error.ts';
|
|
import { getElapsedHours } from '../../../../util/date.ts';
|
|
import { emojify, stripEmojis } from '../../../../util/emoji.ts';
|
|
import { fingerprint } from '../../../../util/fingerprint.ts';
|
|
import { getBranchLastCommitTime } from '../../../../util/git/index.ts';
|
|
import { memoize } from '../../../../util/memoize.ts';
|
|
import { incCountValue, isLimitReached } from '../../../global/limits.ts';
|
|
import type {
|
|
BranchConfig,
|
|
BranchUpgradeConfig,
|
|
PrBlockedBy,
|
|
} from '../../../types.ts';
|
|
import { embedChangelogs } from '../../changelog/index.ts';
|
|
import { resolveBranchStatus } from '../branch/status-checks.ts';
|
|
import { getPrBody } from './body/index.ts';
|
|
import {
|
|
getChangedLabels,
|
|
prepareLabels,
|
|
shouldUpdateLabels,
|
|
} from './labels.ts';
|
|
import { addParticipants } from './participants.ts';
|
|
import { getPrCache, setPrCache } from './pr-cache.ts';
|
|
import {
|
|
generatePrBodyFingerprintConfig,
|
|
validatePrCache,
|
|
} from './pr-fingerprint.ts';
|
|
import { tryReuseAutoclosedPr } from './pr-reuse.ts';
|
|
|
|
export function getPlatformPrOptions(
|
|
config: RenovateConfig & PlatformPrOptions,
|
|
): PlatformPrOptions {
|
|
const usePlatformAutomerge = Boolean(
|
|
config.automerge &&
|
|
(config.automergeType === 'pr' || config.automergeType === 'branch') &&
|
|
config.platformAutomerge,
|
|
);
|
|
|
|
return {
|
|
autoApprove: !!config.autoApprove,
|
|
automergeCommitMessage: config.commitMessage,
|
|
automergeStrategy: config.automergeStrategy,
|
|
azureWorkItemId: config.azureWorkItemId ?? 0,
|
|
bbAutoResolvePrTasks: !!config.bbAutoResolvePrTasks,
|
|
bbUseDefaultReviewers: !!config.bbUseDefaultReviewers,
|
|
gitLabIgnoreApprovals: !!config.gitLabIgnoreApprovals,
|
|
forkModeDisallowMaintainerEdits: !!config.forkModeDisallowMaintainerEdits,
|
|
usePlatformAutomerge,
|
|
};
|
|
}
|
|
|
|
export interface ResultWithPr {
|
|
type: 'with-pr';
|
|
pr: Pr;
|
|
}
|
|
|
|
export interface ResultWithoutPr {
|
|
type: 'without-pr';
|
|
prBlockedBy: PrBlockedBy;
|
|
}
|
|
|
|
export type EnsurePrResult = ResultWithPr | ResultWithoutPr;
|
|
|
|
export function updatePrDebugData(
|
|
targetBranch: string,
|
|
labels: string[],
|
|
debugData: PrDebugData | undefined,
|
|
): PrDebugData {
|
|
const createdByRenovateVersion = debugData?.createdInVer ?? pkg.version;
|
|
const updatedByRenovateVersion = pkg.version;
|
|
|
|
const updatedPrDebugData: PrDebugData = {
|
|
createdInVer: createdByRenovateVersion,
|
|
updatedInVer: updatedByRenovateVersion,
|
|
targetBranch,
|
|
};
|
|
|
|
// Add labels to the debug data object.
|
|
// When to add:
|
|
// 1. Add it when a new PR is created, i.e., when debugData is undefined.
|
|
// 2. Add it if an existing PR already has labels in the debug data, confirming that we can update its labels.
|
|
if (!debugData || isArray(debugData.labels)) {
|
|
updatedPrDebugData.labels = labels;
|
|
}
|
|
|
|
return updatedPrDebugData;
|
|
}
|
|
|
|
function hasNotIgnoredReviewers(pr: Pr, config: BranchConfig): boolean {
|
|
if (
|
|
isNonEmptyArray(config.ignoreReviewers) &&
|
|
isNonEmptyArray(pr.reviewers)
|
|
) {
|
|
const ignoreReviewers = new Set(config.ignoreReviewers);
|
|
return (
|
|
pr.reviewers.filter((reviewer) => !ignoreReviewers.has(reviewer)).length >
|
|
0
|
|
);
|
|
}
|
|
return isNonEmptyArray(pr.reviewers);
|
|
}
|
|
|
|
function addPullRequestNoteIfAttestationHasBeenLost(
|
|
upgrade: BranchUpgradeConfig,
|
|
currentReleaseHasAttestation: boolean | undefined,
|
|
): void {
|
|
const { packageName, depName, currentVersion, newVersion } = upgrade;
|
|
const name = packageName ?? depName;
|
|
|
|
const newRelease = upgrade.releases?.find(
|
|
(release) => release.version === newVersion,
|
|
);
|
|
|
|
if (
|
|
newRelease &&
|
|
currentReleaseHasAttestation === true &&
|
|
newRelease.attestation !== true
|
|
) {
|
|
upgrade.prBodyNotes ??= [];
|
|
upgrade.prBodyNotes.push(
|
|
emojify(
|
|
codeBlock`
|
|
> :stop_sign: **Caution**
|
|
>
|
|
> ${name} ${currentVersion} was released with an attestation, but ${newVersion} has no attestation.
|
|
> Verify that release ${newVersion} was published by the expected author.
|
|
`,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// Ensures that PR exists with matching title/body
|
|
export async function ensurePr(
|
|
prConfig: BranchConfig,
|
|
): Promise<EnsurePrResult> {
|
|
const config: BranchConfig = { ...prConfig };
|
|
const filteredPrConfig = generatePrBodyFingerprintConfig(config);
|
|
const prBodyFingerprint = fingerprint(filteredPrConfig);
|
|
logger.trace({ config }, 'ensurePr');
|
|
// If there is a group, it will use the config of the first upgrade in the array
|
|
const {
|
|
branchName,
|
|
ignoreTests,
|
|
internalChecksAsSuccess,
|
|
prTitle = '',
|
|
upgrades,
|
|
hasAttestation: currentReleaseHasAttestation,
|
|
} = config;
|
|
const getBranchStatus = memoize(() =>
|
|
resolveBranchStatus(branchName, !!internalChecksAsSuccess, ignoreTests),
|
|
);
|
|
const dependencyDashboardCheck =
|
|
config.dependencyDashboardChecks?.[config.branchName];
|
|
const dependencyDashboardApproved =
|
|
dependencyDashboardCheck === 'approvePr' ||
|
|
dependencyDashboardCheck === 'unpend';
|
|
// Check if PR already exists
|
|
const existingPr =
|
|
(await platform.getBranchPr(branchName, config.baseBranch)) ??
|
|
(await tryReuseAutoclosedPr(branchName, prTitle));
|
|
const prCache = getPrCache(branchName);
|
|
if (existingPr) {
|
|
logger.debug('Found existing PR');
|
|
if (existingPr.bodyStruct?.rebaseRequested) {
|
|
logger.debug('PR rebase requested, so skipping cache check');
|
|
} else if (prCache) {
|
|
logger.trace({ prCache }, 'Found existing PR cache');
|
|
// return if pr cache is valid and pr was not changed in the past 24hrs
|
|
// skip cache when autoApprove is set, since new commits may have
|
|
// reset platform approvals (e.g. GitLab's "Remove all approvals" setting)
|
|
if (validatePrCache(prCache, prBodyFingerprint) && !config.autoApprove) {
|
|
return { type: 'with-pr', pr: existingPr };
|
|
}
|
|
} else if (config.repositoryCache === 'enabled') {
|
|
logger.debug('PR cache not found');
|
|
}
|
|
}
|
|
config.upgrades = [];
|
|
|
|
if (config.artifactErrors?.length) {
|
|
logger.debug('Forcing PR because of artifact errors');
|
|
config.forcePr = true;
|
|
}
|
|
|
|
if (dependencyDashboardApproved) {
|
|
logger.debug('Forcing PR because of dependency dashboard approval');
|
|
config.forcePr = true;
|
|
}
|
|
|
|
if (!existingPr) {
|
|
// Only create a PR if a branch automerge has failed
|
|
if (
|
|
config.automerge === true &&
|
|
config.automergeType?.startsWith('branch') &&
|
|
!config.forcePr
|
|
) {
|
|
logger.debug(`Branch automerge is enabled`);
|
|
if (
|
|
config.stabilityStatus !== 'yellow' &&
|
|
(await getBranchStatus()) === 'yellow' &&
|
|
isNumber(config.prNotPendingHours)
|
|
) {
|
|
logger.debug('Checking how long this branch has been pending');
|
|
const lastCommitTime = await getBranchLastCommitTime(branchName);
|
|
if (getElapsedHours(lastCommitTime) >= config.prNotPendingHours) {
|
|
logger.debug(
|
|
`Branch exceeds prNotPending=${config.prNotPendingHours}, hours - forcing PR creation`,
|
|
);
|
|
config.forcePr = true;
|
|
}
|
|
}
|
|
if (config.forcePr || (await getBranchStatus()) === 'red') {
|
|
logger.debug(`Branch tests failed, so will create PR`);
|
|
} else {
|
|
// Branch should be automerged, so we don't want to create a PR
|
|
return { type: 'without-pr', prBlockedBy: 'BranchAutomerge' };
|
|
}
|
|
}
|
|
if (config.prCreation === 'status-success') {
|
|
logger.debug('Checking branch combined status');
|
|
if ((await getBranchStatus()) !== 'green') {
|
|
logger.debug(`Branch status isn't green - not creating PR`);
|
|
return { type: 'without-pr', prBlockedBy: 'AwaitingTests' };
|
|
}
|
|
logger.debug('Branch status success');
|
|
} else if (
|
|
config.prCreation === 'approval' &&
|
|
!dependencyDashboardApproved
|
|
) {
|
|
return { type: 'without-pr', prBlockedBy: 'NeedsApproval' };
|
|
} else if (config.prCreation === 'not-pending' && !config.forcePr) {
|
|
logger.debug('Checking branch combined status');
|
|
if ((await getBranchStatus()) === 'yellow') {
|
|
logger.debug(`Branch status is yellow - checking timeout`);
|
|
const lastCommitTime = await getBranchLastCommitTime(branchName);
|
|
const elapsedHours = getElapsedHours(lastCommitTime);
|
|
if (
|
|
!dependencyDashboardCheck &&
|
|
((config.stabilityStatus && config.stabilityStatus !== 'yellow') ||
|
|
(isNumber(config.prNotPendingHours) &&
|
|
elapsedHours < config.prNotPendingHours))
|
|
) {
|
|
logger.debug(
|
|
`Branch is ${elapsedHours} hours old - skipping PR creation as prNotPendingHours is set to ${config.prNotPendingHours}`,
|
|
);
|
|
return {
|
|
type: 'without-pr',
|
|
prBlockedBy: 'AwaitingTests',
|
|
};
|
|
}
|
|
const prNotPendingHours = String(config.prNotPendingHours);
|
|
logger.debug(
|
|
`prNotPendingHours=${prNotPendingHours} threshold hit - creating PR`,
|
|
);
|
|
}
|
|
logger.debug('Branch status success');
|
|
}
|
|
}
|
|
|
|
const processedUpgrades: string[] = [];
|
|
const commitRepos: string[] = [];
|
|
|
|
function getRepoNameWithSourceDirectory(
|
|
upgrade: BranchUpgradeConfig,
|
|
): string {
|
|
// TODO: types (#22198)
|
|
return `${upgrade.repoName!}${
|
|
upgrade.sourceDirectory ? `:${upgrade.sourceDirectory}` : ''
|
|
}`;
|
|
}
|
|
|
|
// fetch changelogs for matching upgrades.
|
|
await embedChangelogs({
|
|
upgrades: upgrades,
|
|
stage: 'pr',
|
|
});
|
|
|
|
// Get changelog and then generate template strings
|
|
for (const upgrade of upgrades) {
|
|
// TODO: types (#22198)
|
|
const upgradeKey = `${upgrade.depType!}-${upgrade.depName!}-${upgrade.manager}-${upgrade.currentVersion ?? ''}-${upgrade.currentValue ?? ''}-${upgrade.newVersion ?? ''}-${upgrade.newValue ?? ''}`;
|
|
if (processedUpgrades.includes(upgradeKey)) {
|
|
continue;
|
|
}
|
|
processedUpgrades.push(upgradeKey);
|
|
|
|
const logJSON = upgrade.logJSON;
|
|
|
|
if (logJSON) {
|
|
if (isUndefined(logJSON.error)) {
|
|
if (logJSON.project) {
|
|
upgrade.repoName = logJSON.project.repository;
|
|
}
|
|
upgrade.hasReleaseNotes = false;
|
|
upgrade.releases = [];
|
|
if (
|
|
logJSON.hasReleaseNotes &&
|
|
upgrade.repoName &&
|
|
!commitRepos.includes(getRepoNameWithSourceDirectory(upgrade))
|
|
) {
|
|
commitRepos.push(getRepoNameWithSourceDirectory(upgrade));
|
|
upgrade.hasReleaseNotes = logJSON.hasReleaseNotes;
|
|
if (logJSON.versions) {
|
|
for (const version of logJSON.versions) {
|
|
const release = { ...version };
|
|
upgrade.releases.push(release);
|
|
}
|
|
}
|
|
}
|
|
} else if (logJSON.error === 'MissingGithubToken') {
|
|
upgrade.prBodyNotes ??= [];
|
|
upgrade.prBodyNotes.push(
|
|
emojify(
|
|
codeBlock`
|
|
> :exclamation: **Important**
|
|
>
|
|
> Release Notes retrieval for this PR were skipped because no github.com credentials were available.
|
|
> If you are self-hosted, please see [this instruction](https://github.com/renovatebot/renovate/blob/master/docs/usage/examples/self-hosting.md#githubcom-token-for-release-notes).
|
|
`,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
addPullRequestNoteIfAttestationHasBeenLost(
|
|
upgrade,
|
|
currentReleaseHasAttestation,
|
|
);
|
|
|
|
config.upgrades.push(upgrade);
|
|
}
|
|
|
|
config.hasReleaseNotes = config.upgrades.some((upg) => upg.hasReleaseNotes);
|
|
|
|
const releaseNotesSources: string[] = [];
|
|
for (const upgrade of config.upgrades) {
|
|
let notesSourceUrl = upgrade.releases?.[0]?.releaseNotes?.notesSourceUrl;
|
|
// TODO: types (#22198)
|
|
notesSourceUrl ??= `${upgrade.sourceUrl!}${
|
|
upgrade.sourceDirectory ? `:${upgrade.sourceDirectory}` : ''
|
|
}`;
|
|
|
|
if (upgrade.hasReleaseNotes && notesSourceUrl) {
|
|
if (releaseNotesSources.includes(notesSourceUrl)) {
|
|
logger.debug(
|
|
{ depName: upgrade.depName },
|
|
'Removing duplicate release notes',
|
|
);
|
|
upgrade.hasReleaseNotes = false;
|
|
} else {
|
|
releaseNotesSources.push(notesSourceUrl);
|
|
}
|
|
}
|
|
}
|
|
|
|
const prBody = getPrBody(
|
|
config,
|
|
{
|
|
debugData: updatePrDebugData(
|
|
config.baseBranch,
|
|
prepareLabels(config), // include labels in debug data
|
|
existingPr?.bodyStruct?.debugData,
|
|
),
|
|
},
|
|
config,
|
|
);
|
|
|
|
try {
|
|
if (existingPr) {
|
|
logger.debug('Processing existing PR');
|
|
|
|
if (
|
|
!existingPr.hasAssignees &&
|
|
!hasNotIgnoredReviewers(existingPr, config) &&
|
|
config.automerge &&
|
|
!config.assignAutomerge &&
|
|
(await getBranchStatus()) === 'red'
|
|
) {
|
|
logger.debug(`Setting assignees and reviewers as status checks failed`);
|
|
await addParticipants(config, existingPr);
|
|
}
|
|
// Check if existing PR needs updating
|
|
const existingPrTitle = stripEmojis(existingPr.title);
|
|
const existingPrBodyHash = existingPr.bodyStruct?.hash;
|
|
const newPrTitle = stripEmojis(prTitle);
|
|
const newPrBodyHash = hashBody(prBody);
|
|
|
|
const prInitialLabels = existingPr.bodyStruct?.debugData?.labels;
|
|
const prCurrentLabels = existingPr.labels;
|
|
const configuredLabels = prepareLabels(config);
|
|
|
|
const labelsNeedUpdate = shouldUpdateLabels(
|
|
prInitialLabels,
|
|
prCurrentLabels,
|
|
configuredLabels,
|
|
);
|
|
|
|
if (
|
|
existingPr?.targetBranch === config.baseBranch &&
|
|
existingPrTitle === newPrTitle &&
|
|
existingPrBodyHash === newPrBodyHash &&
|
|
!labelsNeedUpdate &&
|
|
!config.autoApprove
|
|
) {
|
|
// adds or-cache for existing PRs
|
|
setPrCache(branchName, prBodyFingerprint, false);
|
|
logger.debug(
|
|
`Pull Request #${existingPr.number} does not need updating`,
|
|
);
|
|
return { type: 'with-pr', pr: existingPr };
|
|
}
|
|
|
|
const updatePrConfig: UpdatePrConfig = {
|
|
number: existingPr.number,
|
|
prTitle,
|
|
prBody,
|
|
platformPrOptions: getPlatformPrOptions(config),
|
|
};
|
|
// PR must need updating
|
|
if (existingPr?.targetBranch !== config.baseBranch) {
|
|
logger.debug(
|
|
{
|
|
branchName,
|
|
oldBaseBranch: existingPr?.targetBranch,
|
|
newBaseBranch: config.baseBranch,
|
|
},
|
|
'PR base branch has changed',
|
|
);
|
|
updatePrConfig.targetBranch = config.baseBranch;
|
|
}
|
|
|
|
if (labelsNeedUpdate) {
|
|
logger.debug(
|
|
{
|
|
branchName,
|
|
prCurrentLabels,
|
|
configuredLabels,
|
|
},
|
|
'PR labels have changed',
|
|
);
|
|
|
|
// Divide labels into three categories:
|
|
// i) addLabels: Labels that need to be added
|
|
// ii) removeLabels: Labels that need to be removed
|
|
// iii) labels: New labels for the PR, replacing the old labels array entirely.
|
|
// This distinction is necessary because different platforms update labels differently
|
|
// For more details, refer to the updatePr function of each platform.
|
|
|
|
const [addLabels, removeLabels] = getChangedLabels(
|
|
prCurrentLabels,
|
|
configuredLabels,
|
|
);
|
|
|
|
// for Gitea
|
|
updatePrConfig.labels = configuredLabels;
|
|
|
|
// for GitHub, GitLab
|
|
updatePrConfig.addLabels = addLabels;
|
|
updatePrConfig.removeLabels = removeLabels;
|
|
}
|
|
if (existingPrTitle !== newPrTitle) {
|
|
logger.debug(
|
|
{
|
|
branchName,
|
|
oldPrTitle: existingPr.title,
|
|
newPrTitle: prTitle,
|
|
},
|
|
'PR title changed',
|
|
);
|
|
} else if (config.autoApprove) {
|
|
logger.debug({ prTitle }, 'PR approval required');
|
|
} else if (!config.committedFiles && !config.rebaseRequested) {
|
|
logger.debug(
|
|
{
|
|
prTitle,
|
|
},
|
|
'PR body changed',
|
|
);
|
|
}
|
|
|
|
if (GlobalConfig.get('dryRun')) {
|
|
logger.info(`DRY-RUN: Would update PR #${existingPr.number}`);
|
|
return { type: 'with-pr', pr: existingPr };
|
|
}
|
|
await platform.updatePr(updatePrConfig);
|
|
logger.info({ pr: existingPr.number, prTitle }, `PR updated`);
|
|
setPrCache(branchName, prBodyFingerprint, true);
|
|
|
|
return {
|
|
type: 'with-pr',
|
|
pr: {
|
|
...existingPr,
|
|
bodyStruct: getPrBodyStruct(prBody),
|
|
title: prTitle,
|
|
targetBranch: config.baseBranch,
|
|
},
|
|
};
|
|
}
|
|
logger.debug({ branch: branchName, prTitle }, `Creating PR`);
|
|
if (config.updateType === 'rollback') {
|
|
logger.info('Creating Rollback PR');
|
|
}
|
|
let pr: Pr | null;
|
|
if (GlobalConfig.get('dryRun')) {
|
|
logger.info(
|
|
{ labels: prepareLabels(config) },
|
|
`DRY-RUN: Would create PR: ${prTitle}`,
|
|
);
|
|
pr = { number: 0 } as never;
|
|
} else {
|
|
try {
|
|
// for a vulnerability alert this checks the VulnerabilityConcurrentPRs count
|
|
if (
|
|
!dependencyDashboardCheck &&
|
|
isLimitReached('ConcurrentPRs', prConfig)
|
|
) {
|
|
logger.debug('Skipping PR - limit reached');
|
|
return { type: 'without-pr', prBlockedBy: 'RateLimited' };
|
|
}
|
|
pr = await platform.createPr({
|
|
sourceBranch: branchName,
|
|
targetBranch: config.baseBranch,
|
|
prTitle,
|
|
prBody,
|
|
labels: prepareLabels(config),
|
|
platformPrOptions: getPlatformPrOptions(config),
|
|
draftPR: !!config.draftPR,
|
|
milestone: config.milestone,
|
|
});
|
|
|
|
incCountValue(
|
|
config.isVulnerabilityAlert
|
|
? 'VulnerabilityConcurrentPRs'
|
|
: 'ConcurrentPRs',
|
|
);
|
|
incCountValue('HourlyPRs');
|
|
logger.info(
|
|
{ pr: pr?.number, prTitle, labels: pr?.labels },
|
|
'PR created',
|
|
);
|
|
} catch (err) {
|
|
logger.debug({ err }, 'Pull request creation error');
|
|
if (
|
|
err.body?.message === 'Validation failed' &&
|
|
err.body.errors?.length &&
|
|
err.body.errors.some((error: { message?: string }) =>
|
|
error.message?.startsWith('A pull request already exists'),
|
|
)
|
|
) {
|
|
logger.warn('A pull requests already exists');
|
|
return { type: 'without-pr', prBlockedBy: 'Error' };
|
|
}
|
|
if (err.statusCode === 502) {
|
|
logger.warn(
|
|
{ branch: branchName },
|
|
'Deleting branch due to server error',
|
|
);
|
|
await scm.deleteBranch(branchName);
|
|
}
|
|
return { type: 'without-pr', prBlockedBy: 'Error' };
|
|
}
|
|
}
|
|
if (
|
|
pr &&
|
|
config.branchAutomergeFailureMessage &&
|
|
!config.suppressNotifications?.includes('branchAutomergeFailure')
|
|
) {
|
|
const topic = 'Branch automerge failure';
|
|
let content =
|
|
'This PR was configured for branch automerge. However, this is not possible, so it has been raised as a PR instead.';
|
|
if (config.branchAutomergeFailureMessage === 'branch status error') {
|
|
content += '\n___\n * Branch has one or more failed status checks';
|
|
}
|
|
if (
|
|
config.branchAutomergeFailureMessage ===
|
|
'automerge aborted - merge queue'
|
|
) {
|
|
content +=
|
|
'\n___\n * The base branch only accepts changes through its merge queue and rejected the direct push, so branch automerge is not possible. Please set `automergeType=pr` instead, or allow Renovate to bypass the merge queue.';
|
|
}
|
|
content = platform.massageMarkdown(content, config.rebaseLabel);
|
|
logger.debug('Adding branch automerge failure message to PR');
|
|
if (GlobalConfig.get('dryRun')) {
|
|
logger.info(`DRY-RUN: Would add comment to PR #${pr.number}`);
|
|
} else {
|
|
await ensureComment({
|
|
number: pr.number,
|
|
topic,
|
|
content,
|
|
});
|
|
}
|
|
}
|
|
// Skip assign and review if automerging PR
|
|
if (pr) {
|
|
if (
|
|
config.automerge &&
|
|
!config.assignAutomerge &&
|
|
(await getBranchStatus()) !== 'red'
|
|
) {
|
|
logger.debug(
|
|
`Skipping assignees and reviewers as automerge=${config.automerge}`,
|
|
);
|
|
} else {
|
|
await addParticipants(config, pr);
|
|
}
|
|
setPrCache(branchName, prBodyFingerprint, true);
|
|
logger.debug(`Created Pull Request #${pr.number}`);
|
|
return { type: 'with-pr', pr };
|
|
}
|
|
} catch (err) {
|
|
if (
|
|
// oxlint-disable-next-line typescript/prefer-optional-chain -- instanceof is not a null guard
|
|
err instanceof ExternalHostError ||
|
|
err.message === REPOSITORY_CHANGED ||
|
|
err.message === PLATFORM_RATE_LIMIT_EXCEEDED ||
|
|
err.message === PLATFORM_INTEGRATION_UNAUTHORIZED
|
|
) {
|
|
logger.debug('Passing error up');
|
|
throw err;
|
|
}
|
|
logger.warn({ err, prTitle }, 'Failed to ensure PR');
|
|
}
|
|
if (existingPr) {
|
|
return { type: 'with-pr', pr: existingPr };
|
|
}
|
|
return { type: 'without-pr', prBlockedBy: 'Error' };
|
|
}
|