Tailor pull requests to your liking.
Tailor is a GitHub bot which can be used to validate that pull requests comply with a set of rules. It can be used for example to ensure that commit messages are properly formatted, that commits are properly structured, or that pull requests have particular labels.
Each repository contains its own rules and configuration. This makes it easy to version the configuration alongside the code. The configuration is stored in /.github/tailor.yaml
and has the following structure:
# This is the list of rules that are applied to each of the pull requests to
# the repository.
rules:
# The name is used as an identifier and can be used in the admin commands.
- name: commit title
# The description is printed in the status messages to inform the submitter
# of what is wrong with their pull request.
description: all commit titles are less than or equal to 50 characters
# The expression describes the rule itself. Refer to the language
# documentation for an overview of the available functionality.
expression: .commits all(.title length < 51)
Each of the rules are run on the entire pull request (the root context). They are run independently and cannot influence one another. Often times, it is useful to use .commits all
to run an expression on each of the commits in the pull request, requiring all of them to comply. This is detailed further in the Expressions section. The rule expression must result in a boolean value, true
indicating a success and false
a failure.
Expressions are made up of a series of infix operators detailed below. Almost all operators have an input value (on the left) and most have an argument value (on the right). Expressions are evaluated from left to right, which the result of the previous operator feeding into the next. Parenthesis can be used to evaluate a contained expression before its surroundings.
The only operator which doesn't take an input is the context operator (.
). The result of this operation is the current context. It can be further limited by using a specifier (e.g. .commits
) if the context is a dictionary. Since the context operator is the only operator which doesn't take an input, all expressions must begin with it.
It might be helpful to break down a few examples.
This expression returns true
if there are exactly ten commits in the pull request: .commits length = 10
. Parenthesis around every operation could be added for clarity: (((.commits) length) = 10)
This expression returns true
if every commit title is no more than fifty characters: .commits all(.title length < 51)
. This expression makes use of the all
operator, which is used for manipulating lists. For every commit, the expression .title length < 51
is evaluated with the context set to the commit in question. This inner expression then uses a context specifier (.title
) to get the commit title and uses length
to get the length of the title and compares to see if there are more than fifty characters. If every one of the inner expressions evaluates to true
, all
also results in true
.
There are a handful of other operators, detailed below.
Operator | Input | Argument | Result | Description |
---|---|---|---|---|
= |
Any value | Any value | Boolean | true if the values are equal |
< |
Numeral | Numeral | Boolean | true if the input is less than the argument |
> |
Numeral | Numeral | Boolean | true if the input is greater than the argument |
Operator | Input | Argument | Result | Description |
---|---|---|---|---|
and |
Boolean | Boolean | Boolean | true if both the input and argument are true |
or |
Boolean | Boolean | Boolean | true if either the input or argument are true |
xor |
Boolean | Boolean | Boolean | true if one of the input or argument are true |
not |
Boolean | Boolean | true if the input is false |
Each of the list manipulation operators (except length
), accepts an expression as an argument and this expression is evaluated for each of the elements in the list. The context for each expression is set to the corresponding element from the input list. As mentioned above, every expression must begin with a context operator.
Operator | Input | Argument | Result | Description |
---|---|---|---|---|
all |
List | Expression | Boolean | true if all of the expression results are true |
any |
List | Expression | Boolean | true if any of the expression results are true |
filter |
List | Expression | List | List of each of the elements who's expression results in true |
map |
List | Expression | List | List the result of each elements expression |
length |
List | Numeral | The number of elements in the list |
Operator | Input | Argument | Result | Description |
---|---|---|---|---|
test |
String | String | Boolean | true if the argument (a regular expression) matches the input |
lines |
String | List | Splits a string by newlines into a list of strings |
Operator | Input | Argument | Result | Description |
---|---|---|---|---|
. |
Value | The current context |
There are a few different types of values that can be used in Tailor:
- numeral - Any positive number (e.g.
25
) - boolean -
true
orfalse
- string - A sequence of characters delimited by double-quotes (e.g.
"this is a string"
). Double-quotes and backslashes can be escaped with a backslash so they can be included in the string (e.g."Escaped \"quotes\""
) - list - A sequence of values delimited by brackets (e.g.
[1 2 3]
) - dictionary - A mapping between strings (the key) and values (the value). These cannot be created in expressions so they are only useful for context specifiers.
The root context is the initial input (a dictionary) into the rule expression. It is always a dictionary of values, derived from the pull request, and is of the following structure. Dictionaries are denoted by indentation, lists are denoted with brackets, and all leaf members are strings.
.
.user
.login
.title
.body
.commits[]
.sha
.author
.name
.email
.date
.github_login
.committer
.name
.email
.date
.github_login
.title
.description
.comments[]
.user
.login
.body
.created_at
.base
.sha
.label
.user
.login
.head
.sha
.label
.user
.login
In some cases, it may be necessary to grant an exemption to the rules. Repository admins can specify exemptions by commenting on the pull request with tailor disable <rule name>
to disable a particular rule or tailor disable all
to disable all rules. Exemptions can be removed by deleting the comment.
After cloning the repository, Tailor can be built and run with cargo run
. The logging verbosity can be increased by adding up to three -v
flags to the invocation (cargo run -- -vvv
).
Tailor is designed to be used as a webhook. Each GitHub repository will need to be configured with a new webhook with the payload URL http://url-of-tailor-instance/hook
, the content type set to application/json
, and just the "Pull request" event. The token that is given to Tailor will only need to have the repo
scope so that it may set statuses and access collaborators.