chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
async function getMaintainers({ github, context }) {
|
||||
const collaborators = await github.paginate(github.rest.repos.listCollaborators, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
return collaborators
|
||||
.filter(({ role_name }) => ["admin", "maintain"].includes(role_name))
|
||||
.map(({ login }) => login)
|
||||
.sort();
|
||||
}
|
||||
|
||||
const EXEMPTION_RULES = [
|
||||
// Exemption for GenAI evaluation PRs.
|
||||
{
|
||||
authors: ["alkispoly-db", "AveshCSingh", "danielseong1", "smoorjani", "SomtochiUmeh", "xsh310"],
|
||||
allowedPatterns: [
|
||||
/^mlflow\/genai\//,
|
||||
/^tests\/genai\//,
|
||||
/^docs\//,
|
||||
/^mlflow\/entities\/(assessment|dataset|evaluation|scorer)/,
|
||||
],
|
||||
excludedPatterns: [/^mlflow\/genai\/(agent_server|git_versioning|prompts|optimize)\//],
|
||||
},
|
||||
// Exemption for UI PRs.
|
||||
{
|
||||
authors: ["daniellok-db", "danielseong1", "hubertzub-db"],
|
||||
allowedPatterns: [/^mlflow\/server\/js\//],
|
||||
},
|
||||
];
|
||||
|
||||
function matchesAnyPattern(path, patterns) {
|
||||
if (!patterns) {
|
||||
return false;
|
||||
}
|
||||
return patterns.some((pattern) => pattern.test(path));
|
||||
}
|
||||
|
||||
function isAllowedPath(path, rule) {
|
||||
return (
|
||||
matchesAnyPattern(path, rule.allowedPatterns) && !matchesAnyPattern(path, rule.excludedPatterns)
|
||||
);
|
||||
}
|
||||
|
||||
function isExempted(authorLogin, files) {
|
||||
let filesToCheck = files;
|
||||
for (const rule of EXEMPTION_RULES) {
|
||||
if (rule.authors.includes(authorLogin)) {
|
||||
filesToCheck = filesToCheck.filter(
|
||||
({ filename, previous_filename }) =>
|
||||
// Keep files where NOT all before/after file paths are allowed by the rule.
|
||||
![filename, previous_filename].filter(Boolean).every((path) => isAllowedPath(path, rule))
|
||||
);
|
||||
if (filesToCheck.length === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasAnyApproval(reviews) {
|
||||
return reviews.some(({ state }) => state === "APPROVED");
|
||||
}
|
||||
|
||||
module.exports = async ({ github, context, core }) => {
|
||||
const { pull_request: pr } = context.payload;
|
||||
const authorLogin = pr?.user?.login;
|
||||
if (authorLogin === "mlflow-app[bot]") {
|
||||
return;
|
||||
}
|
||||
|
||||
const maintainers = await getMaintainers({ github, context });
|
||||
const reviews = await github.paginate(github.rest.pulls.listReviews, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
});
|
||||
const APPROVED_BOTS = new Set(["nailaopus[bot]"]);
|
||||
const maintainerApproved = reviews.some(
|
||||
({ state, user }) =>
|
||||
state === "APPROVED" &&
|
||||
// GitHub returns `user: null` on reviews from accounts that have since
|
||||
// been deleted; skip them rather than crashing on `user.login`.
|
||||
user &&
|
||||
(maintainers.includes(user.login) ||
|
||||
(user.type.toLowerCase() === "bot" && APPROVED_BOTS.has(user.login)))
|
||||
);
|
||||
|
||||
const files = await github.paginate(github.rest.pulls.listFiles, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
});
|
||||
|
||||
if (isExempted(authorLogin, files)) {
|
||||
if (!hasAnyApproval(reviews)) {
|
||||
core.setFailed(
|
||||
"PR from exempted author needs at least one approval (maintainer approval not required)."
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!maintainerApproved) {
|
||||
const maintainerList = maintainers.join(", ");
|
||||
const message = `This PR requires an approval from at least one of the core maintainers: ${maintainerList}.`;
|
||||
core.setFailed(message);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user