Skip to content
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

Added core functionality to auto-merge PRs #5

Merged
merged 40 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
297684f
added log of events
Bullrich Sep 26, 2023
24f68ed
added better log handling
Bullrich Sep 26, 2023
033f3ac
added logic for evaluating the bot commands
Bullrich Sep 26, 2023
13f7fb0
commented unused line
Bullrich Sep 26, 2023
3d3992d
added simple graphql query
Bullrich Sep 26, 2023
cd325e8
catched the error
Bullrich Sep 26, 2023
921062c
fixed missing function
Bullrich Sep 26, 2023
0ac112d
fixed variable name
Bullrich Sep 26, 2023
7b6c5b4
awaited function
Bullrich Sep 26, 2023
4270a1c
added ability to enable automerge
Bullrich Sep 26, 2023
d9686ef
added tests and fixed bug
Bullrich Sep 26, 2023
6700333
added more verbosity
Bullrich Sep 26, 2023
34d9a6a
fixed naming issue
Bullrich Sep 26, 2023
b7cadd3
abstracted logic to class
Bullrich Sep 26, 2023
ea5293f
added ability to comment on PRs
Bullrich Sep 26, 2023
82b198c
imrpoved comment
Bullrich Sep 26, 2023
e5878cb
added reaction to comment
Bullrich Sep 26, 2023
c2f6486
improved message
Bullrich Sep 26, 2023
a5bddf9
added help command
Bullrich Sep 26, 2023
1a99d81
added nitpicking of reaction
Bullrich Sep 26, 2023
7c17565
added filter of users who can use the bot
Bullrich Sep 26, 2023
ed9d090
implemented new bot class
Bullrich Sep 26, 2023
691c40d
fixed small typo in comment text
Bullrich Sep 26, 2023
2dda708
added reaction to failed comment
Bullrich Sep 26, 2023
258e55d
fixed spacing
Bullrich Sep 26, 2023
c740d81
improved method to verify if the user is an org member
Bullrich Sep 26, 2023
685bf2c
added documentation comments
Bullrich Sep 26, 2023
7f22891
added ability to change merge method
Bullrich Sep 26, 2023
218a910
changed merge type
Bullrich Sep 26, 2023
6f3e2fa
changed variable name
Bullrich Sep 26, 2023
2e69224
created readme
Bullrich Sep 26, 2023
1892082
updated config files
Bullrich Sep 26, 2023
da4b77e
ran yarn fix
Bullrich Sep 26, 2023
fd4b55f
fixed linting issues
Bullrich Sep 26, 2023
7251799
implemented bot on itself
Bullrich Sep 26, 2023
b4c9ab1
fixed minor silly problem with the linter
Bullrich Sep 26, 2023
1d5dc8d
added a *why* section to the readme
Bullrich Sep 26, 2023
51696c3
moved devDependencies to dependencies
Bullrich Sep 26, 2023
2736fcb
fixed comment
Bullrich Sep 27, 2023
f20cddc
updated docs to add code check
Bullrich Sep 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/auto-merge-bot.yml
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') }}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, it's the right time to question /bot part, and consider /merge

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left this for a future task: #8

As I need to change a couple of things and thought it would be better there

steps:
- name: Set auto merge
uses: paritytech/auto-merge-bot@main
with:
GITHUB_TOKEN: '${{ github.token }}'
61 changes: 55 additions & 6 deletions README.md
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).
14 changes: 9 additions & 5 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
name: "Example Action"
description: "This values need to be changed"
name: "Auto Merge Bot"
description: "Bot which enables or disable auto-merge"
author: Bullrich
branding:
icon: copy
color: yellow
icon: git-merge
color: red
inputs:
GITHUB_TOKEN:
required: true
description: The token to access the repo
description: The token to access the repo information
MERGE_METHOD:
required: false
description: The merge method to use. Must be one of MERGE, SQUASH or REBASE.
default: SQUASH
outputs:
repo:
description: 'The name of the repo in owner/repo pattern'
Expand Down
12 changes: 7 additions & 5 deletions package.json
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",
Expand All @@ -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": {
Expand Down
107 changes: 107 additions & 0 deletions src/bot.ts
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)) {
Bullrich marked this conversation as resolved.
Show resolved Hide resolved
this.logger.info(
`Ignoring comment ${this.comment.html_url} as it does not start with '${BOT_COMMAND}'`,
);
return;
}

if (this.pr.state === "closed") {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we check against draft too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state object only has closed or open.

I would say no anyways because it still needs to have the reviews. You can always set a project to auto-merge even if it is in draft state.

What do you think?

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();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will it be okay if it's not enabled?
or if you enable - that it's already enabled?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I tested that out and it doesn't change the state if it is already set. Here you have an example where I called cancel even tho it isn't enabled.

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;
}
}
}
47 changes: 47 additions & 0 deletions src/github/comments.ts
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;
}
}
}
50 changes: 50 additions & 0 deletions src/github/merger.ts
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");
}
}
15 changes: 0 additions & 15 deletions src/github/pullRequest.ts

This file was deleted.

Loading
Loading