From 426b2db7121acbdbfbcd07693bb9ea3a91df1e9d Mon Sep 17 00:00:00 2001 From: Valdis Rigdon Date: Thu, 14 Dec 2023 14:27:25 -0500 Subject: [PATCH] feat(gitlab): use forked_from_project for Merge Requests target project Appian specific patch to always use the upstream (forked_from_project) when dealing with Merge Requests or Issues. This ensures that Renovate follows the standard development workflow where branches are created against the "dev" fork and then Merge Requests are opened against the forked (or "prod") fork. In addition, we want the Dependency Dashboard created against the "prod" fork, so any calls to the Issues API goes to the "prod" fork. --- .../gitlab/__snapshots__/index.spec.ts.snap | 2 + lib/modules/platform/gitlab/index.ts | 66 +++++++++++-------- lib/modules/platform/gitlab/types.ts | 4 +- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/lib/modules/platform/gitlab/__snapshots__/index.spec.ts.snap b/lib/modules/platform/gitlab/__snapshots__/index.spec.ts.snap index a0082574098a516..1606ceab4e474e4 100644 --- a/lib/modules/platform/gitlab/__snapshots__/index.spec.ts.snap +++ b/lib/modules/platform/gitlab/__snapshots__/index.spec.ts.snap @@ -232,6 +232,7 @@ exports[`modules/platform/gitlab/index initRepo should fall back respecting when "defaultBranch": "master", "ignorePrAuthor": undefined, "mergeMethod": "merge", + "mergeRequestRepository": "some%2Frepo%2Fproject", "repository": "some%2Frepo%2Fproject", "url": "http://oauth2:123test@mycompany.com/gitlab/some/repo/project.git", }, @@ -247,6 +248,7 @@ exports[`modules/platform/gitlab/index initRepo should use ssh_url_to_repo if gi "defaultBranch": "master", "ignorePrAuthor": undefined, "mergeMethod": "merge", + "mergeRequestRepository": "some%2Frepo%2Fproject", "repository": "some%2Frepo%2Fproject", "url": "ssh://git@gitlab.com/some%2Frepo%2Fproject.git", }, diff --git a/lib/modules/platform/gitlab/index.ts b/lib/modules/platform/gitlab/index.ts index 0988ad0bb817502..a451c6009d0f337 100644 --- a/lib/modules/platform/gitlab/index.ts +++ b/lib/modules/platform/gitlab/index.ts @@ -83,6 +83,7 @@ let config: { cloneSubmodules: boolean | undefined; ignorePrAuthor: boolean | undefined; squash: boolean; + mergeRequestRepository: string; } = {} as any; const defaults = { @@ -323,7 +324,6 @@ export async function initRepo({ ); throw new Error(REPOSITORY_ARCHIVED); } - if (res.body.mirror && includeMirrors !== true) { logger.debug( 'Repository is a mirror - throwing error to abort renovation', @@ -359,6 +359,8 @@ export async function initRepo({ } logger.debug(`${repository} default branch = ${config.defaultBranch}`); delete config.prList; + config.mergeRequestRepository = + res.body.forked_from_project?.id ?? config.repository; logger.debug('Enabling Git FS'); const url = getRepoUrl(repository, gitUrl, res); await git.initRepo({ @@ -555,7 +557,7 @@ async function fetchPrList(): Promise { searchParams.scope = 'created_by_me'; } const query = getQueryString(searchParams); - const urlString = `projects/${config.repository}/merge_requests?${query}`; + const urlString = `projects/${config.mergeRequestRepository}/merge_requests?${query}`; try { const res = await gitlabApi.getJson< { @@ -593,7 +595,7 @@ export async function getPrList(): Promise { async function ignoreApprovals(pr: number): Promise { try { - const url = `projects/${config.repository}/merge_requests/${pr}/approval_rules`; + const url = `projects/${config.mergeRequestRepository}/merge_requests/${pr}/approval_rules`; const { body: rules } = await gitlabApi.getJson< { name: string; @@ -684,7 +686,7 @@ async function tryPrAutomerge( pipeline: { status: string; }; - }>(`projects/${config.repository}/merge_requests/${pr}`, { + }>(`projects/${config.mergeRequestRepository}/merge_requests/${pr}`, { memCache: false, }); // detailed_merge_status is available with Gitlab >=15.6.0 @@ -713,7 +715,7 @@ async function tryPrAutomerge( for (let attempt = 1; attempt <= retryTimes; attempt += 1) { try { await gitlabApi.putJson( - `projects/${config.repository}/merge_requests/${pr}/merge`, + `projects/${config.mergeRequestRepository}/merge_requests/${pr}/merge`, { body: { should_remove_source_branch: true, @@ -739,7 +741,7 @@ async function tryPrAutomerge( async function approvePr(pr: number): Promise { try { await gitlabApi.postJson( - `projects/${config.repository}/merge_requests/${pr}/approve`, + `projects/${config.mergeRequestRepository}/merge_requests/${pr}/approve`, ); } catch (err) { logger.warn({ err }, 'GitLab: Error approving merge request'); @@ -761,6 +763,9 @@ export async function createPr({ } const description = sanitize(rawDescription); logger.debug(`Creating Merge Request: ${title}`); + // the creation is always against the repository with the branch + // but if the target_project_id is forked_from_project, then the Merge + // Request will end up in that repository const res = await gitlabApi.postJson( `projects/${config.repository}/merge_requests`, { @@ -772,6 +777,7 @@ export async function createPr({ description, labels: (labels ?? []).join(','), squash: config.squash, + target_project_id: config.mergeRequestRepository, }, }, ); @@ -794,7 +800,7 @@ export async function createPr({ export async function getPr(iid: number): Promise { logger.debug(`getPr(${iid})`); - const mr = await getMR(config.repository, iid); + const mr = await getMR(config.mergeRequestRepository, iid); // Harmonize fields with GitHub const pr: GitlabPr = { @@ -852,7 +858,7 @@ export async function updatePr({ } await gitlabApi.putJson( - `projects/${config.repository}/merge_requests/${iid}`, + `projects/${config.mergeRequestRepository}/merge_requests/${iid}`, { body }, ); @@ -873,7 +879,7 @@ export async function reattemptPlatformAutomerge({ export async function mergePr({ id }: MergePRConfig): Promise { try { await gitlabApi.putJson( - `projects/${config.repository}/merge_requests/${id}/merge`, + `projects/${config.mergeRequestRepository}/merge_requests/${id}/merge`, { body: { should_remove_source_branch: true, @@ -1010,11 +1016,12 @@ export async function setBranchStatus({ state: renovateState, url: targetUrl, }: BranchStatusConfig): Promise { + const branchSourceProject = config.repository; // First, get the branch commit SHA const branchSha = git.getBranchCommit(branchName); // Now, check the statuses for that commit // TODO: types (#22198) - const url = `projects/${config.repository}/statuses/${branchSha!}`; + const url = `projects/${branchSourceProject}/statuses/${branchSha!}`; let state = 'success'; if (renovateState === 'yellow') { state = 'pending'; @@ -1079,7 +1086,7 @@ export async function getIssueList(): Promise { const query = getQueryString(searchParams); const res = await gitlabApi.getJson< { iid: number; title: string; labels: string[] }[] - >(`projects/${config.repository}/issues?${query}`, { + >(`projects/${config.mergeRequestRepository}/issues?${query}`, { memCache: false, paginate: true, }); @@ -1104,7 +1111,7 @@ export async function getIssue( try { const issueBody = ( await gitlabApi.getJson<{ description: string }>( - `projects/${config.repository}/issues/${number}`, + `projects/${config.mergeRequestRepository}/issues/${number}`, { memCache: useCache }, ) ).body.description; @@ -1151,13 +1158,13 @@ export async function ensureIssue({ if (issue) { const existingDescription = ( await gitlabApi.getJson<{ description: string }>( - `projects/${config.repository}/issues/${issue.iid}`, + `projects/${config.mergeRequestRepository}/issues/${issue.iid}`, ) ).body.description; if (issue.title !== title || existingDescription !== description) { logger.debug('Updating issue'); await gitlabApi.putJson( - `projects/${config.repository}/issues/${issue.iid}`, + `projects/${config.mergeRequestRepository}/issues/${issue.iid}`, { body: { title, @@ -1170,14 +1177,17 @@ export async function ensureIssue({ return 'updated'; } } else { - await gitlabApi.postJson(`projects/${config.repository}/issues`, { - body: { - title, - description, - labels: (labels ?? []).join(','), - confidential: confidential ?? false, + await gitlabApi.postJson( + `projects/${config.mergeRequestRepository}/issues`, + { + body: { + title, + description, + labels: (labels ?? []).join(','), + confidential: confidential ?? false, + }, }, - }); + ); logger.info('Issue created'); // delete issueList so that it will be refetched as necessary delete config.issueList; @@ -1200,7 +1210,7 @@ export async function ensureIssueClosing(title: string): Promise { if (issue.title === title) { logger.debug({ issue }, 'Closing issue'); await gitlabApi.putJson( - `projects/${config.repository}/issues/${issue.iid}`, + `projects/${config.mergeRequestRepository}/issues/${issue.iid}`, { body: { state_event: 'close' }, }, @@ -1285,7 +1295,7 @@ export async function addReviewers( newReviewerIDs = [...new Set(newReviewerIDs)]; try { - await updateMR(config.repository, iid, { + await updateMR(config.mergeRequestRepository, iid, { reviewer_ids: [...existingReviewerIDs, ...newReviewerIDs], }); } catch (err) { @@ -1304,7 +1314,7 @@ export async function deleteLabel( .filter((l: string) => l !== label) .join(','); await gitlabApi.putJson( - `projects/${config.repository}/merge_requests/${issueNo}`, + `projects/${config.mergeRequestRepository}/merge_requests/${issueNo}`, { body: { labels }, }, @@ -1317,7 +1327,7 @@ export async function deleteLabel( async function getComments(issueNo: number): Promise { // GET projects/:owner/:repo/merge_requests/:number/notes logger.debug(`Getting comments for #${issueNo}`); - const url = `projects/${config.repository}/merge_requests/${issueNo}/notes`; + const url = `projects/${config.mergeRequestRepository}/merge_requests/${issueNo}/notes`; const comments = ( await gitlabApi.getJson(url, { paginate: true }) ).body; @@ -1328,7 +1338,7 @@ async function getComments(issueNo: number): Promise { async function addComment(issueNo: number, body: string): Promise { // POST projects/:owner/:repo/merge_requests/:number/notes await gitlabApi.postJson( - `projects/${config.repository}/merge_requests/${issueNo}/notes`, + `projects/${config.mergeRequestRepository}/merge_requests/${issueNo}/notes`, { body: { body }, }, @@ -1342,7 +1352,7 @@ async function editComment( ): Promise { // PUT projects/:owner/:repo/merge_requests/:number/notes/:id await gitlabApi.putJson( - `projects/${config.repository}/merge_requests/${issueNo}/notes/${commentId}`, + `projects/${config.mergeRequestRepository}/merge_requests/${issueNo}/notes/${commentId}`, { body: { body }, }, @@ -1355,7 +1365,7 @@ async function deleteComment( ): Promise { // DELETE projects/:owner/:repo/merge_requests/:number/notes/:id await gitlabApi.deleteJson( - `projects/${config.repository}/merge_requests/${issueNo}/notes/${commentId}`, + `projects/${config.mergeRequestRepository}/merge_requests/${issueNo}/notes/${commentId}`, ); } diff --git a/lib/modules/platform/gitlab/types.ts b/lib/modules/platform/gitlab/types.ts index 897d25175783b45..d33db1956b0bc02 100644 --- a/lib/modules/platform/gitlab/types.ts +++ b/lib/modules/platform/gitlab/types.ts @@ -60,12 +60,14 @@ export interface RepoResponse { empty_repo: boolean; ssh_url_to_repo: string; http_url_to_repo: string; - forked_from_project: boolean; repository_access_level: 'disabled' | 'private' | 'enabled'; merge_requests_access_level: 'disabled' | 'private' | 'enabled'; merge_method: MergeMethod; path_with_namespace: string; squash_option?: 'never' | 'always' | 'default_on' | 'default_off'; + forked_from_project?: { + id: string; + }; } // See https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/types/user_status_type.rb