renovate 44.207.0 → 44.208.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ import { logger } from "../../../logger/index.js";
3
3
  import { getQueryString } from "../../../util/url.js";
4
4
  import { GerritHttp } from "../../../util/http/gerrit.js";
5
5
  import { GerritBranchInfo, GerritChange, GerritChangeMessages, GerritChanges, GerritMergeableInfo, GerritProjectInfo, GerritRepos } from "./schema.js";
6
- import { MAX_GERRIT_COMMENT_SIZE, MIN_GERRIT_VERSION, mapPrStateToGerritFilter } from "./utils.js";
6
+ import { MAX_GERRIT_COMMENT_SIZE, MIN_GERRIT_VERSION, currentGerritTimestamp, mapPrStateToGerritFilter } from "./utils.js";
7
7
  import { isNonEmptyArray } from "@sindresorhus/is";
8
8
  import { z } from "zod/v4";
9
9
  import semver from "semver";
@@ -45,13 +45,12 @@ var GerritClient = class {
45
45
  return changes[0];
46
46
  }
47
47
  async findChanges(repository, findPRConfig) {
48
- const startOffset = findPRConfig.startOffset ?? 0;
49
48
  const query = { n: findPRConfig.singleChange ? 1 : findPRConfig.pageLimit ?? 50 };
50
49
  if (findPRConfig.requestDetails) query.o = findPRConfig.requestDetails;
51
50
  const filters = this.buildSearchFilters(repository, findPRConfig);
52
51
  const allChanges = [];
53
52
  while (true) {
54
- query.S = allChanges.length + startOffset;
53
+ query.S = allChanges.length;
55
54
  const queryString = `q=${filters.join("+")}&${getQueryString(query)}`;
56
55
  const changes = await this.gerritHttp.getJson(`a/changes/?${queryString}`, GerritChanges);
57
56
  logger.trace(`findChanges(${queryString},start=${query.S},limit=${query.n}) => ${changes.body.length}`);
@@ -62,7 +61,10 @@ var GerritClient = class {
62
61
  delete lastChange._more_changes;
63
62
  }
64
63
  allChanges.push(...changes.body);
65
- if (findPRConfig.singleChange || findPRConfig.noPagination || !hasMoreChanges) break;
64
+ if (findPRConfig.shouldFetchNextPage) {
65
+ if (!findPRConfig.shouldFetchNextPage(changes.body)) break;
66
+ }
67
+ if (findPRConfig.singleChange || !hasMoreChanges) break;
66
68
  }
67
69
  return allChanges;
68
70
  }
@@ -74,10 +76,10 @@ var GerritClient = class {
74
76
  return (await this.gerritHttp.getJson(`a/changes/${change._number}/revisions/current/mergeable`, GerritMergeableInfo)).body;
75
77
  }
76
78
  async abandonChange(changeNumber, message) {
77
- await this.gerritHttp.postJson(`a/changes/${changeNumber}/abandon`, { body: {
79
+ return (await this.gerritHttp.postJson(`a/changes/${changeNumber}/abandon`, { body: {
78
80
  message,
79
81
  notify: "OWNER_REVIEWERS"
80
- } });
82
+ } })).body;
81
83
  }
