Skip to content

Commit

Permalink
feat(github): forkOrg (#22886)
Browse files Browse the repository at this point in the history
Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
  • Loading branch information
rarkins and HonkingGoose committed Jun 20, 2023
1 parent c302959 commit ad8ddff
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 37 deletions.
14 changes: 12 additions & 2 deletions docs/usage/self-hosted-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,19 @@ In practice, it is implemented by converting the `force` configuration into a `p
This is set to `true` by default, meaning that any settings (such as `schedule`) take maximum priority even against custom settings existing inside individual repositories.
It will also override any settings in `packageRules`.

## forkToken
## forkOrg

This configuration option lets you choose an organization you want repositories forked into when "fork mode" is enabled.
It must be set to a GitHub Organization name and not a GitHub user account.

This can be used if you're migrating from user-based forks to organization-based forks.

You probably don't need this option - it is an experimental setting developed for the Forking Renovate hosted GitHub App.
If you've set a `forkOrg` then Renovate will:

1. Check if a fork exists in the preferred organization before checking it exists in the fork user's account
1. If no fork exists: it will be created in the `forkOrg`, not the user account

## forkToken

If this value is configured then Renovate:

Expand Down
10 changes: 10 additions & 0 deletions lib/config/options/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,16 @@ const options: RenovateOptions[] = [
supportedPlatforms: ['github'],
experimental: true,
},
{
name: 'forkOrg',
description:
'The preferred organization to create or find forked repositories, when in fork mode.',
stage: 'repository',
type: 'string',
globalOnly: true,
supportedPlatforms: ['github'],
experimental: true,
},
{
name: 'githubTokenWarn',
description: 'Display warnings about GitHub token not being set.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ exports[`modules/platform/github/index initRepo should squash 1`] = `
}
`;

exports[`modules/platform/github/index initRepo should update fork when using forkToken 1`] = `
exports[`modules/platform/github/index initRepo should update fork when using forkToken and forkOrg 1`] = `
{
"defaultBranch": "master",
"isFork": false,
Expand Down
8 changes: 3 additions & 5 deletions lib/modules/platform/github/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,26 +397,24 @@ describe('modules/platform/github/index', () => {
scope.get('/user').reply(200, {
login: 'forked',
});
// getBranchCommit
scope.post(`/repos/${repo}/forks`).reply(500);
await expect(
github.initRepo({
repository: 'some/repo',
forkToken: 'true',
forkOrg: 'forked',
})
).rejects.toThrow(REPOSITORY_CANNOT_FORK);
});

it('should update fork when using forkToken', async () => {
it('should update fork when using forkToken and forkOrg', async () => {
const scope = httpMock.scope(githubApiHost);
forkInitRepoMock(scope, 'some/repo', true);
scope.get('/user').reply(200, {
login: 'forked',
});
scope.patch('/repos/forked/repo/git/refs/heads/master').reply(200);
const config = await github.initRepo({
repository: 'some/repo',
forkToken: 'true',
forkOrg: 'forked',
});
expect(config).toMatchSnapshot();
});
Expand Down
57 changes: 28 additions & 29 deletions lib/modules/platform/github/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,20 +257,6 @@ export async function getJsonFile(
return JSON5.parse(raw);
}

export async function getForkOrgs(token: string): Promise<string[]> {
// This function will be adapted later to support configured forkOrgs
if (!config.renovateForkUser) {
try {
logger.debug('Determining fork user from API');
const userDetails = await getUserDetails(platformConfig.endpoint, token);
config.renovateForkUser = userDetails.username;
} catch (err) {
logger.debug({ err }, 'Error getting username for forkToken');
}
}
return config.renovateForkUser ? [config.renovateForkUser] : [];
}

export async function listForks(
token: string,
repository: string
Expand Down Expand Up @@ -299,35 +285,46 @@ export async function listForks(

export async function findFork(
token: string,
repository: string
repository: string,
forkOrg?: string
): Promise<GhRestRepo | null> {
const forks = await listForks(token, repository);
const orgs = await getForkOrgs(token);
if (!orgs.length) {
throw new Error(REPOSITORY_CANNOT_FORK);
if (forkOrg) {
logger.debug(`Searching for forked repo in forkOrg (${forkOrg})`);
const forkedRepo = forks.find((repo) => repo.owner.login === forkOrg);
if (forkedRepo) {
logger.debug(`Found repo in forkOrg: ${forkedRepo.full_name}`);
return forkedRepo;
}
logger.debug(`No repo found in forkOrg`);
}
let forkedRepo: GhRestRepo | undefined;
for (const forkOrg of orgs) {
logger.debug(`Searching for forked repo in ${forkOrg}`);
forkedRepo = forks.find((repo) => repo.owner.login === forkOrg);
logger.debug(`Searching for forked repo in user account`);
try {
const { username } = await getUserDetails(platformConfig.endpoint, token);
const forkedRepo = forks.find((repo) => repo.owner.login === username);
if (forkedRepo) {
logger.debug(`Found existing forked repo: ${forkedRepo.full_name}`);
break;
logger.debug(`Found repo in user account: ${forkedRepo.full_name}`);
return forkedRepo;
}
} catch (err) {
throw new Error(REPOSITORY_CANNOT_FORK);
}
return forkedRepo ?? null;
logger.debug(`No repo found in user account`);
return null;
}

export async function createFork(
token: string,
repository: string
repository: string,
forkOrg?: string
): Promise<GhRestRepo> {
let forkedRepo: GhRestRepo | undefined;
try {
forkedRepo = (
await githubApi.postJson<GhRestRepo>(`repos/${repository}/forks`, {
token,
body: {
organization: forkOrg ? forkOrg : undefined,
name: config.parentRepo!.replace('/', '-_-'),
default_branch_only: true, // no baseBranches support yet
},
Expand All @@ -339,7 +336,8 @@ export async function createFork(
if (!forkedRepo) {
throw new Error(REPOSITORY_CANNOT_FORK);
}
logger.debug(`Created forked repo ${forkedRepo.full_name}, now sleeping 30s`);
logger.info({ forkedRepo: forkedRepo.full_name }, 'Created forked repo');
logger.debug(`Sleeping 30s after creating fork`);
await delay(30000);
return forkedRepo;
}
Expand All @@ -348,6 +346,7 @@ export async function createFork(
export async function initRepo({
endpoint,
repository,
forkOrg,
forkToken,
renovateUsername,
cloneSubmodules,
Expand Down Expand Up @@ -489,7 +488,7 @@ export async function initRepo({
// save parent name then delete
config.parentRepo = config.repository;
config.repository = null;
let forkedRepo = await findFork(forkToken, repository);
let forkedRepo = await findFork(forkToken, repository, forkOrg);
if (forkedRepo) {
config.repository = forkedRepo.full_name;
const forkDefaultBranch = forkedRepo.default_branch;
Expand Down Expand Up @@ -567,7 +566,7 @@ export async function initRepo({
}
} else {
logger.debug('Forked repo is not found - attempting to create it');
forkedRepo = await createFork(forkToken, repository);
forkedRepo = await createFork(forkToken, repository, forkOrg);
config.repository = forkedRepo.full_name;
}
}
Expand Down
1 change: 1 addition & 0 deletions lib/modules/platform/github/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export interface LocalRepoConfig {
prReviewsRequired: boolean;
repoForceRebase?: boolean;
parentRepo: string | null;
forkOrg?: string;
forkToken?: string;
prList: GhPr[] | null;
issueList: any[] | null;
Expand Down
1 change: 1 addition & 0 deletions lib/modules/platform/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface RepoParams {
repository: string;
endpoint?: string;
gitUrl?: GitUrlOption;
forkOrg?: string;
forkToken?: string;
forkProcessing?: 'enabled' | 'disabled';
renovateUsername?: string;
Expand Down

0 comments on commit ad8ddff

Please sign in to comment.