Skip to content

GitHub webhook handler for closing pull requests that have been merged using rebase etc.

License

Notifications You must be signed in to change notification settings

jirutka/github-pr-closer

Repository files navigation

GitHub Pull Requests Closer

Build Status

This is a simple webhook handler that processes push events from GitHub and automatically closes pull requests that have been merged, but GitHub didn’t automatically detect them (i.e. commits has been rebased and/or squashed).

It’s built with Bottle, a WSGI micro web-framework for Python, and requires Python 3.3+. [1]

Why?

When you rebase commits (or just apply plain patches using git am), git re-applies changes introduced in these commits on the HEAD of the branch you’re rebasing onto. This means that the resulting commits have different SHA-1 hash and also different parents, so the relation to the original commits is lost. Well, almost, you can still compare just content (diff) of the commits. But if you also do some changes when rebasing (e.g. squash some commits, fix typos…), then diffs will not help you much.

Hopefully, there’s one thing that remains the same even when rebasing commits (if it’s done right) – author’s name, email and the date written. [2] It’s very unlikely that one person created two commits in the same repository at exactly the same time, so we can use it to find the original commit(s) and the corresponding pull request.

There may not be one-to-one mapping, e.g. when the committer has squashed some commits. We can say that the pull request will be closed when we find at least one commit that has been merged. This may be a problem, what if the committer applied just some of the changes introduced in the pull request? To minimize this issue, we can also compare set of the filenames that have been changed (i.e. added, modified, or deleted). It’s not bullet proof, but it may be sufficient.

You may ask, how the heck is this useful? I created it for Alpine Linux, that uses GitHub just as a mirror and pull requests are always merged into the main repository as a set of patches using git am. It used to be quite often that someone had merged commits from a pull request, but forgot to close it.

Installation

Clone this repository and install dependencies:

git clone [email protected]:jirutka/github-pr-closer.git
cd github-pr-closer
pip install -r requirements.txt

Requirements

Configuration

Configuration is read from the file settings.ini. You may change location of the settings file using the environment variable CONF_FILE.

Each section of this file (except DEFAULT) corresponds to one or more repositories. The section name represents full name (slug) of a repository or regular expression matching full names of repositories you want to process. Push events from repositories that do not match any section are ignored.

Each section may contain the following fields.

branch_regex

A regular expression that specifies branches which should be processed. Push events from branches that don’t match this regular expression will be ignored. To match all branches, use .*. If you want to match branches master and dev, use (master|dev). Default is master.

cache_maxsize

Maximum number of pull requests' metadata to keep in LRU cache. This field may be declared only in the DEFAULT section. Default is 250.

close_comment

Text that will be added as a comment to a pull request being closed. It may contain placeholder {committer} that will be replaced by GitHub login (prefixed with @) or name (if the login is not available) of the committer, and {commits} that will be replaced by a list of hashes of the actually merged commits. This field is required.

github_token

Your GitHub token for communication with GitHub API. It must have scope public_repo. This field is required.

hook_secret

Webhook secret used by GitHub to sign the message. This field is required.

You can also declare default values for all repositories in special section named DEFAULT.

Run it

There are multiple ways how to run it:

Run with the built-in server

Simply execute the app.py:

python3 app.py

Then you can access the app on http://localhost:8080. The port and host may be changed using environment variables HTTP_PORT and HTTP_HOST.

Run with uWSGI and nginx

If you have many micro-apps like this, it’s IMO kinda overkill to run each in a separate uWSGI process, isn’t it? It’s not so well known, but uWSGI allows to “mount” multiple application in a single uWSGI process and with a single socket.

Sample uWSGI configuration:
[uwsgi]
plugins = python34
socket = /run/uwsgi/main.sock
chdir = /var/www/scripts
logger = file:/var/log/uwsgi/main.log
processes = 1
threads = 2
# map URI paths to applications
mount = /hooks/github-pr-closer=github-pr-closer/app.py
#mount = /other/foo=foo/app.py
manage-script-name = true
Sample nginx configuration as a reverse proxy in front of uWSGI:
server {
    listen 443 ssl;
    server_name example.org;

    ssl_certificate /etc/ssl/nginx/nginx.crt;
    ssl_certificate_key /etc/ssl/nginx/nginx.key;

    location /hooks/github-pr-closer {
        uwsgi_pass unix:/run/uwsgi/main.sock;
        include uwsgi_params;
    }
}

License

This project is licensed under MIT License. For the full text of the license, see the LICENSE file.


1. No, it doesn’t work with legacy Python 2.7 and I’m not gonna support it… Just install Python 3.
2. Git distinguishes author, who created the patch, and committer, who committed it to the tree.

About

GitHub webhook handler for closing pull requests that have been merged using rebase etc.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published