generated from Bullrich/parity-action-template
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added core functionality to auto-merge PRs (#5)
Created base of the bot, with the ability to comment on a PR and to enable/disable auto-merge. You can find a working example in paritytech-stg#1 This resolves #1 The bot can enable auto-merge, disable it and show the available commands. If the user is not a member of the org or the author of the PR, the bot will not work. This is to stop external parties of enabling/disabling the bot. This is a required step to solve polkadot-fellows/runtimes#41 --------- Co-authored-by: Przemek Rzad <[email protected]>
- Loading branch information
Showing
12 changed files
with
874 additions
and
530 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
name: Auto Merge Bot | ||
|
||
on: | ||
# GitHub considers PRs as issues | ||
issue_comment: | ||
types: [created] | ||
|
||
jobs: | ||
set-auto-merge: | ||
runs-on: ubuntu-latest | ||
# Important! This forces the job to run only on Pull Requests | ||
if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/bot') }} | ||
steps: | ||
- name: Set auto merge | ||
uses: paritytech/auto-merge-bot@main | ||
with: | ||
GITHUB_TOKEN: '${{ github.token }}' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,58 @@ | ||
# Parity GitHub Action template | ||
# Auto-Merge-Bot | ||
|
||
Template used to generate GitHub Actions. | ||
Bot which enables or disable [`auto-merge`](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/automatically-merging-a-pull-request) in a repository. | ||
|
||
## To start | ||
## Why? | ||
|
||
- Remember to modify the `action.yml` file to have your required attributes and details. | ||
- You can use [GitHub Action Brandings cheatsheet](https://github.com/haya14busa/github-action-brandings) to set the style of the action. | ||
- Remember to modify the name in the `package.json`. | ||
This action was developed to help external parties merge their own Pull Requests. | ||
|
||
If an external party makes a PR, and it is approved, they still can not merge it. This bot gives them the ability to enable the `auto-merge` function so, once their PR gets approved, it is merged. | ||
|
||
## Configuration | ||
Be sure that **Allow auto-merge** is enabled in the repository options. | ||
|
||
Create a file named `.github/workflows/auto-merge-bot.yml` and add the following: | ||
```yaml | ||
name: Auto Merge Bot | ||
|
||
on: | ||
# GitHub considers PRs as issues | ||
issue_comment: | ||
types: [created] | ||
|
||
jobs: | ||
set-auto-merge: | ||
runs-on: ubuntu-latest | ||
# Important! This forces the job to run only on comments on Pull Requests that starts with '/bot' | ||
if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/bot') }} | ||
steps: | ||
- name: Set auto merge | ||
uses: paritytech/auto-merge-bot@main | ||
with: | ||
GITHUB_TOKEN: '${{ github.token }}' | ||
MERGE_METHOD: "SQUASH" | ||
``` | ||
#### Inputs | ||
You can find all the inputs in [the action file](./action.yml), but let's walk through each one of them: | ||
- `GITHUB_TOKEN`: Token to access to the repository. | ||
- **required** | ||
- This is provided by the repo, you can simply use `${{ github.token }}`. | ||
- `MERGE_METHOD`: Type of merge to enable. | ||
- **Optional**: Defaults to `SQUASH`. | ||
- Available types are `MERGE`, `REBASE` and `SQUASH`. | ||
- Make sure that the type of merge you selected is available in the repository merge options. | ||
|
||
## Usage | ||
|
||
To trigger the bot, you need to write a comment in a Pull Request where the action is installed. The available actions are: | ||
- `/bot merge`: Enables auto-merge for Pull Request | ||
- `/bot cancel`: Cancels auto-merge for Pull Request | ||
- `/bot help`: Shows this menu | ||
|
||
The bot can only be triggered by the author of the PR or by users who *publicly* belongs to the organization of the repository. | ||
|
||
By publicly, I refer to the members of an organization which can be seen by external parties. If you are not sure if you are part of an organization, simply open https://github.com/orgs/**your_organization**/people in a private window. If you don’t see your name there, you are not a public member. | ||
|
||
Find related docs here: [ Publicizing or hiding organization membership](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-your-membership-in-organizations/publicizing-or-hiding-organization-membership). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
{ | ||
"name": "parity-action-template", | ||
"name": "auto-merge-bot", | ||
"version": "0.0.1", | ||
"description": "GitHub action template for Parity", | ||
"description": "Bot which enables or disable auto-merge", | ||
"main": "src/index.ts", | ||
"scripts": { | ||
"start": "node dist", | ||
|
@@ -12,17 +12,19 @@ | |
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/Bullrich/parity-action-template.git" | ||
"url": "git+https://github.com/paritytech/auto-merge-bot.git" | ||
}, | ||
"author": "Javier Bullrich <[email protected]>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/Bullrich/parity-action-template/issues" | ||
"url": "https://github.com/paritytech/auto-merge-bot/issues" | ||
}, | ||
"homepage": "https://github.com/Bullrich/parity-action-template#readme", | ||
"homepage": "https://github.com/paritytech/auto-merge-bot#readme", | ||
"dependencies": { | ||
"@actions/core": "^1.10.1", | ||
"@actions/github": "^5.1.1", | ||
"@octokit/graphql": "^7.0.2", | ||
"@octokit/graphql-schema": "^14.32.1", | ||
"@octokit/webhooks-types": "^7.3.1" | ||
}, | ||
"devDependencies": { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { Issue, IssueComment } from "@octokit/webhooks-types"; | ||
|
||
import { CommentsApi } from "./github/comments"; | ||
import { Merger } from "./github/merger"; | ||
import { ActionLogger } from "./github/types"; | ||
|
||
const BOT_COMMAND = "/bot"; | ||
|
||
type Command = "merge" | "cancel" | "help"; | ||
|
||
const botCommands = ` | ||
**Available commands** | ||
- \`/bot merge\`: Enables auto-merge for Pull Request | ||
- \`/bot cancel\`: Cancels auto-merge for Pull Request | ||
- \`/bot help\`: Shows this menu | ||
For more information see the [documentation](https://github.com/paritytech/auto-merge-bot) | ||
`; | ||
|
||
export class Bot { | ||
constructor( | ||
private readonly comment: IssueComment, | ||
private readonly pr: Issue, | ||
private readonly logger: ActionLogger, | ||
private readonly commentsApi: CommentsApi, | ||
) {} | ||
|
||
/** Verifies if the author is the author of the PR or a member of the org */ | ||
async canTriggerBot(): Promise<boolean> { | ||
this.logger.debug("Evaluating if user can trigger the bot"); | ||
const author = this.pr.user.id; | ||
if (this.comment.user.id === author) { | ||
this.logger.debug("Author of comment is also author of PR"); | ||
return true; | ||
} | ||
this.logger.debug("Author of comment is not the author of the PR"); | ||
|
||
return await this.commentsApi.userBelongsToOrg(this.comment.user.login); | ||
} | ||
|
||
async run(merger: Merger): Promise<void> { | ||
this.logger.info("Running action on comment: " + this.comment.html_url); | ||
if (!this.comment.body.startsWith(BOT_COMMAND)) { | ||
this.logger.info( | ||
`Ignoring comment ${this.comment.html_url} as it does not start with '${BOT_COMMAND}'`, | ||
); | ||
return; | ||
} | ||
|
||
if (this.pr.state === "closed") { | ||
this.logger.info("Ignoring PR as it is closed"); | ||
return; | ||
} | ||
|
||
if (!(await this.canTriggerBot())) { | ||
const { login } = this.comment.user; | ||
const org = this.commentsApi.pullData.owner; | ||
this.logger.warn( | ||
"User is not allowed to trigger the bot. " + | ||
`He is not the author of the PR and does not *publicly* belong to the org: https://github.com/orgs/${org}/people`, | ||
); | ||
await this.commentsApi.reactToComment(this.comment.id, "-1"); | ||
await this.commentsApi.comment( | ||
"## Auto-Merge-Bot\n" + | ||
`User @${login} is not the author of the PR and does not [*publicly* belong to the org \`${org}\`](https://github.com/orgs/${org}/people).\n\n` + | ||
"Only author or *public* org members can trigger the bot.", | ||
); | ||
return; | ||
} | ||
this.logger.debug("User can trigger bot"); | ||
|
||
const [_, command] = this.comment.body.split(" "); | ||
try { | ||
switch (command as Command) { | ||
case "merge": | ||
await this.commentsApi.reactToComment(this.comment.id, "+1"); | ||
await merger.enableAutoMerge(); | ||
await this.commentsApi.comment( | ||
"Enabled `auto-merge` in Pull Request", | ||
); | ||
break; | ||
case "cancel": | ||
await this.commentsApi.reactToComment(this.comment.id, "+1"); | ||
await merger.disableAutoMerge(); | ||
await this.commentsApi.comment( | ||
"Disabled `auto-merge` in Pull Request", | ||
); | ||
break; | ||
case "help": | ||
await this.commentsApi.comment("## Auto-Merge-Bot\n" + botCommands); | ||
break; | ||
default: { | ||
await this.commentsApi.reactToComment(this.comment.id, "confused"); | ||
await this.commentsApi.comment( | ||
"## Auto-Merge-Bot\n" + | ||
`Command \`${command}\` not recognized.\n\n` + | ||
botCommands, | ||
); | ||
} | ||
} | ||
} catch (e) { | ||
this.logger.error(e as Error); | ||
throw e; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { ActionLogger, GitHubClient } from "./types"; | ||
|
||
/** API class that uses the default token to access the data from the pull request and the repository */ | ||
export class CommentsApi { | ||
constructor( | ||
private readonly api: GitHubClient, | ||
private readonly logger: ActionLogger, | ||
public readonly pullData: { repo: string; owner: string; number: number }, | ||
) {} | ||
|
||
async comment(message: string): Promise<void> { | ||
await this.api.rest.issues.createComment({ | ||
...this.pullData, | ||
body: message, | ||
issue_number: this.pullData.number, | ||
}); | ||
} | ||
|
||
async reactToComment( | ||
commentId: number, | ||
reaction: "+1" | "-1" | "confused", | ||
): Promise<void> { | ||
await this.api.rest.reactions.createForIssueComment({ | ||
...this.pullData, | ||
comment_id: commentId, | ||
content: reaction, | ||
}); | ||
} | ||
|
||
async userBelongsToOrg(username: string): Promise<boolean> { | ||
const org = this.pullData.owner; | ||
this.logger.debug( | ||
`Checking if user ${username} belongs to ${org} as a public user.`, | ||
); | ||
// If the user does not belong to the org, this will throw an http error | ||
try { | ||
const { status } = await this.api.rest.orgs.checkPublicMembershipForUser({ | ||
org, | ||
username, | ||
}); | ||
return status === 204; | ||
} catch (error) { | ||
this.logger.warn(error as Error); | ||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { graphql } from "@octokit/graphql"; | ||
import { PullRequestMergeMethod } from "@octokit/graphql-schema"; | ||
|
||
import { ActionLogger } from "./types"; | ||
|
||
// https://docs.github.com/en/graphql/reference/mutations#enablepullrequestautomerge | ||
export const ENABLE_AUTO_MERGE = ` | ||
mutation($prId: ID!, $mergeMethod: PullRequestMergeMethod!) { | ||
enablePullRequestAutoMerge(input: {pullRequestId: $prId, mergeMethod: $mergeMethod}) { | ||
clientMutationId | ||
} | ||
}`; | ||
|
||
// https://docs.github.com/en/graphql/reference/mutations#disablepullrequestautomerge | ||
export const DISABLE_AUTO_MERGE = ` | ||
mutation($prId: ID!) { | ||
disablePullRequestAutoMerge(input: {pullRequestId: $prId}) { | ||
clientMutationId | ||
} | ||
}`; | ||
|
||
export type MergeMethod = "SQUASH" | "MERGE" | "REBASE"; | ||
|
||
export class Merger { | ||
constructor( | ||
private readonly nodeId: string, | ||
private readonly gql: typeof graphql, | ||
private readonly logger: ActionLogger, | ||
private readonly mergeMethod: PullRequestMergeMethod, | ||
) {} | ||
|
||
async enableAutoMerge(): Promise<void> { | ||
await this.gql<{ | ||
enablePullRequestAutoMerge: { clientMutationId: unknown }; | ||
}>(ENABLE_AUTO_MERGE, { | ||
prId: this.nodeId, | ||
mergeMethod: this.mergeMethod, | ||
}); | ||
this.logger.info("Succesfully enabled auto-merge"); | ||
} | ||
|
||
async disableAutoMerge(): Promise<void> { | ||
await this.gql<{ | ||
disablePullRequestAutoMerge: { clientMutationId: unknown }; | ||
}>(DISABLE_AUTO_MERGE, { | ||
prId: this.nodeId, | ||
}); | ||
this.logger.info("Succesfully disabled auto-merge"); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.