diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md index f4e388e6f0d3b5e1510dee4739c77534370346e4..57e7e33536c9e025ba6e22193b3bd5d418bfc3a7 100644 --- a/docs/usage/self-hosted-configuration.md +++ b/docs/usage/self-hosted-configuration.md @@ -577,6 +577,10 @@ If this value is configured then Renovate: Renovate will then create branches on the fork and opens Pull Requests on the parent repository. +<!-- prettier-ignore --> +!!! note + Forked repositories will always be skipped when `forkToken` is set, even if `includeForks` is true. + ## gitNoVerify Controls when Renovate passes the `--no-verify` flag to `git`. diff --git a/lib/constants/error-messages.ts b/lib/constants/error-messages.ts index 95daa453ee8ecbe07ee1f182e5c694a037fe3c26..490e96373341932b6c3cff5c0ee162224f19ff5b 100644 --- a/lib/constants/error-messages.ts +++ b/lib/constants/error-messages.ts @@ -28,6 +28,7 @@ export const REPOSITORY_CLOSED_ONBOARDING = 'disabled-closed-onboarding'; export const REPOSITORY_DISABLED_BY_CONFIG = 'disabled-by-config'; export const REPOSITORY_NO_CONFIG = 'disabled-no-config'; export const REPOSITORY_EMPTY = 'empty'; +export const REPOSITORY_FORK_MODE_FORKED = 'fork-mode-forked'; export const REPOSITORY_FORKED = 'fork'; export const REPOSITORY_MIRRORED = 'mirror'; export const REPOSITORY_NOT_FOUND = 'not-found'; diff --git a/lib/modules/platform/github/graphql.ts b/lib/modules/platform/github/graphql.ts index 09f0addcc06a7c65637a471bf2641850185a2ab6..8f96b4f4fb0a3ae5dd9b7ac7748509e877c95745 100644 --- a/lib/modules/platform/github/graphql.ts +++ b/lib/modules/platform/github/graphql.ts @@ -3,6 +3,9 @@ query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { id isFork + parent { + nameWithOwner + } isArchived nameWithOwner hasIssuesEnabled diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index 11b8efe42f3857bf9a924575de3e69914346bec6..1f7e15ef8f6fd83e041ad0f30aaac7ceb303dfd0 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -7,6 +7,7 @@ import { PLATFORM_RATE_LIMIT_EXCEEDED, PLATFORM_UNKNOWN_ERROR, REPOSITORY_CANNOT_FORK, + REPOSITORY_FORKED, REPOSITORY_NOT_FOUND, REPOSITORY_RENAMED, } from '../../../constants/error-messages'; @@ -483,6 +484,7 @@ describe('modules/platform/github/index', () => { forkExisted: boolean, forkResult = 200, forkDefaultBranch = 'master', + isFork = false, ): void { scope // repo info @@ -490,7 +492,7 @@ describe('modules/platform/github/index', () => { .reply(200, { data: { repository: { - isFork: false, + isFork, isArchived: false, nameWithOwner: repository, hasIssuesEnabled: true, @@ -505,10 +507,10 @@ describe('modules/platform/github/index', () => { }, }, }, - }) - // getForks - .get(`/repos/${repository}/forks?per_page=100`) - .reply( + }); + + if (!isFork) { + scope.get(`/repos/${repository}/forks?per_page=100`).reply( forkResult, forkExisted ? [ @@ -520,6 +522,7 @@ describe('modules/platform/github/index', () => { ] : [], ); + } } describe('initRepo', () => { @@ -547,6 +550,19 @@ describe('modules/platform/github/index', () => { expect(config).toMatchSnapshot(); }); + it('throws if the repo is a fork', async () => { + const repo = 'some/repo'; + const branch = 'master'; + const scope = httpMock.scope(githubApiHost); + forkInitRepoMock(scope, repo, false, 200, branch, true); + await expect( + github.initRepo({ + repository: 'some/repo', + forkToken: 'true', + }), + ).rejects.toThrow(REPOSITORY_FORKED); + }); + it('throws when cannot fork due to username error', async () => { const repo = 'some/repo'; const branch = 'master'; diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index c34abf4abcbf14be17cf62b6ebb2617ba6555edd..a5894572cd52db8669d843c646c8569facb80802 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -16,6 +16,7 @@ import { REPOSITORY_DISABLED, REPOSITORY_EMPTY, REPOSITORY_FORKED, + REPOSITORY_FORK_MODE_FORKED, REPOSITORY_NOT_FOUND, REPOSITORY_RENAMED, } from '../../../constants/error-messages'; @@ -570,6 +571,9 @@ export async function initRepo({ if (err.message.startsWith('Repository access blocked')) { throw new Error(REPOSITORY_BLOCKED); } + if (err.message === REPOSITORY_FORK_MODE_FORKED) { + throw err; + } if (err.message === REPOSITORY_FORKED) { throw err; } @@ -588,6 +592,15 @@ export async function initRepo({ if (forkToken) { logger.debug('Bot is in fork mode'); + if (repo.isFork) { + logger.debug( + `Forked repos cannot be processed when running with a forkToken, so this repo will be skipped`, + ); + logger.debug( + `Parent repo for this forked repo is ${repo.parent?.nameWithOwner}`, + ); + throw new Error(REPOSITORY_FORKED); + } config.forkOrg = forkOrg; config.forkToken = forkToken; // save parent name then delete diff --git a/lib/modules/platform/github/types.ts b/lib/modules/platform/github/types.ts index cd6feee577b74791e4abe02be012f24d1c76a6e2..f3c0e066619fada8221cb2dfb7b4bd381c596670 100644 --- a/lib/modules/platform/github/types.ts +++ b/lib/modules/platform/github/types.ts @@ -115,6 +115,9 @@ export type BranchProtection = any; export interface GhRepo { id: string; isFork: boolean; + parent?: { + nameWithOwner: string; + }; isArchived: boolean; nameWithOwner: string; autoMergeAllowed: boolean; diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts index 0cad95457e4b7e0ef398c932a35db4f3d8755879..5900222c072c115a1702ee00529b88c3f424c637 100644 --- a/lib/util/git/index.ts +++ b/lib/util/git/index.ts @@ -585,7 +585,7 @@ export async function getFileList(): Promise<string[]> { } export function getBranchList(): string[] { - return Object.keys(config.branchCommits); + return Object.keys(config.branchCommits ?? /* istanbul ignore next */ {}); } export async function isBranchBehindBase( diff --git a/lib/workers/repository/error.spec.ts b/lib/workers/repository/error.spec.ts index 6b2ed7a118299c9101e43c6508376ba7dc84ef02..1c2a662370da8aac4582aae17e70e694950cf96e 100644 --- a/lib/workers/repository/error.spec.ts +++ b/lib/workers/repository/error.spec.ts @@ -18,6 +18,7 @@ import { REPOSITORY_DISABLED, REPOSITORY_EMPTY, REPOSITORY_FORKED, + REPOSITORY_FORK_MODE_FORKED, REPOSITORY_MIRRORED, REPOSITORY_NOT_FOUND, REPOSITORY_NO_PACKAGE_FILES, @@ -47,6 +48,7 @@ describe('workers/repository/error', () => { REPOSITORY_DISABLED, REPOSITORY_CHANGED, REPOSITORY_FORKED, + REPOSITORY_FORK_MODE_FORKED, REPOSITORY_NO_PACKAGE_FILES, CONFIG_SECRETS_EXPOSED, CONFIG_VALIDATION, diff --git a/lib/workers/repository/error.ts b/lib/workers/repository/error.ts index 9433458deb14fcfc5e21a2c5e80c55ba460a628c..6e02b093158d78b0293f35fd25a5d311b98e868b 100644 --- a/lib/workers/repository/error.ts +++ b/lib/workers/repository/error.ts @@ -21,6 +21,7 @@ import { REPOSITORY_DISABLED_BY_CONFIG, REPOSITORY_EMPTY, REPOSITORY_FORKED, + REPOSITORY_FORK_MODE_FORKED, REPOSITORY_MIRRORED, REPOSITORY_NOT_FOUND, REPOSITORY_NO_CONFIG, @@ -93,6 +94,12 @@ export default async function handleError( logger.error('Repository is not found'); return err.message; } + if (err.message === REPOSITORY_FORK_MODE_FORKED) { + logger.info( + 'Repository is a fork and cannot be processed when Renovate is running in fork mode itself', + ); + return err.message; + } if (err.message === REPOSITORY_FORKED) { logger.info( 'Repository is a fork and not manually configured - skipping - did you want to run with --fork-processing=enabled?', diff --git a/lib/workers/repository/result.ts b/lib/workers/repository/result.ts index 83829671885d8c5a1f4405f80767bf003727ea0c..70ba5f0c57d9d426ade2df57491923d7faec5638 100644 --- a/lib/workers/repository/result.ts +++ b/lib/workers/repository/result.ts @@ -12,6 +12,7 @@ import { REPOSITORY_DISABLED_BY_CONFIG, REPOSITORY_EMPTY, REPOSITORY_FORKED, + REPOSITORY_FORK_MODE_FORKED, REPOSITORY_MIRRORED, REPOSITORY_NOT_FOUND, REPOSITORY_NO_CONFIG, @@ -47,6 +48,7 @@ export function processResult( REPOSITORY_DISABLED, REPOSITORY_DISABLED_BY_CONFIG, REPOSITORY_EMPTY, + REPOSITORY_FORK_MODE_FORKED, REPOSITORY_FORKED, REPOSITORY_MIRRORED, REPOSITORY_NOT_FOUND,