A merge bot for GitHub Pull Requests
Elixir HTML JavaScript CSS
Latest commit e53d9c8 Jan 20, 2017 @bors-ng bors-ng Merge #69
69: Ignore pull requests that aren't against master


Bors-NG implements a continuous-testing workflow where the master branch never breaks. It integrates GitHub pull requests with a tool like Travis CI that runs your tests.

How to use it

Bors is a GitHub integration, so (assuming you already have Travis CI set up), getting bors set up requires two steps:

  1. Add the integration to your repo in GitHub.
  2. Commit a bors.toml with these contents:

    status = ["continuous-integration/travis-ci/push"]

To use it, you need to stop clicking the big green merge button, and instead leave a comment with this in it on any pull request that looks good to you:

bors r+

As commits are reviewed, bors lumps them into a queue of batches. If everything passes, there will just be two batches; the one that's running, and the one that's waiting to be run (and is accumulating more and more pull requests until it gets a chance to run).

To run a batch, bors creates a merge commit, merging master with all the pull requests that make up the batch. They'll look like this:

Merge #5 #7 #8

5: Rename `bifurcate()` to `bifurcateCrab()`
7: Call `bifurcate()` in the `onland` event handler
8: Fix crash in `drive()`

If the build passes, the master branch gets fast-forwarded to meed the staging branch. Since the master branch contains the exact contents that were just tested, bit-for-bit, it's not broken. (at least, not in any way that the automated tests are able to detect)

If the build fails, bors will follow a strategy called "bisecting". Namely, it splits the batch into two batches, and pushes those to the queue. In this example, the first batch will look like this:

Merge #5 #7

5: Rename `bifurcate()` to `bifurcateCrab()`
7: Call `bifurcate()` in the `onland` event handler

This batch will still fail, because the second patch inserts a call to a function that the first patch removes. It will get bisected again, as a result.

The second will still pass, though.

Merge #8

8: Fix crash in `drive()`

This one will work, causing it to land in master, leaving the first two still in the backlog.

Merge #5

5: Rename `bifurcate()` to `bifurcateCrab()`

This one will pass, since the PR it conflicts with (#7) is sitting behind it in the queue.

Merge #7

7: Call `bifurcate()` in the `onland` event handler

When a batch cannot be bisected (because it only contains one PR), it gets kicked back to the creator so they can fix it.

Note that you can watch this process running on the dashboard page if you want.

The original bors used a more simple system (it just tested one PR at a time all the time). The one-at-a-time strategy is O(N), where N is the total number of pull requests. The batching strategy is O(E log N), where N is again the total number of pull requests and E is the number of pull requests that fail.

How to set up your own instance

Please read this whole guide before you start doing anything.

Step 1: set up a GitHub integration

The first step is to create a GitHub integration on the GitHub web site.

Integration settings

The name, description, and homepage URL are irrelevant, though I suggest pointing the homepage at the dashboard page.

Leave the callback URL blank.

The webhook URL should be at <dashboard page>/webhook/github.

The webhook secret should be a randomly generated string. The mix phoenix.gen.secret command will work awesomely for this.

Required GitHub integration permissions

Repository metadata: Will be read-only. Must be set to receive Repository (Repository created, deleted, publicized, or privatized) events. This is needed to automatically remove entries from our database when a repo is deleted.

Repository administration: No access.

Commit statuses: Must be set to Read & write, to report a testing status. Also must get Status (Commit status updated from the API) events, to integrate with CI systems that report their status via GitHub.

Deployments: No access.

Issues: Must be set to Read-only, because pull requests are issues. Issue comment (Issue comment created, edited, or deleted) events must be enabled, to get the "bors r+" comments.

Pages: No access.

Pull requests: Must be set to Read & write, to be able to post pull request comments. Also, must receive Pull request (Pull request opened, closed, reopened, edited, assigned, unassigned, labeled, unlabeled, or synchronized) events to be able to keep the dashboard working, and must get Pull request review (pull request review submitted) and Pull request review comment (pull request diff comment created, edited, or deleted) events to get those kinds of comments.

Repository contents: Must be set to Read-write, to be able to create merge commits.

Single file: No.

Repository projects: No.

Organization members: No.

Organization projects: No.

After you click the "Create" button

GitHub will send a "ping" notification to your webhook endpoint. Since bors is not actually running yet, that will fail. This is expected.

You'll need to jot down the Integration ID (it's between the "Install" button and the "Transfer ownership" button).

You'll also need to generate the private key. Save the file, because you'll need it later.

Step 2: Set up the oAuth app

To authenticate access to the dashboard page, you'll also need to [set up oAuth].

The only important setting is Authorization callback URL, which is <dashboard url>/auth/github/callback.

Step 3: Set up the server

bors-ng is written in the Elixir programming language, and it uses PostgreSQL as the backend database. Whatever machine you plan to run it on needs to have both of those installed.

bors-ng is built on the Phoenix web framework, and they have docs on how to deploy phoenix apps already. Where you deploy will determine the what the dashboard URL will be, which is needed in the previous steps, so this decision needs to be made before you can set up the Integration or the oAuth app.

You'll need to edit the configuration with a few bors-specific variables.

Deploying on Heroku (and other 12-factor-style systems)

The config file in the repository is already set up to pull the needed information from the environment, so just set the right env variables and deploy the app:

You can do it the easy way:


Or you can do it manually:

$ heroku create --buildpack "https://github.com/HashNuke/heroku-buildpack-elixir.git" bors-ng
$ heroku buildpacks:add https://github.com/gjaldon/heroku-buildpack-phoenix-static.git
$ heroku addons:create heroku-postgresql:hobby-dev
$ heroku config:set \
    MIX_ENV=prod \
    POOL_SIZE=18 \
    PUBLIC_HOST=bors-ng.herokuapp.com \
    GITHUB_INTEGRATION_PEM=`base64 -w0 priv.pem` \
$ git push heroku master

WARNING: bors-ng stores some short-term state inside the web dyno (it uses a sleeping process to implement delays, specifically). It can recover the information after restarting, but it will not work correctly with Heroku's replication system. If you need more throughput than one dyno can provide, you should deploy using a system that allows Erlang clustering to work.

Deploying on your own cluster

Your configuration can be done by modifying config/prod.secret.exs.

Optional step 4: make yourself an admin

bors-ng offers a number of special functions for "administrator" users, including diagnostics and the ability to open a repo dashboard without being a reviewer.

However, there's no UI for adding admins; you'll have to go into Postgres yourself to do it. There's two ways to do that:

From an iex prompt

You can do it from the iex prompt, like this:

shell$ iex -S mix # or `heroku run iex -S mix`
iex> me = Aelita2.Repo.get_by! Aelita2.User, login: "<your login>"
iex> Aelita2.Repo.update! Aelita2.User.changeset(me, %{is_admin: true})

You can do it from a PostgreSQL prompt like this:

postgres=# \c aelita2_dev -- or aelita2_prod
aelita2_dev=# update users set is_admin = true where login = '<your login>';

Copyright license

bors-ng is licensed under the Apache license, version 2.0. It should be included with the source distribution in LICENSE-APACHE. If it is missing, it is at http://www.apache.org/licenses/LICENSE-2.0.