π€ Mergeable helps automate your team's GitHub workflow without a single line of code.
Some examples of what you can do:
- Ensure pull requests follow conventions and prevent merging when it is not followed.
- Notify author of failed guidelines when opening an issue.
- Schedule detection for obsolete (stale) issues and pull requests and notify author and collaborators.
- And more
π Contents: Usage β¦ Configuration β¦ Roadmap β¦ Support β¦ Contributions β¦ Authors
- Install the Mergeable GitHub App.
- Create your recipe(s). Here are some examples.
- Commit and push the recipes to your repository at
.github/mergeable.yml
- (Optional) You can also create a default configuration for an organisation by
creating a repo called
.github
and adding your file there. See Organisation wide defaults for details.
β NOTE: You can also deploy to your own server.
Mergeable is highly configurable.
Define your recipes by creating a .github/mergeable.yml
file in your repository.
The configuration consists of any number of recipes. Recipes are created by tying events with a set of validators and actions together:
version: 2
mergeable:
- when: {{event}}, {{event}} # can be one or more
validate:
# list of validators. Specify one or more.
- do: {{validator}}
{{option}}: # name of an option supported by the validator.
{{sub-option}}: {{value}} # an option will have one or more sub-options.
pass: # list of actions to be executed if all validation passes. Specify one or more. Omit this tag if no actions are needed.
- do: {{action}}
fail: # list of actions to be executed when at least one validation fails. Specify one or more. Omit this tag if no actions are needed.
- do: {{action}}
error: # list of actions to be executed when at least one validator throws an error. Specify one or more. Omit this tag if no actions are needed.
- do: {{action}}
Take a look at some example recipes.
β NOTE: Earlier versions used a different set of convention. It will be supported in the foreseeable future but will eventually be phased out.
Events are specified in the when
tag like this:
- when: pull_request.opened
Multiple events for a recipe are declared comma delimited like this:
- when: pull_request.opened, issues.opened
Events supported for pull requests are as follows:
pull_request.opened
, pull_request.edited
, pull_request_review.submitted
, pull_request_review.edited
, pull_request_review.dismissed
, pull_request.labeled
, pull_request.unlabeled
, pull_request.milestoned
, pull_request.demilestoned
, pull_request.assigned
, pull_request.unassigned
, pull_request.synchronize
,
And for issues:
issues.opened
, issues.edited
, issues.labeled
, issues.unlabeled
, issues.milestoned
, issues.demilestoned
, issues.assigned
, issues.unassigned
, issues.synchronize
β NOTE: More details about events can be found on the GitHub events page.
For convenience, wildcards can be used: pull_request.*
, issues.*
, pull_request_review.*
β NOTE: Each
validator
andaction
declares it's own supported events. Read the validator and action sections to find out which events are supported respectively.
- do: approvals
min:
count: 2 # Number of minimum reviewers. In this case 2.
message: 'Custom message...'
required:
reviewers: [ user1, user2 ] # list of github usernames required to review
owners: true # Optional boolean. When true, the file .github/CODEOWNER is read and owners made required reviewers
assignees: true # Optional boolean. When true, PR assignees are made required reviewers.
pending_reviewer: true # Optional boolean. When true, all the requested reviewer's approval is required
message: 'Custom message...'
block:
changes_requested: true #If true, block all approvals when one of the reviewers gave 'changes_requested' review
message: 'Custom message...'
β NOTE:
owners
sub-option only works in public repos right now, we have plans to enable it for private repos in the future.
Supported events:
'pull_request.*', 'pull_request_review.*'
- do: assignee
max:
count: 2 # There should not be more than 2 assignees
message: 'test string' # this is optional
min:
count: 2 # min number of assignees
message: 'test string' # this is optional
Supported events:
'pull_request.*', 'pull_request_review.*', 'issues.*'
dependent
validates that the files specified are all part of a pull request (added or modified).
- do: dependent
files: ['package.json', 'yarn.lock'] # list of files that are dependent on one another and must all be part of the changes in a PR.
message: 'Custom message...' # this is optional, a default message is used when not specified.
Alternatively, to validate dependent files only when a specific file is part of the pull request, use the changed
option:
- do: dependent
changed:
file: package.json
files: ['package-lock.json', 'yarn.lock']
message: 'Custom message...' # this is optional, a default message is used when not specified.
The above will validate that both the files package-lock.json
and yarn.lock
is part of the modified or added files if and only if package.json
is part of the PR.
size
validates that the size of changes in the pull request conform to a
specified limit. We can pass in three options: total
, additions
or deletions
. Each of this take in a count
and message
.
- do: size
lines:
total:
count: 500
message: Change is very large. Should be under 500 lines of additions and deletions.
additions:
count: 250
message: Change is very large. Should be under 250 lines of additions
deletions:
count: 500
message: Change is very large. Should be under 250 lines of deletions.
max
is an alias for total
, so the below configuration is still valid.
- do: size
lines:
max:
count: 500
message: Change is very large. Should be under 500 lines of additions and deletions.
It also supports an ignore
setting to allow excluding certain files from the
total size (e.g. for ignoring automatically generated files that increase the
size a lot).
This option supports glob patterns, so you can provide either the path to a specific file or ignore whole patterns:
- do: size
ignore: ['package-lock.json', 'src/tests/__snapshots__/**', 'docs/*.md']
lines:
total:
count: 500
message: Change is very large. Should be under 500 lines of additions and deletions
Note that the glob functionality is powered by the minimatch library. Please see their documentation for details on how glob patterns are handled and possible discrepancies with glob handling in other tools.
The size
validator currently excludes from the size count any files that were
completely deleted in the PR.
'pull_request.*', 'pull_request_review.*'
- do: description
no_empty:
enabled: false # Cannot be empty when true.
message: 'Custom message...' # this is optional, a default message is used when not specified.
must_include:
regex: '### Goals|### Changes'
regex_flag: 'none' # Optional. Specify the flag for Regex. default is 'i', to disable default use 'none'
message: >
Please describe the goals (why) and changes (what) of the PR.
# message is is optional, a default message is used when not specified.
must_exclude:
regex: 'DO NOT MERGE'
regex_flag: 'none' # Optional. Specify the flag for Regex. default is 'i', to disable default use 'none'
message: 'Custom message...' # optional
begins_with:
match: '### Goals' # or array of strings
message: 'Some message...' #optional
ends_with:
match: 'Any last sentence' # array of strings
message: 'Come message...' # optional
Supported events:
'pull_request.*', 'pull_request_review.*', 'issues.*'
- do: label
no_empty:
enabled: false # Cannot be empty when true.
message: 'Custom message...'
must_include:
regex: 'type|chore|wont'
regex_flag: 'none' # Optional. Specify the flag for Regex. default is 'i', to disable default use 'none'
message: 'Custom message...'
must_exclude:
regex: 'DO NOT MERGE'
regex_flag: 'none' # Optional. Specify the flag for Regex. default is 'i', to disable default use 'none'
message: 'Custom message...'
begins_with:
match: 'A String' # or array of strings
message: 'Some message...'
ends_with:
match: 'A String' # or array of strings
message: 'Come message...'
# all of the message sub-option is optional
Supported events:
'pull_request.*', 'pull_request_review.*', 'issues.*'
- do: changeset # validate against the files in the PR
no_empty:
enabled: false # Cannot be empty when true.
message: 'Custom message...'
must_include:
regex: 'yarn.lock'
message: 'Custom message...'
must_exclude:
regex: 'package.json'
message: 'Custom message...'
begins_with:
match: 'A String' # or array of strings
message: 'Some message...'
ends_with:
match: 'A String' # or array of strings
message: 'Come message...'
min:
count: 2 # min number of files in a PR
message: 'Custom message...'
max:
count: 2 # max number of files in a PR
message: 'Custom message...'
# all of the message sub-option is optional
Supported events:
'pull_request.*', 'pull_request_review.*'
- do: milestone
no_empty:
enabled: true # Cannot be empty when true.
message: 'Custom message...'
must_include:
regex: 'type|chore|wont'
regex_flag: 'none' # Optional. Specify the flag for Regex. default is 'i', to disable default use 'none'
message: 'Custom message...'
must_exclude:
regex: 'DO NOT MERGE'
regex_flag: 'none' # Optional. Specify the flag for Regex. default is 'i', to disable default use 'none'
message: 'Custom message...'
begins_with:
match: 'A String' # array of strings
message: 'Some message...'
ends_with:
match: 'A String' # array list of strings
message: 'Come message...'
# all of the message sub-option is optional
β NOTE: When a closing keyword is used in the description of a pull request. The annotated issue will be validated against the conditions as well.
Supported events:
'pull_request.*', 'pull_request_review.*', 'issues.*'
- do: project
must_include:
regex: 'type|chore|wont'
message: 'Custom message...'
β NOTE: When a closing keyword is used in the description of a pull request. The annotated issue will be validated against the conditions as well.
Supported events:
'pull_request.*', 'pull_request_review.*', 'issues.*'
- do: stale
days: 20 # number of days ago.
type: pull_request, issues # what items to search for.
Supported events:
'schedule.repository'
β NOTE: This is a special use case. The schedule event runs on an interval. When used with
stale
, it will search for issues and/or pull request that are n days old. See a full example Β»
- do: title
no_empty:
enabled: true # Cannot be empty when true. A bit redundant in this case since GitHub don't really allow it. :-)
message: 'Custom message...'
must_include:
regex: 'doc|feat|fix|chore'
regex_flag: 'none' # Optional. Specify the flag for Regex. default is 'i', to disable default use 'none'
message: 'Custom message...'
must_exclude:
regex: 'DO NOT MERGE|WIP'
regex_flag: 'none' # Optional. Specify the flag for Regex. default is 'i', to disable default use 'none'
message: 'Custom message...'
begins_with:
match: ['doc','feat','fix','chore']
message: 'Some message...'
ends_with:
match: 'A String' # or array of strings
message: 'Come message...'
# all of the message sub-option is optional
- do: commit
message:
regex: '^(feat|docs|chore|fix|refactor|test|style|perf)(\(\w+\))?:.+$'
message: 'Custom message' # Semantic release conventions must be followed
skip_merge: true # Optional, Default is true. Will skip commit with message that includes 'Merge'
oldest_only: false # Optional, Default is false. Only check the regex against the oldest commit
single_commit_only: false # Optional, Default is false. only process this validator if there is one commit
Supported events:
'pull_request.*', 'pull_request_review.*'
Validators can be grouped together with AND
and OR
operators:
- do: description
and:
- must_include:
regex: 'Test Plan'
message: 'Test plan must be included'
- must_include:
regex: 'Goal'
message: 'Please include the goal of the PR'
AND
and OR
operators can also be nested
- do: label
or:
- and:
- must_include:
regex: 'release notes: yes'
message: 'Please include release note: yes'
- must_include:
regex: '^lang\/'
message: 'Please include a language label'
- must_include:
regex: 'release notes: no'
message: 'Please include release note: no'
AND
and OR
can also be used at the validator level:
- do: or
validate:
- do: description
and:
- must_include:
regex: 'Test plan'
message: 'Test plan must be included'
- must_include:
regex: 'Goal'
message: 'Please include the goal of the PR'
- do: label
must_include:
regex: 'test plan: no'
message: 'If no test plan is necessary, please include test plan: no label'
similarly, AND
and OR
can also be nested at the validator level:
- do: or
validate:
- do: and
validate:
- do: description
must_include:
regex: 'Test plan'
message: 'Test plan must be included'
- do: title
must_exclude:
regex: 'WIP'
message: 'PR should not be in WIP'
- do: label
must_include:
regex: 'test plan: no'
message: 'If no test plan is necessary, please include test plan: no label'
To reuse certain parts of the config, you can utilize anchor points that yaml
provides (link), like this
- do: title
must_include: ®ex_semantic
regex: '^(build|chore|ci|docs|feat|fix|improvement|perf|refactor|revert|style|test)(\([a-z0-9_-]+\))?: [^\s].+$'
regex_flag: 'none'
- do: commit
message: *regex_semantic
Actions are listed for execution at the pass
, fail
and error
tags for a recipe based on the results of the validation.
Creates comments in issues and/or pull requests depending on the event specified in the when
tag.
- do: comment
payload:
body: >
Your very long comment can go here.
Supported events:
'schedule.repository', 'pull_request.*', 'issues.*'
- do: assign
assignees: [ 'shine2lay', 'jusx' ] # only array accepted
Supported events:
'pull_request.*', 'issues.*'
Creates comments in issues and/or pull requests depending on the event specified in the when
tag.
- do: labels
# if label doesn't exist, it'll be created
labels: [ 'Triage' ] # Only arrays are accepted
Supported events:
'schedule.repository', 'pull_request.*', 'issues.*'
Note: Some default cases do exists for pass
, fail
and error
cases but if you provide these cases, the defaults will be overwritten
- do: checks # default pass case
status: 'success' # Can be: success, failure, neutral, cancelled, timed_out, or action_required
payload:
title: 'Mergeable Run have been Completed!'
summary: "All the validators have returned 'pass'! \n Here are some stats of the run: \n {{validationCount}} validations were ran"
You can pass in Handlebars template to show the details result of the run.
- do: checks # default fail case
status: 'failure' # Can be: success, failure, neutral, cancelled, timed_out, or action_required
payload:
title: 'Mergeable Run have been Completed!'
summary: |
### Status: {{toUpperCase validationStatus}}
Here are some stats of the run:
{{validationCount}} validations were ran.
{{passCount}} PASSED
{{failCount}} FAILED
text: "{{#each validationSuites}}\n
#### {{{statusIcon status}}} Validator: {{toUpperCase name}}\n
{{#each validations }} * {{{statusIcon status}}} ***{{{ description }}}***\n
Input : {{{details.input}}}\n
Settings : {{{displaySettings details.settings}}}\n
{{/each}}\n
{{/each}}"
Supported events:
'pull_request.*', 'pull_request_review.*'
Creates comments in issues and/or pull requests depending on the event specified in the when
tag.
- do: request_review
reviewers: ['name1', 'name2']
This is only enforced for reviewers who has not been requested already
Note: The reviewers must be collaborator, otherwise, github api will throw error
Supported events:
'pull_request.*'
Close an Issue or Pull Request
- do: close
Supported events:
'pull_request.*', 'issues.*'
Validate pull requests for mergeability based on content and structure of your PR (title, labels, milestone, project, description, approvals, etc). Here are a few examples:
Work In Progress: Prevent accidental merging of Pull Requests that are work in progress by labeling it WIP
or prefixing the title with the abbreviation.
π See Recipe
version: 2
mergeable:
- when: pull_request.*
validate:
- do: title
must_exclude:
regex: ^\[WIP\]
- do: label
must_exclude:
regex: 'wip'
Description: Ensure all Pull Requests have a description so that reviewers have context.
π See Recipe
version: 2
mergeable:
- when: pull_request.*
validate:
- do: description
no_empty:
enabled: true
message: Description matter and should not be empty. Provide detail with **what** was changed, **why** it was changed, and **how** it was changed.
Dependent Files: Certain files are related and you want to ensure that they are updated as part of the PR (i.e. if package.json
is updated, so should yarn.lock
and package-lock.json
)
π See Recipe
version: 2
mergeable:
- when: pull_request.*
validate:
- do: dependent
changed:
file: 'package.json' # also supports globs expressions
required: ['package-lock.json', 'yarn.lock'] # alias: `files` for backward compatibility
Milestone: Ensure that all Pull Requests have a milestone associated. Mergeable will also detect when you are closing an issue that is associated with the specified milestone.
π See Recipe
version: 2
mergeable:
- when: pull_request.*
validate:
- do: milestone
must_include:
regex: Release 1
Size: Ensure that PRs don't exceed a certain size in terms of lines changed
(excluding file patterns specified with ignore
).
π See Recipe
version: 2
mergeable:
- when: pull_request.*
validate:
- do: size
ignore: ['ignore_me.js', 'ignore_this_directory/*', '**/ignore_this_prefix*.js']
lines:
max:
count: 500
message: Change is very large. Should be under 500 lines of addtions and deletions.
Read the configuration options for more options.
Automatically create a comment when a new issue is openened
to remind the author when the title does not follow conventions or is missing a label.
π See Recipe
version: 2
mergeable:
- when: issues.opened
validate:
- do: title
begins_with:
match: ['AUTH', 'SOCIAL', 'CORE']
- do: label
must_include:
regex: bug|enhancement
fail:
- do: comment
payload:
body: >
The following problems were found with this issue:
- Title must begin with `AUTH`, `SOCIAL` or `CORE`
- The issue should either be labeled `bug` or `enhancement`
Read the configuration options for more options.
Detect issues and pull requests that are n
days old (stale) and notify authors and collaborators by creating a comment.
π See Recipe
version: 2
mergeable:
- when: schedule.repository
validate:
- do: stale
days: 20
type: pull_request, issues
pass:
- do: comment
payload:
body: This is old. Is it still relevant?
You can specify a default configuration to be applied across your GitHub organisation. This can help reduce how many configuration files you need to maintain and make it easier to get started with Mergeable.
To add a default configuration:
- Create a repository called
.github
in your organisation. - Create a file with the path
.github/mergeable.yml
in this repository.
The final path of the file (including the repo name) should be <YOUR_ORG>/.github/.github/mergeable.yml
Mergeable will now use this file as the default when it cannot find one in a given repository or PR. It determines the file to use in the following order:
- A
mergeable.yml
inside the PR. - A
mergeable.yml
inside the repository the PR is for. - A
mergeable.yml
at<YOUR_ORG>/.github/.github/mergeable.yml
.
Note: Mergeable will only ever use a single file. It does not merge files.
The Probots library that Mergeable uses automatically searches for config files
in a repo named .github
within the organisation.
The double nesting of the <YOUR_ORG>/.github/.github/mergeable.yml
default
file is unfortunately necessary. The GitHub app permissions model only lets you
specify a single path for your probot to access, so it must be the same as in
regular repositories.
- Additional actions like
label
andassign
- Potentially, integration with external tools like pivotal tracker, slack and trello.
- More likely coveralls or sonarqube.
Found a bug? Have a question? Or just want to chat?
We need your help:
- Have an π‘idea for a new feature? Please create a new issue and tell us!
- Fix a bug, implement a new validator or action and open a pull request!
βοΈ NOTE: For development and testing. You'll want to read about how to run it locally.
- Originally created by @jusx π follow him on Twitter.
- Co-authored by @shine2lay
- Logo by @minap0lis π follow her on Instagram.
AGPL, Copyright (c) 2019 Justin Law & Shine Lee