-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Automerge action #10069
Comments
More robust code: name: Automerge PRs
on:
schedule:
- cron: '*/10 * * * *' # Run every 10 minutes
jobs:
merge:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Automerge PRs with "automerge" label created within the last month and are mergeable
uses: actions/github-script@v5
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const { repo: { owner, repo } } = context;
const MAX_RETRIES = 3;
const RETRY_INTERVAL_MS = 10000; // 10 seconds
// Get date from a month ago in ISO format
const oneMonthAgo = new Date();
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
const sinceDate = oneMonthAgo.toISOString();
// List PRs with the "automerge" label created within the last month
const { data: issues } = await github.issues.listForRepo({
owner,
repo,
labels: 'automerge',
since: sinceDate
});
// Filter for pull requests from the list of issues
const pullRequests = issues.filter(issue => issue.pull_request);
for (const pr of pullRequests) {
let pullRequest;
for (let i = 0; i < MAX_RETRIES; i++) {
// Fetch PR details
pullRequest = await github.pulls.get({
owner,
repo,
pull_number: pr.number
}).then(res => res.data);
if (pullRequest.mergeable !== null) {
break;
}
console.log(`Waiting for mergeability calculation for PR #${pr.number}...`);
await new Promise(resolve => setTimeout(resolve, RETRY_INTERVAL_MS));
}
// If mergeable is false or still null after retries, skip this PR
if (pullRequest.mergeable === null || pullRequest.mergeable === false) {
console.log(`PR #${pr.number} is not mergeable. Skipping this PR.`);
continue;
}
// Check the status of all checks for the PR
const { data: checkRuns } = await github.checks.listForRef({
owner,
repo,
ref: pullRequest.head.sha
});
const allChecksPassed = checkRuns.check_runs.every(run => run.conclusion === 'success');
if (allChecksPassed) {
try {
await github.pulls.merge({
owner,
repo,
pull_number: pr.number,
commit_title: `Merge pull request #${pr.number}`,
commit_message: `automatic merge of PR #${pr.number}`
});
console.log(`Merged PR #${pr.number}`);
} catch (error) {
console.log(`Failed to merge PR #${pr.number}. Reason: ${error.message}`);
}
} else {
console.log(`Checks not ready or PR not approved for PR #${pr.number}. Skipping merge.`);
}
}
|
@TomeHirata Would you be interested in working on this? |
@harupy Yes, please let me work on this. I have several questions I'd like to clarify
|
Yes, it's manual. If your PR is approved and all you need to do is wait, then you can apply this label.
Good question.
We can increase the interval (e.g. 30 minutes or 1 hour) if necessary. |
I understand, thank you for the clarification. I also learned that |
@TomeHirata Thanks! Can you create a repo (that's okay to be broken) for testing? |
We can create the action above + one more job that protects master/main branch, and then file a PR with automerge label applied. |
Asked ChatGPT to make a few updates: name: Automerge PRs
on:
schedule:
- cron: '*/10 * * * *' # Run every 10 minutes
jobs:
merge:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Automerge PRs with "automerge" label created within the last month and are mergeable
uses: actions/github-script@v5
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const { repo: { owner, repo } } = context;
const MAX_RETRIES = 3;
const RETRY_INTERVAL_MS = 10000; // 10 seconds
const MERGE_INTERVAL_MS = 5000; // 5 seconds pause after a merge
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function logRateLimit() {
const { data: rateLimit } = await github.rateLimit.get();
console.log(`Rate limit remaining: ${rateLimit.rate.remaining}`);
console.log(`Rate limit resets at: ${new Date(rateLimit.rate.reset * 1000).toISOString()}`);
}
async function fetchPullRequestDetails(prNumber) {
for (let i = 0; i < MAX_RETRIES; i++) {
const pullRequest = await github.pulls.get({
owner,
repo,
pull_number: prNumber
}).then(res => res.data);
if (pullRequest.mergeable !== null) {
return pullRequest;
}
console.log(`Waiting for mergeability calculation for PR #${prNumber}...`);
await sleep(RETRY_INTERVAL_MS);
}
return null;
}
async function isPRApproved(prNumber) {
const { data: reviews } = await github.pulls.listReviews({
owner,
repo,
pull_number: prNumber
});
return reviews.some(review => review.state === 'APPROVED');
}
async function areAllChecksPassed(sha) {
const { data: checkRuns } = await github.checks.listForRef({
owner,
repo,
ref: sha
});
return checkRuns.check_runs.every(run => run.conclusion === 'success');
}
// Get date from a month ago in ISO format
const oneMonthAgo = new Date();
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
const sinceDate = oneMonthAgo.toISOString();
// List PRs with the "automerge" label created within the last month
const { data: issues } = await github.issues.listForRepo({
owner,
repo,
labels: 'automerge',
since: sinceDate
});
// Filter for pull requests from the list of issues
const pullRequests = issues.filter(issue => issue.pull_request);
for (const pr of pullRequests) {
const pullRequest = await fetchPullRequestDetails(pr.number);
if (!pullRequest || pullRequest.mergeable !== true) {
console.log(`PR #${pr.number} is not mergeable or could not fetch details. Skipping this PR.`);
await logRateLimit();
continue;
}
if (!await isPRApproved(pr.number)) {
console.log(`PR #${pr.number} hasn't been approved. Skipping merge.`);
await logRateLimit();
continue;
}
if (await areAllChecksPassed(pullRequest.head.sha)) {
try {
await github.pulls.merge({
owner,
repo,
pull_number: pr.number
});
console.log(`Merged PR #${pr.number}`);
await sleep(MERGE_INTERVAL_MS);
await logRateLimit();
} catch (error) {
console.log(`Failed to merge PR #${pr.number}. Reason: ${error.message}`);
}
} else {
console.log(`Checks not ready for PR #${pr.number}. Skipping merge.`);
await logRateLimit();
}
} |
Sure, will create a small test repo. Could you assign this ticket to me so that it's easy to track? |
@TomeHirata Assigned! |
I didn't know |
@mlflow/mlflow-team Please assign a maintainer and start triaging this issue. |
Summary
GitHub's auto-merge feature only checks required jobs. In this repo, we have jobs that are conditionally triggered. Those conditional jobs can't be marked as required (if we marked them as required, they would block the PR forever) but should block the PR. We need our own auto-merge mechanism that has the following features:
Example code
ChatGPT wrote this code. I'm not sure if this really works. We need to test in a different repository:
Notes
Make sure to open a PR from a non-master branch.
Sign off the commit using the
-s
flag when making a commit:Include
#{issue_number}
(e.g.#123
) in the PR description when opening a PR.The text was updated successfully, but these errors were encountered: