diff --git a/lib/modules/platform/azure/__snapshots__/index.spec.ts.snap b/lib/modules/platform/azure/__snapshots__/index.spec.ts.snap index 32ee7b4ee7d0f99d2d842af4fa1a5175dba3a7b3..667c22b640b052d112a3a50f7ad79a9d36fd196c 100644 --- a/lib/modules/platform/azure/__snapshots__/index.spec.ts.snap +++ b/lib/modules/platform/azure/__snapshots__/index.spec.ts.snap @@ -2,7 +2,9 @@ exports[`modules/platform/azure/index createPr() should create and return a PR object 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #456", "number": 456, @@ -16,7 +18,9 @@ Object { exports[`modules/platform/azure/index createPr() should create and return a PR object from base branch 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #456", "number": 456, @@ -33,7 +37,9 @@ Object { "autoCompleteSetBy": Object { "id": 123, }, - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "completionOptions": Object { "deleteSourceBranch": true, "mergeCommitMessage": "The Title", @@ -56,7 +62,9 @@ Object { exports[`modules/platform/azure/index createPr() should create and return an approved PR object 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "createdBy": Object { "id": 123, @@ -133,7 +141,9 @@ content", exports[`modules/platform/azure/index findPr(branchName, prTitle, state) returns pr if found it all state 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #1", "number": 1, @@ -150,7 +160,9 @@ Object { exports[`modules/platform/azure/index findPr(branchName, prTitle, state) returns pr if found it close 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #1", "number": 1, @@ -167,7 +179,9 @@ Object { exports[`modules/platform/azure/index findPr(branchName, prTitle, state) returns pr if found it open 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #1", "number": 1, @@ -184,7 +198,9 @@ Object { exports[`modules/platform/azure/index findPr(branchName, prTitle, state) returns pr if found not open 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #1", "number": 1, @@ -217,7 +233,9 @@ Array [ exports[`modules/platform/azure/index getPr(prNo) should return a pr in the right format 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #1234", "hasReviewers": false, diff --git a/lib/modules/platform/azure/__snapshots__/util.spec.ts.snap b/lib/modules/platform/azure/__snapshots__/util.spec.ts.snap index a2c0f8bc33f6ca49c09f4a3139e88370378f006f..8ccfec1443f366d143228cf53a30be24b2999425 100644 --- a/lib/modules/platform/azure/__snapshots__/util.spec.ts.snap +++ b/lib/modules/platform/azure/__snapshots__/util.spec.ts.snap @@ -16,7 +16,9 @@ Object { exports[`modules/platform/azure/util getRenovatePRFormat should be formated (closed v2) 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #undefined", "number": undefined, @@ -30,7 +32,9 @@ Object { exports[`modules/platform/azure/util getRenovatePRFormat should be formated (closed) 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #undefined", "number": undefined, @@ -44,7 +48,9 @@ Object { exports[`modules/platform/azure/util getRenovatePRFormat should be formated (not closed) 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #undefined", "number": undefined, diff --git a/lib/modules/platform/azure/util.ts b/lib/modules/platform/azure/util.ts index 915b0cb1c1b4a5a16f7cc527497ec96a6982a0e6..0b7aa652faa983a2481b83da605cb6c6f53087c7 100644 --- a/lib/modules/platform/azure/util.ts +++ b/lib/modules/platform/azure/util.ts @@ -9,6 +9,7 @@ import { HostRule, PrState } from '../../../types'; import type { GitOptions } from '../../../types/git'; import { addSecretForSanitizing } from '../../../util/sanitize'; import { toBase64 } from '../../../util/string'; +import { getPrBodyStruct } from '../pr-body'; import type { AzurePr } from './types'; export function getNewBranchName(branchName?: string): string | undefined { @@ -97,7 +98,7 @@ export function getRenovatePRFormat(azurePr: GitPullRequest): AzurePr { const targetBranch = getBranchNameWithoutRefsheadsPrefix( azurePr.targetRefName ); - const body = azurePr.description; + const bodyStruct = getPrBodyStruct(azurePr.description); const createdAt = azurePr.creationDate?.toISOString(); // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion @@ -111,7 +112,7 @@ export function getRenovatePRFormat(azurePr: GitPullRequest): AzurePr { state, number, displayNumber, - body, + bodyStruct, sourceRefName, targetBranch, createdAt, diff --git a/lib/modules/platform/bitbucket-server/__snapshots__/index.spec.ts.snap b/lib/modules/platform/bitbucket-server/__snapshots__/index.spec.ts.snap index ef262cf28d8d5d30c9375fc36e036332691bb77f..741dd2b173996d9d7a0f3731875195003fbea979 100644 --- a/lib/modules/platform/bitbucket-server/__snapshots__/index.spec.ts.snap +++ b/lib/modules/platform/bitbucket-server/__snapshots__/index.spec.ts.snap @@ -12,8 +12,9 @@ exports[`modules/platform/bitbucket-server/index endpoint with no path deleteLAb exports[`modules/platform/bitbucket-server/index endpoint with no path findPr() has pr 1`] = ` Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "number": 5, "sourceBranch": "userName1/pullRequest5", @@ -26,8 +27,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with no path getBranchPr() has pr 1`] = ` Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "displayNumber": "Pull Request #5", "hasReviewers": true, @@ -45,8 +47,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with no path getPr() canRebase 1`] = ` Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "displayNumber": "Pull Request #5", "hasReviewers": true, @@ -64,8 +67,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with no path getPr() canRebase 2`] = ` Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "displayNumber": "Pull Request #5", "hasReviewers": true, @@ -83,8 +87,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with no path getPr() canRebase 3`] = ` Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "displayNumber": "Pull Request #5", "hasReviewers": true, @@ -102,8 +107,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with no path getPr() gets a PR 1`] = ` Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "displayNumber": "Pull Request #5", "hasReviewers": true, @@ -121,7 +127,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with no path getPr() gets a closed PR 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #undefined", "hasReviewers": false, @@ -138,8 +146,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with no path getPrList() has pr 1`] = ` Array [ Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "number": 5, "sourceBranch": "userName1/pullRequest5", @@ -215,8 +224,9 @@ exports[`modules/platform/bitbucket-server/index endpoint with path deleteLAbel( exports[`modules/platform/bitbucket-server/index endpoint with path findPr() has pr 1`] = ` Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "number": 5, "sourceBranch": "userName1/pullRequest5", @@ -229,8 +239,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with path getBranchPr() has pr 1`] = ` Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "displayNumber": "Pull Request #5", "hasReviewers": true, @@ -248,8 +259,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with path getPr() canRebase 1`] = ` Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "displayNumber": "Pull Request #5", "hasReviewers": true, @@ -267,8 +279,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with path getPr() canRebase 2`] = ` Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "displayNumber": "Pull Request #5", "hasReviewers": true, @@ -286,8 +299,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with path getPr() canRebase 3`] = ` Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "displayNumber": "Pull Request #5", "hasReviewers": true, @@ -305,8 +319,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with path getPr() gets a PR 1`] = ` Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "displayNumber": "Pull Request #5", "hasReviewers": true, @@ -324,7 +339,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with path getPr() gets a closed PR 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #undefined", "hasReviewers": false, @@ -341,8 +358,9 @@ Object { exports[`modules/platform/bitbucket-server/index endpoint with path getPrList() has pr 1`] = ` Array [ Object { - "body": "* Line 1 -* Line 2", + "bodyStruct": Object { + "hash": "7980dafc4eb6f0c79278fd929d3e8e5954b32b68ae118a22565c7c369fc2f591", + }, "createdAt": 1547853840016, "number": 5, "sourceBranch": "userName1/pullRequest5", diff --git a/lib/modules/platform/bitbucket-server/utils.ts b/lib/modules/platform/bitbucket-server/utils.ts index 8db62147876af15c65a45cd12f63fd30d1581cb5..5f8f88f2d49d80c8027c2b90b79f50cf4d7d2d81 100644 --- a/lib/modules/platform/bitbucket-server/utils.ts +++ b/lib/modules/platform/bitbucket-server/utils.ts @@ -10,6 +10,7 @@ import type { HttpPostOptions, HttpResponse, } from '../../../util/http/types'; +import { getPrBodyStruct } from '../pr-body'; import type { BbsPr, BbsRestPr, BbsRestRepo, BitbucketError } from './types'; export const BITBUCKET_INVALID_REVIEWERS_EXCEPTION = @@ -28,7 +29,7 @@ export function prInfo(pr: BbsRestPr): BbsPr { return { version: pr.version, number: pr.id, - body: pr.description, + bodyStruct: getPrBodyStruct(pr.description), sourceBranch: pr.fromRef.displayId, targetBranch: pr.toRef.displayId, title: pr.title, diff --git a/lib/modules/platform/bitbucket/__snapshots__/index.spec.ts.snap b/lib/modules/platform/bitbucket/__snapshots__/index.spec.ts.snap index f61be46c071ded940547d5a14af3d9a394c19660..a541332388fcfae9146203f6e5dc5eaaaf44aed5 100644 --- a/lib/modules/platform/bitbucket/__snapshots__/index.spec.ts.snap +++ b/lib/modules/platform/bitbucket/__snapshots__/index.spec.ts.snap @@ -15,7 +15,9 @@ Object { exports[`modules/platform/bitbucket/index findPr() finds pr 1`] = ` Object { - "body": "summary", + "bodyStruct": Object { + "hash": "761b7ad8ad439b2855fcbb611331c646ef0870b0631247bba3f3025cb6df5a53", + }, "createdAt": "2018-07-02T07:02:25.275030+00:00", "displayNumber": "Pull Request #5", "number": 5, @@ -28,7 +30,9 @@ Object { exports[`modules/platform/bitbucket/index getBranchPr() bitbucket finds PR for branch 1`] = ` Object { - "body": "summary", + "bodyStruct": Object { + "hash": "761b7ad8ad439b2855fcbb611331c646ef0870b0631247bba3f3025cb6df5a53", + }, "createdAt": "2018-07-02T07:02:25.275030+00:00", "displayNumber": "Pull Request #5", "hasReviewers": false, @@ -61,7 +65,9 @@ Array [ exports[`modules/platform/bitbucket/index getPr() canRebase 1`] = ` Object { - "body": "summary", + "bodyStruct": Object { + "hash": "761b7ad8ad439b2855fcbb611331c646ef0870b0631247bba3f3025cb6df5a53", + }, "createdAt": "2018-07-02T07:02:25.275030+00:00", "displayNumber": "Pull Request #3", "hasReviewers": false, @@ -75,7 +81,9 @@ Object { exports[`modules/platform/bitbucket/index getPr() canRebase 2`] = ` Object { - "body": "summary", + "bodyStruct": Object { + "hash": "761b7ad8ad439b2855fcbb611331c646ef0870b0631247bba3f3025cb6df5a53", + }, "createdAt": "2018-07-02T07:02:25.275030+00:00", "displayNumber": "Pull Request #5", "hasReviewers": false, @@ -89,7 +97,9 @@ Object { exports[`modules/platform/bitbucket/index getPr() canRebase 3`] = ` Object { - "body": "summary", + "bodyStruct": Object { + "hash": "761b7ad8ad439b2855fcbb611331c646ef0870b0631247bba3f3025cb6df5a53", + }, "createdAt": "2018-07-02T07:02:25.275030+00:00", "displayNumber": "Pull Request #5", "hasReviewers": false, @@ -103,7 +113,9 @@ Object { exports[`modules/platform/bitbucket/index getPr() exists 1`] = ` Object { - "body": "summary", + "bodyStruct": Object { + "hash": "761b7ad8ad439b2855fcbb611331c646ef0870b0631247bba3f3025cb6df5a53", + }, "createdAt": "2018-07-02T07:02:25.275030+00:00", "displayNumber": "Pull Request #5", "hasReviewers": false, @@ -118,7 +130,9 @@ Object { exports[`modules/platform/bitbucket/index getPrList() filters PR list by author 1`] = ` Array [ Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": undefined, "displayNumber": "Pull Request #1", "number": 1, diff --git a/lib/modules/platform/bitbucket/utils.ts b/lib/modules/platform/bitbucket/utils.ts index 9ba9a497d9ef08b77523bc3c6bdde2b5ad3f34e6..c72b76a54cf14f0c5a16155e18530b4b7b21a4e3 100644 --- a/lib/modules/platform/bitbucket/utils.ts +++ b/lib/modules/platform/bitbucket/utils.ts @@ -7,6 +7,7 @@ import type { HttpPostOptions, HttpResponse, } from '../../../util/http/types'; +import { getPrBodyStruct } from '../pr-body'; import type { Pr } from '../types'; import type { BitbucketMergeStrategy, MergeRequestBody } from './types'; @@ -180,7 +181,7 @@ export function prInfo(pr: PrResponse): Pr { return { number: pr.id, displayNumber: `Pull Request #${pr.id}`, - body: pr.summary?.raw, + bodyStruct: getPrBodyStruct(pr.summary?.raw), sourceBranch: pr.source?.branch?.name, targetBranch: pr.destination?.branch?.name, title: pr.title, diff --git a/lib/modules/platform/gitea/__snapshots__/index.spec.ts.snap b/lib/modules/platform/gitea/__snapshots__/index.spec.ts.snap index 620a5d636b16914fa3a1a4ac7ac471a34964b76e..844b609082e0fad9b317ac264ea0ba32edd51cdb 100644 --- a/lib/modules/platform/gitea/__snapshots__/index.spec.ts.snap +++ b/lib/modules/platform/gitea/__snapshots__/index.spec.ts.snap @@ -2,7 +2,9 @@ exports[`modules/platform/gitea/index createPr should use base branch by default 1`] = ` Object { - "body": "pr-body", + "bodyStruct": Object { + "hash": "9d586a6aedc4e7cb205276933c9e474cd3c2b341d3340458c31eb750795f197d", + }, "cannotMergeReason": undefined, "createdAt": "2014-04-01T05:14:20Z", "displayNumber": "Pull Request #42", @@ -19,7 +21,9 @@ Object { exports[`modules/platform/gitea/index createPr should use default branch if requested 1`] = ` Object { - "body": "pr-body", + "bodyStruct": Object { + "hash": "9d586a6aedc4e7cb205276933c9e474cd3c2b341d3340458c31eb750795f197d", + }, "cannotMergeReason": undefined, "createdAt": "2014-04-01T05:14:20Z", "displayNumber": "Pull Request #42", @@ -36,7 +40,9 @@ Object { exports[`modules/platform/gitea/index getPr should fallback to direct fetching if cache fails 1`] = ` Object { - "body": "some random pull request", + "bodyStruct": Object { + "hash": "f41557d6153a316ee747e13de8952c4068de931585c1a18d095d6703254de6af", + }, "cannotMergeReason": "pr.mergeable=\\"false\\"", "createdAt": "2015-03-22T20:36:16Z", "displayNumber": "Pull Request #1", @@ -53,7 +59,9 @@ Object { exports[`modules/platform/gitea/index getPr should return enriched pull request which exists if open 1`] = ` Object { - "body": "some random pull request", + "bodyStruct": Object { + "hash": "f41557d6153a316ee747e13de8952c4068de931585c1a18d095d6703254de6af", + }, "cannotMergeReason": undefined, "createdAt": "2015-03-22T20:36:16Z", "displayNumber": "Pull Request #1", @@ -78,7 +86,9 @@ Object { exports[`modules/platform/gitea/index getPrList should filter list by creator 2`] = ` Array [ Object { - "body": "some random pull request", + "bodyStruct": Object { + "hash": "f41557d6153a316ee747e13de8952c4068de931585c1a18d095d6703254de6af", + }, "cannotMergeReason": undefined, "createdAt": "2015-03-22T20:36:16Z", "displayNumber": "Pull Request #1", @@ -92,7 +102,9 @@ Array [ "title": "Some PR", }, Object { - "body": "other random pull request", + "bodyStruct": Object { + "hash": "916e5965a20785df1883ff5dc219508a1070ae1f37ccb64e954526f3ca1d22f4", + }, "cannotMergeReason": undefined, "createdAt": "2011-08-18T22:30:38Z", "displayNumber": "Pull Request #2", @@ -111,7 +123,9 @@ Array [ exports[`modules/platform/gitea/index getPrList should return list of pull requests 1`] = ` Array [ Object { - "body": "some random pull request", + "bodyStruct": Object { + "hash": "f41557d6153a316ee747e13de8952c4068de931585c1a18d095d6703254de6af", + }, "cannotMergeReason": undefined, "createdAt": "2015-03-22T20:36:16Z", "displayNumber": "Pull Request #1", @@ -125,7 +139,9 @@ Array [ "title": "Some PR", }, Object { - "body": "other random pull request", + "bodyStruct": Object { + "hash": "916e5965a20785df1883ff5dc219508a1070ae1f37ccb64e954526f3ca1d22f4", + }, "cannotMergeReason": undefined, "createdAt": "2011-08-18T22:30:38Z", "displayNumber": "Pull Request #2", diff --git a/lib/modules/platform/gitea/index.ts b/lib/modules/platform/gitea/index.ts index 54a82bfff5f7ab379cf342e138822b28f3570c60..8b62ab12dc9cc9229ff204b75b421e9790ec1caa 100644 --- a/lib/modules/platform/gitea/index.ts +++ b/lib/modules/platform/gitea/index.ts @@ -18,6 +18,7 @@ import * as hostRules from '../../../util/host-rules'; import { setBaseUrl } from '../../../util/http/gitea'; import { sanitize } from '../../../util/sanitize'; import { ensureTrailingSlash } from '../../../util/url'; +import { getPrBodyStruct, hashBody } from '../pr-body'; import type { BranchStatusConfig, CreatePRConfig, @@ -96,7 +97,7 @@ function toRenovatePR(data: helper.PR): Pr | null { displayNumber: `Pull Request #${data.number}`, state: data.state, title: data.title, - body: data.body, + bodyStruct: getPrBodyStruct(data.body), sha: data.head.sha, sourceBranch: data.head.label, targetBranch: data.base.ref, @@ -524,8 +525,8 @@ const platform: Platform = { }); // If a valid PR was found, return and gracefully recover from the error. Otherwise, abort and throw error. - if (pr) { - if (pr.title !== title || pr.body !== body) { + if (pr?.bodyStruct) { + if (pr.title !== title || pr.bodyStruct.hash !== hashBody(body)) { logger.debug( `Recovered from 409 Conflict, but PR for ${sourceBranch} is outdated. Updating...` ); @@ -535,7 +536,7 @@ const platform: Platform = { prBody: body, }); pr.title = title; - pr.body = body; + pr.bodyStruct = getPrBodyStruct(body); } else { logger.debug( `Recovered from 409 Conflict and PR for ${sourceBranch} is up-to-date` diff --git a/lib/modules/platform/github/__snapshots__/index.spec.ts.snap b/lib/modules/platform/github/__snapshots__/index.spec.ts.snap index c200deb87d269df4da92229f7d68e6b4a0ecb368..ed91cf8007a916621b09238c66114e044c5a6de0 100644 --- a/lib/modules/platform/github/__snapshots__/index.spec.ts.snap +++ b/lib/modules/platform/github/__snapshots__/index.spec.ts.snap @@ -2,7 +2,9 @@ exports[`modules/platform/github/index getBranchPr(branchName) should cache and return the PR object 1`] = ` Object { - "body": "dummy body", + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "displayNumber": "Pull Request #91", "number": 91, "sourceBranch": "somebranch", @@ -14,7 +16,9 @@ Object { exports[`modules/platform/github/index getBranchPr(branchName) should cache and return the PR object in fork mode 1`] = ` Object { - "body": "dummy body", + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "displayNumber": "Pull Request #90", "number": 90, "sourceBranch": "somebranch", @@ -26,7 +30,9 @@ Object { exports[`modules/platform/github/index getBranchPr(branchName) should reopen and cache autoclosed PR 1`] = ` Object { - "body": "dummy body", + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "displayNumber": "Pull Request #91", "number": 91, "sourceBranch": "somebranch", @@ -38,7 +44,9 @@ Object { exports[`modules/platform/github/index getPr(prNo) should return PR 1`] = ` Object { - "body": "dummy body", + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "displayNumber": "Pull Request #2500", "number": 2500, "sourceBranch": "renovate/jest-monorepo", @@ -50,7 +58,9 @@ Object { exports[`modules/platform/github/index getPr(prNo) should return a PR object - 0 1`] = ` Object { - "body": "dummy body", + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "createdAt": "01-01-2022", "displayNumber": "Pull Request #1234", "hasAssignees": true, @@ -68,7 +78,9 @@ Object { exports[`modules/platform/github/index getPr(prNo) should return a PR object - 1 1`] = ` Object { - "body": "dummy body", + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "displayNumber": "Pull Request #1234", "hasAssignees": true, "hasReviewers": true, @@ -81,7 +93,9 @@ Object { exports[`modules/platform/github/index getPr(prNo) should return a PR object - 2 1`] = ` Object { - "body": "dummy body", + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "displayNumber": "Pull Request #1234", "number": 1234, "sourceBranch": "some/branch", diff --git a/lib/modules/platform/github/common.ts b/lib/modules/platform/github/common.ts index b7d2c52ed63a987b27c1c10fabd87cf2ae00fc31..121b52274b8740cad0e19078298c69a2263519c9 100644 --- a/lib/modules/platform/github/common.ts +++ b/lib/modules/platform/github/common.ts @@ -1,5 +1,6 @@ import is from '@sindresorhus/is'; import { PrState } from '../../../types'; +import { getPrBodyStruct } from '../pr-body'; import type { Pr } from '../types'; import type { GhRestPr } from './types'; @@ -11,6 +12,7 @@ export function coerceRestPr(pr: GhRestPr | null | undefined): Pr | null { return null; } + const bodyStruct = pr.bodyStruct ?? getPrBodyStruct(pr.body); const result: Pr = { displayNumber: `Pull Request #${pr.number}`, number: pr.number, @@ -20,7 +22,7 @@ export function coerceRestPr(pr: GhRestPr | null | undefined): Pr | null { pr.state === PrState.Closed && is.string(pr.merged_at) ? PrState.Merged : pr.state, - body: pr.body ?? 'dummy body', + bodyStruct, }; if (pr.head?.sha) { diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index 4167cc6c1031a2aa961561c8b18d69249e779ab8..376bb2c31c5239352d583a8cfcff1e9f5c6cab86 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ import { DateTime } from 'luxon'; import * as httpMock from '../../../../test/http-mock'; import { logger, mocked } from '../../../../test/util'; @@ -12,6 +11,7 @@ import * as _git from '../../../util/git'; import * as _hostRules from '../../../util/host-rules'; import { setBaseUrl } from '../../../util/http/github'; import { toBase64 } from '../../../util/string'; +import { hashBody } from '../pr-body'; import type { CreatePRConfig, UpdatePrConfig } from '../types'; import type { ApiPageCache, GhRestPr } from './types'; import * as github from '.'; @@ -559,25 +559,31 @@ describe('modules/platform/github/index', () => { const t3 = t.plus({ minutes: 3 }).toISO(); const t4 = t.plus({ minutes: 4 }).toISO(); - const pr1 = { + const pr1: GhRestPr = { number: 1, - head: { ref: 'branch-1', repo: { full_name: 'some/repo' } }, + head: { ref: 'branch-1', sha: '111', repo: { full_name: 'some/repo' } }, + base: { repo: { pushed_at: '' } }, state: PrState.Open, title: 'PR #1', + created_at: t1, updated_at: t1, + mergeable_state: 'clean', + node_id: '12345', }; - const pr2 = { + const pr2: GhRestPr = { + ...pr1, number: 2, - head: { ref: 'branch-2', repo: { full_name: 'some/repo' } }, + head: { ref: 'branch-2', sha: '222', repo: { full_name: 'some/repo' } }, state: PrState.Open, title: 'PR #2', updated_at: t2, }; - const pr3 = { + const pr3: GhRestPr = { + ...pr1, number: 3, - head: { ref: 'branch-3', repo: { full_name: 'some/repo' } }, + head: { ref: 'branch-3', sha: '333', repo: { full_name: 'some/repo' } }, state: PrState.Open, title: 'PR #3', updated_at: t3, @@ -663,65 +669,110 @@ describe('modules/platform/github/index', () => { ]); }); - it('removes url data from response', async () => { - const scope = httpMock.scope(githubApiHost); - initRepoMock(scope, 'some/repo'); - scope.get(pagePath(1)).reply(200, [ - { - ...pr1, - url: 'https://example.com', - example_url: 'https://example.com', - _links: { foo: { href: 'https:/example.com' } }, - repo: { example_url: 'https://example.com' }, - }, - ]); - await github.initRepo({ repository: 'some/repo' } as never); + describe('Url cleanup', () => { + type GhRestPrWithUrls = GhRestPr & { + url: string; + example_url: string; + repo: { + example_url: string; + }; + }; - await github.getPrList(); - const cache = repository.getCache().platform!.github! - .prCache as ApiPageCache<GhRestPr>; - const item = cache.items['1']; + type PrCache = ApiPageCache<GhRestPrWithUrls>; - expect(item['_links']).toBeUndefined(); - // TODO: fix types #7154 - expect((item as any)['url']).toBeUndefined(); - expect((item as any)['example_url']).toBeUndefined(); - expect((item as any)['repo']['example_url']).toBeUndefined(); + const prWithUrls = (): GhRestPrWithUrls => ({ + ...pr1, + url: 'https://example.com', + example_url: 'https://example.com', + _links: { foo: { href: 'https://example.com' } }, + repo: { example_url: 'https://example.com' }, + }); + + it('removes url data from response', async () => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope.get(pagePath(1)).reply(200, [prWithUrls()]); + await github.initRepo({ repository: 'some/repo' } as never); + + await github.getPrList(); + + const repoCache = repository.getCache(); + const prCache = repoCache.platform?.github?.prCache as PrCache; + expect(prCache).toMatchObject({ items: {} }); + + const item = prCache.items[1]; + expect(item).toBeDefined(); + expect(item._links).toBeUndefined(); + expect(item.url).toBeUndefined(); + expect(item.example_url).toBeUndefined(); + expect(item.repo.example_url).toBeUndefined(); + }); + + it('removes url data from existing cache', async () => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope.get(pagePath(1, 20)).reply(200, []); + await github.initRepo({ repository: 'some/repo' } as never); + const repoCache = repository.getCache(); + const prCache: PrCache = { items: { 1: prWithUrls() } }; + repoCache.platform = { github: { prCache } }; + + await github.getPrList(); + + const item = prCache.items[1]; + expect(item._links).toBeUndefined(); + expect(item.url).toBeUndefined(); + expect(item.example_url).toBeUndefined(); + expect(item.repo.example_url).toBeUndefined(); + }); }); - it('removes url data from existing cache', async () => { - const scope = httpMock.scope(githubApiHost); - initRepoMock(scope, 'some/repo'); - scope.get(pagePath(1, 20)).reply(200, [ - { - ...pr1, - url: 'https://example.com', - example_url: 'https://example.com', - _links: { foo: { href: 'https:/example.com' } }, - repo: { example_url: 'https://example.com' }, - }, - ]); - await github.initRepo({ repository: 'some/repo' } as never); - const repoCache = repository.getCache(); - const item = { - head: { ref: 'branch-1', repo: { full_name: 'some/repo' } }, - number: 1, - repo: { example_url: 'https://example.com' }, - state: 'open', - title: 'PR #1', - updated_at: '2000-01-01T02:01:00.000+02:00', - _links: { foo: { href: 'https:/example.com' } }, - url: 'https://example.com', - }; - repoCache.platform = { github: { prCache: { items: { '1': item } } } }; + describe('Body compaction', () => { + type PrCache = ApiPageCache<GhRestPr>; - await github.getPrList(); + const prWithBody = (body: string): GhRestPr => ({ + ...pr1, + body, + }); - expect(item['_links']).toBeUndefined(); - expect(item['url']).toBeUndefined(); - // TODO: fix types #7154 - expect((item as any)['example_url']).toBeUndefined(); - expect(item['repo']['example_url']).toBeUndefined(); + it('compacts body from response', async () => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope.get(pagePath(1)).reply(200, [prWithBody('foo')]); + await github.initRepo({ repository: 'some/repo' } as never); + + await github.getPrList(); + + const repoCache = repository.getCache(); + const prCache = repoCache.platform?.github?.prCache as PrCache; + expect(prCache).toMatchObject({ items: {} }); + + const item = prCache.items[1]; + expect(item).toBeDefined(); + expect(item.body).toBeUndefined(); + expect(item.bodyStruct).toEqual({ hash: hashBody('foo') }); + }); + + it('removes url data from existing cache', async () => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope.get(pagePath(1)).reply(200, [prWithBody('foo')]); + await github.initRepo({ repository: 'some/repo' } as never); + const repoCache = repository.getCache(); + const prCache: PrCache = { + items: { 1: prWithBody('bar'), 2: prWithBody('baz') }, + }; + repoCache.platform = { github: { prCache } }; + + await github.getPrList(); + + expect(prCache.items[2]).toBeUndefined(); + + const item = prCache.items[1]; + expect(item).toBeDefined(); + expect(item.body).toBeUndefined(); + expect(item.bodyStruct).toEqual({ hash: hashBody('foo') }); + }); }); }); diff --git a/lib/modules/platform/github/pr.ts b/lib/modules/platform/github/pr.ts index 3629bdf9ffccfcc1b799f9828d7d62826d895637..416afd3a9136401a12ab0a652f32be1adf78fb9b 100644 --- a/lib/modules/platform/github/pr.ts +++ b/lib/modules/platform/github/pr.ts @@ -5,6 +5,7 @@ import { ExternalHostError } from '../../../types/errors/external-host-error'; import { getCache } from '../../../util/cache/repository'; import type { GithubHttp, GithubHttpOptions } from '../../../util/http/github'; import { parseLinkHeader } from '../../../util/url'; +import { getPrBodyStruct } from '../pr-body'; import type { Pr } from '../types'; import { ApiCache } from './api-cache'; import { coerceRestPr } from './common'; @@ -22,11 +23,23 @@ function removeUrlFields(input: unknown): void { } } +function compactPrBodyStructure(input: unknown): void { + if (is.plainObject(input)) { + if (!input.bodyStruct && is.string(input.body)) { + input.bodyStruct = getPrBodyStruct(input.body); + delete input.body; + } + } +} + function massageGhRestPr(ghPr: GhRestPr): GhRestPr { removeUrlFields(ghPr); - delete ghPr?.head?.repo?.pushed_at; - delete ghPr?.base?.repo?.pushed_at; - delete ghPr?._links; + delete ghPr.head?.repo?.pushed_at; + delete ghPr.base?.repo?.pushed_at; + delete ghPr._links; + + compactPrBodyStructure(ghPr); + return ghPr; } @@ -39,7 +52,11 @@ function getPrApiCache(): ApiCache<GhRestPr> { .prCache as ApiPageCache<GhRestPr>; const items = Object.values(apiPageCache.items); - if (items?.[0]?._links) { + + const firstItem = items?.[0]; + if (firstItem?.body) { + apiPageCache.items = {}; + } else if (firstItem?._links) { for (const ghPr of items) { massageGhRestPr(ghPr); } @@ -145,7 +162,8 @@ export async function getPrCache( throw new ExternalHostError(err, PlatformId.Github); } - for (const ghPr of prApiCache.getItems()) { + const cacheItems = prApiCache.getItems(); + for (const ghPr of cacheItems) { const pr = coerceRestPr(ghPr); if (pr) { prCache[ghPr.number] = pr; diff --git a/lib/modules/platform/github/types.ts b/lib/modules/platform/github/types.ts index 0c3427a64b71ef796d8af6f49f5c602e9d101321..dcb6c6ee006e6e6d8cf656c7929f42ff36e94dc8 100644 --- a/lib/modules/platform/github/types.ts +++ b/lib/modules/platform/github/types.ts @@ -1,4 +1,4 @@ -import type { Pr } from '../types'; +import type { Pr, PrBodyStruct } from '../types'; // https://developer.github.com/v3/repos/statuses // https://developer.github.com/v3/checks/runs/ @@ -37,11 +37,12 @@ export interface GhRestPr { mergeable_state: string; number: number; title: string; - body: string; + body?: string; + bodyStruct?: PrBodyStruct; state: string; - merged_at: string; + merged_at?: string; created_at: string; - closed_at: string; + closed_at?: string; updated_at: string; user?: { login?: string }; node_id: string; diff --git a/lib/modules/platform/gitlab/__snapshots__/index.spec.ts.snap b/lib/modules/platform/gitlab/__snapshots__/index.spec.ts.snap index 209ce225289bad061c5a996a3fe55e0e9d11514c..a19ca9e09da9c2ac8aab274679d4225038ec47c4 100644 --- a/lib/modules/platform/gitlab/__snapshots__/index.spec.ts.snap +++ b/lib/modules/platform/gitlab/__snapshots__/index.spec.ts.snap @@ -70,7 +70,9 @@ Object { exports[`modules/platform/gitlab/index getBranchPr(branchName) should return the PR object 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "displayNumber": "Merge Request #91", "hasAssignees": false, "hasReviewers": false, @@ -86,7 +88,9 @@ Object { exports[`modules/platform/gitlab/index getBranchPr(branchName) should strip deprecated draft prefix from title 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "displayNumber": "Merge Request #91", "hasAssignees": false, "hasReviewers": false, @@ -103,7 +107,9 @@ Object { exports[`modules/platform/gitlab/index getBranchPr(branchName) should strip draft prefix from title 1`] = ` Object { - "body": undefined, + "bodyStruct": Object { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, "displayNumber": "Merge Request #91", "hasAssignees": false, "hasReviewers": false, @@ -120,7 +126,9 @@ Object { exports[`modules/platform/gitlab/index getPr(prNo) removes deprecated draft prefix from returned title 1`] = ` Object { - "body": "a merge request", + "bodyStruct": Object { + "hash": "23f41dbec0785a6c77457dd6ebf99ae5970c5fffc9f7a8ad7f66c1b8eeba5b90", + }, "displayNumber": "Merge Request #12345", "hasAssignees": false, "hasReviewers": false, @@ -137,7 +145,9 @@ Object { exports[`modules/platform/gitlab/index getPr(prNo) removes draft prefix from returned title 1`] = ` Object { - "body": "a merge request", + "bodyStruct": Object { + "hash": "23f41dbec0785a6c77457dd6ebf99ae5970c5fffc9f7a8ad7f66c1b8eeba5b90", + }, "displayNumber": "Merge Request #12345", "hasAssignees": false, "hasReviewers": false, @@ -154,7 +164,9 @@ Object { exports[`modules/platform/gitlab/index getPr(prNo) returns the PR 1`] = ` Object { - "body": "a merge request", + "bodyStruct": Object { + "hash": "23f41dbec0785a6c77457dd6ebf99ae5970c5fffc9f7a8ad7f66c1b8eeba5b90", + }, "displayNumber": "Merge Request #12345", "hasAssignees": false, "hasReviewers": false, @@ -170,7 +182,9 @@ Object { exports[`modules/platform/gitlab/index getPr(prNo) returns the PR with nonexisting branch 1`] = ` Object { - "body": "a merge request", + "bodyStruct": Object { + "hash": "23f41dbec0785a6c77457dd6ebf99ae5970c5fffc9f7a8ad7f66c1b8eeba5b90", + }, "displayNumber": "Merge Request #12345", "hasAssignees": true, "hasReviewers": false, @@ -186,7 +200,9 @@ Object { exports[`modules/platform/gitlab/index getPr(prNo) returns the mergeable PR 1`] = ` Object { - "body": "a merge request", + "bodyStruct": Object { + "hash": "23f41dbec0785a6c77457dd6ebf99ae5970c5fffc9f7a8ad7f66c1b8eeba5b90", + }, "displayNumber": "Merge Request #12345", "hasAssignees": true, "hasReviewers": false, diff --git a/lib/modules/platform/gitlab/index.ts b/lib/modules/platform/gitlab/index.ts index f0ca2cf8b0fed62be9d8f2d40957a31eadf02f29..6584bcb9152984d1031ef0ac6184af0e3010d233 100644 --- a/lib/modules/platform/gitlab/index.ts +++ b/lib/modules/platform/gitlab/index.ts @@ -30,6 +30,7 @@ import { getQueryString, parseUrl, } from '../../../util/url'; +import { getPrBodyStruct } from '../pr-body'; import type { BranchStatusConfig, CreatePRConfig, @@ -607,7 +608,7 @@ export async function getPr(iid: number): Promise<Pr> { targetBranch: mr.target_branch, number: mr.iid, displayNumber: `Merge Request #${mr.iid}`, - body: mr.description, + bodyStruct: getPrBodyStruct(mr.description), state: mr.state === 'opened' ? PrState.Open : mr.state, hasAssignees: !!(mr.assignee?.id || mr.assignees?.[0]?.id), hasReviewers: !!mr.reviewers?.length, diff --git a/lib/modules/platform/pr-body.spec.ts b/lib/modules/platform/pr-body.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..485a08376cda68c1f925daa99301e60556281296 --- /dev/null +++ b/lib/modules/platform/pr-body.spec.ts @@ -0,0 +1,30 @@ +import { getPrBodyStruct, hashBody } from './pr-body'; + +describe('modules/platform/pr-body', () => { + describe('getPrBodyStruct', () => { + it('returns hash for empty inputs', () => { + expect(getPrBodyStruct(null)).toEqual({ + hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }); + expect(getPrBodyStruct(undefined)).toEqual({ + hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }); + expect(getPrBodyStruct('')).toEqual({ + hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }); + }); + + it('returns rebaseRequested flag', () => { + expect(getPrBodyStruct('- [x] <!-- rebase-check -->')).toEqual({ + hash: '023952693e1e00a52a71b65d9b4804bca6ca9f215c20f6e029dbf420f322d541', + rebaseRequested: true, + }); + }); + + it('strips reviewable section', () => { + expect(getPrBodyStruct('foo<!-- Reviewable:start -->bar')).toEqual({ + hash: hashBody('foo'), + }); + }); + }); +}); diff --git a/lib/modules/platform/pr-body.ts b/lib/modules/platform/pr-body.ts new file mode 100644 index 0000000000000000000000000000000000000000..6427d0ecaa763e5bbcaa7ba624003d8268027903 --- /dev/null +++ b/lib/modules/platform/pr-body.ts @@ -0,0 +1,41 @@ +import hasha from 'hasha'; +import { stripEmojis } from '../../util/emoji'; +import { regEx } from '../../util/regex'; +import type { PrBodyStruct } from './types'; + +function noWhitespaceOrHeadings(input: string): string { + return input.replace(regEx(/\r?\n|\r|\s|#/g), ''); +} + +const reviewableRegex = regEx(/\s*<!-- Reviewable:start -->/); + +export function hashBody(body: string | undefined): string { + let result = body?.trim() ?? ''; + const reviewableIndex = result.search(reviewableRegex); + if (reviewableIndex > -1) { + result = result.slice(0, reviewableIndex); + } + result = stripEmojis(result); + result = noWhitespaceOrHeadings(result); + result = hasha(result, { algorithm: 'sha256' }); + return result; +} + +export function isRebaseRequested(body: string | undefined): boolean { + return !!body?.includes(`- [x] <!-- rebase-check -->`); +} + +export function getPrBodyStruct( + input: string | undefined | null +): PrBodyStruct { + const str = input ?? ''; + const hash = hashBody(str); + const result: PrBodyStruct = { hash }; + + const rebaseRequested = isRebaseRequested(str); + if (rebaseRequested) { + result.rebaseRequested = rebaseRequested; + } + + return result; +} diff --git a/lib/modules/platform/types.ts b/lib/modules/platform/types.ts index e6de7e8852e34ab9ea788b904b9475b0c6cdba8a..aff2f2168876cafa308952b4556db6594b799543 100644 --- a/lib/modules/platform/types.ts +++ b/lib/modules/platform/types.ts @@ -43,11 +43,16 @@ export interface RepoParams { ignorePrAuthor?: boolean; } +export interface PrBodyStruct { + hash: string; + rebaseRequested?: boolean; +} + /** * */ export interface Pr { - body?: string; + bodyStruct?: PrBodyStruct; sourceBranch: string; cannotMergeReason?: string; // for reflecting platform policies which may prevent merging createdAt?: string; diff --git a/lib/workers/repository/onboarding/pr/index.spec.ts b/lib/workers/repository/onboarding/pr/index.spec.ts index efcd1e96c8f1277144386463f24032b1b02df2b2..6308236390ce6dc3a7e02e3d11cdc3a045a13440 100644 --- a/lib/workers/repository/onboarding/pr/index.spec.ts +++ b/lib/workers/repository/onboarding/pr/index.spec.ts @@ -20,6 +20,10 @@ describe('workers/repository/onboarding/pr/index', () => { let packageFiles: Record<string, PackageFile[]>; let branches: BranchConfig[]; + const bodyStruct = { + hash: '8d5d8373c3fc54803f573ea57ded60686a9df8eb0430ad25da281472eed9ce4e', + }; + beforeEach(() => { jest.resetAllMocks(); config = { @@ -35,8 +39,6 @@ describe('workers/repository/onboarding/pr/index', () => { GlobalConfig.reset(); }); - let createPrBody: string; - it('returns if onboarded', async () => { config.repoIsOnboarded = true; await expect( @@ -47,7 +49,6 @@ describe('workers/repository/onboarding/pr/index', () => { it('creates PR', async () => { await ensureOnboardingPr(config, packageFiles, branches); expect(platform.createPr).toHaveBeenCalledTimes(1); - createPrBody = platform.createPr.mock.calls[0][0].prBody; }); it('creates PR with labels', async () => { @@ -123,7 +124,7 @@ describe('workers/repository/onboarding/pr/index', () => { platform.getBranchPr.mockResolvedValue( partial<Pr>({ title: 'Configure Renovate', - body: createPrBody, + bodyStruct, }) ); await ensureOnboardingPr(config, packageFiles, branches); @@ -136,7 +137,7 @@ describe('workers/repository/onboarding/pr/index', () => { platform.getBranchPr.mockResolvedValueOnce( partial<Pr>({ title: 'Configure Renovate', - body: createPrBody, + bodyStruct, }) ); git.isBranchConflicted.mockResolvedValueOnce(true); @@ -151,7 +152,7 @@ describe('workers/repository/onboarding/pr/index', () => { platform.getBranchPr.mockResolvedValueOnce( partial<Pr>({ title: 'Configure Renovate', - body: createPrBody, + bodyStruct, }) ); git.isBranchModified.mockResolvedValueOnce(true); @@ -172,7 +173,7 @@ describe('workers/repository/onboarding/pr/index', () => { platform.getBranchPr.mockResolvedValueOnce( partial<Pr>({ title: 'Configure Renovate', - body: createPrBody, + bodyStruct, }) ); git.isBranchConflicted.mockResolvedValueOnce(true); diff --git a/lib/workers/repository/onboarding/pr/index.ts b/lib/workers/repository/onboarding/pr/index.ts index cc90b3e6408b4b1e707cb1b37ac9dd7c187d5278..4bcd159f9d396c72a4284eb26244935d7feb01c5 100644 --- a/lib/workers/repository/onboarding/pr/index.ts +++ b/lib/workers/repository/onboarding/pr/index.ts @@ -4,6 +4,7 @@ import type { RenovateConfig } from '../../../../config/types'; import { logger } from '../../../../logger'; import type { PackageFile } from '../../../../modules/manager/types'; import { platform } from '../../../../modules/platform'; +import { hashBody } from '../../../../modules/platform/pr-body'; import { emojify } from '../../../../util/emoji'; import { deleteBranch, @@ -115,9 +116,8 @@ If you need any further assistance then you can also [request help here](${confi if (existingPr) { logger.debug('Found open onboarding PR'); // Check if existing PR needs updating - if ( - existingPr.body.trim() === prBody.trim() // Bitbucket strips trailing \n - ) { + const prBodyHash = hashBody(prBody); + if (existingPr.bodyStruct?.hash === prBodyHash) { logger.debug(`${existingPr.displayNumber} does not need updating`); return; } diff --git a/lib/workers/repository/update/branch/index.spec.ts b/lib/workers/repository/update/branch/index.spec.ts index 1079c41d859605441bf4d08f1eeab900fed80ad5..1be57ad581c5e42da906448811259c6e6e6c7bbb 100644 --- a/lib/workers/repository/update/branch/index.spec.ts +++ b/lib/workers/repository/update/branch/index.spec.ts @@ -14,6 +14,7 @@ import { } from '../../../../constants/error-messages'; import * as _npmPostExtract from '../../../../modules/manager/npm/post-update'; import type { WriteExistingFilesResult } from '../../../../modules/manager/npm/post-update/types'; +import { hashBody } from '../../../../modules/platform/pr-body'; import { PrState } from '../../../../types'; import * as _exec from '../../../../util/exec'; import type { FileChange, StatusResult } from '../../../../util/git/types'; @@ -903,7 +904,10 @@ describe('workers/repository/update/branch/index', () => { platform.getBranchPr.mockResolvedValueOnce({ title: 'rebase!', state: PrState.Open, - body: `- [x] <!-- rebase-check -->`, + bodyStruct: { + hash: hashBody(`- [x] <!-- rebase-check -->`), + rebaseRequested: true, + }, } as Pr); git.isBranchModified.mockResolvedValueOnce(true); schedule.isScheduledNow.mockReturnValueOnce(false); @@ -936,7 +940,10 @@ describe('workers/repository/update/branch/index', () => { platform.getBranchPr.mockResolvedValueOnce({ title: 'rebase!', state: PrState.Open, - body: `- [x] <!-- rebase-check -->`, + bodyStruct: { + hash: hashBody(`- [x] <!-- rebase-check -->`), + rebaseRequested: true, + }, } as Pr); git.isBranchModified.mockResolvedValueOnce(true); schedule.isScheduledNow.mockReturnValueOnce(false); @@ -971,7 +978,10 @@ describe('workers/repository/update/branch/index', () => { platform.getBranchPr.mockResolvedValueOnce({ title: 'rebase!', state: PrState.Open, - body: `- [x] <!-- rebase-check -->`, + bodyStruct: { + hash: hashBody(`- [x] <!-- rebase-check -->`), + rebaseRequested: true, + }, } as Pr); git.isBranchModified.mockResolvedValueOnce(true); schedule.isScheduledNow.mockReturnValueOnce(false); @@ -1004,7 +1014,7 @@ describe('workers/repository/update/branch/index', () => { title: 'rebase!', state: PrState.Open, labels: ['stop-updating'], - body: `- [ ] <!-- rebase-check -->`, + bodyStruct: { hash: hashBody(`- [ ] <!-- rebase-check -->`) }, } as Pr); git.isBranchModified.mockResolvedValueOnce(true); schedule.isScheduledNow.mockReturnValueOnce(false); @@ -1039,7 +1049,10 @@ describe('workers/repository/update/branch/index', () => { title: 'Update dependency', state: PrState.Open, labels: ['stop-updating'], - body: `- [x] <!-- rebase-check -->`, + bodyStruct: { + hash: hashBody(`- [x] <!-- rebase-check -->`), + rebaseRequested: true, + }, } as Pr); git.isBranchModified.mockResolvedValueOnce(true); schedule.isScheduledNow.mockReturnValueOnce(false); @@ -1084,7 +1097,10 @@ describe('workers/repository/update/branch/index', () => { platform.getBranchPr.mockResolvedValueOnce({ title: 'rebase!', state: PrState.Open, - body: `- [x] <!-- rebase-check -->`, + bodyStruct: { + hash: hashBody(`- [x] <!-- rebase-check -->`), + rebaseRequested: true, + }, } as Pr); git.isBranchModified.mockResolvedValueOnce(true); git.getRepoStatus.mockResolvedValueOnce({ @@ -1170,7 +1186,10 @@ describe('workers/repository/update/branch/index', () => { platform.getBranchPr.mockResolvedValueOnce({ title: 'rebase!', state: PrState.Open, - body: `- [x] <!-- rebase-check -->`, + bodyStruct: { + hash: hashBody(`- [x] <!-- rebase-check -->`), + rebaseRequested: true, + }, } as never); git.isBranchModified.mockResolvedValueOnce(true); git.getRepoStatus.mockResolvedValueOnce({ @@ -1247,7 +1266,10 @@ describe('workers/repository/update/branch/index', () => { platform.getBranchPr.mockResolvedValueOnce({ title: 'rebase!', state: PrState.Open, - body: `- [x] <!-- rebase-check -->`, + bodyStruct: { + hash: hashBody(`- [x] <!-- rebase-check -->`), + rebaseRequested: true, + }, } as Pr); git.isBranchModified.mockResolvedValueOnce(true); git.getRepoStatus.mockResolvedValueOnce({ @@ -1327,7 +1349,10 @@ describe('workers/repository/update/branch/index', () => { platform.getBranchPr.mockResolvedValueOnce({ title: 'rebase!', state: PrState.Open, - body: `- [x] <!-- rebase-check -->`, + bodyStruct: { + hash: hashBody(`- [x] <!-- rebase-check -->`), + rebaseRequested: true, + }, } as Pr); git.isBranchModified.mockResolvedValueOnce(true); git.getRepoStatus @@ -1472,7 +1497,10 @@ describe('workers/repository/update/branch/index', () => { platform.getBranchPr.mockResolvedValueOnce({ title: 'rebase!', state: PrState.Open, - body: `- [x] <!-- rebase-check -->`, + bodyStruct: { + hash: hashBody(`- [x] <!-- rebase-check -->`), + rebaseRequested: true, + }, } as Pr); git.isBranchModified.mockResolvedValueOnce(true); git.getRepoStatus.mockResolvedValueOnce({ diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index cedd9bb615976f01350462de9ce7381d4d296ccb..a7fed9f6bbe79be6f002b0c91dd8a664526ef16e 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -20,6 +20,7 @@ import { ensureComment, ensureCommentRemoval, } from '../../../../modules/platform/comment'; +import { hashBody } from '../../../../modules/platform/pr-body'; import { BranchStatus, PrState } from '../../../../types'; import { ExternalHostError } from '../../../../types/errors/external-host-error'; import { getElapsedDays } from '../../../../util/date'; @@ -37,11 +38,11 @@ import { isActiveConfidenceLevel, satisfiesConfidenceLevel, } from '../../../../util/merge-confidence'; -import { regEx } from '../../../../util/regex'; import { Limit, isLimitReached } from '../../../global/limits'; import { BranchConfig, BranchResult, PrBlockedBy } from '../../../types'; import { ensurePr, getPlatformPrOptions } from '../pr'; import { checkAutoMerge } from '../pr/automerge'; +import { getPrBody } from '../pr/body'; import { setArtifactErrorStatus } from './artifacts'; import { tryBranchAutomerge } from './automerge'; import { prAlreadyExisted } from './check-existing'; @@ -56,15 +57,11 @@ import { setConfidence, setStability } from './status-checks'; function rebaseCheck(config: RenovateConfig, branchPr: Pr): boolean { const titleRebase = branchPr.title?.startsWith('rebase!'); const labelRebase = branchPr.labels?.includes(config.rebaseLabel); - const prRebaseChecked = branchPr.body?.includes( - `- [x] <!-- rebase-check -->` - ); + const prRebaseChecked = !!branchPr.bodyStruct?.rebaseRequested; return titleRebase || labelRebase || prRebaseChecked; } -const rebasingRegex = regEx(/\*\*Rebasing\*\*: .*/); - async function deleteBranchSilently(branchName: string): Promise<void> { try { await deleteBranch(branchName); @@ -181,11 +178,12 @@ export async function processBranch( if (dependencyDashboardCheck || config.rebaseRequested) { logger.debug('Manual rebase has been requested for PR'); } else { - const newBody = branchPr.body?.replace( - rebasingRegex, - '**Rebasing**: Renovate will not automatically rebase this PR, because other commits have been found.' - ); - if (newBody !== branchPr.body) { + const newBody = await getPrBody(branchConfig, { + rebasingNotice: + 'Renovate will not automatically rebase this PR, because other commits have been found.', + }); + const newBodyHash = hashBody(newBody); + if (newBodyHash !== branchPr.bodyStruct?.hash) { logger.debug( 'Updating existing PR to indicate that rebasing is not possible' ); @@ -453,9 +451,7 @@ export async function processBranch( config.stopUpdating = branchPr?.labels?.includes(config.stopUpdatingLabel); - const prRebaseChecked = branchPr?.body?.includes( - `- [x] <!-- rebase-check -->` - ); + const prRebaseChecked = !!branchPr?.bodyStruct?.rebaseRequested; if (branchExists && dependencyDashboardCheck && config.stopUpdating) { if (!prRebaseChecked) { diff --git a/lib/workers/repository/update/branch/reuse.spec.ts b/lib/workers/repository/update/branch/reuse.spec.ts index 02e4f9edaf1727827745bb3912f87201249377a5..ceda5f86b94681a4381576b3bd7b2f947f2115fc 100644 --- a/lib/workers/repository/update/branch/reuse.spec.ts +++ b/lib/workers/repository/update/branch/reuse.spec.ts @@ -126,7 +126,10 @@ describe('workers/repository/update/branch/reuse', () => { platform.getBranchPr.mockResolvedValueOnce({ ...pr, title: 'Update foo to v4', - body: 'blah\nblah\n- [x] <!-- rebase-check -->foo\n', + bodyStruct: { + hash: '123', + rebaseRequested: true, + }, }); const res = await shouldReuseExistingBranch(config); expect(res.reuseExistingBranch).toBeFalse(); diff --git a/lib/workers/repository/update/branch/reuse.ts b/lib/workers/repository/update/branch/reuse.ts index 32ab7abc6cf65d13d1bab5fd4626d8b412e11698..8a6447b036dbf1d842d746686ab0220906972d41 100644 --- a/lib/workers/repository/update/branch/reuse.ts +++ b/lib/workers/repository/update/branch/reuse.ts @@ -36,7 +36,7 @@ export async function shouldReuseExistingBranch( logger.debug(`Manual rebase requested via PR title for #${pr.number}`); return result; } - if (pr.body?.includes(`- [x] <!-- rebase-check -->`)) { + if (pr.bodyStruct?.rebaseRequested) { logger.debug(`Manual rebase requested via PR checkbox for #${pr.number}`); return result; } diff --git a/lib/workers/repository/update/pr/index.spec.ts b/lib/workers/repository/update/pr/index.spec.ts index 5b7d10fd6b50629a809d2f302454f192c1427773..eb0d70dcc90219c536352d5b266ad04466f55635 100644 --- a/lib/workers/repository/update/pr/index.spec.ts +++ b/lib/workers/repository/update/pr/index.spec.ts @@ -7,6 +7,7 @@ import { REPOSITORY_CHANGED, } from '../../../../constants/error-messages'; import * as _comment from '../../../../modules/platform/comment'; +import { getPrBodyStruct } from '../../../../modules/platform/pr-body'; import type { Pr } from '../../../../modules/platform/types'; import { BranchStatus, PrState } from '../../../../types'; import { ExternalHostError } from '../../../../types/errors/external-host-error'; @@ -45,12 +46,13 @@ describe('workers/repository/update/pr/index', () => { const sourceBranch = 'renovate-branch'; const prTitle = 'Some title'; const body = 'Some body'; + const bodyStruct = getPrBodyStruct(body); const pr: Pr = { number, sourceBranch, title: prTitle, - body, + bodyStruct, state: PrState.Open, }; @@ -255,7 +257,10 @@ describe('workers/repository/update/pr/index', () => { }); it('updates PR due to body change', async () => { - const changedPr: Pr = { ...pr, body: `${body} updated` }; + const changedPr: Pr = { + ...pr, + bodyStruct: getPrBodyStruct(`${body} updated`), + }; platform.getBranchPr.mockResolvedValueOnce(changedPr); const res = await ensurePr(config); @@ -265,12 +270,15 @@ describe('workers/repository/update/pr/index', () => { expect(platform.createPr).not.toHaveBeenCalled(); }); - it('ignores eviewable content ', async () => { + it('ignores reviewable content ', async () => { // See: https://reviewable.io/ const reviewableContent = '<!-- Reviewable:start -->something<!-- Reviewable:end -->'; - const changedPr: Pr = { ...pr, body: `${body}${reviewableContent}` }; + const changedPr: Pr = { + ...pr, + bodyStruct: getPrBodyStruct(`${body}${reviewableContent}`), + }; platform.getBranchPr.mockResolvedValueOnce(changedPr); const res = await ensurePr(config); diff --git a/lib/workers/repository/update/pr/index.ts b/lib/workers/repository/update/pr/index.ts index 523f0ed4c1ef328b55a35409c3df6807db6e9b55..ce4f4cf7d2d476eb2a8299e0a8a89f756b34c937 100644 --- a/lib/workers/repository/update/pr/index.ts +++ b/lib/workers/repository/update/pr/index.ts @@ -9,12 +9,12 @@ import { import { logger } from '../../../../logger'; import { PlatformPrOptions, Pr, platform } from '../../../../modules/platform'; import { ensureComment } from '../../../../modules/platform/comment'; +import { hashBody } from '../../../../modules/platform/pr-body'; import { BranchStatus } from '../../../../types'; import { ExternalHostError } from '../../../../types/errors/external-host-error'; import { stripEmojis } from '../../../../util/emoji'; import { deleteBranch, getBranchLastCommitTime } from '../../../../util/git'; import { memoize } from '../../../../util/memoize'; -import { regEx } from '../../../../util/regex'; import { Limit, incLimitedValue, isLimitReached } from '../../../global/limits'; import type { BranchConfig, @@ -27,10 +27,6 @@ import { ChangeLogError } from './changelog/types'; import { prepareLabels } from './labels'; import { addParticipants } from './participants'; -function noWhitespaceOrHeadings(input: string): string { - return input.replace(regEx(/\r?\n|\r|\s|#/g), ''); -} - export function getPlatformPrOptions( config: RenovateConfig & PlatformPrOptions ): PlatformPrOptions { @@ -269,22 +265,13 @@ export async function ensurePr( await addParticipants(config, existingPr); } // Check if existing PR needs updating - existingPr.body ??= ''; - const reviewableIndex = existingPr.body.indexOf( - '<!-- Reviewable:start -->' - ); - let existingPrBody = existingPr.body; - if (reviewableIndex > -1) { - logger.debug('Stripping Reviewable content'); - existingPrBody = existingPrBody.slice(0, reviewableIndex); - } const existingPrTitle = stripEmojis(existingPr.title); + const existingPrBodyHash = existingPr.bodyStruct?.hash; const newPrTitle = stripEmojis(prTitle); - existingPrBody = existingPrBody.trim(); + const newPrBodyHash = hashBody(prBody); if ( existingPrTitle === newPrTitle && - noWhitespaceOrHeadings(stripEmojis(existingPrBody)) === - noWhitespaceOrHeadings(stripEmojis(prBody)) + existingPrBodyHash === newPrBodyHash ) { logger.debug(`${existingPr.displayNumber} does not need updating`); return { type: 'with-pr', pr: existingPr };