renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
123 lines • 5.62 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.codeOwnersForPr = codeOwnersForPr;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const ignore_1 = tslib_1.__importDefault(require("ignore"));
const logger_1 = require("../../../../logger");
const platform_1 = require("../../../../modules/platform");
const fs_1 = require("../../../../util/fs");
const git_1 = require("../../../../util/git");
const regex_1 = require("../../../../util/regex");
function extractOwnersFromLine(line) {
const [pattern, ...usernames] = line.split((0, regex_1.regEx)(/\s+/));
const matchPattern = (0, ignore_1.default)().add(pattern);
return {
usernames,
pattern,
score: pattern.length,
match: (path) => matchPattern.ignores(path),
};
}
function matchFileToOwners(file, rules) {
const usernames = new Map();
for (const rule of rules) {
if (!rule.match(file)) {
continue;
}
for (const user of rule.usernames) {
usernames.set(user, rule.score);
}
}
return { file, userScoreMap: usernames };
}
function getOwnerList(filesWithOwners) {
const userFileMap = new Map();
for (const fileName of filesWithOwners) {
for (const [username, score] of fileName.userScoreMap.entries()) {
// Get / create user file score
const fileMap = userFileMap.get(username) ?? new Map();
if (!userFileMap.has(username)) {
userFileMap.set(username, fileMap);
}
// Add file to user
fileMap.set(fileName.file, (fileMap.get(fileName.file) ?? 0) + score);
}
}
return Array.from(userFileMap.entries()).map(([key, value]) => ({
username: key,
fileScoreMap: value,
}));
}
function parseCodeOwnersContent(codeOwnersFile) {
return (codeOwnersFile
.split(regex_1.newlineRegex)
// Remove comments
.map((line) => line.split('#')[0])
// Remove empty lines
.map((line) => line.trim())
.filter(is_1.default.nonEmptyString));
}
async function codeOwnersForPr(pr) {
logger_1.logger.debug('Searching for CODEOWNERS file');
try {
// Find CODEOWNERS file
const codeOwnersFile = (await (0, fs_1.readLocalFile)('CODEOWNERS', 'utf8')) ??
(await (0, fs_1.readLocalFile)('.bitbucket/CODEOWNERS', 'utf8')) ??
(await (0, fs_1.readLocalFile)('.github/CODEOWNERS', 'utf8')) ??
(await (0, fs_1.readLocalFile)('.gitlab/CODEOWNERS', 'utf8')) ??
(await (0, fs_1.readLocalFile)('docs/CODEOWNERS', 'utf8'));
if (!codeOwnersFile) {
logger_1.logger.debug('No CODEOWNERS file found');
return [];
}
logger_1.logger.debug(`Found CODEOWNERS file: ${codeOwnersFile}`);
// Get list of modified files in PR
// if the commit sha is known, we can directly compare against the parent, otherwise use the branch
const prFiles = pr.sha
? await (0, git_1.getBranchFilesFromCommit)(pr.sha)
: await (0, git_1.getBranchFiles)(pr.sourceBranch);
if (!prFiles?.length) {
logger_1.logger.debug('PR includes no files');
return [];
}
// Convert CODEOWNERS file into list of matching rules
const cleanedLines = parseCodeOwnersContent(codeOwnersFile);
const fileOwnerRules = platform_1.platform.extractRulesFromCodeOwnersLines?.(cleanedLines) ??
cleanedLines.map(extractOwnersFromLine);
logger_1.logger.debug({ prFiles, fileOwnerRules }, 'PR files and rules to match for CODEOWNERS');
// Apply rules & get list of owners for each prFile
const emptyRules = fileOwnerRules.filter((rule) => rule.usernames.length === 0);
const fileOwners =
// Map through all prFiles and match said file(s) with all the rules
prFiles
.map((file) => matchFileToOwners(file, fileOwnerRules))
// Match file again but this time only with emptyRules, to ensure that files which have no owner set remain owner-less
.map((matchedFile) => {
const matchEmpty = emptyRules.find((rule) => rule.match(matchedFile.file));
if (matchEmpty) {
return { ...matchedFile, userScoreMap: new Map() };
}
return matchedFile;
});
logger_1.logger.debug(`CODEOWNERS matched the following files: ${fileOwners
.map((f) => f.file)
.join(', ')}`);
// Get list of all matched users and the files they own (reverse keys of fileOwners)
const usersWithOwnedFiles = getOwnerList(fileOwners);
// Calculate a match score for each user. This allows sorting of the final user array in a way that guarantees that users matched with more precise patterns are first and users matched with less precise patterns are last (wildcards)
const userScore = usersWithOwnedFiles
.map((userMatch) => ({
user: userMatch.username,
score: Array.from(userMatch.fileScoreMap.values()).reduce((acc, score) => acc + score, 0),
}))
.sort((a, b) => b.score - a.score);
logger_1.logger.debug(`CODEOWNERS matched the following users: ${JSON.stringify(userScore)}`);
return userScore.map((u) => u.user);
}
catch (err) {
logger_1.logger.warn({ err, pr }, 'Failed to determine CODEOWNERS for PR.');
return [];
}
}
//# sourceMappingURL=code-owners.js.map