renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
133 lines (132 loc) • 4.99 kB
JavaScript
import { GlobalConfig } from "../../../config/global.js";
import { logger } from "../../../logger/index.js";
import { parseLinkHeader } from "../../../util/url.js";
import { ExternalHostError } from "../../../types/errors/external-host-error.js";
import { instrument } from "../../../instrumentation/index.js";
import { getCache } from "../../../util/cache/repository/index.js";
import { repoCacheProvider } from "../../../util/http/cache/repository-http-cache-provider.js";
import { coerceRestPr } from "./common.js";
import { ApiCache } from "./api-cache.js";
import { isEmptyArray, isNonEmptyArray } from "@sindresorhus/is";
import { DateTime } from "luxon";
//#region lib/modules/platform/github/pr.ts
function getPrApiCache() {
const repoCache = getCache();
if (!repoCache?.platform?.github?.pullRequestsCache) {
logger.debug("PR cache: cached data not found, creating new cache");
repoCache.platform ??= {};
repoCache.platform.github ??= {};
repoCache.platform.github.pullRequestsCache ??= { items: {} };
}
return new ApiCache(repoCache.platform.github.pullRequestsCache);
}
/**
* Fetch and return Pull Requests from GitHub repository:
*
* 1. Synchronize long-term cache.
*
* 2. Store items in raw format, i.e. exactly what
* has been returned by GitHub REST API.
*
* 3. Convert items to the Renovate format and return.
*
* In order synchronize ApiCache properly, we handle 3 cases:
*
* a. We never fetched PR list for this repo before.
* If cached PR list is empty, we assume it's the case.
*
* In this case, we're falling back to quick fetch via
* `paginate=true` option (see `util/http/github.ts`).
*
* b. Some of PRs had changed since last run.
*
* In this case, we sequentially fetch page by page
* until the oldest item on an unfiltered page predates
* the cache's `lastModified` timestamp.
*
* We expect to fetch just one page per run in average,
* since it's rare to have more than 100 updated PRs.
*/
async function getPrCache(http, repo, username) {
const prApiCache = getPrApiCache();
const isInitial = isEmptyArray(prApiCache.getItems());
let lastModifiedRaw = prApiCache.getLastModified();
if (!lastModifiedRaw && !isInitial) {
const items = prApiCache.getItems();
for (const item of items) if (!lastModifiedRaw || item.updated_at > lastModifiedRaw) lastModifiedRaw = item.updated_at;
}
const cutoffTime = lastModifiedRaw ? DateTime.fromISO(lastModifiedRaw) : null;
try {
const maxSyncPages = GlobalConfig.get("prCacheSyncMaxPages");
const startTime = Date.now();
let requestsTotal = 0;
let apiQuotaAffected = false;
let needNextPageFetch = true;
let needNextPageSync = true;
let pageIdx = 1;
await instrument("sync GitHub PR cache", async () => {
while (needNextPageFetch && needNextPageSync) {
const opts = {
paginate: false,
memCache: false
};
if (pageIdx === 1) {
opts.cacheProvider = repoCacheProvider;
if (isInitial) opts.paginate = true;
}
let perPage;
if (isInitial) {
logger.debug("PR cache: initial fetch");
perPage = 100;
} else {
logger.debug("PR cache: sync fetch");
perPage = 20;
}
const urlPath = `repos/${repo}/pulls?per_page=${perPage}&state=all&sort=updated&direction=desc&page=${pageIdx}`;
const res = await http.getJsonUnchecked(urlPath, opts);
apiQuotaAffected = true;
requestsTotal += 1;
const { headers: { link: linkHeader } } = res;
let { body: page } = res;
if (!isInitial && cutoffTime && isNonEmptyArray(page)) {
prApiCache.updateLastModified(page[0].updated_at);
if (DateTime.fromISO(page[page.length - 1].updated_at) < cutoffTime) needNextPageSync = false;
}
if (username) {
const filteredPage = page.filter((ghPr) => ghPr?.user?.login && ghPr.user.login === username);
logger.debug(`PR cache: Filtered ${page.length} PRs to ${filteredPage.length} (user=${username})`);
page = filteredPage;
}
const items = page.map(coerceRestPr);
if (isNonEmptyArray(items)) prApiCache.reconcile(items);
needNextPageFetch = !!parseLinkHeader(linkHeader)?.next;
if (pageIdx === 1) needNextPageFetch &&= !opts.paginate;
if (!isInitial && needNextPageFetch && needNextPageSync && pageIdx >= maxSyncPages) {
logger.warn({
repo,
pages: pageIdx
}, "PR cache: hit max sync pages, stopping");
needNextPageSync = false;
}
pageIdx += 1;
}
});
const durationMs = Math.round(Date.now() - startTime);
logger.debug({
pullsTotal: prApiCache.getItems().length,
requestsTotal,
apiQuotaAffected,
durationMs
}, `PR cache: getPrList success`);
} catch (err) /* v8 ignore next */ {
logger.debug({ err }, "PR cache: getPrList err");
throw new ExternalHostError(err, "github");
}
return prApiCache.getItems();
}
function updatePrCache(pr) {
getPrApiCache().updateItem(pr);
}
//#endregion
export { getPrCache, updatePrCache };
//# sourceMappingURL=pr.js.map