82
84
  async submitChange(changeNumber) {
83
85
  return (await this.gerritHttp.postJson(`a/changes/${changeNumber}/submit`)).body;
@@ -87,11 +89,12 @@ var GerritClient = class {
87
89
  }
88
90
  async addMessage(changeNumber, fullMessage, tag) {
89
91
  const message = this.normalizeMessage(fullMessage);
90
- await this.gerritHttp.postJson(`a/changes/${changeNumber}/revisions/current/review`, { body: {
92
+ const url = `a/changes/${changeNumber}/revisions/current/review`;
93
+ return (await this.gerritHttp.postJson(url, { body: {
91
94
  message,
92
95
  tag,
93
96
  notify: "NONE"
94
- } });
97
+ } })).body.change_info?.updated ?? currentGerritTimestamp();
95
98
  }
96
99
  async checkForExistingMessage(changeNumber, newMessage, msgType) {
97
100
  return (await this.getMessages(changeNumber)).some((existingMsg) => (msgType === void 0 || msgType === existingMsg.tag) && existingMsg.message.includes(newMessage));
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../../../lib/modules/platform/gerrit/client.ts"],"sourcesContent":["import { isNonEmptyArray } from '@sindresorhus/is';\nimport semver from 'semver';\nimport { z } from 'zod/v4';\nimport { REPOSITORY_ARCHIVED } from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { GerritHttp } from '../../../util/http/gerrit.ts';\nimport type { HttpOptions } from '../../../util/http/types.ts';\nimport { getQueryString } from '../../../util/url.ts';\nimport type { GerritAccountInfo, GerritChangeMessageInfo } from './schema.ts';\nimport {\n GerritBranchInfo,\n GerritChange,\n GerritChangeMessages,\n GerritChanges,\n GerritMergeableInfo,\n GerritProjectInfo,\n GerritRepos,\n} from './schema.ts';\nimport type {\n GerritFindPRConfig,\n GerritHashtagsInput,\n GerritRequestDetail,\n} from './types.ts';\nimport {\n MAX_GERRIT_COMMENT_SIZE,\n MIN_GERRIT_VERSION,\n mapPrStateToGerritFilter,\n} from './utils.ts';\n\nclass GerritClient {\n // memCache is disabled because GerritPrCache will provide a smarter caching\n private gerritHttp = new GerritHttp({ memCache: false });\n private gerritVersion = MIN_GERRIT_VERSION;\n\n setGerritVersion(version: string): void {\n this.gerritVersion = version;\n }\n\n async getGerritVersion(\n options: Pick<HttpOptions, 'username' | 'password'>,\n ): Promise<string> {\n const res = await this.gerritHttp.getJson(\n 'a/config/server/version',\n options,\n z.string(),\n );\n return res.body;\n }\n\n async getRepos(): Promise<string[]> {\n const res = await this.gerritHttp.getJson(\n 'a/projects/?type=CODE&state=ACTIVE',\n GerritRepos,\n );\n return Object.keys(res.body);\n }\n\n async getProjectInfo(repository: string): Promise<GerritProjectInfo> {\n const projectInfo = await this.gerritHttp.getJson(\n `a/projects/${encodeURIComponent(repository)}`,\n GerritProjectInfo,\n );\n if (projectInfo.body.state !== 'ACTIVE') {\n throw new Error(REPOSITORY_ARCHIVED);\n }\n return projectInfo.body;\n }\n\n async getBranchInfo(repository: string): Promise<GerritBranchInfo> {\n const branchInfo = await this.gerritHttp.getJson(\n `a/projects/${encodeURIComponent(repository)}/branches/HEAD`,\n GerritBranchInfo,\n );\n return branchInfo.body;\n }\n\n async getBranchChange(\n repository: string,\n config: Pick<\n GerritFindPRConfig,\n 'branchName' | 'state' | 'targetBranch' | 'requestDetails'\n >,\n ): Promise<GerritChange | null> {\n const changes = await this.findChanges(repository, {\n branchName: config.branchName,\n state: config.state,\n singleChange: config.targetBranch ? false : true,\n requestDetails: config.requestDetails,\n });\n\n if (changes.length === 0) {\n return null;\n }\n\n if (changes.length === 1) {\n return changes[0];\n }\n\n // If multiple changes are found, prefer the one matching the target branch\n if (config.targetBranch) {\n const change = changes.find((c) => c.branch === config.targetBranch);\n if (change) {\n return change;\n }\n }\n\n // Otherwise return the first one\n return changes[0];\n }\n\n async findChanges(\n repository: string,\n findPRConfig: GerritFindPRConfig,\n ): Promise<GerritChange[]> {\n const startOffset = findPRConfig.startOffset ?? 0;\n const pageLimit = findPRConfig.singleChange\n ? 1\n : (findPRConfig.pageLimit ?? 50);\n\n const query: Record<string, any> = {\n n: pageLimit,\n };\n if (findPRConfig.requestDetails) {\n query.o = findPRConfig.requestDetails;\n }\n\n const filters = this.buildSearchFilters(repository, findPRConfig);\n\n const allChanges: GerritChange[] = [];\n\n while (true) {\n query.S = allChanges.length + startOffset;\n const queryString = `q=${filters.join('+')}&${getQueryString(query)}`;\n const changes = await this.gerritHttp.getJson(\n `a/changes/?${queryString}`,\n GerritChanges,\n );\n\n logger.trace(\n `findChanges(${queryString},start=${query.S},limit=${query.n}) => ${changes.body.length}`,\n );\n\n const lastChange = changes.body.at(-1);\n let hasMoreChanges = false;\n if (lastChange?._more_changes) {\n hasMoreChanges = true;\n delete lastChange._more_changes;\n }\n\n allChanges.push(...changes.body);\n\n if (\n findPRConfig.singleChange ||\n findPRConfig.noPagination ||\n !hasMoreChanges\n ) {\n break;\n }\n }\n\n return allChanges;\n }\n\n async getChange(\n changeNumber: number,\n requestDetails?: GerritRequestDetail[],\n ): Promise<GerritChange> {\n const queryString = getQueryString({ o: requestDetails });\n const changes = await this.gerritHttp.getJson(\n `a/changes/${changeNumber}?${queryString}`,\n GerritChange,\n );\n return changes.body;\n }\n\n async getMergeableInfo(change: GerritChange): Promise<GerritMergeableInfo> {\n const mergeable = await this.gerritHttp.getJson(\n `a/changes/${change._number}/revisions/current/mergeable`,\n GerritMergeableInfo,\n );\n return mergeable.body;\n }\n\n async abandonChange(changeNumber: number, message?: string): Promise<void> {\n await this.gerritHttp.postJson(`a/changes/${changeNumber}/abandon`, {\n body: {\n message,\n notify: 'OWNER_REVIEWERS', // Avoids notifying cc's\n },\n });\n }\n\n async submitChange(changeNumber: number): Promise<GerritChange> {\n const change = await this.gerritHttp.postJson<GerritChange>(\n `a/changes/${changeNumber}/submit`,\n );\n return change.body;\n }\n\n async getMessages(changeNumber: number): Promise<GerritChangeMessageInfo[]> {\n const messages = await this.gerritHttp.getJson(\n `a/changes/${changeNumber}/messages`,\n GerritChangeMessages,\n );\n return messages.body;\n }\n\n async addMessage(\n changeNumber: number,\n fullMessage: string,\n tag?: string,\n ): Promise<void> {\n const message = this.normalizeMessage(fullMessage);\n await this.gerritHttp.postJson(\n `a/changes/${changeNumber}/revisions/current/review`,\n { body: { message, tag, notify: 'NONE' } },\n );\n }\n\n async checkForExistingMessage(\n changeNumber: number,\n newMessage: string,\n msgType?: string,\n ): Promise<boolean> {\n const messagesToSearch = await this.getMessages(changeNumber);\n\n return messagesToSearch.some(\n (existingMsg) =>\n (msgType === undefined || msgType === existingMsg.tag) &&\n existingMsg.message.includes(newMessage),\n );\n }\n\n async addMessageIfNotAlreadyExists(\n changeNumber: number,\n message: string,\n tag?: string,\n ): Promise<void> {\n const newMsg = this.normalizeMessage(message);\n if (!(await this.checkForExistingMessage(changeNumber, newMsg, tag))) {\n await this.addMessage(changeNumber, newMsg, tag);\n }\n }\n\n async setLabel(\n changeNumber: number,\n label: string,\n value: number,\n ): Promise<void> {\n await this.gerritHttp.postJson(\n `a/changes/${changeNumber}/revisions/current/review`,\n { body: { labels: { [label]: value }, notify: 'NONE' } },\n );\n }\n\n async setHashtags(\n changeNumber: number,\n hashtagsInput: GerritHashtagsInput,\n ): Promise<void> {\n const { add, remove } = hashtagsInput;\n if (isNonEmptyArray(add) || isNonEmptyArray(remove)) {\n await this.gerritHttp.postJson(`a/changes/${changeNumber}/hashtags`, {\n body: { add, remove },\n });\n }\n }\n\n async addReviewers(changeNumber: number, reviewers: string[]): Promise<void> {\n await this.gerritHttp.postJson(\n `a/changes/${changeNumber}/revisions/current/review`,\n {\n body: {\n reviewers: reviewers.map((r) => ({ reviewer: r })),\n notify: 'OWNER_REVIEWERS', // Avoids notifying cc's\n },\n },\n );\n }\n\n async addAssignee(changeNumber: number, assignee: string): Promise<void> {\n await this.gerritHttp.putJson<GerritAccountInfo>(\n // TODO: use CCs instead as Gerrit 3.8+ no longer supports assignees\n `a/changes/${changeNumber}/assignee`,\n {\n body: { assignee },\n },\n );\n }\n\n async getFile(\n repo: string,\n branch: string,\n fileName: string,\n ): Promise<string> {\n const base64Content = await this.gerritHttp.getText(\n `a/projects/${encodeURIComponent(\n repo,\n )}/branches/${encodeURIComponent(branch)}/files/${encodeURIComponent(fileName)}/content`,\n );\n return Buffer.from(base64Content.body, 'base64').toString();\n }\n\n async moveChange(\n changeNumber: number,\n destinationBranch: string,\n ): Promise<GerritChange> {\n const change = await this.gerritHttp.postJson<GerritChange>(\n `a/changes/${changeNumber}/move`,\n {\n body: {\n destination_branch: destinationBranch,\n },\n },\n );\n return change.body;\n }\n\n normalizeMessage(message: string): string {\n // Gerrit would trim it anyway\n let msg = message.trim();\n\n const encoder = new TextEncoder();\n const bytes = encoder.encode(msg);\n if (bytes.length > MAX_GERRIT_COMMENT_SIZE) {\n const truncationNotice = '\\n\\n[Truncated by Renovate]';\n const truncationNoticeBytes = encoder.encode(truncationNotice);\n const maxContentBytes =\n MAX_GERRIT_COMMENT_SIZE - truncationNoticeBytes.length;\n const truncatedBytes = bytes.slice(0, maxContentBytes);\n const decoded = new TextDecoder().decode(truncatedBytes);\n msg = decoded + truncationNotice;\n }\n\n return msg;\n }\n\n private buildSearchFilters(\n repository: string,\n searchConfig: GerritFindPRConfig,\n ): string[] {\n const filters = [\n 'owner:self',\n `project:${repository}`,\n '-is:wip',\n '-is:private',\n ];\n const filterState = mapPrStateToGerritFilter(searchConfig.state);\n if (filterState) {\n filters.push(filterState);\n }\n if (searchConfig.branchName) {\n filters.push(`footer:Renovate-Branch=${searchConfig.branchName}`);\n } else if (semver.gte(this.gerritVersion, '3.6.0')) {\n filters.push('hasfooter:Renovate-Branch');\n } else {\n filters.push('message:\"Renovate-Branch: \"');\n }\n if (searchConfig.targetBranch) {\n filters.push(`branch:${searchConfig.targetBranch}`);\n }\n if (searchConfig.label) {\n filters.push(`label:Code-Review=${searchConfig.label}`);\n }\n if (searchConfig.prTitle) {\n // Quotes in the search operators must be escaped with a backslash:\n // https://gerrit-review.googlesource.com/Documentation/user-search.html#search-operators\n const escapedTitle = searchConfig.prTitle.replaceAll('\"', '\\\\\"');\n if (semver.gte(this.gerritVersion, '3.8.0')) {\n filters.push(`subject:\"${escapedTitle}\"`);\n } else {\n filters.push(`message:\"${escapedTitle}\"`);\n }\n }\n return filters;\n }\n}\n\nexport const client = new GerritClient();\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../../lib/modules/platform/gerrit/client.ts"],"sourcesContent":["import { isNonEmptyArray } from '@sindresorhus/is';\nimport semver from 'semver';\nimport { z } from 'zod/v4';\nimport { REPOSITORY_ARCHIVED } from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { GerritHttp } from '../../../util/http/gerrit.ts';\nimport type { HttpOptions } from '../../../util/http/types.ts';\nimport { getQueryString } from '../../../util/url.ts';\nimport type {\n GerritAccountInfo,\n GerritChangeMessageInfo,\n GerritReviewResult,\n} from './schema.ts';\nimport {\n GerritBranchInfo,\n GerritChange,\n GerritChangeMessages,\n GerritChanges,\n GerritMergeableInfo,\n GerritProjectInfo,\n GerritRepos,\n} from './schema.ts';\nimport type {\n GerritFindPRConfig,\n GerritHashtagsInput,\n GerritRequestDetail,\n} from './types.ts';\nimport {\n MAX_GERRIT_COMMENT_SIZE,\n MIN_GERRIT_VERSION,\n currentGerritTimestamp,\n mapPrStateToGerritFilter,\n} from './utils.ts';\n\nclass GerritClient {\n // memCache is disabled because GerritPrCache provides a smarter caching\n private gerritHttp = new GerritHttp({ memCache: false });\n private gerritVersion = MIN_GERRIT_VERSION;\n\n setGerritVersion(version: string): void {\n this.gerritVersion = version;\n }\n\n async getGerritVersion(\n options: Pick<HttpOptions, 'username' | 'password'>,\n ): Promise<string> {\n const res = await this.gerritHttp.getJson(\n 'a/config/server/version',\n options,\n z.string(),\n );\n return res.body;\n }\n\n async getRepos(): Promise<string[]> {\n const res = await this.gerritHttp.getJson(\n 'a/projects/?type=CODE&state=ACTIVE',\n GerritRepos,\n );\n return Object.keys(res.body);\n }\n\n async getProjectInfo(repository: string): Promise<GerritProjectInfo> {\n const projectInfo = await this.gerritHttp.getJson(\n `a/projects/${encodeURIComponent(repository)}`,\n GerritProjectInfo,\n );\n if (projectInfo.body.state !== 'ACTIVE') {\n throw new Error(REPOSITORY_ARCHIVED);\n }\n return projectInfo.body;\n }\n\n async getBranchInfo(repository: string): Promise<GerritBranchInfo> {\n const branchInfo = await this.gerritHttp.getJson(\n `a/projects/${encodeURIComponent(repository)}/branches/HEAD`,\n GerritBranchInfo,\n );\n return branchInfo.body;\n }\n\n async getBranchChange(\n repository: string,\n config: Pick<\n GerritFindPRConfig,\n 'branchName' | 'state' | 'targetBranch' | 'requestDetails'\n >,\n ): Promise<GerritChange | null> {\n const changes = await this.findChanges(repository, {\n branchName: config.branchName,\n state: config.state,\n singleChange: config.targetBranch ? false : true,\n requestDetails: config.requestDetails,\n });\n\n if (changes.length === 0) {\n return null;\n }\n\n if (changes.length === 1) {\n return changes[0];\n }\n\n // If multiple changes are found, prefer the one matching the target branch\n if (config.targetBranch) {\n const change = changes.find((c) => c.branch === config.targetBranch);\n if (change) {\n return change;\n }\n }\n\n // Otherwise return the first one\n return changes[0];\n }\n\n async findChanges(\n repository: string,\n findPRConfig: GerritFindPRConfig,\n ): Promise<GerritChange[]> {\n const pageLimit = findPRConfig.singleChange\n ? 1\n : (findPRConfig.pageLimit ?? 50);\n\n const query: Record<string, any> = {\n n: pageLimit,\n };\n if (findPRConfig.requestDetails) {\n query.o = findPRConfig.requestDetails;\n }\n\n const filters = this.buildSearchFilters(repository, findPRConfig);\n\n const allChanges: GerritChange[] = [];\n\n while (true) {\n query.S = allChanges.length;\n const queryString = `q=${filters.join('+')}&${getQueryString(query)}`;\n const changes = await this.gerritHttp.getJson(\n `a/changes/?${queryString}`,\n GerritChanges,\n );\n\n logger.trace(\n `findChanges(${queryString},start=${query.S},limit=${query.n}) => ${changes.body.length}`,\n );\n\n const lastChange = changes.body.at(-1);\n let hasMoreChanges = false;\n if (lastChange?._more_changes) {\n hasMoreChanges = true;\n delete lastChange._more_changes;\n }\n\n allChanges.push(...changes.body);\n\n // Honor predicate if it is provided\n if (findPRConfig.shouldFetchNextPage) {\n const shouldContinue = findPRConfig.shouldFetchNextPage(changes.body);\n if (!shouldContinue) {\n break;\n }\n }\n\n if (findPRConfig.singleChange || !hasMoreChanges) {\n break;\n }\n }\n\n return allChanges;\n }\n\n async getChange(\n changeNumber: number,\n requestDetails?: GerritRequestDetail[],\n ): Promise<GerritChange> {\n const queryString = getQueryString({ o: requestDetails });\n const changes = await this.gerritHttp.getJson(\n `a/changes/${changeNumber}?${queryString}`,\n GerritChange,\n );\n return changes.body;\n }\n\n async getMergeableInfo(change: GerritChange): Promise<GerritMergeableInfo> {\n const mergeable = await this.gerritHttp.getJson(\n `a/changes/${change._number}/revisions/current/mergeable`,\n GerritMergeableInfo,\n );\n return mergeable.body;\n }\n\n async abandonChange(\n changeNumber: number,\n message?: string,\n ): Promise<GerritChange> {\n const change = await this.gerritHttp.postJson<GerritChange>(\n `a/changes/${changeNumber}/abandon`,\n {\n body: {\n message,\n notify: 'OWNER_REVIEWERS', // Avoids notifying cc's\n },\n },\n );\n return change.body;\n }\n\n async submitChange(changeNumber: number): Promise<GerritChange> {\n const change = await this.gerritHttp.postJson<GerritChange>(\n `a/changes/${changeNumber}/submit`,\n );\n return change.body;\n }\n\n async getMessages(changeNumber: number): Promise<GerritChangeMessageInfo[]> {\n const messages = await this.gerritHttp.getJson(\n `a/changes/${changeNumber}/messages`,\n GerritChangeMessages,\n );\n return messages.body;\n }\n\n async addMessage(\n changeNumber: number,\n fullMessage: string,\n tag?: string,\n ): Promise<string> {\n const message = this.normalizeMessage(fullMessage);\n const url = `a/changes/${changeNumber}/revisions/current/review`;\n const response = await this.gerritHttp.postJson<GerritReviewResult>(url, {\n body: { message, tag, notify: 'NONE' },\n });\n // change_info is only present on Gerrit >= 3.10\n return response.body.change_info?.updated ?? currentGerritTimestamp();\n }\n\n async checkForExistingMessage(\n changeNumber: number,\n newMessage: string,\n msgType?: string,\n ): Promise<boolean> {\n const messagesToSearch = await this.getMessages(changeNumber);\n\n return messagesToSearch.some(\n (existingMsg) =>\n (msgType === undefined || msgType === existingMsg.tag) &&\n existingMsg.message.includes(newMessage),\n );\n }\n\n async addMessageIfNotAlreadyExists(\n changeNumber: number,\n message: string,\n tag?: string,\n ): Promise<void> {\n const newMsg = this.normalizeMessage(message);\n if (!(await this.checkForExistingMessage(changeNumber, newMsg, tag))) {\n await this.addMessage(changeNumber, newMsg, tag);\n }\n }\n\n async setLabel(\n changeNumber: number,\n label: string,\n value: number,\n ): Promise<void> {\n await this.gerritHttp.postJson(\n `a/changes/${changeNumber}/revisions/current/review`,\n { body: { labels: { [label]: value }, notify: 'NONE' } },\n );\n }\n\n async setHashtags(\n changeNumber: number,\n hashtagsInput: GerritHashtagsInput,\n ): Promise<void> {\n const { add, remove } = hashtagsInput;\n if (isNonEmptyArray(add) || isNonEmptyArray(remove)) {\n await this.gerritHttp.postJson(`a/changes/${changeNumber}/hashtags`, {\n body: { add, remove },\n });\n }\n }\n\n async addReviewers(changeNumber: number, reviewers: string[]): Promise<void> {\n await this.gerritHttp.postJson(\n `a/changes/${changeNumber}/revisions/current/review`,\n {\n body: {\n reviewers: reviewers.map((r) => ({ reviewer: r })),\n notify: 'OWNER_REVIEWERS', // Avoids notifying cc's\n },\n },\n );\n }\n\n async addAssignee(changeNumber: number, assignee: string): Promise<void> {\n await this.gerritHttp.putJson<GerritAccountInfo>(\n // TODO: use CCs instead as Gerrit 3.8+ no longer supports assignees\n `a/changes/${changeNumber}/assignee`,\n {\n body: { assignee },\n },\n );\n }\n\n async getFile(\n repo: string,\n branch: string,\n fileName: string,\n ): Promise<string> {\n const base64Content = await this.gerritHttp.getText(\n `a/projects/${encodeURIComponent(\n repo,\n )}/branches/${encodeURIComponent(branch)}/files/${encodeURIComponent(fileName)}/content`,\n );\n return Buffer.from(base64Content.body, 'base64').toString();\n }\n\n async moveChange(\n changeNumber: number,\n destinationBranch: string,\n ): Promise<GerritChange> {\n const change = await this.gerritHttp.postJson<GerritChange>(\n `a/changes/${changeNumber}/move`,\n {\n body: {\n destination_branch: destinationBranch,\n },\n },\n );\n return change.body;\n }\n\n normalizeMessage(message: string): string {\n // Gerrit would trim it anyway\n let msg = message.trim();\n\n const encoder = new TextEncoder();\n const bytes = encoder.encode(msg);\n if (bytes.length > MAX_GERRIT_COMMENT_SIZE) {\n const truncationNotice = '\\n\\n[Truncated by Renovate]';\n const truncationNoticeBytes = encoder.encode(truncationNotice);\n const maxContentBytes =\n MAX_GERRIT_COMMENT_SIZE - truncationNoticeBytes.length;\n const truncatedBytes = bytes.slice(0, maxContentBytes);\n const decoded = new TextDecoder().decode(truncatedBytes);\n msg = decoded + truncationNotice;\n }\n\n return msg;\n }\n\n private buildSearchFilters(\n repository: string,\n searchConfig: GerritFindPRConfig,\n ): string[] {\n const filters = [\n 'owner:self',\n `project:${repository}`,\n '-is:wip',\n '-is:private',\n ];\n const filterState = mapPrStateToGerritFilter(searchConfig.state);\n if (filterState) {\n filters.push(filterState);\n }\n if (searchConfig.branchName) {\n filters.push(`footer:Renovate-Branch=${searchConfig.branchName}`);\n } else if (semver.gte(this.gerritVersion, '3.6.0')) {\n filters.push('hasfooter:Renovate-Branch');\n } else {\n filters.push('message:\"Renovate-Branch: \"');\n }\n if (searchConfig.targetBranch) {\n filters.push(`branch:${searchConfig.targetBranch}`);\n }\n if (searchConfig.label) {\n filters.push(`label:Code-Review=${searchConfig.label}`);\n }\n if (searchConfig.prTitle) {\n // Quotes in the search operators must be escaped with a backslash:\n // https://gerrit-review.googlesource.com/Documentation/user-search.html#search-operators\n const escapedTitle = searchConfig.prTitle.replaceAll('\"', '\\\\\"');\n if (semver.gte(this.gerritVersion, '3.8.0')) {\n filters.push(`subject:\"${escapedTitle}\"`);\n } else {\n filters.push(`message:\"${escapedTitle}\"`);\n }\n }\n return filters;\n }\n}\n\nexport const client = new GerritClient();\n"],"mappings":"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"}
@@ -3,18 +3,21 @@ import { coerceObject } from "../../../util/object.js";
3
3
  import { getEnv } from "../../../util/env.js";
4
4
  import { regEx } from "../../../util/regex.js";
5
5
  import { logger } from "../../../logger/index.js";
6
+ import { clone } from "../../../util/clone.js";
6
7
  import { ensureTrailingSlash } from "../../../util/url.js";
7
8
  import { parseJson } from "../../../util/common.js";
8
9
  import { toLongCommitSha } from "../../../util/schema-utils/git.js";
9
10
  import { initRepo as initRepo$1, setVirtualBranch } from "../../../util/git/index.js";
10
11
  import { setBaseUrl } from "../../../util/http/gerrit.js";
11
- import { MAX_GERRIT_COMMENT_SIZE, REQUEST_DETAILS_FOR_PRS, TAG_PULL_REQUEST_BODY, extractSourceBranch, getGerritRepoUrl, mapBranchStatusToLabel, mapGerritChangeToPr } from "./utils.js";
12
+ import { hashBody } from "../pr-body.js";
13
+ import { MAX_GERRIT_COMMENT_SIZE, REQUEST_DETAILS_FOR_PRS, TAG_PULL_REQUEST_BODY, convertGerritDateToISO, extractSourceBranch, getGerritRepoUrl, mapBranchStatusToLabel, mapGerritChangeToPr, prStateMatchesFilter } from "./utils.js";
12
14
  import { client } from "./client.js";
13
15
  import { configureScm, pushForReview } from "./scm.js";
14
16
  import { repoFingerprint } from "../util.js";
15
17
  import { smartTruncate } from "../utils/pr-body.js";
16
18
  import readOnlyIssueBody from "../utils/read-only-issue-body.js";
17
- import { isTruthy, isUndefined } from "@sindresorhus/is";
19
+ import { GerritPrCache } from "./pr-cache.js";
20
+ import { isUndefined } from "@sindresorhus/is";
18
21
  import semver from "semver";
19
22
  //#region lib/modules/platform/gerrit/index.ts
20
23
  var gerrit_exports = /* @__PURE__ */ __exportAll({
@@ -148,31 +151,71 @@ async function initRepo({ repository, cloneSubmodules, cloneSubmodulesFilter, gi
148
151
  };
149
152
  }
150
153
  async function findPr(findPRConfig) {
151
- const change = (await client.findChanges(config.repository, {
152
- ...findPRConfig,
153
- singleChange: true,
154
- requestDetails: REQUEST_DETAILS_FOR_PRS
155
- })).pop();
156
- return change ? mapGerritChangeToPr(change, { sourceBranch: findPRConfig.branchName }) : null;
154
+ const cached = (await GerritPrCache.getPrs(config.repository)).find((pr) => {
155
+ if (findPRConfig.branchName && pr.sourceBranch !== findPRConfig.branchName) return false;
156
+ if (findPRConfig.targetBranch && pr.targetBranch !== findPRConfig.targetBranch) return false;
157
+ if (findPRConfig.prTitle && pr.title !== findPRConfig.prTitle) return false;
158
+ if (!prStateMatchesFilter(pr.state, findPRConfig.state)) return false;
159
+ return true;
160
+ });
161
+ logger.trace(`findPr: using cached gerrit change ${cached?.number} for ${findPRConfig.branchName}`);
162
+ return cached ?? null;
157
163
  }
158
164
  async function getPr(number) {
159
- try {
160
- const change = await client.getChange(number, REQUEST_DETAILS_FOR_PRS);
161
- return mapGerritChangeToPr(change);
162
- } catch (err) {
163
- if (err.statusCode === 404) return null;
164
- throw err;
165
+ let pr = (await GerritPrCache.getPrs(config.repository)).find((p) => p.number === number) ?? null;
166
+ if (pr) logger.trace(`getPr: using cached gerrit change ${pr.sourceBranch} for ${number}`);
167
+ else {
168
+ logger.debug({ number }, "PR not found in cached PRs - trying to fetch directly");
169
+ try {
170
+ const change = await client.getChange(number, REQUEST_DETAILS_FOR_PRS);
171
+ pr = mapGerritChangeToPr(change);
172
+ if (pr) await GerritPrCache.setPr(config.repository, pr);
173
+ } catch (err) {
174
+ if (err.statusCode === 404) {
175
+ logger.debug({ number }, "Change not found in Gerrit");
176
+ return null;
177
+ }
178
+ throw err;
179
+ }
165
180
  }
181
+ return pr;
166
182
  }
167
183
  async function updatePr(prConfig) {
168
184
  logger.debug(`updatePr(${prConfig.number}, ${prConfig.prTitle})`);
169
- if (prConfig.prBody) await client.addMessageIfNotAlreadyExists(prConfig.number, prConfig.prBody, TAG_PULL_REQUEST_BODY);
185
+ const cached = await getPr(prConfig.number);
186
+ if (!cached) {
187
+ logger.warn({ number: prConfig.number }, "updatePr: change not found in cache or Gerrit");
188
+ return;
189
+ }
190
+ const pr = clone(cached);
191
+ let updated = false;
192
+ const newBodyHash = prConfig.prBody ? hashBody(prConfig.prBody) : void 0;
193
+ if (newBodyHash && newBodyHash !== pr.bodyStruct?.hash) {
194
+ const updatedDate = await client.addMessage(prConfig.number, prConfig.prBody, TAG_PULL_REQUEST_BODY);
195
+ pr.bodyStruct = { hash: newBodyHash };
196
+ pr.updatedAt = convertGerritDateToISO(updatedDate);
197
+ updated = true;
198
+ }
170
199
  await client.setHashtags(prConfig.number, {
171
200
  add: prConfig.addLabels,
172
201
  remove: prConfig.removeLabels
173
202
  });
174
- if (prConfig.targetBranch) await client.moveChange(prConfig.number, prConfig.targetBranch);
175
- if (prConfig.state && prConfig.state === "closed") await client.abandonChange(prConfig.number);
203
+ if (prConfig.targetBranch) {
204
+ const change = await client.moveChange(prConfig.number, prConfig.targetBranch);
205
+ pr.targetBranch = change.branch;
206
+ pr.updatedAt = convertGerritDateToISO(change.updated);
207
+ updated = true;
208
+ }
209
+ if (prConfig.state === "closed") {
210
+ const change = await client.abandonChange(prConfig.number);
211
+ pr.state = "closed";
212
+ pr.updatedAt = convertGerritDateToISO(change.updated);
213
+ updated = true;
214
+ }
215
+ if (updated) {
216
+ logger.debug(`updatePr: updating gerrit change ${prConfig.number} in cache`);
217
+ await GerritPrCache.setPr(config.repository, pr);
218
+ }
176
219
  }
177
220
  async function createPr(prConfig) {
178
221
  logger.debug(`createPr(${prConfig.sourceBranch}, ${prConfig.prTitle}, ${prConfig.labels?.toString() ?? ""})`);
@@ -195,31 +238,42 @@ async function createPr(prConfig) {
195
238
  const sha = toLongCommitSha(change.current_revision);
196
239
  const ref = change.revisions[sha].ref;
197
240
  await setVirtualBranch(prConfig.sourceBranch, ref, sha);
198
- await client.addMessage(change._number, prConfig.prBody, TAG_PULL_REQUEST_BODY);
199
- return mapGerritChangeToPr(change, {
241
+ const updatedDate = await client.addMessage(change._number, prConfig.prBody, TAG_PULL_REQUEST_BODY);
242
+ const pr = mapGerritChangeToPr(change, {
200
243
  sourceBranch: prConfig.sourceBranch,
201
244
  prBody: prConfig.prBody
202
245
  });
246
+ pr.updatedAt = convertGerritDateToISO(updatedDate);
247
+ logger.debug(`createPr: saving gerrit change ${pr.number} to cache`);
248
+ await GerritPrCache.setPr(config.repository, pr);
249
+ return pr;
203
250
  }
204
251
  async function getBranchPr(branchName, targetBranch) {
205
- const change = await client.getBranchChange(config.repository, {
206
- branchName,
207
- state: "open",
208
- targetBranch,
209
- requestDetails: REQUEST_DETAILS_FOR_PRS
252
+ const cachedPrs = (await GerritPrCache.getPrs(config.repository)).filter((pr) => {
253
+ if (pr.sourceBranch !== branchName) return false;
254
+ if (pr.state !== "open") return false;
255
+ return true;
210
256
  });
211
- return change ? mapGerritChangeToPr(change, { sourceBranch: branchName }) : null;
257
+ let result;
258
+ if (targetBranch) {
259
+ const found = cachedPrs.find((pr) => pr.targetBranch === targetBranch);
260
+ if (found) result = found;
261
+ }
262
+ result ??= cachedPrs[0];
263
+ logger.trace(`getBranchPr: using cached gerrit change ${result?.number} for ${branchName}`);
264
+ return result ?? null;
212
265
  }
213
266
  async function getPrList() {
214
- return (await client.findChanges(config.repository, {
215
- branchName: "",
216
- requestDetails: REQUEST_DETAILS_FOR_PRS
217
- })).map((change) => mapGerritChangeToPr(change)).filter(isTruthy);
267
+ const cached = await GerritPrCache.getPrs(config.repository);
268
+ logger.debug(`getPrList: using ${cached.length} cached changes`);
269
+ return cached;
218
270
  }
219
- async function mergePr(config) {
220
- logger.debug(`mergePr(${config.id}, ${config.branchName}, ${config.strategy})`);
271
+ async function mergePr(mergeConfig) {
272
+ logger.debug(`mergePr(${mergeConfig.id}, ${mergeConfig.branchName}, ${mergeConfig.strategy})`);
273
+ let change;
221
274
  try {
222
- return (await client.submitChange(config.id)).status === "MERGED";
275
+ change = await client.submitChange(mergeConfig.id);
276
+ if (change.status !== "MERGED") return false;
223
277
  } catch (err) {
224
278
  if (err.statusCode === 409) {
225
279
  logger.warn({ err }, "Can't submit the change, because the submit rule doesn't allow it.");
@@ -227,6 +281,15 @@ async function mergePr(config) {
227
281
  }
228
282
  throw err;
229
283
  }
284
+ const cached = await getPr(mergeConfig.id);
285
+ if (cached) {
286
+ const pr = clone(cached);
287
+ pr.state = "merged";
288
+ pr.updatedAt = convertGerritDateToISO(change.updated);
289
+ logger.debug(`mergePr: updating gerrit change ${mergeConfig.id} in cache`);
290
+ await GerritPrCache.setPr(config.repository, pr);
291
+ } else logger.warn({ number: mergeConfig.id }, "mergePr: merged change but could not update PR cache");
292
+ return true;
230
293
  }
231
294
  /**
232
295
  * BranchStatus for Gerrit assumes that the branchName refers to a change.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["git.initRepo","git.setVirtualBranch"],"sources":["../../../../lib/modules/platform/gerrit/index.ts"],"sourcesContent":["import { isTruthy, isUndefined } from '@sindresorhus/is';\nimport semver from 'semver';\nimport { logger } from '../../../logger/index.ts';\nimport type { BranchStatus } from '../../../types/index.ts';\nimport { parseJson } from '../../../util/common.ts';\nimport { getEnv } from '../../../util/env.ts';\nimport * as git from '../../../util/git/index.ts';\nimport type { VirtualBranch } from '../../../util/git/types.ts';\nimport { setBaseUrl } from '../../../util/http/gerrit.ts';\nimport { coerceObject } from '../../../util/object.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { toLongCommitSha } from '../../../util/schema-utils/git.ts';\nimport { ensureTrailingSlash } from '../../../util/url.ts';\nimport type {\n BranchStatusConfig,\n CreatePRConfig,\n EnsureCommentConfig,\n EnsureCommentRemovalConfigByContent,\n EnsureCommentRemovalConfigByTopic,\n EnsureIssueConfig,\n EnsureIssueResult,\n FindPRConfig,\n Issue,\n MergePRConfig,\n PlatformParams,\n PlatformResult,\n Pr,\n RepoParams,\n RepoResult,\n UpdatePrConfig,\n} from '../types.ts';\nimport { repoFingerprint } from '../util.ts';\nimport { smartTruncate } from '../utils/pr-body.ts';\nimport { readOnlyIssueBody } from '../utils/read-only-issue-body.ts';\nimport { client } from './client.ts';\nimport type { GerritLabels, GerritProjectInfo } from './schema.ts';\nimport { configureScm, pushForReview } from './scm.ts';\nimport {\n MAX_GERRIT_COMMENT_SIZE,\n REQUEST_DETAILS_FOR_PRS,\n TAG_PULL_REQUEST_BODY,\n extractSourceBranch,\n getGerritRepoUrl,\n mapBranchStatusToLabel,\n mapGerritChangeToPr,\n} from './utils.ts';\n\nexport const id = 'gerrit';\nexport const experimental = true;\n\nconst defaults: {\n endpoint?: string;\n} = {};\n\nlet config: {\n repository?: string;\n head?: string;\n config?: GerritProjectInfo;\n labels: GerritLabels;\n gerritUsername?: string;\n} = {\n labels: {},\n};\n\nexport function writeToConfig(newConfig: typeof config): void {\n config = { ...config, ...newConfig };\n}\n\nexport async function initPlatform({\n endpoint,\n username,\n password,\n}: PlatformParams): Promise<PlatformResult> {\n logger.debug(`initPlatform(${endpoint!}, ${username!})`);\n if (!endpoint) {\n throw new Error('Init: You must configure a Gerrit Server endpoint');\n }\n if (!(username && password)) {\n throw new Error(\n 'Init: You must configure a Gerrit Server username/password',\n );\n }\n config.gerritUsername = username;\n defaults.endpoint = ensureTrailingSlash(endpoint);\n setBaseUrl(defaults.endpoint);\n\n let gerritVersion: string;\n try {\n const env = getEnv();\n /* v8 ignore if: experimental feature */\n if (env.RENOVATE_X_PLATFORM_VERSION) {\n gerritVersion = env.RENOVATE_X_PLATFORM_VERSION;\n } else {\n gerritVersion = await client.getGerritVersion({\n username,\n password,\n });\n }\n } catch (err) {\n logger.debug(\n { err },\n 'Error authenticating with Gerrit. Check your credentials',\n );\n throw new Error('Init: Authentication failure');\n }\n\n logger.debug(`Gerrit version is: ${gerritVersion}`);\n // Example: 3.13.0-rc3-148-gb478dbbb57\n const parsed = semver.parse(gerritVersion);\n if (!parsed) {\n throw new Error(`Unable to parse Gerrit version: ${gerritVersion}`);\n }\n gerritVersion = `${parsed.major}.${parsed.minor}.${parsed.patch}`;\n client.setGerritVersion(gerritVersion);\n\n const platformConfig: PlatformResult = {\n endpoint: defaults.endpoint,\n };\n return platformConfig;\n}\n\n/**\n * Get all state=\"ACTIVE\" and type=\"CODE\" repositories from gerrit\n */\nexport async function getRepos(): Promise<string[]> {\n logger.debug(`getRepos()`);\n return await client.getRepos();\n}\n\n/**\n * Clone repository to local directory\n * @param config\n */\nexport async function initRepo({\n repository,\n cloneSubmodules,\n cloneSubmodulesFilter,\n gitUrl,\n}: RepoParams): Promise<RepoResult> {\n logger.debug(`initRepo(${repository}, ${gitUrl})`);\n const projectInfo = await client.getProjectInfo(repository);\n const branchInfo = await client.getBranchInfo(repository);\n\n config = {\n ...config,\n repository,\n head: branchInfo.revision,\n config: projectInfo,\n labels: coerceObject(projectInfo.labels),\n };\n\n //abandon \"open\" and \"rejected\" changes at startup\n const rejectedChanges = await client.findChanges(repository, {\n branchName: '',\n state: 'open',\n label: '-2',\n });\n for (const change of rejectedChanges) {\n await client.abandonChange(\n change._number,\n 'This change has been abandoned as it was voted with Code-Review -2.',\n );\n logger.info(\n `Abandoned change ${change._number} with Code-Review -2 in repository ${repository}`,\n );\n }\n\n // Collect open Gerrit changes to initialize as virtual branches.\n // This allows DefaultGitScm to treat them as regular Git branches.\n const openChanges = await client.findChanges(repository, {\n branchName: '',\n state: 'open',\n requestDetails: ['CURRENT_REVISION', 'COMMIT_FOOTERS'],\n });\n const virtualBranches: Record<string, VirtualBranch> = {};\n for (const change of openChanges) {\n const branchName = extractSourceBranch(change);\n if (!branchName) {\n continue;\n }\n const sha = toLongCommitSha(change.current_revision);\n const ref = change.revisions![sha].ref;\n virtualBranches[branchName] = { ref, sha };\n }\n\n const virtualBranchCount = Object.keys(virtualBranches).length;\n if (virtualBranchCount > 0) {\n logger.debug(\n `Will fetch ${virtualBranchCount} Gerrit changes as virtual branches`,\n );\n }\n\n const baseUrl = defaults.endpoint!;\n const url = getGerritRepoUrl(\n repository,\n baseUrl,\n gitUrl,\n config.gerritUsername!,\n );\n configureScm(repository, config.labels);\n await git.initRepo({\n url,\n cloneSubmodules,\n cloneSubmodulesFilter,\n virtualBranches,\n });\n\n const repoConfig: RepoResult = {\n defaultBranch: config.head!,\n isFork: false,\n repoFingerprint: repoFingerprint(repository, baseUrl),\n };\n return repoConfig;\n}\n\nexport async function findPr(findPRConfig: FindPRConfig): Promise<Pr | null> {\n const change = (\n await client.findChanges(config.repository!, {\n ...findPRConfig,\n singleChange: true,\n requestDetails: REQUEST_DETAILS_FOR_PRS,\n })\n ).pop();\n return change\n ? mapGerritChangeToPr(change, {\n sourceBranch: findPRConfig.branchName,\n })\n : null;\n}\n\nexport async function getPr(number: number): Promise<Pr | null> {\n try {\n const change = await client.getChange(number, REQUEST_DETAILS_FOR_PRS);\n return mapGerritChangeToPr(change);\n } catch (err) {\n if (err.statusCode === 404) {\n return null;\n }\n throw err;\n }\n}\n\nexport async function updatePr(prConfig: UpdatePrConfig): Promise<void> {\n logger.debug(`updatePr(${prConfig.number}, ${prConfig.prTitle})`);\n if (prConfig.prBody) {\n await client.addMessageIfNotAlreadyExists(\n prConfig.number,\n prConfig.prBody,\n TAG_PULL_REQUEST_BODY,\n );\n }\n await client.setHashtags(prConfig.number, {\n add: prConfig.addLabels,\n remove: prConfig.removeLabels,\n });\n if (prConfig.targetBranch) {\n await client.moveChange(prConfig.number, prConfig.targetBranch);\n }\n if (prConfig.state && prConfig.state === 'closed') {\n await client.abandonChange(prConfig.number);\n }\n}\n\nexport async function createPr(prConfig: CreatePRConfig): Promise<Pr | null> {\n logger.debug(\n `createPr(${prConfig.sourceBranch}, ${prConfig.prTitle}, ${\n prConfig.labels?.toString() ?? ''\n })`,\n );\n\n logger.debug(\n `Pushing commit to refs/for/${prConfig.targetBranch} to create Gerrit change`,\n );\n const pushResult = await pushForReview({\n sourceRef: prConfig.sourceBranch,\n targetBranch: prConfig.targetBranch,\n files: [],\n autoApprove: prConfig.platformPrOptions?.autoApprove,\n labels: prConfig.labels ?? undefined,\n });\n\n if (!pushResult) {\n throw new Error(\n `Failed to push commit to refs/for/${prConfig.targetBranch} to create Gerrit change`,\n );\n }\n\n const change = (\n await client.findChanges(config.repository!, {\n branchName: prConfig.sourceBranch,\n targetBranch: prConfig.targetBranch,\n state: 'open',\n singleChange: true,\n requestDetails: REQUEST_DETAILS_FOR_PRS,\n })\n ).pop();\n if (isUndefined(change)) {\n throw new Error(\n `Could not find the Gerrit change after pushing to refs/for/${prConfig.targetBranch}`,\n );\n }\n\n // Register the virtual branch now that the change exists and we have the ref.\n const sha = toLongCommitSha(change.current_revision);\n const ref = change.revisions![sha].ref;\n await git.setVirtualBranch(prConfig.sourceBranch, ref, sha);\n\n await client.addMessage(\n change._number,\n prConfig.prBody,\n TAG_PULL_REQUEST_BODY,\n );\n return mapGerritChangeToPr(change, {\n sourceBranch: prConfig.sourceBranch,\n prBody: prConfig.prBody,\n });\n}\n\nexport async function getBranchPr(\n branchName: string,\n targetBranch?: string,\n): Promise<Pr | null> {\n const change = await client.getBranchChange(config.repository!, {\n branchName,\n state: 'open',\n targetBranch,\n requestDetails: REQUEST_DETAILS_FOR_PRS,\n });\n\n return change\n ? mapGerritChangeToPr(change, {\n sourceBranch: branchName,\n })\n : null;\n}\n\nexport async function getPrList(): Promise<Pr[]> {\n const changes = await client.findChanges(config.repository!, {\n branchName: '',\n requestDetails: REQUEST_DETAILS_FOR_PRS,\n });\n return changes.map((change) => mapGerritChangeToPr(change)).filter(isTruthy);\n}\n\nexport async function mergePr(config: MergePRConfig): Promise<boolean> {\n logger.debug(\n `mergePr(${config.id}, ${config.branchName!}, ${config.strategy!})`,\n );\n try {\n const change = await client.submitChange(config.id);\n return change.status === 'MERGED';\n } catch (err) {\n if (err.statusCode === 409) {\n logger.warn(\n { err },\n \"Can't submit the change, because the submit rule doesn't allow it.\",\n );\n return false;\n }\n throw err;\n }\n}\n\n/**\n * BranchStatus for Gerrit assumes that the branchName refers to a change.\n * @param branchName\n */\nexport async function getBranchStatus(\n branchName: string,\n): Promise<BranchStatus> {\n logger.debug(`getBranchStatus(${branchName})`);\n const change = (\n await client.findChanges(config.repository!, {\n state: 'open',\n branchName,\n singleChange: true,\n requestDetails: ['LABELS', 'SUBMITTABLE', 'CHECK'],\n })\n ).pop();\n if (change) {\n const hasProblems = change.problems && change.problems.length > 0;\n if (hasProblems) {\n return 'red';\n }\n const hasBlockingLabels = Object.values(coerceObject(change.labels)).some(\n (label) => label.blocking,\n );\n if (hasBlockingLabels) {\n return 'red';\n }\n // v8 ignore else -- TODO: add test #40625\n if (change.submittable) {\n return 'green';\n }\n }\n return 'yellow';\n}\n\n/**\n * check the gerrit-change for the presence of the corresponding \"$context\" Gerrit label if configured,\n * return 'yellow' if not configured or not set\n * @param branchName\n * @param context renovate/stability-days || ...\n */\nexport async function getBranchStatusCheck(\n branchName: string,\n context: string,\n): Promise<BranchStatus | null> {\n const labelConfig = config.labels[context];\n if (labelConfig) {\n const change = (\n await client.findChanges(config.repository!, {\n branchName,\n state: 'open',\n singleChange: true,\n requestDetails: ['LABELS'],\n })\n ).pop();\n // v8 ignore else -- TODO: add test #40625\n if (change) {\n const label = change.labels![context];\n // v8 ignore else -- TODO: add test #40625\n if (label) {\n // Check for rejected or blocking first, as a label could have both rejected and approved\n if (label.rejected || label.blocking) {\n return 'red';\n }\n // v8 ignore else -- TODO: add test #40625\n if (label.approved) {\n return 'green';\n }\n }\n }\n }\n return 'yellow';\n}\n\n/**\n * Apply the branch state $context to the corresponding gerrit label (if available)\n * context === \"renovate/stability-days\" / \"renovate/merge-confidence\" and state === \"green\"/...\n * @param branchStatusConfig\n */\nexport async function setBranchStatus(\n branchStatusConfig: BranchStatusConfig,\n): Promise<void> {\n const label = config.labels[branchStatusConfig.context];\n const labelValue =\n label && mapBranchStatusToLabel(branchStatusConfig.state, label);\n if (branchStatusConfig.context && labelValue) {\n const change = (\n await client.findChanges(config.repository!, {\n branchName: branchStatusConfig.branchName,\n state: 'open',\n singleChange: true,\n requestDetails: ['LABELS'],\n })\n ).pop();\n\n const labelKey = branchStatusConfig.context;\n if (!change?.labels || !Object.hasOwn(change.labels, labelKey)) {\n return;\n }\n\n await client.setLabel(change._number, labelKey, labelValue);\n }\n}\n\nexport async function getRawFile(\n fileName: string,\n repoName?: string,\n branchOrTag?: string,\n): Promise<string | null> {\n const repo = repoName ?? config.repository;\n if (!repo) {\n logger.debug('No repo so cannot getRawFile');\n return null;\n }\n // v8 ignore next -- TODO: add test #40625\n const branch =\n branchOrTag ??\n (repo === config.repository ? (config.head ?? 'HEAD') : 'HEAD');\n const result = await client.getFile(repo, branch, fileName);\n return result;\n}\n\nexport async function getJsonFile(\n fileName: string,\n repoName?: string,\n branchOrTag?: string,\n): Promise<any> {\n const raw = await getRawFile(fileName, repoName, branchOrTag);\n return parseJson(raw, fileName);\n}\n\nexport async function addReviewers(\n number: number,\n reviewers: string[],\n): Promise<void> {\n await client.addReviewers(number, reviewers);\n}\n\n/**\n * add \"CC\" (only one possible)\n */\nexport async function addAssignees(\n number: number,\n assignees: string[],\n): Promise<void> {\n // v8 ignore else -- TODO: add test #40625\n if (assignees.length) {\n // v8 ignore else -- TODO: add test #40625\n if (assignees.length > 1) {\n logger.debug(\n `addAssignees(${number}, ${assignees.toString()}) called with more then one assignee! Gerrit only supports one assignee! Using the first from list.`,\n );\n }\n await client.addAssignee(number, assignees[0]);\n }\n}\n\nexport async function ensureComment(\n ensureComment: EnsureCommentConfig,\n): Promise<boolean> {\n logger.debug(\n `ensureComment(${ensureComment.number}, ${ensureComment.topic!}, ${\n ensureComment.content\n })`,\n );\n await client.addMessageIfNotAlreadyExists(\n ensureComment.number,\n ensureComment.content,\n ensureComment.topic ?? undefined,\n );\n return true;\n}\n\nexport function massageMarkdown(prBody: string, rebaseLabel: string): string {\n return (\n smartTruncate(readOnlyIssueBody(prBody), maxBodyLength())\n .replace('Branch creation', 'Change creation')\n .replace(\n 'close this Pull Request unmerged',\n 'abandon or vote this change with Code-Review -2',\n )\n .replace(\n 'Close this PR',\n 'Abandon or vote this change with Code-Review -2',\n )\n .replace(\n 'you tick the rebase/retry checkbox',\n `you add the _${rebaseLabel}_ hashtag to this change`,\n )\n .replace(\n 'checking the rebase/retry box above',\n `adding the _${rebaseLabel}_ hashtag to this change`,\n )\n .replace(regEx(/\\b(?:Pull Request|PR)/g), 'change')\n // Remove HTML tags not supported in Gerrit markdown\n .replace(regEx(/<\\/?summary>/g), '**')\n .replace(regEx(/<\\/?(?:details|blockquote)>/g), '')\n .replace(regEx(`\\n---\\n\\n.*?<!-- rebase-check -->.*?\\n`), '')\n .replace(regEx(/<!--renovate-(?:debug|config-hash):.*?-->/g), '')\n // Remove zero-width-space not supported in Gerrit markdown\n .replace(regEx(/&#8203;/g), '')\n );\n}\n\nexport function maxBodyLength(): number {\n return MAX_GERRIT_COMMENT_SIZE;\n}\n\nexport async function deleteLabel(\n number: number,\n label: string,\n): Promise<void> {\n await client.setHashtags(number, { remove: [label] });\n}\n\nexport function ensureCommentRemoval(\n _ensureCommentRemoval:\n EnsureCommentRemovalConfigByTopic | EnsureCommentRemovalConfigByContent,\n): Promise<void> {\n return Promise.resolve();\n}\n\nexport function ensureIssueClosing(_title: string): Promise<void> {\n return Promise.resolve();\n}\n\nexport function ensureIssue(\n _issueConfig: EnsureIssueConfig,\n): Promise<EnsureIssueResult | null> {\n return Promise.resolve(null);\n}\n\nexport function findIssue(_title: string): Promise<Issue | null> {\n return Promise.resolve(null);\n}\n\nexport function getIssueList(): Promise<Issue[]> {\n return Promise.resolve([]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["git.initRepo","git.setVirtualBranch"],"sources":["../../../../lib/modules/platform/gerrit/index.ts"],"sourcesContent":["import { isUndefined } from '@sindresorhus/is';\nimport semver from 'semver';\nimport { logger } from '../../../logger/index.ts';\nimport type { BranchStatus } from '../../../types/index.ts';\nimport { clone } from '../../../util/clone.ts';\nimport { parseJson } from '../../../util/common.ts';\nimport { getEnv } from '../../../util/env.ts';\nimport * as git from '../../../util/git/index.ts';\nimport type { VirtualBranch } from '../../../util/git/types.ts';\nimport { setBaseUrl } from '../../../util/http/gerrit.ts';\nimport { coerceObject } from '../../../util/object.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { toLongCommitSha } from '../../../util/schema-utils/git.ts';\nimport { ensureTrailingSlash } from '../../../util/url.ts';\nimport { hashBody } from '../pr-body.ts';\nimport type {\n BranchStatusConfig,\n CreatePRConfig,\n EnsureCommentConfig,\n EnsureCommentRemovalConfigByContent,\n EnsureCommentRemovalConfigByTopic,\n EnsureIssueConfig,\n EnsureIssueResult,\n FindPRConfig,\n Issue,\n MergePRConfig,\n PlatformParams,\n PlatformResult,\n Pr,\n RepoParams,\n RepoResult,\n UpdatePrConfig,\n} from '../types.ts';\nimport { repoFingerprint } from '../util.ts';\nimport { smartTruncate } from '../utils/pr-body.ts';\nimport { readOnlyIssueBody } from '../utils/read-only-issue-body.ts';\nimport { client } from './client.ts';\nimport { GerritPrCache } from './pr-cache.ts';\nimport type {\n GerritChange,\n GerritLabels,\n GerritProjectInfo,\n} from './schema.ts';\nimport { configureScm, pushForReview } from './scm.ts';\nimport {\n MAX_GERRIT_COMMENT_SIZE,\n REQUEST_DETAILS_FOR_PRS,\n TAG_PULL_REQUEST_BODY,\n convertGerritDateToISO,\n extractSourceBranch,\n getGerritRepoUrl,\n mapBranchStatusToLabel,\n mapGerritChangeToPr,\n prStateMatchesFilter,\n} from './utils.ts';\n\nexport const id = 'gerrit';\nexport const experimental = true;\n\nconst defaults: {\n endpoint?: string;\n} = {};\n\nlet config: {\n repository?: string;\n head?: string;\n config?: GerritProjectInfo;\n labels: GerritLabels;\n gerritUsername?: string;\n} = {\n labels: {},\n};\n\nexport function writeToConfig(newConfig: typeof config): void {\n config = { ...config, ...newConfig };\n}\n\nexport async function initPlatform({\n endpoint,\n username,\n password,\n}: PlatformParams): Promise<PlatformResult> {\n logger.debug(`initPlatform(${endpoint!}, ${username!})`);\n if (!endpoint) {\n throw new Error('Init: You must configure a Gerrit Server endpoint');\n }\n if (!(username && password)) {\n throw new Error(\n 'Init: You must configure a Gerrit Server username/password',\n );\n }\n config.gerritUsername = username;\n defaults.endpoint = ensureTrailingSlash(endpoint);\n setBaseUrl(defaults.endpoint);\n\n let gerritVersion: string;\n try {\n const env = getEnv();\n /* v8 ignore if: experimental feature */\n if (env.RENOVATE_X_PLATFORM_VERSION) {\n gerritVersion = env.RENOVATE_X_PLATFORM_VERSION;\n } else {\n gerritVersion = await client.getGerritVersion({\n username,\n password,\n });\n }\n } catch (err) {\n logger.debug(\n { err },\n 'Error authenticating with Gerrit. Check your credentials',\n );\n throw new Error('Init: Authentication failure');\n }\n\n logger.debug(`Gerrit version is: ${gerritVersion}`);\n // Example: 3.13.0-rc3-148-gb478dbbb57\n const parsed = semver.parse(gerritVersion);\n if (!parsed) {\n throw new Error(`Unable to parse Gerrit version: ${gerritVersion}`);\n }\n gerritVersion = `${parsed.major}.${parsed.minor}.${parsed.patch}`;\n client.setGerritVersion(gerritVersion);\n\n const platformConfig: PlatformResult = {\n endpoint: defaults.endpoint,\n };\n return platformConfig;\n}\n\n/**\n * Get all state=\"ACTIVE\" and type=\"CODE\" repositories from gerrit\n */\nexport async function getRepos(): Promise<string[]> {\n logger.debug(`getRepos()`);\n return await client.getRepos();\n}\n\n/**\n * Clone repository to local directory\n * @param config\n */\nexport async function initRepo({\n repository,\n cloneSubmodules,\n cloneSubmodulesFilter,\n gitUrl,\n}: RepoParams): Promise<RepoResult> {\n logger.debug(`initRepo(${repository}, ${gitUrl})`);\n const projectInfo = await client.getProjectInfo(repository);\n const branchInfo = await client.getBranchInfo(repository);\n\n config = {\n ...config,\n repository,\n head: branchInfo.revision,\n config: projectInfo,\n labels: coerceObject(projectInfo.labels),\n };\n\n // Abandon all changes voted with Code-Review -2\n // The GerritPrCache cannot be used here otherwise initializeCaches(), which is not called yet, would erase it later\n const rejectedChanges = await client.findChanges(repository, {\n branchName: '',\n state: 'open',\n label: '-2',\n });\n for (const change of rejectedChanges) {\n await client.abandonChange(\n change._number,\n 'This change has been abandoned as it was voted with Code-Review -2.',\n );\n logger.info(\n `Abandoned change ${change._number} with Code-Review -2 in repository ${repository}`,\n );\n }\n\n // Collect open Gerrit changes to initialize as virtual branches.\n // This allows DefaultGitScm to treat them as regular Git branches.\n const openChanges = await client.findChanges(repository, {\n branchName: '',\n state: 'open',\n requestDetails: ['CURRENT_REVISION', 'COMMIT_FOOTERS'],\n });\n const virtualBranches: Record<string, VirtualBranch> = {};\n for (const change of openChanges) {\n const branchName = extractSourceBranch(change);\n if (!branchName) {\n continue;\n }\n const sha = toLongCommitSha(change.current_revision);\n const ref = change.revisions![sha].ref;\n virtualBranches[branchName] = { ref, sha };\n }\n\n const virtualBranchCount = Object.keys(virtualBranches).length;\n if (virtualBranchCount > 0) {\n logger.debug(\n `Will fetch ${virtualBranchCount} Gerrit changes as virtual branches`,\n );\n }\n\n const baseUrl = defaults.endpoint!;\n const url = getGerritRepoUrl(\n repository,\n baseUrl,\n gitUrl,\n config.gerritUsername!,\n );\n configureScm(repository, config.labels);\n await git.initRepo({\n url,\n cloneSubmodules,\n cloneSubmodulesFilter,\n virtualBranches,\n });\n\n const repoConfig: RepoResult = {\n defaultBranch: config.head!,\n isFork: false,\n repoFingerprint: repoFingerprint(repository, baseUrl),\n };\n return repoConfig;\n}\n\nexport async function findPr(findPRConfig: FindPRConfig): Promise<Pr | null> {\n const prs = await GerritPrCache.getPrs(config.repository!);\n // Find matching PR from cache\n const cached = prs.find((pr) => {\n if (\n findPRConfig.branchName &&\n pr.sourceBranch !== findPRConfig.branchName\n ) {\n return false;\n }\n if (\n findPRConfig.targetBranch &&\n pr.targetBranch !== findPRConfig.targetBranch\n ) {\n return false;\n }\n if (findPRConfig.prTitle && pr.title !== findPRConfig.prTitle) {\n return false;\n }\n if (!prStateMatchesFilter(pr.state, findPRConfig.state)) {\n return false;\n }\n return true;\n });\n logger.trace(\n `findPr: using cached gerrit change ${cached?.number} for ${findPRConfig.branchName}`,\n );\n return cached ?? null;\n}\n\nexport async function getPr(number: number): Promise<Pr | null> {\n const prs = await GerritPrCache.getPrs(config.repository!);\n let pr = prs.find((p) => p.number === number) ?? null;\n if (pr) {\n logger.trace(\n `getPr: using cached gerrit change ${pr.sourceBranch} for ${number}`,\n );\n } else {\n logger.debug(\n { number },\n 'PR not found in cached PRs - trying to fetch directly',\n );\n try {\n const change = await client.getChange(number, REQUEST_DETAILS_FOR_PRS);\n pr = mapGerritChangeToPr(change);\n\n // Add to cache for further queries\n if (pr) {\n await GerritPrCache.setPr(config.repository!, pr);\n }\n } catch (err) {\n if (err.statusCode === 404) {\n logger.debug({ number }, 'Change not found in Gerrit');\n return null;\n }\n throw err;\n }\n }\n\n return pr;\n}\n\nexport async function updatePr(prConfig: UpdatePrConfig): Promise<void> {\n logger.debug(`updatePr(${prConfig.number}, ${prConfig.prTitle})`);\n const cached = await getPr(prConfig.number);\n if (!cached) {\n logger.warn(\n { number: prConfig.number },\n 'updatePr: change not found in cache or Gerrit',\n );\n return;\n }\n\n const pr = clone(cached);\n let updated = false;\n\n // Only add message if the body has really changed\n const newBodyHash = prConfig.prBody ? hashBody(prConfig.prBody) : undefined;\n if (newBodyHash && newBodyHash !== pr.bodyStruct?.hash) {\n const updatedDate = await client.addMessage(\n prConfig.number,\n prConfig.prBody!,\n TAG_PULL_REQUEST_BODY,\n );\n pr.bodyStruct = {\n hash: newBodyHash,\n };\n pr.updatedAt = convertGerritDateToISO(updatedDate);\n updated = true;\n }\n await client.setHashtags(prConfig.number, {\n add: prConfig.addLabels,\n remove: prConfig.removeLabels,\n });\n if (prConfig.targetBranch) {\n const change = await client.moveChange(\n prConfig.number,\n prConfig.targetBranch,\n );\n pr.targetBranch = change.branch;\n pr.updatedAt = convertGerritDateToISO(change.updated);\n updated = true;\n }\n // TODO: support restoring change if prConfig.state === 'open'\n if (prConfig.state === 'closed') {\n const change = await client.abandonChange(prConfig.number);\n pr.state = 'closed';\n pr.updatedAt = convertGerritDateToISO(change.updated);\n updated = true;\n }\n\n if (updated) {\n logger.debug(\n `updatePr: updating gerrit change ${prConfig.number} in cache`,\n );\n await GerritPrCache.setPr(config.repository!, pr);\n }\n}\n\nexport async function createPr(prConfig: CreatePRConfig): Promise<Pr | null> {\n logger.debug(\n `createPr(${prConfig.sourceBranch}, ${prConfig.prTitle}, ${\n prConfig.labels?.toString() ?? ''\n })`,\n );\n\n logger.debug(\n `Pushing commit to refs/for/${prConfig.targetBranch} to create Gerrit change`,\n );\n const pushResult = await pushForReview({\n sourceRef: prConfig.sourceBranch,\n targetBranch: prConfig.targetBranch,\n files: [],\n autoApprove: prConfig.platformPrOptions?.autoApprove,\n labels: prConfig.labels ?? undefined,\n });\n\n if (!pushResult) {\n throw new Error(\n `Failed to push commit to refs/for/${prConfig.targetBranch} to create Gerrit change`,\n );\n }\n\n const change = (\n await client.findChanges(config.repository!, {\n branchName: prConfig.sourceBranch,\n targetBranch: prConfig.targetBranch,\n state: 'open',\n singleChange: true,\n requestDetails: REQUEST_DETAILS_FOR_PRS,\n })\n ).pop();\n if (isUndefined(change)) {\n throw new Error(\n `Could not find the Gerrit change after pushing to refs/for/${prConfig.targetBranch}`,\n );\n }\n\n // Register the virtual branch now that the change exists and we have the ref.\n const sha = toLongCommitSha(change.current_revision);\n const ref = change.revisions![sha].ref;\n await git.setVirtualBranch(prConfig.sourceBranch, ref, sha);\n\n const updatedDate = await client.addMessage(\n change._number,\n prConfig.prBody,\n TAG_PULL_REQUEST_BODY,\n );\n\n const pr = mapGerritChangeToPr(change, {\n sourceBranch: prConfig.sourceBranch,\n prBody: prConfig.prBody,\n })!;\n pr.updatedAt = convertGerritDateToISO(updatedDate);\n logger.debug(`createPr: saving gerrit change ${pr.number} to cache`);\n await GerritPrCache.setPr(config.repository!, pr);\n\n return pr;\n}\n\nexport async function getBranchPr(\n branchName: string,\n targetBranch?: string,\n): Promise<Pr | null> {\n const prs = await GerritPrCache.getPrs(config.repository!);\n const cachedPrs = prs.filter((pr) => {\n if (pr.sourceBranch !== branchName) {\n return false;\n }\n if (pr.state !== 'open') {\n return false;\n }\n return true;\n });\n let result: Pr | undefined;\n if (targetBranch) {\n const found = cachedPrs.find((pr) => pr.targetBranch === targetBranch);\n if (found) {\n result = found;\n }\n }\n result ??= cachedPrs[0];\n logger.trace(\n `getBranchPr: using cached gerrit change ${result?.number} for ${branchName}`,\n );\n return result ?? null;\n}\n\nexport async function getPrList(): Promise<Pr[]> {\n const cached = await GerritPrCache.getPrs(config.repository!);\n logger.debug(`getPrList: using ${cached.length} cached changes`);\n return cached;\n}\n\nexport async function mergePr(mergeConfig: MergePRConfig): Promise<boolean> {\n logger.debug(\n `mergePr(${mergeConfig.id}, ${mergeConfig.branchName!}, ${mergeConfig.strategy!})`,\n );\n\n let change: GerritChange;\n try {\n change = await client.submitChange(mergeConfig.id);\n if (change.status !== 'MERGED') {\n return false;\n }\n } catch (err) {\n if (err.statusCode === 409) {\n logger.warn(\n { err },\n \"Can't submit the change, because the submit rule doesn't allow it.\",\n );\n return false;\n }\n throw err;\n }\n\n const cached = await getPr(mergeConfig.id);\n if (cached) {\n const pr = clone(cached);\n pr.state = 'merged';\n pr.updatedAt = convertGerritDateToISO(change.updated);\n logger.debug(`mergePr: updating gerrit change ${mergeConfig.id} in cache`);\n await GerritPrCache.setPr(config.repository!, pr);\n } else {\n logger.warn(\n { number: mergeConfig.id },\n 'mergePr: merged change but could not update PR cache',\n );\n }\n\n return true;\n}\n\n/**\n * BranchStatus for Gerrit assumes that the branchName refers to a change.\n * @param branchName\n */\nexport async function getBranchStatus(\n branchName: string,\n): Promise<BranchStatus> {\n logger.debug(`getBranchStatus(${branchName})`);\n const change = (\n await client.findChanges(config.repository!, {\n state: 'open',\n branchName,\n singleChange: true,\n requestDetails: ['LABELS', 'SUBMITTABLE', 'CHECK'],\n })\n ).pop();\n if (change) {\n const hasProblems = change.problems && change.problems.length > 0;\n if (hasProblems) {\n return 'red';\n }\n const hasBlockingLabels = Object.values(coerceObject(change.labels)).some(\n (label) => label.blocking,\n );\n if (hasBlockingLabels) {\n return 'red';\n }\n // v8 ignore else -- TODO: add test #40625\n if (change.submittable) {\n return 'green';\n }\n }\n return 'yellow';\n}\n\n/**\n * check the gerrit-change for the presence of the corresponding \"$context\" Gerrit label if configured,\n * return 'yellow' if not configured or not set\n * @param branchName\n * @param context renovate/stability-days || ...\n */\nexport async function getBranchStatusCheck(\n branchName: string,\n context: string,\n): Promise<BranchStatus | null> {\n const labelConfig = config.labels[context];\n if (labelConfig) {\n const change = (\n await client.findChanges(config.repository!, {\n branchName,\n state: 'open',\n singleChange: true,\n requestDetails: ['LABELS'],\n })\n ).pop();\n // v8 ignore else -- TODO: add test #40625\n if (change) {\n const label = change.labels![context];\n // v8 ignore else -- TODO: add test #40625\n if (label) {\n // Check for rejected or blocking first, as a label could have both rejected and approved\n if (label.rejected || label.blocking) {\n return 'red';\n }\n // v8 ignore else -- TODO: add test #40625\n if (label.approved) {\n return 'green';\n }\n }\n }\n }\n return 'yellow';\n}\n\n/**\n * Apply the branch state $context to the corresponding gerrit label (if available)\n * context === \"renovate/stability-days\" / \"renovate/merge-confidence\" and state === \"green\"/...\n * @param branchStatusConfig\n */\nexport async function setBranchStatus(\n branchStatusConfig: BranchStatusConfig,\n): Promise<void> {\n const label = config.labels[branchStatusConfig.context];\n const labelValue =\n label && mapBranchStatusToLabel(branchStatusConfig.state, label);\n if (branchStatusConfig.context && labelValue) {\n const change = (\n await client.findChanges(config.repository!, {\n branchName: branchStatusConfig.branchName,\n state: 'open',\n singleChange: true,\n requestDetails: ['LABELS'],\n })\n ).pop();\n\n const labelKey = branchStatusConfig.context;\n if (!change?.labels || !Object.hasOwn(change.labels, labelKey)) {\n return;\n }\n\n await client.setLabel(change._number, labelKey, labelValue);\n }\n}\n\nexport async function getRawFile(\n fileName: string,\n repoName?: string,\n branchOrTag?: string,\n): Promise<string | null> {\n const repo = repoName ?? config.repository;\n if (!repo) {\n logger.debug('No repo so cannot getRawFile');\n return null;\n }\n // v8 ignore next -- TODO: add test #40625\n const branch =\n branchOrTag ??\n (repo === config.repository ? (config.head ?? 'HEAD') : 'HEAD');\n const result = await client.getFile(repo, branch, fileName);\n return result;\n}\n\nexport async function getJsonFile(\n fileName: string,\n repoName?: string,\n branchOrTag?: string,\n): Promise<any> {\n const raw = await getRawFile(fileName, repoName, branchOrTag);\n return parseJson(raw, fileName);\n}\n\nexport async function addReviewers(\n number: number,\n reviewers: string[],\n): Promise<void> {\n await client.addReviewers(number, reviewers);\n}\n\n/**\n * add \"CC\" (only one possible)\n */\nexport async function addAssignees(\n number: number,\n assignees: string[],\n): Promise<void> {\n // v8 ignore else -- TODO: add test #40625\n if (assignees.length) {\n // v8 ignore else -- TODO: add test #40625\n if (assignees.length > 1) {\n logger.debug(\n `addAssignees(${number}, ${assignees.toString()}) called with more then one assignee! Gerrit only supports one assignee! Using the first from list.`,\n );\n }\n await client.addAssignee(number, assignees[0]);\n }\n}\n\nexport async function ensureComment(\n ensureComment: EnsureCommentConfig,\n): Promise<boolean> {\n logger.debug(\n `ensureComment(${ensureComment.number}, ${ensureComment.topic!}, ${\n ensureComment.content\n })`,\n );\n await client.addMessageIfNotAlreadyExists(\n ensureComment.number,\n ensureComment.content,\n ensureComment.topic ?? undefined,\n );\n return true;\n}\n\nexport function massageMarkdown(prBody: string, rebaseLabel: string): string {\n return (\n smartTruncate(readOnlyIssueBody(prBody), maxBodyLength())\n .replace('Branch creation', 'Change creation')\n .replace(\n 'close this Pull Request unmerged',\n 'abandon or vote this change with Code-Review -2',\n )\n .replace(\n 'Close this PR',\n 'Abandon or vote this change with Code-Review -2',\n )\n .replace(\n 'you tick the rebase/retry checkbox',\n `you add the _${rebaseLabel}_ hashtag to this change`,\n )\n .replace(\n 'checking the rebase/retry box above',\n `adding the _${rebaseLabel}_ hashtag to this change`,\n )\n .replace(regEx(/\\b(?:Pull Request|PR)/g), 'change')\n // Remove HTML tags not supported in Gerrit markdown\n .replace(regEx(/<\\/?summary>/g), '**')\n .replace(regEx(/<\\/?(?:details|blockquote)>/g), '')\n .replace(regEx(`\\n---\\n\\n.*?<!-- rebase-check -->.*?\\n`), '')\n .replace(regEx(/<!--renovate-(?:debug|config-hash):.*?-->/g), '')\n // Remove zero-width-space not supported in Gerrit markdown\n .replace(regEx(/&#8203;/g), '')\n );\n}\n\nexport function maxBodyLength(): number {\n return MAX_GERRIT_COMMENT_SIZE;\n}\n\nexport async function deleteLabel(\n number: number,\n label: string,\n): Promise<void> {\n await client.setHashtags(number, { remove: [label] });\n}\n\nexport function ensureCommentRemoval(\n _ensureCommentRemoval:\n EnsureCommentRemovalConfigByTopic | EnsureCommentRemovalConfigByContent,\n): Promise<void> {\n return Promise.resolve();\n}\n\nexport function ensureIssueClosing(_title: string): Promise<void> {\n return Promise.resolve();\n}\n\nexport function ensureIssue(\n _issueConfig: EnsureIssueConfig,\n): Promise<EnsureIssueResult | null> {\n return Promise.resolve(null);\n}\n\nexport function findIssue(_title: string): Promise<Issue | null> {\n return Promise.resolve(null);\n}\n\nexport function getIssueList(): Promise<Issue[]> {\n return Promise.resolve([]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,MAAa,KAAK;AAGlB,MAAM,WAEF,CAAC;AAEL,IAAI,SAMA,EACF,QAAQ,CAAC,EACX;AAEA,SAAgB,cAAc,WAAgC;CAC5D,SAAS;EAAE,GAAG;EAAQ,GAAG;CAAU;AACrC;AAEA,eAAsB,aAAa,EACjC,UACA,UACA,YAC0C;CAC1C,OAAO,MAAM,gBAAgB,SAAU,IAAI,SAAU,EAAE;CACvD,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,mDAAmD;CAErE,IAAI,EAAE,YAAY,WAChB,MAAM,IAAI,MACR,4DACF;CAEF,OAAO,iBAAiB;CACxB,SAAS,WAAW,oBAAoB,QAAQ;CAChD,WAAW,SAAS,QAAQ;CAE5B,IAAI;CACJ,IAAI;EACF,MAAM,MAAM,OAAO;;EAEnB,IAAI,IAAI,6BACN,gBAAgB,IAAI;OAEpB,gBAAgB,MAAM,OAAO,iBAAiB;GAC5C;GACA;EACF,CAAC;CAEL,SAAS,KAAK;EACZ,OAAO,MACL,EAAE,IAAI,GACN,0DACF;EACA,MAAM,IAAI,MAAM,8BAA8B;CAChD;CAEA,OAAO,MAAM,sBAAsB,eAAe;CAElD,MAAM,SAAS,OAAO,MAAM,aAAa;CACzC,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,mCAAmC,eAAe;CAEpE,gBAAgB,GAAG,OAAO,MAAM,GAAG,OAAO,MAAM,GAAG,OAAO;CAC1D,OAAO,iBAAiB,aAAa;CAKrC,OAAO,EAFL,UAAU,SAAS,SAED;AACtB;;;;AAKA,eAAsB,WAA8B;CAClD,OAAO,MAAM,YAAY;CACzB,OAAO,MAAM,OAAO,SAAS;AAC/B;;;;;AAMA,eAAsB,SAAS,EAC7B,YACA,iBACA,uBACA,UACkC;CAClC,OAAO,MAAM,YAAY,WAAW,IAAI,OAAO,EAAE;CACjD,MAAM,cAAc,MAAM,OAAO,eAAe,UAAU;CAC1D,MAAM,aAAa,MAAM,OAAO,cAAc,UAAU;CAExD,SAAS;EACP,GAAG;EACH;EACA,MAAM,WAAW;EACjB,QAAQ;EACR,QAAQ,aAAa,YAAY,MAAM;CACzC;CAIA,MAAM,kBAAkB,MAAM,OAAO,YAAY,YAAY;EAC3D,YAAY;EACZ,OAAO;EACP,OAAO;CACT,CAAC;CACD,KAAK,MAAM,UAAU,iBAAiB;EACpC,MAAM,OAAO,cACX,OAAO,SACP,qEACF;EACA,OAAO,KACL,oBAAoB,OAAO,QAAQ,qCAAqC,YAC1E;CACF;CAIA,MAAM,cAAc,MAAM,OAAO,YAAY,YAAY;EACvD,YAAY;EACZ,OAAO;EACP,gBAAgB,CAAC,oBAAoB,gBAAgB;CACvD,CAAC;CACD,MAAM,kBAAiD,CAAC;CACxD,KAAK,MAAM,UAAU,aAAa;EAChC,MAAM,aAAa,oBAAoB,MAAM;EAC7C,IAAI,CAAC,YACH;EAEF,MAAM,MAAM,gBAAgB,OAAO,gBAAgB;EAEnD,gBAAgB,cAAc;GAAE,KADpB,OAAO,UAAW,IAAI,CAAC;GACE;EAAI;CAC3C;CAEA,MAAM,qBAAqB,OAAO,KAAK,eAAe,CAAC,CAAC;CACxD,IAAI,qBAAqB,GACvB,OAAO,MACL,cAAc,mBAAmB,oCACnC;CAGF,MAAM,UAAU,SAAS;CACzB,MAAM,MAAM,iBACV,YACA,SACA,QACA,OAAO,cACT;CACA,aAAa,YAAY,OAAO,MAAM;CACtC,MAAMA,WAAa;EACjB;EACA;EACA;EACA;CACF,CAAC;CAOD,OAAO;EAJL,eAAe,OAAO;EACtB,QAAQ;EACR,iBAAiB,gBAAgB,YAAY,OAAO;CAEtC;AAClB;AAEA,eAAsB,OAAO,cAAgD;CAG3E,MAAM,UAAS,MAFG,cAAc,OAAO,OAAO,UAAW,EAAA,CAEtC,MAAM,OAAO;EAC9B,IACE,aAAa,cACb,GAAG,iBAAiB,aAAa,YAEjC,OAAO;EAET,IACE,aAAa,gBACb,GAAG,iBAAiB,aAAa,cAEjC,OAAO;EAET,IAAI,aAAa,WAAW,GAAG,UAAU,aAAa,SACpD,OAAO;EAET,IAAI,CAAC,qBAAqB,GAAG,OAAO,aAAa,KAAK,GACpD,OAAO;EAET,OAAO;CACT,CAAC;CACD,OAAO,MACL,sCAAsC,QAAQ,OAAO,OAAO,aAAa,YAC3E;CACA,OAAO,UAAU;AACnB;AAEA,eAAsB,MAAM,QAAoC;CAE9D,IAAI,MAAK,MADS,cAAc,OAAO,OAAO,UAAW,EAAA,CAC5C,MAAM,MAAM,EAAE,WAAW,MAAM,KAAK;CACjD,IAAI,IACF,OAAO,MACL,qCAAqC,GAAG,aAAa,OAAO,QAC9D;MACK;EACL,OAAO,MACL,EAAE,OAAO,GACT,uDACF;EACA,IAAI;GACF,MAAM,SAAS,MAAM,OAAO,UAAU,QAAQ,uBAAuB;GACrE,KAAK,oBAAoB,MAAM;GAG/B,IAAI,IACF,MAAM,cAAc,MAAM,OAAO,YAAa,EAAE;EAEpD,SAAS,KAAK;GACZ,IAAI,IAAI,eAAe,KAAK;IAC1B,OAAO,MAAM,EAAE,OAAO,GAAG,4BAA4B;IACrD,OAAO;GACT;GACA,MAAM;EACR;CACF;CAEA,OAAO;AACT;AAEA,eAAsB,SAAS,UAAyC;CACtE,OAAO,MAAM,YAAY,SAAS,OAAO,IAAI,SAAS,QAAQ,EAAE;CAChE,MAAM,SAAS,MAAM,MAAM,SAAS,MAAM;CAC1C,IAAI,CAAC,QAAQ;EACX,OAAO,KACL,EAAE,QAAQ,SAAS,OAAO,GAC1B,+CACF;EACA;CACF;CAEA,MAAM,KAAK,MAAM,MAAM;CACvB,IAAI,UAAU;CAGd,MAAM,cAAc,SAAS,SAAS,SAAS,SAAS,MAAM,IAAI,KAAA;CAClE,IAAI,eAAe,gBAAgB,GAAG,YAAY,MAAM;EACtD,MAAM,cAAc,MAAM,OAAO,WAC/B,SAAS,QACT,SAAS,QACT,qBACF;EACA,GAAG,aAAa,EACd,MAAM,YACR;EACA,GAAG,YAAY,uBAAuB,WAAW;EACjD,UAAU;CACZ;CACA,MAAM,OAAO,YAAY,SAAS,QAAQ;EACxC,KAAK,SAAS;EACd,QAAQ,SAAS;CACnB,CAAC;CACD,IAAI,SAAS,cAAc;EACzB,MAAM,SAAS,MAAM,OAAO,WAC1B,SAAS,QACT,SAAS,YACX;EACA,GAAG,eAAe,OAAO;EACzB,GAAG,YAAY,uBAAuB,OAAO,OAAO;EACpD,UAAU;CACZ;CAEA,IAAI,SAAS,UAAU,UAAU;EAC/B,MAAM,SAAS,MAAM,OAAO,cAAc,SAAS,MAAM;EACzD,GAAG,QAAQ;EACX,GAAG,YAAY,uBAAuB,OAAO,OAAO;EACpD,UAAU;CACZ;CAEA,IAAI,SAAS;EACX,OAAO,MACL,oCAAoC,SAAS,OAAO,UACtD;EACA,MAAM,cAAc,MAAM,OAAO,YAAa,EAAE;CAClD;AACF;AAEA,eAAsB,SAAS,UAA8C;CAC3E,OAAO,MACL,YAAY,SAAS,aAAa,IAAI,SAAS,QAAQ,IACrD,SAAS,QAAQ,SAAS,KAAK,GAChC,EACH;CAEA,OAAO,MACL,8BAA8B,SAAS,aAAa,yBACtD;CASA,IAAI,CAAC,MARoB,cAAc;EACrC,WAAW,SAAS;EACpB,cAAc,SAAS;EACvB,OAAO,CAAC;EACR,aAAa,SAAS,mBAAmB;EACzC,QAAQ,SAAS,UAAU,KAAA;CAC7B,CAAC,GAGC,MAAM,IAAI,MACR,qCAAqC,SAAS,aAAa,yBAC7D;CAGF,MAAM,UACJ,MAAM,OAAO,YAAY,OAAO,YAAa;EAC3C,YAAY,SAAS;EACrB,cAAc,SAAS;EACvB,OAAO;EACP,cAAc;EACd,gBAAgB;CAClB,CAAC,EAAA,CACD,IAAI;CACN,IAAI,YAAY,MAAM,GACpB,MAAM,IAAI,MACR,8DAA8D,SAAS,cACzE;CAIF,MAAM,MAAM,gBAAgB,OAAO,gBAAgB;CACnD,MAAM,MAAM,OAAO,UAAW,IAAI,CAAC;CACnC,MAAMC,iBAAqB,SAAS,cAAc,KAAK,GAAG;CAE1D,MAAM,cAAc,MAAM,OAAO,WAC/B,OAAO,SACP,SAAS,QACT,qBACF;CAEA,MAAM,KAAK,oBAAoB,QAAQ;EACrC,cAAc,SAAS;EACvB,QAAQ,SAAS;CACnB,CAAC;CACD,GAAG,YAAY,uBAAuB,WAAW;CACjD,OAAO,MAAM,kCAAkC,GAAG,OAAO,UAAU;CACnE,MAAM,cAAc,MAAM,OAAO,YAAa,EAAE;CAEhD,OAAO;AACT;AAEA,eAAsB,YACpB,YACA,cACoB;CAEpB,MAAM,aAAY,MADA,cAAc,OAAO,OAAO,UAAW,EAAA,CACnC,QAAQ,OAAO;EACnC,IAAI,GAAG,iBAAiB,YACtB,OAAO;EAET,IAAI,GAAG,UAAU,QACf,OAAO;EAET,OAAO;CACT,CAAC;CACD,IAAI;CACJ,IAAI,cAAc;EAChB,MAAM,QAAQ,UAAU,MAAM,OAAO,GAAG,iBAAiB,YAAY;EACrE,IAAI,OACF,SAAS;CAEb;CACA,WAAW,UAAU;CACrB,OAAO,MACL,2CAA2C,QAAQ,OAAO,OAAO,YACnE;CACA,OAAO,UAAU;AACnB;AAEA,eAAsB,YAA2B;CAC/C,MAAM,SAAS,MAAM,cAAc,OAAO,OAAO,UAAW;CAC5D,OAAO,MAAM,oBAAoB,OAAO,OAAO,gBAAgB;CAC/D,OAAO;AACT;AAEA,eAAsB,QAAQ,aAA8C;CAC1E,OAAO,MACL,WAAW,YAAY,GAAG,IAAI,YAAY,WAAY,IAAI,YAAY,SAAU,EAClF;CAEA,IAAI;CACJ,IAAI;EACF,SAAS,MAAM,OAAO,aAAa,YAAY,EAAE;EACjD,IAAI,OAAO,WAAW,UACpB,OAAO;CAEX,SAAS,KAAK;EACZ,IAAI,IAAI,eAAe,KAAK;GAC1B,OAAO,KACL,EAAE,IAAI,GACN,oEACF;GACA,OAAO;EACT;EACA,MAAM;CACR;CAEA,MAAM,SAAS,MAAM,MAAM,YAAY,EAAE;CACzC,IAAI,QAAQ;EACV,MAAM,KAAK,MAAM,MAAM;EACvB,GAAG,QAAQ;EACX,GAAG,YAAY,uBAAuB,OAAO,OAAO;EACpD,OAAO,MAAM,mCAAmC,YAAY,GAAG,UAAU;EACzE,MAAM,cAAc,MAAM,OAAO,YAAa,EAAE;CAClD,OACE,OAAO,KACL,EAAE,QAAQ,YAAY,GAAG,GACzB,sDACF;CAGF,OAAO;AACT;;;;;AAMA,eAAsB,gBACpB,YACuB;CACvB,OAAO,MAAM,mBAAmB,WAAW,EAAE;CAC7C,MAAM,UACJ,MAAM,OAAO,YAAY,OAAO,YAAa;EAC3C,OAAO;EACP;EACA,cAAc;EACd,gBAAgB;GAAC;GAAU;GAAe;EAAO;CACnD,CAAC,EAAA,CACD,IAAI;CACN,IAAI,QAAQ;EAEV,IADoB,OAAO,YAAY,OAAO,SAAS,SAAS,GAE9D,OAAO;EAKT,IAH0B,OAAO,OAAO,aAAa,OAAO,MAAM,CAAC,CAAC,CAAC,MAClE,UAAU,MAAM,QAEC,GAClB,OAAO;;EAGT,IAAI,OAAO,aACT,OAAO;CAEX;CACA,OAAO;AACT;;;;;;;AAQA,eAAsB,qBACpB,YACA,SAC8B;CAE9B,IADoB,OAAO,OAAO,UACjB;EACf,MAAM,UACJ,MAAM,OAAO,YAAY,OAAO,YAAa;GAC3C;GACA,OAAO;GACP,cAAc;GACd,gBAAgB,CAAC,QAAQ;EAC3B,CAAC,EAAA,CACD,IAAI;;EAEN,IAAI,QAAQ;GACV,MAAM,QAAQ,OAAO,OAAQ;;GAE7B,IAAI,OAAO;IAET,IAAI,MAAM,YAAY,MAAM,UAC1B,OAAO;;IAGT,IAAI,MAAM,UACR,OAAO;GAEX;EACF;CACF;CACA,OAAO;AACT;;;;;;AAOA,eAAsB,gBACpB,oBACe;CACf,MAAM,QAAQ,OAAO,OAAO,mBAAmB;CAC/C,MAAM,aACJ,SAAS,uBAAuB,mBAAmB,OAAO,KAAK;CACjE,IAAI,mBAAmB,WAAW,YAAY;EAC5C,MAAM,UACJ,MAAM,OAAO,YAAY,OAAO,YAAa;GAC3C,YAAY,mBAAmB;GAC/B,OAAO;GACP,cAAc;GACd,gBAAgB,CAAC,QAAQ;EAC3B,CAAC,EAAA,CACD,IAAI;EAEN,MAAM,WAAW,mBAAmB;EACpC,IAAI,CAAC,QAAQ,UAAU,CAAC,OAAO,OAAO,OAAO,QAAQ,QAAQ,GAC3D;EAGF,MAAM,OAAO,SAAS,OAAO,SAAS,UAAU,UAAU;CAC5D;AACF;AAEA,eAAsB,WACpB,UACA,UACA,aACwB;CACxB,MAAM,OAAO,YAAY,OAAO;CAChC,IAAI,CAAC,MAAM;EACT,OAAO,MAAM,8BAA8B;EAC3C,OAAO;CACT;;CAEA,MAAM,SACJ,gBACC,SAAS,OAAO,aAAc,OAAO,QAAQ,SAAU;CAE1D,OAAO,MADc,OAAO,QAAQ,MAAM,QAAQ,QAAQ;AAE5D;AAEA,eAAsB,YACpB,UACA,UACA,aACc;CACd,MAAM,MAAM,MAAM,WAAW,UAAU,UAAU,WAAW;CAC5D,OAAO,UAAU,KAAK,QAAQ;AAChC;AAEA,eAAsB,aACpB,QACA,WACe;CACf,MAAM,OAAO,aAAa,QAAQ,SAAS;AAC7C;;;;AAKA,eAAsB,aACpB,QACA,WACe;;CAEf,IAAI,UAAU,QAAQ;;EAEpB,IAAI,UAAU,SAAS,GACrB,OAAO,MACL,gBAAgB,OAAO,IAAI,UAAU,SAAS,EAAE,oGAClD;EAEF,MAAM,OAAO,YAAY,QAAQ,UAAU,EAAE;CAC/C;AACF;AAEA,eAAsB,cACpB,eACkB;CAClB,OAAO,MACL,iBAAiB,cAAc,OAAO,IAAI,cAAc,MAAO,IAC7D,cAAc,QACf,EACH;CACA,MAAM,OAAO,6BACX,cAAc,QACd,cAAc,SACd,cAAc,SAAS,KAAA,CACzB;CACA,OAAO;AACT;AAEA,SAAgB,gBAAgB,QAAgB,aAA6B;CAC3E,OACE,cAAc,kBAAkB,MAAM,GAAG,cAAc,CAAC,CAAC,CACtD,QAAQ,mBAAmB,iBAAiB,CAAC,CAC7C,QACC,oCACA,iDACF,CAAC,CACA,QACC,iBACA,iDACF,CAAC,CACA,QACC,sCACA,gBAAgB,YAAY,yBAC9B,CAAC,CACA,QACC,uCACA,eAAe,YAAY,yBAC7B,CAAC,CACA,QAAQ,MAAM,wBAAwB,GAAG,QAAQ,CAAC,CAElD,QAAQ,MAAM,eAAe,GAAG,IAAI,CAAC,CACrC,QAAQ,MAAM,8BAA8B,GAAG,EAAE,CAAC,CAClD,QAAQ,MAAM,wCAAwC,GAAG,EAAE,CAAC,CAC5D,QAAQ,MAAM,4CAA4C,GAAG,EAAE,CAAC,CAEhE,QAAQ,MAAM,UAAU,GAAG,EAAE;AAEpC;AAEA,SAAgB,gBAAwB;CACtC,OAAO;AACT;AAEA,eAAsB,YACpB,QACA,OACe;CACf,MAAM,OAAO,YAAY,QAAQ,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;AACtD;AAEA,SAAgB,qBACd,uBAEe;CACf,OAAO,QAAQ,QAAQ;AACzB;AAEA,SAAgB,mBAAmB,QAA+B;CAChE,OAAO,QAAQ,QAAQ;AACzB;AAEA,SAAgB,YACd,cACmC;CACnC,OAAO,QAAQ,QAAQ,IAAI;AAC7B;AAEA,SAAgB,UAAU,QAAuC;CAC/D,OAAO,QAAQ,QAAQ,IAAI;AAC7B;AAEA,SAAgB,eAAiC;CAC/C,OAAO,QAAQ,QAAQ,CAAC,CAAC;AAC3B"}
@@ -0,0 +1,116 @@
1
+ import { get, set } from "../../../util/cache/memory/index.js";
2
+ import { logger } from "../../../logger/index.js";
3
+ import { getCache } from "../../../util/cache/repository/index.js";
4
+ import { REQUEST_DETAILS_FOR_PRS, convertGerritDateToISO, mapGerritChangeToPr } from "./utils.js";
5
+ import { client } from "./client.js";
6
+ //#region lib/modules/platform/gerrit/pr-cache.ts
7
+ /**
8
+ * Page size for initial cache population (when cache is empty).
9
+ */
10
+ const INITIAL_SYNC_PAGE_SIZE = 100;
11
+ /**
12
+ * Page size for incremental cache updates (when cache exists).
13
+ */
14
+ const INCREMENTAL_PAGE_SIZE = 20;
15
+ /**
16
+ * Smart cache for Gerrit pull requests (changes).
17
+ * Uses the Gerrit API's sorting by update time to efficiently sync changes.
18
+ * Only fetches changes until we encounter ones that match our cache.
19
+ */
20
+ var GerritPrCache = class GerritPrCache {
21
+ cache;
22
+ items = [];
23
+ repository;
24
+ constructor(repository) {
25
+ this.repository = repository;
26
+ const repoCache = getCache();
27
+ repoCache.platform ??= {};
28
+ const platformCache = repoCache.platform;
29
+ platformCache.gerrit ??= {};
30
+ platformCache.gerrit.pullRequestsCache ??= { items: {} };
31
+ this.cache = platformCache.gerrit.pullRequestsCache;
32
+ this.updateItems();
33
+ }
34
+ static async init(repository) {
35
+ const res = new GerritPrCache(repository);
36
+ if (!get("gerrit-pr-cache-synced")) {
37
+ await res.sync();
38
+ set("gerrit-pr-cache-synced", true);
39
+ }
40
+ return res;
41
+ }
42
+ getPrs() {
43
+ logger.trace(`Returning ${this.items.length} PRs from cache`);
44
+ return this.items;
45
+ }
46
+ static async getPrs(repository) {
47
+ return (await GerritPrCache.init(repository)).getPrs();
48
+ }
49
+ setPr(item) {
50
+ logger.trace(`Updating PR ${item.number} in cache`);
51
+ this.cache.items[item.number] = item;
52
+ this.updateItems();
53
+ }
54
+ static async setPr(repository, item) {
55
+ (await GerritPrCache.init(repository)).setPr(item);
56
+ }
57
+ /**
58
+ * Reconciles a page of changes with the cache.
59
+ *
60
+ * Gerrit API returns changes sorted by last update time (most recent first).
61
+ * When we encounter a change whose "updated" timestamp matches our cached
62
+ * Pr's updatedAt, we finish reconciling the rest of this page and skip
63
+ * further pages.
64
+ *
65
+ * @param changes Page of changes from Gerrit API
66
+ * @returns true if more pages can be fetched, false if not needed
67
+ */
68
+ reconcile(changes) {
69
+ const { items } = this.cache;
70
+ let changeCount = 0;
71
+ let needNextPage = true;
72
+ for (const change of changes) {
73
+ const cachedPr = items[change._number];
74
+ const changeUpdatedAt = convertGerritDateToISO(change.updated);
75
+ if (cachedPr?.updatedAt === changeUpdatedAt) {
76
+ needNextPage = false;
77
+ continue;
78
+ }
79
+ const pr = mapGerritChangeToPr(change);
80
+ if (!pr) continue;
81
+ items[change._number] = pr;
82
+ changeCount++;
83
+ }
84
+ if (!needNextPage) logger.debug(`Reconciled ${changeCount} changes on page; skipping further pages as the rest is already cached`);
85
+ return needNextPage;
86
+ }
87
+ async sync() {
88
+ logger.debug("Syncing Gerrit PR cache");
89
+ const pageLimit = this.items.length ? INCREMENTAL_PAGE_SIZE : INITIAL_SYNC_PAGE_SIZE;
90
+ await client.findChanges(this.repository, {
91
+ branchName: "",
92
+ state: "all",
93
+ pageLimit,
94
+ requestDetails: REQUEST_DETAILS_FOR_PRS,
95
+ shouldFetchNextPage: (changes) => {
96
+ return this.reconcile(changes);
97
+ }
98
+ });
99
+ this.updateItems();
100
+ logger.debug(`Synced ${this.items.length} changes to cache`);
101
+ return this;
102
+ }
103
+ /**
104
+ * Converts the cache items map to an array and sorts by updatedAt (most recent first).
105
+ */
106
+ updateItems() {
107
+ this.items = Object.values(this.cache.items).sort((a, b) => {
108
+ if (!a.updatedAt || !b.updatedAt) return 0;
109
+ return b.updatedAt.localeCompare(a.updatedAt);
110
+ });
111
+ }
112
+ };
113
+ //#endregion
114
+ export { GerritPrCache };
115
+
116
+ //# sourceMappingURL=pr-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr-cache.js","names":["memCache.get"],"sources":["../../../../lib/modules/platform/gerrit/pr-cache.ts"],"sourcesContent":["import { logger } from '../../../logger/index.ts';\nimport * as memCache from '../../../util/cache/memory/index.ts';\nimport { getCache } from '../../../util/cache/repository/index.ts';\nimport type { Pr } from '../types.ts';\nimport { client } from './client.ts';\nimport type { GerritChange } from './schema.ts';\nimport type { GerritPrCacheData } from './types.ts';\nimport {\n REQUEST_DETAILS_FOR_PRS,\n convertGerritDateToISO,\n mapGerritChangeToPr,\n} from './utils.ts';\n\n/**\n * Page size for initial cache population (when cache is empty).\n */\nconst INITIAL_SYNC_PAGE_SIZE = 100;\n\n/**\n * Page size for incremental cache updates (when cache exists).\n */\nconst INCREMENTAL_PAGE_SIZE = 20;\n\ninterface GerritPlatformCache {\n gerrit?: {\n pullRequestsCache?: GerritPrCacheData;\n };\n}\n\n/**\n * Smart cache for Gerrit pull requests (changes).\n * Uses the Gerrit API's sorting by update time to efficiently sync changes.\n * Only fetches changes until we encounter ones that match our cache.\n */\nexport class GerritPrCache {\n private cache: GerritPrCacheData;\n private items: Pr[] = [];\n private repository: string;\n\n private constructor(repository: string) {\n this.repository = repository;\n const repoCache = getCache();\n repoCache.platform ??= {};\n const platformCache = repoCache.platform as GerritPlatformCache;\n platformCache.gerrit ??= {};\n platformCache.gerrit.pullRequestsCache ??= {\n items: {},\n };\n this.cache = platformCache.gerrit.pullRequestsCache;\n this.updateItems();\n }\n\n private static async init(repository: string): Promise<GerritPrCache> {\n const res = new GerritPrCache(repository);\n const isSynced = memCache.get<true | undefined>('gerrit-pr-cache-synced');\n\n if (!isSynced) {\n await res.sync();\n memCache.set('gerrit-pr-cache-synced', true);\n }\n\n return res;\n }\n\n private getPrs(): Pr[] {\n logger.trace(`Returning ${this.items.length} PRs from cache`);\n return this.items;\n }\n\n static async getPrs(repository: string): Promise<Pr[]> {\n const prCache = await GerritPrCache.init(repository);\n return prCache.getPrs();\n }\n\n private setPr(item: Pr): void {\n logger.trace(`Updating PR ${item.number} in cache`);\n this.cache.items[item.number] = item;\n this.updateItems();\n }\n\n static async setPr(repository: string, item: Pr): Promise<void> {\n const prCache = await GerritPrCache.init(repository);\n prCache.setPr(item);\n }\n\n /**\n * Reconciles a page of changes with the cache.\n *\n * Gerrit API returns changes sorted by last update time (most recent first).\n * When we encounter a change whose \"updated\" timestamp matches our cached\n * Pr's updatedAt, we finish reconciling the rest of this page and skip\n * further pages.\n *\n * @param changes Page of changes from Gerrit API\n * @returns true if more pages can be fetched, false if not needed\n */\n private reconcile(changes: GerritChange[]): boolean {\n const { items } = this.cache;\n let changeCount = 0;\n let needNextPage = true;\n\n for (const change of changes) {\n const cachedPr = items[change._number];\n const changeUpdatedAt = convertGerritDateToISO(change.updated);\n\n // If this change exists in cache and has the same updated timestamp, it\n // hasn't been modified. Since changes are sorted by update time, further\n // changes hasn't been modified either, but we still process the current\n // page as it's already fetched anyway.\n if (cachedPr?.updatedAt === changeUpdatedAt) {\n needNextPage = false;\n continue;\n }\n\n const pr = mapGerritChangeToPr(change);\n if (!pr) {\n continue;\n }\n\n items[change._number] = pr;\n changeCount++;\n }\n\n if (!needNextPage) {\n logger.debug(\n `Reconciled ${changeCount} changes on page; skipping further pages as the rest is already cached`,\n );\n }\n\n return needNextPage;\n }\n\n private async sync(): Promise<GerritPrCache> {\n logger.debug('Syncing Gerrit PR cache');\n\n // Determine page size based on whether cache exists\n const pageLimit = this.items.length\n ? INCREMENTAL_PAGE_SIZE\n : INITIAL_SYNC_PAGE_SIZE;\n\n // Use client.findChanges with pagination and early termination via reconcile.\n // Gerrit API returns changes sorted by update time (newest first),\n // so we can stop fetching once we hit the cached timestamp.\n await client.findChanges(this.repository, {\n branchName: '',\n state: 'all',\n pageLimit,\n requestDetails: REQUEST_DETAILS_FOR_PRS,\n shouldFetchNextPage: (changes: GerritChange[]) => {\n // reconcile returns true if we need the next page\n return this.reconcile(changes);\n },\n });\n\n this.updateItems();\n\n logger.debug(`Synced ${this.items.length} changes to cache`);\n return this;\n }\n\n /**\n * Converts the cache items map to an array and sorts by updatedAt (most recent first).\n */\n private updateItems(): void {\n this.items = Object.values(this.cache.items).sort((a, b) => {\n // Sort by updatedAt descending (most recent first)\n if (!a.updatedAt || !b.updatedAt) {\n return 0;\n }\n return b.updatedAt.localeCompare(a.updatedAt);\n });\n }\n}\n"],"mappings":";;;;;;;;;AAgBA,MAAM,yBAAyB;;;;AAK/B,MAAM,wBAAwB;;;;;;AAa9B,IAAa,gBAAb,MAAa,cAAc;CACzB;CACA,QAAsB,CAAC;CACvB;CAEA,YAAoB,YAAoB;EACtC,KAAK,aAAa;EAClB,MAAM,YAAY,SAAS;EAC3B,UAAU,aAAa,CAAC;EACxB,MAAM,gBAAgB,UAAU;EAChC,cAAc,WAAW,CAAC;EAC1B,cAAc,OAAO,sBAAsB,EACzC,OAAO,CAAC,EACV;EACA,KAAK,QAAQ,cAAc,OAAO;EAClC,KAAK,YAAY;CACnB;CAEA,aAAqB,KAAK,YAA4C;EACpE,MAAM,MAAM,IAAI,cAAc,UAAU;EAGxC,IAAI,CAFaA,IAA+B,wBAEpC,GAAG;GACb,MAAM,IAAI,KAAK;GACf,IAAa,0BAA0B,IAAI;EAC7C;EAEA,OAAO;CACT;CAEA,SAAuB;EACrB,OAAO,MAAM,aAAa,KAAK,MAAM,OAAO,gBAAgB;EAC5D,OAAO,KAAK;CACd;CAEA,aAAa,OAAO,YAAmC;EAErD,QAAO,MADe,cAAc,KAAK,UAAU,EAAA,CACpC,OAAO;CACxB;CAEA,MAAc,MAAgB;EAC5B,OAAO,MAAM,eAAe,KAAK,OAAO,UAAU;EAClD,KAAK,MAAM,MAAM,KAAK,UAAU;EAChC,KAAK,YAAY;CACnB;CAEA,aAAa,MAAM,YAAoB,MAAyB;EAE9D,CAAA,MADsB,cAAc,KAAK,UAAU,EAAA,CAC3C,MAAM,IAAI;CACpB;;;;;;;;;;;;CAaA,UAAkB,SAAkC;EAClD,MAAM,EAAE,UAAU,KAAK;EACvB,IAAI,cAAc;EAClB,IAAI,eAAe;EAEnB,KAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,WAAW,MAAM,OAAO;GAC9B,MAAM,kBAAkB,uBAAuB,OAAO,OAAO;GAM7D,IAAI,UAAU,cAAc,iBAAiB;IAC3C,eAAe;IACf;GACF;GAEA,MAAM,KAAK,oBAAoB,MAAM;GACrC,IAAI,CAAC,IACH;GAGF,MAAM,OAAO,WAAW;GACxB;EACF;EAEA,IAAI,CAAC,cACH,OAAO,MACL,cAAc,YAAY,uEAC5B;EAGF,OAAO;CACT;CAEA,MAAc,OAA+B;EAC3C,OAAO,MAAM,yBAAyB;EAGtC,MAAM,YAAY,KAAK,MAAM,SACzB,wBACA;EAKJ,MAAM,OAAO,YAAY,KAAK,YAAY;GACxC,YAAY;GACZ,OAAO;GACP;GACA,gBAAgB;GAChB,sBAAsB,YAA4B;IAEhD,OAAO,KAAK,UAAU,OAAO;GAC/B;EACF,CAAC;EAED,KAAK,YAAY;EAEjB,OAAO,MAAM,UAAU,KAAK,MAAM,OAAO,kBAAkB;EAC3D,OAAO;CACT;;;;CAKA,cAA4B;EAC1B,KAAK,QAAQ,OAAO,OAAO,KAAK,MAAM,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM;GAE1D,IAAI,CAAC,EAAE,aAAa,CAAC,EAAE,WACrB,OAAO;GAET,OAAO,EAAE,UAAU,cAAc,EAAE,SAAS;EAC9C,CAAC;CACH;AACF"}
@@ -42,6 +42,7 @@ const GerritChange = z.object({
42
42
  "ABANDONED"
43
43
  ]),
44
44
  created: z.string(),
45
+ updated: z.string(),
45
46
  hashtags: z.array(z.string()),
46
47
  submittable: z.boolean().optional(),
47
48
  _number: z.number(),
@@ -85,6 +86,9 @@ const GerritMergeableInfo = z.object({
85
86
  });
86
87
  const GerritRepos = z.record(z.string(), z.object({}).loose());
87
88
  const GerritChangeMessages = LooseArray(GerritChangeMessageInfo);
89
+ z.object({
90
+ /** Only present on Gerrit >= 3.10 */
91
+ change_info: GerritChange.optional() });
88
92
  //#endregion
89
93
  export { GerritAccountInfo, GerritActionInfo, GerritBranchInfo, GerritChange, GerritChangeMessageInfo, GerritChangeMessages, GerritChanges, GerritLabelInfo, GerritLabelTypeInfo, GerritMergeableInfo, GerritProjectInfo, GerritRepos, GerritRevisionInfo };
90
94
 
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","names":[],"sources":["../../../../lib/modules/platform/gerrit/schema.ts"],"sourcesContent":["import { z } from 'zod/v4';\nimport { LooseArray } from '../../../util/schema-utils/index.ts';\n\n/**\n * The Schemas for the Gerrit API Responses ({@link https://gerrit-review.googlesource.com/Documentation/rest-api.html | REST-API})\n * minimized to only needed properties.\n *\n * @packageDocumentation\n */\n\nexport const GerritAccountInfo = z.object({\n _account_id: z.number(),\n username: z.string().optional(),\n});\nexport type GerritAccountInfo = z.infer<typeof GerritAccountInfo>;\n\nexport const GerritLabelInfo = z.object({\n approved: GerritAccountInfo.optional(),\n rejected: GerritAccountInfo.optional(),\n blocking: z.boolean().optional(),\n});\nexport type GerritLabelInfo = z.infer<typeof GerritLabelInfo>;\n\nexport const GerritActionInfo = z.object({\n method: z.string().optional(),\n enabled: z.boolean().optional(),\n});\nexport type GerritActionInfo = z.infer<typeof GerritActionInfo>;\n\nexport const GerritRevisionInfo = z.object({\n uploader: GerritAccountInfo,\n ref: z.string(),\n created: z.string(),\n actions: z.record(z.string(), GerritActionInfo).optional(),\n commit_with_footers: z.string().optional(),\n});\nexport type GerritRevisionInfo = z.infer<typeof GerritRevisionInfo>;\n\nexport const GerritChangeMessageInfo = z.object({\n id: z.string(),\n message: z.string(),\n tag: z.string().optional(),\n});\nexport type GerritChangeMessageInfo = z.infer<typeof GerritChangeMessageInfo>;\n\nexport const GerritChange = z.object({\n branch: z.string(),\n change_id: z.string(),\n subject: z.string(),\n status: z.enum(['NEW', 'MERGED', 'ABANDONED']),\n created: z.string(),\n hashtags: z.array(z.string()),\n submittable: z.boolean().optional(),\n _number: z.number(),\n labels: z.record(z.string(), GerritLabelInfo).optional(),\n reviewers: z\n .object({\n REVIEWER: LooseArray(GerritAccountInfo).optional(),\n })\n .optional(),\n messages: LooseArray(GerritChangeMessageInfo).optional(),\n current_revision: z.string().optional(),\n revisions: z.record(z.string(), GerritRevisionInfo).optional(),\n problems: z.array(z.unknown()).optional(),\n _more_changes: z.boolean().optional(),\n});\nexport type GerritChange = z.infer<typeof GerritChange>;\n\nexport const GerritChanges = LooseArray(GerritChange);\n\nexport const GerritLabelTypeInfo = z.object({\n values: z.record(z.coerce.number(), z.string()),\n default_value: z.number(),\n});\nexport type GerritLabelTypeInfo = z.infer<typeof GerritLabelTypeInfo>;\n\n/** Map of label name (e.g. \"Code-Review\") to its definition on a project. */\nexport type GerritLabels = Record<string, GerritLabelTypeInfo>;\n\nexport const GerritProjectInfo = z.object({\n id: z.string(),\n name: z.string(),\n state: z.enum(['ACTIVE', 'READ_ONLY', 'HIDDEN']).optional(),\n labels: z.record(z.string(), GerritLabelTypeInfo).optional(),\n});\nexport type GerritProjectInfo = z.infer<typeof GerritProjectInfo>;\n\nexport const GerritBranchInfo = z.object({\n ref: z.string(),\n revision: z.string(),\n});\nexport type GerritBranchInfo = z.infer<typeof GerritBranchInfo>;\n\nexport const GerritMergeableInfo = z.object({\n submit_type: z.enum([\n 'MERGE_IF_NECESSARY',\n 'FAST_FORWARD_ONLY',\n 'REBASE_IF_NECESSARY',\n 'REBASE_ALWAYS',\n 'MERGE_ALWAYS',\n 'CHERRY_PICK',\n ]),\n mergeable: z.boolean(),\n});\nexport type GerritMergeableInfo = z.infer<typeof GerritMergeableInfo>;\n\nexport const GerritRepos = z.record(z.string(), z.object({}).loose());\n\nexport const GerritChangeMessages = LooseArray(GerritChangeMessageInfo);\n"],"mappings":";;;;;;;;;AAUA,MAAa,oBAAoB,EAAE,OAAO;CACxC,aAAa,EAAE,OAAO;CACtB,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;AAChC,CAAC;AAGD,MAAa,kBAAkB,EAAE,OAAO;CACtC,UAAU,kBAAkB,SAAS;CACrC,UAAU,kBAAkB,SAAS;CACrC,UAAU,EAAE,QAAQ,CAAC,CAAC,SAAS;AACjC,CAAC;AAGD,MAAa,mBAAmB,EAAE,OAAO;CACvC,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,SAAS,EAAE,QAAQ,CAAC,CAAC,SAAS;AAChC,CAAC;AAGD,MAAa,qBAAqB,EAAE,OAAO;CACzC,UAAU;CACV,KAAK,EAAE,OAAO;CACd,SAAS,EAAE,OAAO;CAClB,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAAC,CAAC,SAAS;CACzD,qBAAqB,EAAE,OAAO,CAAC,CAAC,SAAS;AAC3C,CAAC;AAGD,MAAa,0BAA0B,EAAE,OAAO;CAC9C,IAAI,EAAE,OAAO;CACb,SAAS,EAAE,OAAO;CAClB,KAAK,EAAE,OAAO,CAAC,CAAC,SAAS;AAC3B,CAAC;AAGD,MAAa,eAAe,EAAE,OAAO;CACnC,QAAQ,EAAE,OAAO;CACjB,WAAW,EAAE,OAAO;CACpB,SAAS,EAAE,OAAO;CAClB,QAAQ,EAAE,KAAK;EAAC;EAAO;EAAU;CAAW,CAAC;CAC7C,SAAS,EAAE,OAAO;CAClB,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC;CAC5B,aAAa,EAAE,QAAQ,CAAC,CAAC,SAAS;CAClC,SAAS,EAAE,OAAO;CAClB,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,eAAe,CAAC,CAAC,SAAS;CACvD,WAAW,EACR,OAAO,EACN,UAAU,WAAW,iBAAiB,CAAC,CAAC,SAAS,EACnD,CAAC,CAAC,CACD,SAAS;CACZ,UAAU,WAAW,uBAAuB,CAAC,CAAC,SAAS;CACvD,kBAAkB,EAAE,OAAO,CAAC,CAAC,SAAS;CACtC,WAAW,EAAE,OAAO,EAAE,OAAO,GAAG,kBAAkB,CAAC,CAAC,SAAS;CAC7D,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS;CACxC,eAAe,EAAE,QAAQ,CAAC,CAAC,SAAS;AACtC,CAAC;AAGD,MAAa,gBAAgB,WAAW,YAAY;AAEpD,MAAa,sBAAsB,EAAE,OAAO;CAC1C,QAAQ,EAAE,OAAO,EAAE,OAAO,OAAO,GAAG,EAAE,OAAO,CAAC;CAC9C,eAAe,EAAE,OAAO;AAC1B,CAAC;AAMD,MAAa,oBAAoB,EAAE,OAAO;CACxC,IAAI,EAAE,OAAO;CACb,MAAM,EAAE,OAAO;CACf,OAAO,EAAE,KAAK;EAAC;EAAU;EAAa;CAAQ,CAAC,CAAC,CAAC,SAAS;CAC1D,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,mBAAmB,CAAC,CAAC,SAAS;AAC7D,CAAC;AAGD,MAAa,mBAAmB,EAAE,OAAO;CACvC,KAAK,EAAE,OAAO;CACd,UAAU,EAAE,OAAO;AACrB,CAAC;AAGD,MAAa,sBAAsB,EAAE,OAAO;CAC1C,aAAa,EAAE,KAAK;EAClB;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CACD,WAAW,EAAE,QAAQ;AACvB,CAAC;AAGD,MAAa,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AAEpE,MAAa,uBAAuB,WAAW,uBAAuB"}
1
+ {"version":3,"file":"schema.js","names":[],"sources":["../../../../lib/modules/platform/gerrit/schema.ts"],"sourcesContent":["import { z } from 'zod/v4';\nimport { LooseArray } from '../../../util/schema-utils/index.ts';\n\n/**\n * The Schemas for the Gerrit API Responses ({@link https://gerrit-review.googlesource.com/Documentation/rest-api.html | REST-API})\n * minimized to only needed properties.\n *\n * @packageDocumentation\n */\n\nexport const GerritAccountInfo = z.object({\n _account_id: z.number(),\n username: z.string().optional(),\n});\nexport type GerritAccountInfo = z.infer<typeof GerritAccountInfo>;\n\nexport const GerritLabelInfo = z.object({\n approved: GerritAccountInfo.optional(),\n rejected: GerritAccountInfo.optional(),\n blocking: z.boolean().optional(),\n});\nexport type GerritLabelInfo = z.infer<typeof GerritLabelInfo>;\n\nexport const GerritActionInfo = z.object({\n method: z.string().optional(),\n enabled: z.boolean().optional(),\n});\nexport type GerritActionInfo = z.infer<typeof GerritActionInfo>;\n\nexport const GerritRevisionInfo = z.object({\n uploader: GerritAccountInfo,\n ref: z.string(),\n created: z.string(),\n actions: z.record(z.string(), GerritActionInfo).optional(),\n commit_with_footers: z.string().optional(),\n});\nexport type GerritRevisionInfo = z.infer<typeof GerritRevisionInfo>;\n\nexport const GerritChangeMessageInfo = z.object({\n id: z.string(),\n message: z.string(),\n tag: z.string().optional(),\n});\nexport type GerritChangeMessageInfo = z.infer<typeof GerritChangeMessageInfo>;\n\nexport const GerritChange = z.object({\n branch: z.string(),\n change_id: z.string(),\n subject: z.string(),\n status: z.enum(['NEW', 'MERGED', 'ABANDONED']),\n created: z.string(),\n updated: z.string(),\n hashtags: z.array(z.string()),\n submittable: z.boolean().optional(),\n _number: z.number(),\n labels: z.record(z.string(), GerritLabelInfo).optional(),\n reviewers: z\n .object({\n REVIEWER: LooseArray(GerritAccountInfo).optional(),\n })\n .optional(),\n messages: LooseArray(GerritChangeMessageInfo).optional(),\n current_revision: z.string().optional(),\n revisions: z.record(z.string(), GerritRevisionInfo).optional(),\n problems: z.array(z.unknown()).optional(),\n _more_changes: z.boolean().optional(),\n});\nexport type GerritChange = z.infer<typeof GerritChange>;\n\nexport const GerritChanges = LooseArray(GerritChange);\n\nexport const GerritLabelTypeInfo = z.object({\n values: z.record(z.coerce.number(), z.string()),\n default_value: z.number(),\n});\nexport type GerritLabelTypeInfo = z.infer<typeof GerritLabelTypeInfo>;\n\n/** Map of label name (e.g. \"Code-Review\") to its definition on a project. */\nexport type GerritLabels = Record<string, GerritLabelTypeInfo>;\n\nexport const GerritProjectInfo = z.object({\n id: z.string(),\n name: z.string(),\n state: z.enum(['ACTIVE', 'READ_ONLY', 'HIDDEN']).optional(),\n labels: z.record(z.string(), GerritLabelTypeInfo).optional(),\n});\nexport type GerritProjectInfo = z.infer<typeof GerritProjectInfo>;\n\nexport const GerritBranchInfo = z.object({\n ref: z.string(),\n revision: z.string(),\n});\nexport type GerritBranchInfo = z.infer<typeof GerritBranchInfo>;\n\nexport const GerritMergeableInfo = z.object({\n submit_type: z.enum([\n 'MERGE_IF_NECESSARY',\n 'FAST_FORWARD_ONLY',\n 'REBASE_IF_NECESSARY',\n 'REBASE_ALWAYS',\n 'MERGE_ALWAYS',\n 'CHERRY_PICK',\n ]),\n mergeable: z.boolean(),\n});\nexport type GerritMergeableInfo = z.infer<typeof GerritMergeableInfo>;\n\nexport const GerritRepos = z.record(z.string(), z.object({}).loose());\n\nexport const GerritChangeMessages = LooseArray(GerritChangeMessageInfo);\n\nexport const GerritReviewResult = z.object({\n /** Only present on Gerrit >= 3.10 */\n change_info: GerritChange.optional(),\n});\nexport type GerritReviewResult = z.infer<typeof GerritReviewResult>;\n"],"mappings":";;;;;;;;;AAUA,MAAa,oBAAoB,EAAE,OAAO;CACxC,aAAa,EAAE,OAAO;CACtB,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;AAChC,CAAC;AAGD,MAAa,kBAAkB,EAAE,OAAO;CACtC,UAAU,kBAAkB,SAAS;CACrC,UAAU,kBAAkB,SAAS;CACrC,UAAU,EAAE,QAAQ,CAAC,CAAC,SAAS;AACjC,CAAC;AAGD,MAAa,mBAAmB,EAAE,OAAO;CACvC,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,SAAS,EAAE,QAAQ,CAAC,CAAC,SAAS;AAChC,CAAC;AAGD,MAAa,qBAAqB,EAAE,OAAO;CACzC,UAAU;CACV,KAAK,EAAE,OAAO;CACd,SAAS,EAAE,OAAO;CAClB,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAAC,CAAC,SAAS;CACzD,qBAAqB,EAAE,OAAO,CAAC,CAAC,SAAS;AAC3C,CAAC;AAGD,MAAa,0BAA0B,EAAE,OAAO;CAC9C,IAAI,EAAE,OAAO;CACb,SAAS,EAAE,OAAO;CAClB,KAAK,EAAE,OAAO,CAAC,CAAC,SAAS;AAC3B,CAAC;AAGD,MAAa,eAAe,EAAE,OAAO;CACnC,QAAQ,EAAE,OAAO;CACjB,WAAW,EAAE,OAAO;CACpB,SAAS,EAAE,OAAO;CAClB,QAAQ,EAAE,KAAK;EAAC;EAAO;EAAU;CAAW,CAAC;CAC7C,SAAS,EAAE,OAAO;CAClB,SAAS,EAAE,OAAO;CAClB,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC;CAC5B,aAAa,EAAE,QAAQ,CAAC,CAAC,SAAS;CAClC,SAAS,EAAE,OAAO;CAClB,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,eAAe,CAAC,CAAC,SAAS;CACvD,WAAW,EACR,OAAO,EACN,UAAU,WAAW,iBAAiB,CAAC,CAAC,SAAS,EACnD,CAAC,CAAC,CACD,SAAS;CACZ,UAAU,WAAW,uBAAuB,CAAC,CAAC,SAAS;CACvD,kBAAkB,EAAE,OAAO,CAAC,CAAC,SAAS;CACtC,WAAW,EAAE,OAAO,EAAE,OAAO,GAAG,kBAAkB,CAAC,CAAC,SAAS;CAC7D,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS;CACxC,eAAe,EAAE,QAAQ,CAAC,CAAC,SAAS;AACtC,CAAC;AAGD,MAAa,gBAAgB,WAAW,YAAY;AAEpD,MAAa,sBAAsB,EAAE,OAAO;CAC1C,QAAQ,EAAE,OAAO,EAAE,OAAO,OAAO,GAAG,EAAE,OAAO,CAAC;CAC9C,eAAe,EAAE,OAAO;AAC1B,CAAC;AAMD,MAAa,oBAAoB,EAAE,OAAO;CACxC,IAAI,EAAE,OAAO;CACb,MAAM,EAAE,OAAO;CACf,OAAO,EAAE,KAAK;EAAC;EAAU;EAAa;CAAQ,CAAC,CAAC,CAAC,SAAS;CAC1D,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,mBAAmB,CAAC,CAAC,SAAS;AAC7D,CAAC;AAGD,MAAa,mBAAmB,EAAE,OAAO;CACvC,KAAK,EAAE,OAAO;CACd,UAAU,EAAE,OAAO;AACrB,CAAC;AAGD,MAAa,sBAAsB,EAAE,OAAO;CAC1C,aAAa,EAAE,KAAK;EAClB;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CACD,WAAW,EAAE,QAAQ;AACvB,CAAC;AAGD,MAAa,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AAEpE,MAAa,uBAAuB,WAAW,uBAAuB;AAEpC,EAAE,OAAO;;AAEzC,aAAa,aAAa,SAAS,EACrC,CAAC"}
@@ -6,6 +6,8 @@ import { joinUrlParts, parseUrl } from "../../../util/url.js";
6
6
  import { find } from "../../../util/host-rules.js";
7
7
  import { toLongCommitSha } from "../../../util/schema-utils/git.js";
8
8
  import { hashBody } from "../pr-body.js";
9
+ import { isUndefined } from "@sindresorhus/is";
10
+ import { DateTime } from "luxon";
9
11
  //#region lib/modules/platform/gerrit/utils.ts
10
12
  const MIN_GERRIT_VERSION = "3.0.0";
11
13
  const TAG_PULL_REQUEST_BODY = "pull-request";
@@ -52,6 +54,11 @@ function mapPrStateToGerritFilter(state) {
52
54
  default: return null;
53
55
  }
54
56
  }
57
+ function prStateMatchesFilter(prState, findState) {
58
+ if (isUndefined(findState) || findState === "all") return true;
59
+ if (findState === "!open") return prState !== "open";
60
+ return prState === findState;
61
+ }
55
62
  function mapGerritChangeToPr(change, knownProperties) {
56
63
  const sourceBranch = knownProperties?.sourceBranch ?? extractSourceBranch(change);
57
64
  if (!sourceBranch) return null;
@@ -62,6 +69,7 @@ function mapGerritChangeToPr(change, knownProperties) {
62
69
  targetBranch: change.branch,
63
70
  title: change.subject,
64
71
  createdAt: convertGerritDateToISO(change.created),
72
+ updatedAt: convertGerritDateToISO(change.updated),
65
73
  labels: change.hashtags,
66
74
  reviewers: coerceArray(change.reviewers?.REVIEWER?.map((reviewer) => reviewer.username)),
67
75
  bodyStruct: { hash: hashBody(knownProperties?.prBody ?? findPullRequestBody(change)) },
@@ -102,7 +110,13 @@ function mapBranchStatusToLabel(state, label) {
102
110
  function convertGerritDateToISO(date) {
103
111
  return date.replace(" ", "T");
104
112
  }
113
+ /**
114
+ * Current time formatted like a Gerrit timestamp
115
+ */
116
+ function currentGerritTimestamp() {
117
+ return DateTime.utc().toFormat("yyyy-MM-dd HH:mm:ss.SSS000000");
118
+ }
105
119
  //#endregion
106
- export { MAX_GERRIT_COMMENT_SIZE, MIN_GERRIT_VERSION, REQUEST_DETAILS_FOR_PRS, TAG_PULL_REQUEST_BODY, convertGerritDateToISO, extractSourceBranch, findPullRequestBody, getGerritRepoUrl, mapBranchStatusToLabel, mapGerritChangeStateToPrState, mapGerritChangeToPr, mapPrStateToGerritFilter };
120
+ export { MAX_GERRIT_COMMENT_SIZE, MIN_GERRIT_VERSION, REQUEST_DETAILS_FOR_PRS, TAG_PULL_REQUEST_BODY, convertGerritDateToISO, currentGerritTimestamp, extractSourceBranch, findPullRequestBody, getGerritRepoUrl, mapBranchStatusToLabel, mapGerritChangeStateToPrState, mapGerritChangeToPr, mapPrStateToGerritFilter, prStateMatchesFilter };
107
121
 
108
122
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":["hostRules.find"],"sources":["../../../../lib/modules/platform/gerrit/utils.ts"],"sourcesContent":["import { CONFIG_GIT_URL_UNAVAILABLE } from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport type { BranchStatus, PrState } from '../../../types/index.ts';\nimport { coerceArray } from '../../../util/array.ts';\nimport * as hostRules from '../../../util/host-rules.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { toLongCommitSha } from '../../../util/schema-utils/git.ts';\nimport { joinUrlParts, parseUrl } from '../../../util/url.ts';\nimport { hashBody } from '../pr-body.ts';\nimport type { GitUrlOption, Pr } from '../types.ts';\nimport type { GerritChange, GerritLabelTypeInfo } from './schema.ts';\nimport type { GerritChangeStatus, GerritRequestDetail } from './types.ts';\n\nexport const MIN_GERRIT_VERSION = '3.0.0';\n\nexport const TAG_PULL_REQUEST_BODY = 'pull-request';\n\nconst DEFAULT_SSH_PORT = `29418`;\n\n/**\n * Max comment size in Gerrit (16kiB by default)\n * https://gerrit-review.googlesource.com/Documentation/config-gerrit.html#change:~:text=change.commentSizeLimit\n */\nexport const MAX_GERRIT_COMMENT_SIZE = 16 * 1024;\n\nexport const REQUEST_DETAILS_FOR_PRS: GerritRequestDetail[] = [\n 'MESSAGES', // to get the pr body\n 'LABELS', // to get the reviewers\n 'DETAILED_ACCOUNTS', // to get the reviewers usernames\n 'CURRENT_REVISION', // to get the commit message\n 'COMMIT_FOOTERS', // to get the commit message\n] as const;\n\nexport function getGerritRepoUrl(\n repository: string,\n endpoint: string,\n gitUrl: GitUrlOption | undefined,\n username: string,\n): string {\n const endpointUrl = parseUrl(endpoint);\n if (!endpointUrl) {\n throw new Error(CONFIG_GIT_URL_UNAVAILABLE);\n }\n\n const url =\n gitUrl === 'ssh'\n ? createSshUrl(endpointUrl, repository, username)\n : createHttpUrl(endpointUrl, endpoint, repository);\n logger.trace({ url }, 'using URL based on configured endpoint');\n\n return url;\n}\n\nfunction createSshUrl(url: URL, repository: string, username: string): string {\n return `ssh://${username}@${url.hostname}:${DEFAULT_SSH_PORT}/${repository}`;\n}\n\nfunction createHttpUrl(url: URL, endpoint: string, repository: string): string {\n // Find options for current host and determine Git endpoint\n const opts = hostRules.find({\n hostType: 'gerrit',\n url: endpoint,\n });\n\n if (!(opts.username && opts.password)) {\n throw new Error(\n 'Init: You must configure a Gerrit Server username/password',\n );\n }\n\n url.username = opts.username;\n url.password = opts.password;\n url.pathname = joinUrlParts(url.pathname, 'a', repository);\n return url.toString();\n}\n\nexport function mapPrStateToGerritFilter(state?: PrState): string | null {\n switch (state) {\n case 'merged':\n return 'status:merged';\n case 'open':\n return 'status:open';\n case 'closed':\n return 'status:abandoned';\n case '!open':\n return '-status:open';\n case 'all':\n default:\n return null;\n }\n}\n\nexport function mapGerritChangeToPr(\n change: GerritChange,\n knownProperties?: {\n sourceBranch?: string;\n prBody?: string;\n },\n): Pr | null {\n const sourceBranch =\n knownProperties?.sourceBranch ?? extractSourceBranch(change);\n if (!sourceBranch) {\n return null;\n }\n return {\n number: change._number,\n state: mapGerritChangeStateToPrState(change.status),\n sourceBranch,\n targetBranch: change.branch,\n title: change.subject,\n createdAt: convertGerritDateToISO(change.created),\n labels: change.hashtags,\n reviewers: coerceArray(\n change.reviewers?.REVIEWER?.map((reviewer) => reviewer.username!),\n ),\n bodyStruct: {\n hash: hashBody(knownProperties?.prBody ?? findPullRequestBody(change)),\n },\n sha: toLongCommitSha(change.current_revision),\n };\n}\n\nexport function mapGerritChangeStateToPrState(\n state: GerritChangeStatus,\n): 'merged' | 'open' | 'closed' {\n switch (state) {\n case 'NEW':\n return 'open';\n case 'MERGED':\n return 'merged';\n case 'ABANDONED':\n return 'closed';\n }\n}\n\nexport function extractSourceBranch(change: GerritChange): string | undefined {\n let sourceBranch: string | undefined = undefined;\n\n if (change.current_revision) {\n const re = regEx(/^Renovate-Branch: (?<branch>.+)$/m);\n const currentRevision = change.revisions![change.current_revision];\n const message = currentRevision.commit_with_footers;\n // v8 ignore else -- TODO: add test #40625\n if (message) {\n sourceBranch = re.exec(message)?.groups?.branch;\n }\n }\n\n return sourceBranch ?? undefined;\n}\n\nexport function findPullRequestBody(change: GerritChange): string | undefined {\n const msg = Array.from(coerceArray(change.messages))\n .reverse()\n .find((msg) => msg.tag === TAG_PULL_REQUEST_BODY);\n if (msg) {\n // Gerrit adds a \"Patch Set X:\" prefix to comments\n return msg.message.replace(regEx(/^Patch Set \\d+:\\n\\n/), '');\n }\n return undefined;\n}\n\nexport function mapBranchStatusToLabel(\n state: BranchStatus | 'UNKNOWN', // suppress default path code removal\n label: GerritLabelTypeInfo,\n): number {\n const numbers = Object.keys(label.values).map((x) => parseInt(x, 10));\n switch (state) {\n case 'green':\n return Math.max(...numbers);\n case 'yellow':\n case 'red':\n return Math.min(...numbers);\n }\n /* v8 ignore next -- only reachable for the artificial 'UNKNOWN' state, which callers never pass */\n return label.default_value;\n}\n\n// Convert Gerrit date format to ISO format\nexport function convertGerritDateToISO(date: string): string {\n return date.replace(' ', 'T');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","names":["hostRules.find"],"sources":["../../../../lib/modules/platform/gerrit/utils.ts"],"sourcesContent":["import { isUndefined } from '@sindresorhus/is';\nimport { DateTime } from 'luxon';\nimport { CONFIG_GIT_URL_UNAVAILABLE } from '../../../constants/error-messages.ts';\nimport { logger } from '../../../logger/index.ts';\nimport type { BranchStatus, PrState } from '../../../types/index.ts';\nimport { coerceArray } from '../../../util/array.ts';\nimport * as hostRules from '../../../util/host-rules.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { toLongCommitSha } from '../../../util/schema-utils/git.ts';\nimport { joinUrlParts, parseUrl } from '../../../util/url.ts';\nimport { hashBody } from '../pr-body.ts';\nimport type { FindPRConfig, GitUrlOption, Pr } from '../types.ts';\nimport type { GerritChange, GerritLabelTypeInfo } from './schema.ts';\nimport type { GerritChangeStatus, GerritRequestDetail } from './types.ts';\n\nexport const MIN_GERRIT_VERSION = '3.0.0';\n\nexport const TAG_PULL_REQUEST_BODY = 'pull-request';\n\nconst DEFAULT_SSH_PORT = `29418`;\n\n/**\n * Max comment size in Gerrit (16kiB by default)\n * https://gerrit-review.googlesource.com/Documentation/config-gerrit.html#change:~:text=change.commentSizeLimit\n */\nexport const MAX_GERRIT_COMMENT_SIZE = 16 * 1024;\n\nexport const REQUEST_DETAILS_FOR_PRS: GerritRequestDetail[] = [\n 'MESSAGES', // to get the pr body\n 'LABELS', // to get the reviewers\n 'DETAILED_ACCOUNTS', // to get the reviewers usernames\n 'CURRENT_REVISION', // to get the commit message\n 'COMMIT_FOOTERS', // to get the commit message\n] as const;\n\nexport function getGerritRepoUrl(\n repository: string,\n endpoint: string,\n gitUrl: GitUrlOption | undefined,\n username: string,\n): string {\n const endpointUrl = parseUrl(endpoint);\n if (!endpointUrl) {\n throw new Error(CONFIG_GIT_URL_UNAVAILABLE);\n }\n\n const url =\n gitUrl === 'ssh'\n ? createSshUrl(endpointUrl, repository, username)\n : createHttpUrl(endpointUrl, endpoint, repository);\n logger.trace({ url }, 'using URL based on configured endpoint');\n\n return url;\n}\n\nfunction createSshUrl(url: URL, repository: string, username: string): string {\n return `ssh://${username}@${url.hostname}:${DEFAULT_SSH_PORT}/${repository}`;\n}\n\nfunction createHttpUrl(url: URL, endpoint: string, repository: string): string {\n // Find options for current host and determine Git endpoint\n const opts = hostRules.find({\n hostType: 'gerrit',\n url: endpoint,\n });\n\n if (!(opts.username && opts.password)) {\n throw new Error(\n 'Init: You must configure a Gerrit Server username/password',\n );\n }\n\n url.username = opts.username;\n url.password = opts.password;\n url.pathname = joinUrlParts(url.pathname, 'a', repository);\n return url.toString();\n}\n\nexport function mapPrStateToGerritFilter(state?: PrState): string | null {\n switch (state) {\n case 'merged':\n return 'status:merged';\n case 'open':\n return 'status:open';\n case 'closed':\n return 'status:abandoned';\n case '!open':\n return '-status:open';\n case 'all':\n default:\n return null;\n }\n}\n\nexport function prStateMatchesFilter(\n prState: Pr['state'],\n findState: FindPRConfig['state'],\n): boolean {\n if (isUndefined(findState) || findState === 'all') {\n return true;\n }\n if (findState === '!open') {\n return prState !== 'open';\n }\n return prState === findState;\n}\n\nexport function mapGerritChangeToPr(\n change: GerritChange,\n knownProperties?: {\n sourceBranch?: string;\n prBody?: string;\n },\n): Pr | null {\n const sourceBranch =\n knownProperties?.sourceBranch ?? extractSourceBranch(change);\n if (!sourceBranch) {\n return null;\n }\n return {\n number: change._number,\n state: mapGerritChangeStateToPrState(change.status),\n sourceBranch,\n targetBranch: change.branch,\n title: change.subject,\n createdAt: convertGerritDateToISO(change.created),\n updatedAt: convertGerritDateToISO(change.updated),\n labels: change.hashtags,\n reviewers: coerceArray(\n change.reviewers?.REVIEWER?.map((reviewer) => reviewer.username!),\n ),\n bodyStruct: {\n hash: hashBody(knownProperties?.prBody ?? findPullRequestBody(change)),\n },\n sha: toLongCommitSha(change.current_revision),\n };\n}\n\nexport function mapGerritChangeStateToPrState(\n state: GerritChangeStatus,\n): 'merged' | 'open' | 'closed' {\n switch (state) {\n case 'NEW':\n return 'open';\n case 'MERGED':\n return 'merged';\n case 'ABANDONED':\n return 'closed';\n }\n}\n\nexport function extractSourceBranch(change: GerritChange): string | undefined {\n let sourceBranch: string | undefined = undefined;\n\n if (change.current_revision) {\n const re = regEx(/^Renovate-Branch: (?<branch>.+)$/m);\n const currentRevision = change.revisions![change.current_revision];\n const message = currentRevision.commit_with_footers;\n // v8 ignore else -- TODO: add test #40625\n if (message) {\n sourceBranch = re.exec(message)?.groups?.branch;\n }\n }\n\n return sourceBranch ?? undefined;\n}\n\nexport function findPullRequestBody(change: GerritChange): string | undefined {\n const msg = Array.from(coerceArray(change.messages))\n .reverse()\n .find((msg) => msg.tag === TAG_PULL_REQUEST_BODY);\n if (msg) {\n // Gerrit adds a \"Patch Set X:\" prefix to comments\n return msg.message.replace(regEx(/^Patch Set \\d+:\\n\\n/), '');\n }\n return undefined;\n}\n\nexport function mapBranchStatusToLabel(\n state: BranchStatus | 'UNKNOWN', // suppress default path code removal\n label: GerritLabelTypeInfo,\n): number {\n const numbers = Object.keys(label.values).map((x) => parseInt(x, 10));\n switch (state) {\n case 'green':\n return Math.max(...numbers);\n case 'yellow':\n case 'red':\n return Math.min(...numbers);\n }\n /* v8 ignore next -- only reachable for the artificial 'UNKNOWN' state, which callers never pass */\n return label.default_value;\n}\n\n// Convert Gerrit date format to ISO format\nexport function convertGerritDateToISO(date: string): string {\n return date.replace(' ', 'T');\n}\n\n/**\n * Current time formatted like a Gerrit timestamp\n */\nexport function currentGerritTimestamp(): string {\n return DateTime.utc().toFormat('yyyy-MM-dd HH:mm:ss.SSS000000');\n}\n"],"mappings":"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"}
@@ -76,6 +76,8 @@ export interface Pr {
76
76
  state: string;
77
77
  targetBranch?: string;
78
78
  title: string;
79
+ /** Only set by Gerrit platform for now */
80
+ updatedAt?: string;
79
81
  isDraft?: boolean;
80
82
  }
81
83
  /**
@@ -161,6 +161,9 @@ export interface RepoCacheData {
161
161
  'bitbucket-server'?: {
162
162
  pullRequestsCache?: unknown;
163
163
  };
164
+ gerrit?: {
165
+ pullRequestsCache?: unknown;
166
+ };
164
167
  };
165
168
  prComments?: Record<number, Record<string, string>>;
166
169
  onboardingBranchCache?: OnboardingBranchCache;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "renovate",
3
3
  "description": "Automated dependency updates. Flexible so you don't need to be.",
4
- "version": "44.207.0",
4
+ "version": "44.208.0",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "renovate": "dist/renovate.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$id": "https://docs.renovatebot.com/renovate-schema.json",
3
- "title": "JSON schema for Renovate 44.207.0 config files (https://renovatebot.com/)",
3
+ "title": "JSON schema for Renovate 44.208.0 config files (https://renovatebot.com/)",
4
4
  "$schema": "http://json-schema.org/draft-07/schema#",
5
- "x-renovate-version": "44.207.0",
5
+ "x-renovate-version": "44.208.0",
6
6
  "allowComments": true,
7
7
  "type": "object",
8
8
  "definitions": {