renovate 44.84.0 → 44.85.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.
- package/dist/config/presets/forgejo/index.js +3 -2
- package/dist/config/presets/forgejo/index.js.map +1 -1
- package/dist/config/presets/gitea/index.js +10 -3
- package/dist/config/presets/gitea/index.js.map +1 -1
- package/dist/modules/platform/api.js +6 -6
- package/dist/modules/platform/forgejo/index.js +21 -587
- package/dist/modules/platform/forgejo/index.js.map +1 -1
- package/dist/modules/platform/gitea/gitea-helper.js +90 -74
- package/dist/modules/platform/gitea/gitea-helper.js.map +1 -1
- package/dist/modules/platform/gitea/index.js +561 -529
- package/dist/modules/platform/gitea/index.js.map +1 -1
- package/dist/modules/platform/gitea/labels.js +42 -0
- package/dist/modules/platform/gitea/labels.js.map +1 -0
- package/dist/modules/platform/gitea/pr-cache.js +66 -28
- package/dist/modules/platform/gitea/pr-cache.js.map +1 -1
- package/dist/modules/platform/gitea/schema.js +141 -5
- package/dist/modules/platform/gitea/schema.js.map +1 -1
- package/dist/modules/platform/gitea/utils.js +10 -8
- package/dist/modules/platform/gitea/utils.js.map +1 -1
- package/dist/util/cache/repository/types.d.ts +13 -8
- package/dist/util/http/forgejo.js +6 -35
- package/dist/util/http/forgejo.js.map +1 -1
- package/dist/workers/repository/update/pr/changelog/forgejo/index.js +1 -1
- package/dist/workers/repository/update/pr/changelog/forgejo/index.js.map +1 -1
- package/dist/workers/repository/update/pr/changelog/gitea/index.js +3 -3
- package/dist/workers/repository/update/pr/changelog/gitea/index.js.map +1 -1
- package/package.json +1 -1
- package/renovate-schema.json +2 -2
- package/dist/modules/platform/forgejo/forgejo-helper.js +0 -235
- package/dist/modules/platform/forgejo/forgejo-helper.js.map +0 -1
- package/dist/modules/platform/forgejo/pr-cache.js +0 -114
- package/dist/modules/platform/forgejo/pr-cache.js.map +0 -1
- package/dist/modules/platform/forgejo/schema.js +0 -149
- package/dist/modules/platform/forgejo/schema.js.map +0 -1
- package/dist/modules/platform/forgejo/utils.js +0 -115
- package/dist/modules/platform/forgejo/utils.js.map +0 -1
|
@@ -9,12 +9,13 @@ import { ensureTrailingSlash } from "../../../util/url.js";
|
|
|
9
9
|
import { parseJson } from "../../../util/common.js";
|
|
10
10
|
import { map } from "../../../util/promises.js";
|
|
11
11
|
import { getBranchCommit, initRepo as initRepo$1 } from "../../../util/git/index.js";
|
|
12
|
-
import { setBaseUrl } from "../../../util/http/gitea.js";
|
|
12
|
+
import { GiteaHttp, setBaseUrl } from "../../../util/http/gitea.js";
|
|
13
13
|
import { getPrBodyStruct, hashBody } from "../pr-body.js";
|
|
14
14
|
import { repoFingerprint } from "../util.js";
|
|
15
15
|
import { smartTruncate } from "../utils/pr-body.js";
|
|
16
|
-
import { DRAFT_PREFIX, getMergeMethod, getRepoUrl, isAllowed, smartLinks, toRenovatePR, trimTrailingApiPath, usableRepo } from "./utils.js";
|
|
17
|
-
import { closeIssue, createComment, createCommitStatus, createIssue, createPR, deleteComment, getCombinedCommitStatus, getComments, getCurrentUser, getIssue as getIssue$1,
|
|
16
|
+
import { DRAFT_PREFIX, getMergeMethod, getRepoUrl, isAllowed, platformDisplayName, smartLinks, toRenovatePR, trimTrailingApiPath, usableRepo } from "./utils.js";
|
|
17
|
+
import { closeIssue, createComment, createCommitStatus, createIssue, createPR, deleteComment, getCombinedCommitStatus, getComments, getCurrentUser, getIssue as getIssue$1, getPR, getPRByBranch, getRepo, getRepoContents, getVersion, isOrg, mergePR, orgListRepos, requestPrReviewers, searchIssues, searchRepos, toPlatformStatusMapping, toRenovateStatusMapping, unassignLabel, updateComment, updateIssue, updateIssueLabels, updatePR } from "./gitea-helper.js";
|
|
18
|
+
import { lookupLabelByName } from "./labels.js";
|
|
18
19
|
import { GiteaPrCache } from "./pr-cache.js";
|
|
19
20
|
import { isNumber, isString } from "@sindresorhus/is";
|
|
20
21
|
import semver from "semver";
|
|
@@ -22,6 +23,7 @@ import semver from "semver";
|
|
|
22
23
|
var gitea_exports = /* @__PURE__ */ __exportAll({
|
|
23
24
|
addAssignees: () => addAssignees,
|
|
24
25
|
addReviewers: () => addReviewers,
|
|
26
|
+
createPlatform: () => createPlatform,
|
|
25
27
|
createPr: () => createPr,
|
|
26
28
|
deleteLabel: () => deleteLabel,
|
|
27
29
|
ensureComment: () => ensureComment,
|
|
@@ -40,6 +42,7 @@ var gitea_exports = /* @__PURE__ */ __exportAll({
|
|
|
40
42
|
getPrList: () => getPrList,
|
|
41
43
|
getRawFile: () => getRawFile,
|
|
42
44
|
getRepos: () => getRepos,
|
|
45
|
+
giteaHttp: () => giteaHttp,
|
|
43
46
|
id: () => id,
|
|
44
47
|
initPlatform: () => initPlatform,
|
|
45
48
|
initRepo: () => initRepo,
|
|
@@ -51,25 +54,7 @@ var gitea_exports = /* @__PURE__ */ __exportAll({
|
|
|
51
54
|
updatePr: () => updatePr
|
|
52
55
|
});
|
|
53
56
|
const id = "gitea";
|
|
54
|
-
const
|
|
55
|
-
hostType: "gitea",
|
|
56
|
-
endpoint: "https://gitea.com/",
|
|
57
|
-
version: "0.0.0",
|
|
58
|
-
isForgejo: false
|
|
59
|
-
};
|
|
60
|
-
let config = {};
|
|
61
|
-
let botUserID;
|
|
62
|
-
let botUserName;
|
|
63
|
-
function resetPlatform() {
|
|
64
|
-
config = {};
|
|
65
|
-
botUserID = void 0;
|
|
66
|
-
botUserName = void 0;
|
|
67
|
-
defaults.hostType = "gitea";
|
|
68
|
-
defaults.endpoint = "https://gitea.com/";
|
|
69
|
-
defaults.version = "0.0.0";
|
|
70
|
-
defaults.isForgejo = false;
|
|
71
|
-
setBaseUrl(defaults.endpoint);
|
|
72
|
-
}
|
|
57
|
+
const giteaHttp = new GiteaHttp();
|
|
73
58
|
function toRenovateIssue(data) {
|
|
74
59
|
return {
|
|
75
60
|
number: data.number,
|
|
@@ -89,538 +74,585 @@ function findCommentByTopic(comments, topic) {
|
|
|
89
74
|
function findCommentByContent(comments, content) {
|
|
90
75
|
return comments.find((c) => c.body.trim() === content) ?? null;
|
|
91
76
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Builds the platform implementation shared by every platform which speaks the
|
|
79
|
+
* Gitea API. Each call owns its own module state, so `gitea` and `forgejo` do
|
|
80
|
+
* not share caches or configuration.
|
|
81
|
+
*/
|
|
82
|
+
function createPlatform(options) {
|
|
83
|
+
const { id, http, setBaseUrl, logDetectedVersion, checkNativeAutomerge, minReviewerVersion } = options;
|
|
84
|
+
const name = platformDisplayName[id];
|
|
85
|
+
const defaults = {
|
|
86
|
+
endpoint: options.defaultEndpoint,
|
|
87
|
+
version: "0.0.0"
|
|
88
|
+
};
|
|
89
|
+
let config = {};
|
|
90
|
+
let botUserID;
|
|
91
|
+
let botUserName;
|
|
92
|
+
const prCache = new GiteaPrCache(http, id);
|
|
93
|
+
function resetPlatform() {
|
|
94
|
+
config = {};
|
|
95
|
+
prCache.reset();
|
|
96
|
+
botUserID = void 0;
|
|
97
|
+
botUserName = void 0;
|
|
98
|
+
defaults.endpoint = options.defaultEndpoint;
|
|
99
|
+
defaults.version = "0.0.0";
|
|
100
|
+
setBaseUrl(defaults.endpoint);
|
|
106
101
|
}
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
102
|
+
async function fetchIssueList() {
|
|
103
|
+
const issueList = (await searchIssues(http, config.repository, { state: "all" }, { memCache: false })).map(toRenovateIssue);
|
|
104
|
+
logger.debug(`Retrieved ${issueList.length} Issues`);
|
|
105
|
+
return issueList;
|
|
106
|
+
}
|
|
107
|
+
async function fetchRepositories({ topic, sort, order }) {
|
|
108
|
+
return (await searchRepos(http, {
|
|
109
|
+
uid: botUserID,
|
|
110
|
+
archived: false,
|
|
111
|
+
...topic && {
|
|
112
|
+
topic: true,
|
|
113
|
+
q: topic
|
|
114
|
+
},
|
|
115
|
+
...sort && { sort },
|
|
116
|
+
...order && { order }
|
|
117
|
+
})).filter(usableRepo).map((r) => r.full_name);
|
|
118
|
+
}
|
|
119
|
+
const platform = {
|
|
120
|
+
async initPlatform({ endpoint, token }) {
|
|
121
|
+
if (!token) throw new Error(`Init: You must configure a ${name} personal access token`);
|
|
122
|
+
if (endpoint) {
|
|
123
|
+
let baseEndpoint = trimTrailingApiPath(endpoint);
|
|
124
|
+
baseEndpoint = ensureTrailingSlash(baseEndpoint);
|
|
125
|
+
defaults.endpoint = baseEndpoint;
|
|
126
|
+
} else logger.debug(`Using default ${name} endpoint: ${defaults.endpoint}`);
|
|
127
|
+
setBaseUrl(defaults.endpoint);
|
|
128
|
+
let gitAuthor;
|
|
129
|
+
try {
|
|
130
|
+
const user = await getCurrentUser(http, { token });
|
|
131
|
+
gitAuthor = `${user.full_name || user.login} <${user.email}>`;
|
|
132
|
+
botUserID = user.id;
|
|
133
|
+
botUserName = user.login;
|
|
134
|
+
const env = getEnv();
|
|
135
|
+
/* v8 ignore if: experimental feature */
|
|
136
|
+
if (semver.valid(env.RENOVATE_X_PLATFORM_VERSION)) defaults.version = env.RENOVATE_X_PLATFORM_VERSION;
|
|
137
|
+
else defaults.version = await getVersion(http, { token });
|
|
138
|
+
logDetectedVersion(defaults.version);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
logger.debug({ err }, `Error authenticating with ${name}. Check your token`);
|
|
141
|
+
throw new Error("Init: Authentication failure");
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
endpoint: defaults.endpoint,
|
|
145
|
+
gitAuthor
|
|
146
|
+
};
|
|
120
147
|
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
else defaults.version = await getVersion({ token });
|
|
144
|
-
if (defaults.version?.includes("gitea-")) {
|
|
145
|
-
defaults.isForgejo = true;
|
|
146
|
-
logger.info(`Detected Forgejo instance, please use 'forgejo' platform instead`);
|
|
148
|
+
async getRawFile(fileName, repoName, branchOrTag) {
|
|
149
|
+
const repo = repoName ?? config.repository;
|
|
150
|
+
const contents = await getRepoContents(http, repo, fileName, branchOrTag);
|
|
151
|
+
if (contents.type !== "file") return null;
|
|
152
|
+
return contents.contentString;
|
|
153
|
+
},
|
|
154
|
+
async getJsonFile(fileName, repoName, branchOrTag) {
|
|
155
|
+
const raw = await platform.getRawFile(fileName, repoName, branchOrTag);
|
|
156
|
+
return parseJson(raw, fileName);
|
|
157
|
+
},
|
|
158
|
+
async initRepo({ repository, cloneSubmodules, cloneSubmodulesFilter, gitUrl }) {
|
|
159
|
+
let repo;
|
|
160
|
+
config = {};
|
|
161
|
+
config.repository = repository;
|
|
162
|
+
config.cloneSubmodules = !!cloneSubmodules;
|
|
163
|
+
config.cloneSubmodulesFilter = cloneSubmodulesFilter;
|
|
164
|
+
config.ignorePrAuthor = GlobalConfig.get("ignorePrAuthor");
|
|
165
|
+
try {
|
|
166
|
+
repo = await getRepo(http, repository);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
logger.debug({ err }, `Unknown ${name} initRepo error`);
|
|
169
|
+
throw err;
|
|
147
170
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
throw new Error("Init: Authentication failure");
|
|
152
|
-
}
|
|
153
|
-
return {
|
|
154
|
-
endpoint: defaults.endpoint,
|
|
155
|
-
gitAuthor
|
|
156
|
-
};
|
|
157
|
-
},
|
|
158
|
-
async getRawFile(fileName, repoName, branchOrTag) {
|
|
159
|
-
const repo = repoName ?? config.repository;
|
|
160
|
-
return (await getRepoContents(repo, fileName, branchOrTag)).contentString ?? null;
|
|
161
|
-
},
|
|
162
|
-
async getJsonFile(fileName, repoName, branchOrTag) {
|
|
163
|
-
const raw = await platform.getRawFile(fileName, repoName, branchOrTag);
|
|
164
|
-
return parseJson(raw, fileName);
|
|
165
|
-
},
|
|
166
|
-
async initRepo({ repository, cloneSubmodules, cloneSubmodulesFilter, gitUrl }) {
|
|
167
|
-
let repo;
|
|
168
|
-
config = {};
|
|
169
|
-
config.repository = repository;
|
|
170
|
-
config.cloneSubmodules = !!cloneSubmodules;
|
|
171
|
-
config.cloneSubmodulesFilter = cloneSubmodulesFilter;
|
|
172
|
-
config.ignorePrAuthor = GlobalConfig.get("ignorePrAuthor");
|
|
173
|
-
try {
|
|
174
|
-
repo = await getRepo(repository);
|
|
175
|
-
} catch (err) {
|
|
176
|
-
logger.debug({ err }, "Unknown Gitea initRepo error");
|
|
177
|
-
throw err;
|
|
178
|
-
}
|
|
179
|
-
if (repo.archived) {
|
|
180
|
-
logger.debug("Repository is archived - aborting renovation");
|
|
181
|
-
throw new Error(REPOSITORY_ARCHIVED);
|
|
182
|
-
}
|
|
183
|
-
if (repo.mirror) {
|
|
184
|
-
logger.debug("Repository is a mirror - aborting renovation");
|
|
185
|
-
throw new Error(REPOSITORY_MIRRORED);
|
|
186
|
-
}
|
|
187
|
-
if (repo.permissions.pull === false || repo.permissions.push === false) {
|
|
188
|
-
logger.debug("Repository does not permit pull or push - aborting renovation");
|
|
189
|
-
throw new Error(REPOSITORY_ACCESS_FORBIDDEN);
|
|
190
|
-
}
|
|
191
|
-
if (repo.empty) {
|
|
192
|
-
logger.debug("Repository is empty - aborting renovation");
|
|
193
|
-
throw new Error(REPOSITORY_EMPTY);
|
|
194
|
-
}
|
|
195
|
-
if (repo.has_pull_requests === false) {
|
|
196
|
-
logger.debug("Repo has disabled pull requests - aborting renovation");
|
|
197
|
-
throw new Error(REPOSITORY_BLOCKED);
|
|
198
|
-
}
|
|
199
|
-
const mergeStyle = [
|
|
200
|
-
repo.default_merge_style,
|
|
201
|
-
"fast-forward-only",
|
|
202
|
-
"squash",
|
|
203
|
-
"merge",
|
|
204
|
-
"rebase",
|
|
205
|
-
"rebase-merge"
|
|
206
|
-
].find((style) => isAllowed(style, repo));
|
|
207
|
-
if (mergeStyle) config.mergeMethod = mergeStyle;
|
|
208
|
-
else {
|
|
209
|
-
logger.debug("Repository has no allowed merge methods - aborting renovation");
|
|
210
|
-
throw new Error(REPOSITORY_BLOCKED);
|
|
211
|
-
}
|
|
212
|
-
config.defaultBranch = repo.default_branch;
|
|
213
|
-
logger.debug(`${repository} default branch = ${config.defaultBranch}`);
|
|
214
|
-
const url = getRepoUrl(repo, gitUrl, defaults.endpoint);
|
|
215
|
-
await initRepo$1({
|
|
216
|
-
...config,
|
|
217
|
-
url
|
|
218
|
-
});
|
|
219
|
-
config.issueList = null;
|
|
220
|
-
config.labelList = null;
|
|
221
|
-
config.hasIssuesEnabled = !repo.external_tracker && repo.has_issues;
|
|
222
|
-
return {
|
|
223
|
-
defaultBranch: config.defaultBranch,
|
|
224
|
-
isFork: !!repo.fork,
|
|
225
|
-
repoFingerprint: repoFingerprint(repo.id, defaults.endpoint)
|
|
226
|
-
};
|
|
227
|
-
},
|
|
228
|
-
async getRepos(config) {
|
|
229
|
-
logger.debug("Auto-discovering Gitea repositories");
|
|
230
|
-
try {
|
|
231
|
-
if (config?.topics) {
|
|
232
|
-
logger.debug({ topics: config.topics }, "Auto-discovering by topics");
|
|
233
|
-
const fetchRepoArgs = config.topics.map((topic) => {
|
|
234
|
-
return {
|
|
235
|
-
topic,
|
|
236
|
-
sort: config.sort,
|
|
237
|
-
order: config.order
|
|
238
|
-
};
|
|
239
|
-
});
|
|
240
|
-
const repos = await map(fetchRepoArgs, fetchRepositories);
|
|
241
|
-
return deduplicateArray(repos.flat());
|
|
171
|
+
if (repo.archived) {
|
|
172
|
+
logger.debug("Repository is archived - aborting renovation");
|
|
173
|
+
throw new Error(REPOSITORY_ARCHIVED);
|
|
242
174
|
}
|
|
243
|
-
if (
|
|
244
|
-
logger.debug(
|
|
245
|
-
|
|
246
|
-
return (await orgListRepos(organization)).filter((r) => !r.mirror && !r.archived).map((r) => r.full_name);
|
|
247
|
-
});
|
|
248
|
-
return deduplicateArray(repos.flat());
|
|
175
|
+
if (repo.mirror) {
|
|
176
|
+
logger.debug("Repository is a mirror - aborting renovation");
|
|
177
|
+
throw new Error(REPOSITORY_MIRRORED);
|
|
249
178
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
});
|
|
254
|
-
} catch (err) {
|
|
255
|
-
logger.error({ err }, "Gitea getRepos() error");
|
|
256
|
-
throw err;
|
|
257
|
-
}
|
|
258
|
-
},
|
|
259
|
-
async setBranchStatus({ branchName, context, description, state, url: target_url }) {
|
|
260
|
-
try {
|
|
261
|
-
const branchCommit = getBranchCommit(branchName);
|
|
262
|
-
await createCommitStatus(config.repository, branchCommit, {
|
|
263
|
-
state: renovateToGiteaStatusMapping[state] || "pending",
|
|
264
|
-
context,
|
|
265
|
-
description,
|
|
266
|
-
...target_url && { target_url }
|
|
267
|
-
});
|
|
268
|
-
await getCombinedCommitStatus(config.repository, branchName, { memCache: false });
|
|
269
|
-
} catch (err) {
|
|
270
|
-
logger.warn({ err }, "Failed to set branch status");
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
async getBranchStatus(branchName, internalChecksAsSuccess) {
|
|
274
|
-
let ccs;
|
|
275
|
-
try {
|
|
276
|
-
ccs = await getCombinedCommitStatus(config.repository, branchName);
|
|
277
|
-
} catch (err) {
|
|
278
|
-
if (err.statusCode === 404) {
|
|
279
|
-
logger.debug("Received 404 when checking branch status, assuming branch deletion");
|
|
280
|
-
throw new Error(REPOSITORY_CHANGED);
|
|
179
|
+
if (!repo.permissions.pull || !repo.permissions.push) {
|
|
180
|
+
logger.debug("Repository does not permit pull or push - aborting renovation");
|
|
181
|
+
throw new Error(REPOSITORY_ACCESS_FORBIDDEN);
|
|
281
182
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
},
|
|
316
|
-
async findPr({ branchName, prTitle: title, state = "all", includeOtherAuthors, targetBranch }) {
|
|
317
|
-
logger.debug(`findPr(${branchName}, ${title}, ${state})`);
|
|
318
|
-
if (includeOtherAuthors && isString(targetBranch)) {
|
|
319
|
-
const pr = await getPRByBranch(config.repository, targetBranch, branchName);
|
|
320
|
-
if (!pr) return null;
|
|
321
|
-
return toRenovatePR(pr, null);
|
|
322
|
-
}
|
|
323
|
-
const pr = (await platform.getPrList()).find((p) => p.sourceRepo === config.repository && p.sourceBranch === branchName && matchesState(p.state, state) && (!title || p.title === title));
|
|
324
|
-
if (pr) logger.debug(`Found PR #${pr.number}`);
|
|
325
|
-
return pr ?? null;
|
|
326
|
-
},
|
|
327
|
-
async createPr({ sourceBranch, targetBranch, prTitle, prBody: rawBody, labels: labelNames, platformPrOptions, draftPR }) {
|
|
328
|
-
let title = prTitle;
|
|
329
|
-
const base = targetBranch;
|
|
330
|
-
const head = sourceBranch;
|
|
331
|
-
const body = sanitize(rawBody);
|
|
332
|
-
if (draftPR) title = DRAFT_PREFIX + title;
|
|
333
|
-
logger.debug(`Creating pull request: ${title} (${head} => ${base})`);
|
|
334
|
-
try {
|
|
335
|
-
const labels = Array.isArray(labelNames) ? await map(labelNames, lookupLabelByName) : [];
|
|
336
|
-
const gpr = await createPR(config.repository, {
|
|
337
|
-
base,
|
|
338
|
-
head,
|
|
339
|
-
title,
|
|
340
|
-
body,
|
|
341
|
-
labels: labels.filter(isNumber)
|
|
183
|
+
if (repo.empty) {
|
|
184
|
+
logger.debug("Repository is empty - aborting renovation");
|
|
185
|
+
throw new Error(REPOSITORY_EMPTY);
|
|
186
|
+
}
|
|
187
|
+
if (repo.has_pull_requests === false) {
|
|
188
|
+
logger.debug("Repo has disabled pull requests - aborting renovation");
|
|
189
|
+
throw new Error(REPOSITORY_BLOCKED);
|
|
190
|
+
}
|
|
191
|
+
const mergeStyle = [
|
|
192
|
+
...repo.default_merge_style ? [repo.default_merge_style] : [],
|
|
193
|
+
"fast-forward-only",
|
|
194
|
+
"squash",
|
|
195
|
+
"merge",
|
|
196
|
+
"rebase",
|
|
197
|
+
"rebase-merge"
|
|
198
|
+
].find((style) => isAllowed(style, repo));
|
|
199
|
+
if (mergeStyle) config.mergeMethod = mergeStyle;
|
|
200
|
+
else {
|
|
201
|
+
logger.debug("Repository has no allowed merge methods - aborting renovation");
|
|
202
|
+
throw new Error(REPOSITORY_BLOCKED);
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
config.isOrgRepo = await isOrg(http, id, repo.owner.login);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
logger.debug({ err }, `${name} initRepo() error`);
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
config.defaultBranch = repo.default_branch;
|
|
211
|
+
logger.debug(`${repository} default branch = ${config.defaultBranch}`);
|
|
212
|
+
const url = getRepoUrl(repo, gitUrl, defaults.endpoint, id);
|
|
213
|
+
await initRepo$1({
|
|
214
|
+
...config,
|
|
215
|
+
url
|
|
342
216
|
});
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
217
|
+
config.issueList = null;
|
|
218
|
+
config.labelList = null;
|
|
219
|
+
prCache.initRepo(config.repository, config.ignorePrAuthor, botUserName);
|
|
220
|
+
config.hasIssuesEnabled = !repo.external_tracker && repo.has_issues;
|
|
221
|
+
config.orgName = repo.owner.login;
|
|
222
|
+
return {
|
|
223
|
+
defaultBranch: config.defaultBranch,
|
|
224
|
+
isFork: !!repo.fork,
|
|
225
|
+
repoFingerprint: repoFingerprint(repo.id, defaults.endpoint)
|
|
226
|
+
};
|
|
227
|
+
},
|
|
228
|
+
async getRepos(config) {
|
|
229
|
+
logger.debug(`Auto-discovering ${name} repositories`);
|
|
230
|
+
try {
|
|
231
|
+
if (config?.topics) {
|
|
232
|
+
logger.debug({ topics: config.topics }, "Auto-discovering by topics");
|
|
233
|
+
const fetchRepoArgs = config.topics.map((topic) => {
|
|
234
|
+
return {
|
|
235
|
+
topic,
|
|
236
|
+
sort: config.sort,
|
|
237
|
+
order: config.order
|
|
238
|
+
};
|
|
349
239
|
});
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
240
|
+
const repos = await map(fetchRepoArgs, fetchRepositories);
|
|
241
|
+
return deduplicateArray(repos.flat());
|
|
242
|
+
}
|
|
243
|
+
if (config?.namespaces) {
|
|
244
|
+
logger.debug({ namespaces: config.namespaces }, "Auto-discovering by organization");
|
|
245
|
+
const repos = await map(config.namespaces, async (organization) => {
|
|
246
|
+
return (await orgListRepos(http, organization)).filter((r) => !r.mirror && !r.archived).map((r) => r.full_name);
|
|
247
|
+
});
|
|
248
|
+
return deduplicateArray(repos.flat());
|
|
356
249
|
}
|
|
357
|
-
|
|
250
|
+
return await fetchRepositories({
|
|
251
|
+
sort: config?.sort,
|
|
252
|
+
order: config?.order
|
|
253
|
+
});
|
|
254
|
+
} catch (err) {
|
|
255
|
+
logger.error({
|
|
256
|
+
err,
|
|
257
|
+
platform: id
|
|
258
|
+
}, "Error auto-discovering repositories");
|
|
259
|
+
throw err;
|
|
358
260
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
261
|
+
},
|
|
262
|
+
async setBranchStatus({ branchName, context, description, state, url: target_url }) {
|
|
263
|
+
try {
|
|
264
|
+
const branchCommit = getBranchCommit(branchName);
|
|
265
|
+
await createCommitStatus(http, config.repository, branchCommit, {
|
|
266
|
+
state: toPlatformStatusMapping[state] || "pending",
|
|
267
|
+
context,
|
|
268
|
+
description,
|
|
269
|
+
...target_url && { target_url }
|
|
270
|
+
});
|
|
271
|
+
await getCombinedCommitStatus(http, config.repository, branchName, { memCache: false });
|
|
272
|
+
} catch (err) {
|
|
273
|
+
logger.warn({ err }, "Failed to set branch status");
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
async getBranchStatus(branchName, internalChecksAsSuccess) {
|
|
277
|
+
let ccs;
|
|
278
|
+
try {
|
|
279
|
+
ccs = await getCombinedCommitStatus(http, config.repository, branchName);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
if (err.statusCode === 404) {
|
|
282
|
+
logger.debug("Received 404 when checking branch status, assuming branch deletion");
|
|
283
|
+
throw new Error(REPOSITORY_CHANGED);
|
|
284
|
+
}
|
|
285
|
+
logger.debug("Unknown error when checking branch status");
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
logger.debug({ ccs }, "Branch status check result");
|
|
289
|
+
if (!internalChecksAsSuccess && ccs.worstStatus === "success" && ccs.statuses.every((status) => status.context?.startsWith("renovate/"))) {
|
|
290
|
+
logger.debug("Successful checks are all internal renovate/ checks, so returning \"pending\" branch status");
|
|
291
|
+
return "yellow";
|
|
292
|
+
}
|
|
293
|
+
return toRenovateStatusMapping[ccs.worstStatus];
|
|
294
|
+
},
|
|
295
|
+
async getBranchStatusCheck(branchName, context) {
|
|
296
|
+
const cs = (await getCombinedCommitStatus(http, config.repository, branchName)).statuses.find((s) => s.context === context);
|
|
297
|
+
if (!cs) return null;
|
|
298
|
+
return toRenovateStatusMapping[cs.status];
|
|
299
|
+
},
|
|
300
|
+
getPrList() {
|
|
301
|
+
return prCache.getPrs();
|
|
302
|
+
},
|
|
303
|
+
async getPr(number) {
|
|
304
|
+
let pr = (await platform.getPrList()).find((p) => p.number === number) ?? null;
|
|
305
|
+
if (pr) logger.debug("Returning from cached PRs");
|
|
306
|
+
else {
|
|
307
|
+
logger.debug("PR not found in cached PRs - trying to fetch directly");
|
|
308
|
+
const gpr = await getPR(http, config.repository, number);
|
|
309
|
+
pr = toRenovatePR(gpr, botUserName);
|
|
310
|
+
if (pr) await prCache.setPr(pr);
|
|
311
|
+
}
|
|
312
|
+
if (!pr) return null;
|
|
362
313
|
return pr;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
314
|
+
},
|
|
315
|
+
async findPr({ branchName, prTitle: title, state = "all", includeOtherAuthors, targetBranch }) {
|
|
316
|
+
logger.debug(`findPr(${branchName}, ${title}, ${state})`);
|
|
317
|
+
if (includeOtherAuthors && isString(targetBranch)) {
|
|
318
|
+
const pr = await getPRByBranch(http, config.repository, targetBranch, branchName);
|
|
319
|
+
if (!pr) return null;
|
|
320
|
+
return toRenovatePR(pr, null);
|
|
321
|
+
}
|
|
322
|
+
const pr = (await platform.getPrList()).find((p) => p.sourceRepo === config.repository && p.sourceBranch === branchName && matchesState(p.state, state) && (!title || p.title === title));
|
|
323
|
+
if (pr) logger.debug(`Found PR #${pr.number}`);
|
|
324
|
+
return pr ?? null;
|
|
325
|
+
},
|
|
326
|
+
async createPr({ sourceBranch, targetBranch, prTitle, prBody: rawBody, labels: labelNames, platformPrOptions, draftPR }) {
|
|
327
|
+
let title = prTitle;
|
|
328
|
+
const base = targetBranch;
|
|
329
|
+
const head = sourceBranch;
|
|
330
|
+
const body = sanitize(rawBody);
|
|
331
|
+
if (draftPR) title = DRAFT_PREFIX + title;
|
|
332
|
+
logger.debug(`Creating pull request: ${title} (${head} => ${base})`);
|
|
333
|
+
try {
|
|
334
|
+
const labels = Array.isArray(labelNames) ? await map(labelNames, (name) => lookupLabelByName(http, config, name)) : [];
|
|
335
|
+
const gpr = await createPR(http, config.repository, {
|
|
336
|
+
base,
|
|
337
|
+
head,
|
|
338
|
+
title,
|
|
339
|
+
body,
|
|
340
|
+
labels: labels.filter(isNumber)
|
|
373
341
|
});
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
prBody: body
|
|
342
|
+
if (platformPrOptions?.usePlatformAutomerge) {
|
|
343
|
+
const unsupportedReason = checkNativeAutomerge(defaults.version);
|
|
344
|
+
if (unsupportedReason === null) try {
|
|
345
|
+
await mergePR(http, config.repository, gpr.number, {
|
|
346
|
+
Do: getMergeMethod(platformPrOptions?.automergeStrategy) ?? config.mergeMethod,
|
|
347
|
+
merge_when_checks_succeed: true,
|
|
348
|
+
delete_branch_after_merge: true
|
|
382
349
|
});
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
350
|
+
logger.debug({ prNumber: gpr.number }, `${name}-native automerge: success`);
|
|
351
|
+
} catch (err) {
|
|
352
|
+
logger.warn({
|
|
353
|
+
err,
|
|
354
|
+
prNumber: gpr.number,
|
|
355
|
+
platform: id
|
|
356
|
+
}, "Platform-native automerge: fail");
|
|
357
|
+
}
|
|
358
|
+
else logger.debug({ prNumber: gpr.number }, unsupportedReason);
|
|
387
359
|
}
|
|
360
|
+
const pr = toRenovatePR(gpr, botUserName);
|
|
361
|
+
if (!pr) throw new Error("Can not parse newly created Pull Request");
|
|
362
|
+
await prCache.setPr(pr);
|
|
363
|
+
return pr;
|
|
364
|
+
} catch (err) {
|
|
365
|
+
if (err.statusCode === 409) {
|
|
366
|
+
logger.warn({
|
|
367
|
+
prTitle: title,
|
|
368
|
+
sourceBranch
|
|
369
|
+
}, "Attempting to gracefully recover from 409 Conflict response in createPr()");
|
|
370
|
+
prCache.forceSync();
|
|
371
|
+
const pr = await platform.findPr({
|
|
372
|
+
branchName: sourceBranch,
|
|
373
|
+
state: "open"
|
|
374
|
+
});
|
|
375
|
+
// v8 ignore else -- TODO: add test #40625
|
|
376
|
+
if (pr?.bodyStruct) {
|
|
377
|
+
if (pr.title !== title || pr.bodyStruct.hash !== hashBody(body)) {
|
|
378
|
+
logger.debug(`Recovered from 409 Conflict, but PR for ${sourceBranch} is outdated. Updating...`);
|
|
379
|
+
await platform.updatePr({
|
|
380
|
+
number: pr.number,
|
|
381
|
+
prTitle: title,
|
|
382
|
+
prBody: body
|
|
383
|
+
});
|
|
384
|
+
pr.title = title;
|
|
385
|
+
pr.bodyStruct = getPrBodyStruct(body);
|
|
386
|
+
} else logger.debug(`Recovered from 409 Conflict and PR for ${sourceBranch} is up-to-date`);
|
|
387
|
+
return pr;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
throw err;
|
|
388
391
|
}
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
...body && { body },
|
|
398
|
-
...state && { state }
|
|
399
|
-
};
|
|
400
|
-
if (targetBranch) prUpdateParams.base = targetBranch;
|
|
401
|
-
/**
|
|
402
|
-
* Update PR labels.
|
|
403
|
-
* In the Gitea API, labels are replaced on each update if the field is present.
|
|
404
|
-
* If the field is not present (i.e., undefined), labels aren't updated.
|
|
405
|
-
* However, the labels array must contain label IDs instead of names,
|
|
406
|
-
* so a lookup is performed to fetch the details (including the ID) of each label.
|
|
407
|
-
*/
|
|
408
|
-
if (Array.isArray(labels)) {
|
|
409
|
-
prUpdateParams.labels = (await map(labels, lookupLabelByName)).filter(isNumber);
|
|
410
|
-
if (labels.length !== prUpdateParams.labels.length) logger.warn("Some labels could not be looked up. Renovate may halt label updates assuming changes by others.");
|
|
411
|
-
}
|
|
412
|
-
const gpr = await updatePR(config.repository, number, prUpdateParams);
|
|
413
|
-
const pr = toRenovatePR(gpr, botUserName);
|
|
414
|
-
if (pr) await GiteaPrCache.setPr(giteaHttp, config.repository, config.ignorePrAuthor, botUserName, pr);
|
|
415
|
-
},
|
|
416
|
-
async mergePr({ id, strategy }) {
|
|
417
|
-
try {
|
|
418
|
-
await mergePR(config.repository, id, { Do: getMergeMethod(strategy) ?? config.mergeMethod });
|
|
419
|
-
return true;
|
|
420
|
-
} catch (err) {
|
|
421
|
-
logger.warn({
|
|
422
|
-
err,
|
|
423
|
-
id
|
|
424
|
-
}, "Merging of PR failed");
|
|
425
|
-
return false;
|
|
426
|
-
}
|
|
427
|
-
},
|
|
428
|
-
getIssueList() {
|
|
429
|
-
if (config.hasIssuesEnabled === false) return Promise.resolve([]);
|
|
430
|
-
config.issueList ??= searchIssues(config.repository, { state: "all" }, { memCache: false }).then((issues) => {
|
|
431
|
-
const issueList = issues.map(toRenovateIssue);
|
|
432
|
-
logger.debug(`Retrieved ${issueList.length} Issues`);
|
|
433
|
-
return issueList;
|
|
434
|
-
});
|
|
435
|
-
return config.issueList;
|
|
436
|
-
},
|
|
437
|
-
async getIssue(number, memCache = true) {
|
|
438
|
-
if (config.hasIssuesEnabled === false) return null;
|
|
439
|
-
try {
|
|
440
|
-
return {
|
|
441
|
-
number,
|
|
442
|
-
body: (await getIssue$1(config.repository, number, { memCache })).body
|
|
392
|
+
},
|
|
393
|
+
async updatePr({ number, prTitle, prBody: body, labels, state, targetBranch }) {
|
|
394
|
+
let title = prTitle;
|
|
395
|
+
if ((await platform.getPrList()).find((pr) => pr.number === number)?.isDraft) title = DRAFT_PREFIX + title;
|
|
396
|
+
const prUpdateParams = {
|
|
397
|
+
title,
|
|
398
|
+
...body && { body },
|
|
399
|
+
...state && { state }
|
|
443
400
|
};
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
401
|
+
if (targetBranch) prUpdateParams.base = targetBranch;
|
|
402
|
+
/**
|
|
403
|
+
* Update PR labels.
|
|
404
|
+
* In the Gitea API, labels are replaced on each update if the field is present.
|
|
405
|
+
* If the field is not present (i.e., undefined), labels aren't updated.
|
|
406
|
+
* However, the labels array must contain label IDs instead of names,
|
|
407
|
+
* so a lookup is performed to fetch the details (including the ID) of each label.
|
|
408
|
+
*/
|
|
409
|
+
if (Array.isArray(labels)) {
|
|
410
|
+
prUpdateParams.labels = (await map(labels, (name) => lookupLabelByName(http, config, name))).filter(isNumber);
|
|
411
|
+
if (labels.length !== prUpdateParams.labels.length) logger.warn("Some labels could not be looked up. Renovate may halt label updates assuming changes by others.");
|
|
412
|
+
}
|
|
413
|
+
const gpr = await updatePR(http, config.repository, number, prUpdateParams);
|
|
414
|
+
const pr = toRenovatePR(gpr, botUserName);
|
|
415
|
+
if (pr) await prCache.setPr(pr);
|
|
416
|
+
},
|
|
417
|
+
async mergePr({ id: prNumber, strategy }) {
|
|
418
|
+
try {
|
|
419
|
+
await mergePR(http, config.repository, prNumber, { Do: getMergeMethod(strategy) ?? config.mergeMethod });
|
|
420
|
+
return true;
|
|
421
|
+
} catch (err) {
|
|
422
|
+
logger.warn({
|
|
423
|
+
err,
|
|
424
|
+
id: prNumber
|
|
425
|
+
}, "Merging of PR failed");
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
getIssueList() {
|
|
430
|
+
if (config.hasIssuesEnabled === false) return Promise.resolve([]);
|
|
431
|
+
config.issueList ??= fetchIssueList();
|
|
432
|
+
return config.issueList;
|
|
433
|
+
},
|
|
434
|
+
async getIssue(number, memCache = true) {
|
|
435
|
+
if (config.hasIssuesEnabled === false) return null;
|
|
436
|
+
try {
|
|
437
|
+
return {
|
|
438
|
+
number,
|
|
439
|
+
body: (await getIssue$1(http, config.repository, number, { memCache })).body
|
|
440
|
+
};
|
|
441
|
+
} catch (err) {
|
|
442
|
+
logger.debug({
|
|
443
|
+
err,
|
|
444
|
+
number
|
|
445
|
+
}, "Error getting issue");
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
async findIssue(title) {
|
|
450
|
+
const issue = (await platform.getIssueList()).find((i) => i.state === "open" && i.title === title);
|
|
451
|
+
if (!issue) return null;
|
|
452
|
+
logger.debug(`Found Issue #${issue.number}`);
|
|
453
|
+
return platform.getIssue(issue.number);
|
|
454
|
+
},
|
|
455
|
+
async ensureIssue({ title, reuseTitle, body: content, labels: labelNames, shouldReOpen, once }) {
|
|
456
|
+
logger.debug(`ensureIssue(${title})`);
|
|
457
|
+
if (config.hasIssuesEnabled === false) {
|
|
458
|
+
logger.info("Cannot ensure issue because issues are disabled in this repository");
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
const body = smartLinks(content);
|
|
463
|
+
const issueList = await platform.getIssueList();
|
|
464
|
+
let issues = issueList.filter((i) => i.title === title);
|
|
465
|
+
if (!issues.length) issues = issueList.filter((i) => i.title === reuseTitle);
|
|
466
|
+
const labels = Array.isArray(labelNames) ? (await Promise.all(labelNames.map((name) => lookupLabelByName(http, config, name)))).filter(isNumber) : void 0;
|
|
467
|
+
if (issues.length) {
|
|
468
|
+
let activeIssue = issues.find((i) => i.state === "open");
|
|
469
|
+
if (!activeIssue) {
|
|
470
|
+
if (once) {
|
|
471
|
+
logger.debug("Issue already closed - skipping update");
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
if (shouldReOpen) logger.debug("Reopening previously closed Issue");
|
|
475
|
+
activeIssue = issues.at(-1);
|
|
476
|
+
}
|
|
477
|
+
for (const issue of issues) if (issue.state === "open" && issue.number !== activeIssue.number) {
|
|
478
|
+
logger.warn({ issueNo: issue.number }, "Closing duplicate issue");
|
|
479
|
+
await closeIssue(http, config.repository, issue.number);
|
|
480
|
+
}
|
|
481
|
+
if (activeIssue.title === title && activeIssue.body === body && activeIssue.state === "open") {
|
|
482
|
+
logger.debug(`Issue #${activeIssue.number} is open and up to date - nothing to do`);
|
|
475
483
|
return null;
|
|
476
484
|
}
|
|
477
|
-
if (shouldReOpen
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
485
|
+
if (shouldReOpen || activeIssue.state === "open") {
|
|
486
|
+
logger.debug(`Updating Issue #${activeIssue.number}`);
|
|
487
|
+
const existingIssue = await updateIssue(http, config.repository, activeIssue.number, {
|
|
488
|
+
body,
|
|
489
|
+
title,
|
|
490
|
+
state: "open"
|
|
491
|
+
});
|
|
492
|
+
const existingLabelIds = coerceArray(existingIssue.labels).map((label) => label.id);
|
|
493
|
+
if (labels && (labels.length !== existingLabelIds.length || labels.filter((labelId) => !existingLabelIds.includes(labelId)).length !== 0)) await updateIssueLabels(http, config.repository, activeIssue.number, { labels });
|
|
494
|
+
return "updated";
|
|
495
|
+
}
|
|
487
496
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
497
|
+
const issue = await createIssue(http, config.repository, {
|
|
498
|
+
body,
|
|
499
|
+
title,
|
|
500
|
+
labels
|
|
501
|
+
});
|
|
502
|
+
logger.debug(`Created new Issue #${issue.number}`);
|
|
503
|
+
config.issueList = null;
|
|
504
|
+
return "created";
|
|
505
|
+
} catch (err) {
|
|
506
|
+
logger.warn({ err }, "Could not ensure issue");
|
|
507
|
+
}
|
|
508
|
+
return null;
|
|
509
|
+
},
|
|
510
|
+
async ensureIssueClosing(title) {
|
|
511
|
+
logger.debug(`ensureIssueClosing(${title})`);
|
|
512
|
+
if (config.hasIssuesEnabled === false) return;
|
|
513
|
+
const issueList = await platform.getIssueList();
|
|
514
|
+
for (const issue of issueList) if (issue.state === "open" && issue.title === title) {
|
|
515
|
+
logger.debug(`Closing issue...issueNo: ${issue.number}`);
|
|
516
|
+
await closeIssue(http, config.repository, issue.number);
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
async deleteLabel(issue, labelName) {
|
|
520
|
+
logger.debug(`Deleting label ${labelName} from Issue #${issue}`);
|
|
521
|
+
const label = await lookupLabelByName(http, config, labelName);
|
|
522
|
+
if (label) await unassignLabel(http, config.repository, issue, label);
|
|
523
|
+
else logger.warn({
|
|
524
|
+
issue,
|
|
525
|
+
labelName
|
|
526
|
+
}, "Failed to lookup label for deletion");
|
|
527
|
+
},
|
|
528
|
+
async ensureComment({ number: issue, topic, content }) {
|
|
529
|
+
try {
|
|
530
|
+
let body = sanitize(content);
|
|
531
|
+
const commentList = await getComments(http, config.repository, issue);
|
|
532
|
+
let comment = null;
|
|
533
|
+
if (topic) {
|
|
534
|
+
comment = findCommentByTopic(commentList, topic);
|
|
535
|
+
body = `### ${topic}\n\n${body}`;
|
|
536
|
+
} else comment = findCommentByContent(commentList, body);
|
|
537
|
+
if (!comment) {
|
|
538
|
+
comment = await createComment(http, config.repository, issue, body);
|
|
539
|
+
logger.info({
|
|
540
|
+
repository: config.repository,
|
|
541
|
+
issue,
|
|
542
|
+
comment: comment.id
|
|
543
|
+
}, "Comment added");
|
|
544
|
+
} else if (comment.body === body) logger.debug(`Comment #${comment.id} is already up-to-date`);
|
|
545
|
+
else {
|
|
546
|
+
await updateComment(http, config.repository, comment.id, body);
|
|
547
|
+
logger.debug({
|
|
548
|
+
repository: config.repository,
|
|
549
|
+
issue,
|
|
550
|
+
comment: comment.id
|
|
551
|
+
}, "Comment updated");
|
|
498
552
|
}
|
|
553
|
+
return true;
|
|
554
|
+
} catch (err) {
|
|
555
|
+
logger.warn({
|
|
556
|
+
err,
|
|
557
|
+
issue,
|
|
558
|
+
subject: topic
|
|
559
|
+
}, "Error ensuring comment");
|
|
560
|
+
return false;
|
|
499
561
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
config.issueList = null;
|
|
507
|
-
return "created";
|
|
508
|
-
} catch (err) {
|
|
509
|
-
logger.warn({ err }, "Could not ensure issue");
|
|
510
|
-
}
|
|
511
|
-
return null;
|
|
512
|
-
},
|
|
513
|
-
async ensureIssueClosing(title) {
|
|
514
|
-
logger.debug(`ensureIssueClosing(${title})`);
|
|
515
|
-
if (config.hasIssuesEnabled === false) return;
|
|
516
|
-
const issueList = await platform.getIssueList();
|
|
517
|
-
for (const issue of issueList) if (issue.state === "open" && issue.title === title) {
|
|
518
|
-
logger.debug(`Closing issue...issueNo: ${issue.number}`);
|
|
519
|
-
await closeIssue(config.repository, issue.number);
|
|
520
|
-
}
|
|
521
|
-
},
|
|
522
|
-
async deleteLabel(issue, labelName) {
|
|
523
|
-
logger.debug(`Deleting label ${labelName} from Issue #${issue}`);
|
|
524
|
-
const label = await lookupLabelByName(labelName);
|
|
525
|
-
if (label) await unassignLabel(config.repository, issue, label);
|
|
526
|
-
else logger.warn({
|
|
527
|
-
issue,
|
|
528
|
-
labelName
|
|
529
|
-
}, "Failed to lookup label for deletion");
|
|
530
|
-
},
|
|
531
|
-
async ensureComment({ number: issue, topic, content }) {
|
|
532
|
-
try {
|
|
533
|
-
let body = sanitize(content);
|
|
534
|
-
const commentList = await getComments(config.repository, issue);
|
|
562
|
+
},
|
|
563
|
+
async ensureCommentRemoval(deleteConfig) {
|
|
564
|
+
const { number: issue } = deleteConfig;
|
|
565
|
+
const key = deleteConfig.type === "by-topic" ? deleteConfig.topic : deleteConfig.content;
|
|
566
|
+
logger.debug(`Ensuring comment "${key}" in #${issue} is removed`);
|
|
567
|
+
const commentList = await getComments(http, config.repository, issue);
|
|
535
568
|
let comment = null;
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
comment: comment.id
|
|
546
|
-
}, "Comment added");
|
|
547
|
-
} else if (comment.body === body) logger.debug(`Comment #${comment.id} is already up-to-date`);
|
|
548
|
-
else {
|
|
549
|
-
await updateComment(config.repository, comment.id, body);
|
|
550
|
-
logger.debug({
|
|
551
|
-
repository: config.repository,
|
|
569
|
+
// v8 ignore else -- TODO: add test #40625
|
|
570
|
+
if (deleteConfig.type === "by-topic") comment = findCommentByTopic(commentList, deleteConfig.topic);
|
|
571
|
+
else if (deleteConfig.type === "by-content") comment = findCommentByContent(commentList, sanitize(deleteConfig.content));
|
|
572
|
+
if (!comment) return;
|
|
573
|
+
try {
|
|
574
|
+
await deleteComment(http, config.repository, comment.id);
|
|
575
|
+
} catch (err) {
|
|
576
|
+
logger.warn({
|
|
577
|
+
err,
|
|
552
578
|
issue,
|
|
553
|
-
|
|
554
|
-
}, "
|
|
579
|
+
config: deleteConfig
|
|
580
|
+
}, "Error deleting comment");
|
|
555
581
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
logger.
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
563
|
-
return
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
number,
|
|
610
|
-
reviewers
|
|
611
|
-
}, "Failed to assign reviewer");
|
|
612
|
-
}
|
|
613
|
-
},
|
|
614
|
-
massageMarkdown(prBody) {
|
|
615
|
-
return smartTruncate(smartLinks(prBody), maxBodyLength());
|
|
616
|
-
},
|
|
617
|
-
maxBodyLength
|
|
618
|
-
};
|
|
619
|
-
function maxBodyLength() {
|
|
620
|
-
return 1e6;
|
|
582
|
+
},
|
|
583
|
+
async getBranchPr(branchName) {
|
|
584
|
+
logger.debug(`getBranchPr(${branchName})`);
|
|
585
|
+
const pr = await platform.findPr({
|
|
586
|
+
branchName,
|
|
587
|
+
state: "open"
|
|
588
|
+
});
|
|
589
|
+
return pr ? platform.getPr(pr.number) : null;
|
|
590
|
+
},
|
|
591
|
+
async addAssignees(number, assignees) {
|
|
592
|
+
logger.debug(`Updating assignees '${assignees?.join(", ")}' on Issue #${number}`);
|
|
593
|
+
await updateIssue(http, config.repository, number, { assignees });
|
|
594
|
+
},
|
|
595
|
+
async addReviewers(number, reviewers) {
|
|
596
|
+
logger.debug(`Adding reviewers '${reviewers?.join(", ")}' to #${number}`);
|
|
597
|
+
if (minReviewerVersion && semver.lt(defaults.version, minReviewerVersion)) {
|
|
598
|
+
logger.debug({ version: defaults.version }, "Adding reviewer not yet supported.");
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
const teamReviewers = new Set(reviewers.filter((r) => r.startsWith("team:")).map((r) => r.substring(5)));
|
|
603
|
+
const userReviewers = new Set(reviewers.filter((r) => !r.startsWith("team:")));
|
|
604
|
+
await requestPrReviewers(http, config.repository, number, {
|
|
605
|
+
reviewers: [...userReviewers],
|
|
606
|
+
...teamReviewers.size && { team_reviewers: [...teamReviewers] }
|
|
607
|
+
});
|
|
608
|
+
} catch (err) {
|
|
609
|
+
logger.warn({
|
|
610
|
+
err,
|
|
611
|
+
number,
|
|
612
|
+
reviewers
|
|
613
|
+
}, "Failed to assign reviewer");
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
massageMarkdown(prBody) {
|
|
617
|
+
return smartTruncate(smartLinks(prBody), maxBodyLength());
|
|
618
|
+
},
|
|
619
|
+
maxBodyLength
|
|
620
|
+
};
|
|
621
|
+
function maxBodyLength() {
|
|
622
|
+
return 1e6;
|
|
623
|
+
}
|
|
624
|
+
return {
|
|
625
|
+
platform,
|
|
626
|
+
resetPlatform
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Forgejo reports the Gitea version it is compatible with as part of its own
|
|
631
|
+
* version, for example `11.0.1-99-c504062+gitea-1.22.0`.
|
|
632
|
+
*/
|
|
633
|
+
function isForgejo(version) {
|
|
634
|
+
return version.includes("gitea-");
|
|
621
635
|
}
|
|
622
|
-
|
|
636
|
+
function logDetectedVersion(version) {
|
|
637
|
+
if (isForgejo(version)) logger.info(`Detected Forgejo instance, please use 'forgejo' platform instead`);
|
|
638
|
+
logger.debug(`${isForgejo(version) ? "Forgejo" : "Gitea"} version: ${version}`);
|
|
639
|
+
}
|
|
640
|
+
function checkNativeAutomerge(version) {
|
|
641
|
+
const minVersion = isForgejo(version) ? "10.0.0" : "1.24.0";
|
|
642
|
+
if (semver.gte(version, minVersion)) return null;
|
|
643
|
+
return `Gitea-native automerge: not supported on this version of ${isForgejo(version) ? "Forgejo" : "Gitea"}. Use ${minVersion} or newer.`;
|
|
644
|
+
}
|
|
645
|
+
const { platform, resetPlatform } = createPlatform({
|
|
646
|
+
id,
|
|
647
|
+
defaultEndpoint: "https://gitea.com/",
|
|
648
|
+
http: giteaHttp,
|
|
649
|
+
setBaseUrl,
|
|
650
|
+
logDetectedVersion,
|
|
651
|
+
checkNativeAutomerge,
|
|
652
|
+
minReviewerVersion: "1.14.0"
|
|
653
|
+
});
|
|
654
|
+
const { addAssignees, addReviewers, createPr, deleteLabel, ensureComment, ensureCommentRemoval, ensureIssue, ensureIssueClosing, findIssue, findPr, getBranchPr, getBranchStatus, getBranchStatusCheck, getIssue, getRawFile, getJsonFile, getIssueList, getPr, massageMarkdown, maxBodyLength, getPrList, getRepos, initPlatform, initRepo, mergePr, setBranchStatus, updatePr } = platform;
|
|
623
655
|
//#endregion
|
|
624
|
-
export { addAssignees, addReviewers, createPr, deleteLabel, ensureComment, ensureCommentRemoval, ensureIssue, ensureIssueClosing, findIssue, findPr, getBranchPr, getBranchStatus, getBranchStatusCheck, getIssue, getIssueList, getJsonFile, getPr, getPrList, getRawFile, getRepos, gitea_exports, id, initPlatform, initRepo, massageMarkdown, maxBodyLength, mergePr, resetPlatform, setBranchStatus, updatePr };
|
|
656
|
+
export { addAssignees, addReviewers, createPlatform, createPr, deleteLabel, ensureComment, ensureCommentRemoval, ensureIssue, ensureIssueClosing, findIssue, findPr, getBranchPr, getBranchStatus, getBranchStatusCheck, getIssue, getIssueList, getJsonFile, getPr, getPrList, getRawFile, getRepos, giteaHttp, gitea_exports, id, initPlatform, initRepo, massageMarkdown, maxBodyLength, mergePr, resetPlatform, setBranchStatus, updatePr };
|
|
625
657
|
|
|
626
658
|
//# sourceMappingURL=index.js.map
|