# HG changeset patch # User Peter Sanchez # Date 1576624175 28800 # Tue Dec 17 15:09:35 2019 -0800 # Node ID 97abc414288b5248960b7217a2249397e32ff9d5 # Parent 0000000000000000000000000000000000000000 Initial commit. Adds sendchanges.py hg hook. diff --git a/.hgignore b/.hgignore new file mode 100644 --- /dev/null +++ b/.hgignore @@ -0,0 +1,23 @@ +syntax:glob +.svn +.hgsvn +.settings.json +settings_local.py +db_reset.sh +.*.swp +**.pyc +*.*~ +.coverage + +# virtualenv +syntax:regexp +^env$ +^testenv$ +^media$ +^static$ +^fixtures$ + +# Unit test / coverage reports +htmlcov/ + +celerybeat-schedule diff --git a/README.md b/README.md new file mode 100644 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# srht-glue + +Simple scripts and utilities we've created to help with our internal sourcehut +deployment. + +## sendchanges.py + +As of now, sourcehut does not have an API for the `hg.sr.ht` code base. As such +there is also no ability to wire up webhooks. + +You can use sendchanges.py to notify your broker of any new changesets that +have been pushed to your repository. It's designed to specifically work with +[django-trello-broker][tb] but you can edit the payload and `requests.post` +call to fit virtually any webhook endpoint. + +[tb]: https://hg.code.netlandish.com/~netlandish/django-trello-broker diff --git a/sendchanges.py b/sendchanges.py new file mode 100644 --- /dev/null +++ b/sendchanges.py @@ -0,0 +1,127 @@ +""" + Setup in .hg/hgrc. access_key, repo_name and timeout are optional + + [hooks] + changegroup.sendchanges = python:/path/to/sendchanges.py:push + + [sendchanges] + broker_url = https://example.com/trello_broker + repo_url = https://hg.code.netlandish.com/~username/repo + access_key = youraccesskey + repo_name = repo_name + timeout = 5 + + Format commit message like so: + + { + 'cannon_url': 'https://hg.code.netlandish.com', + 'respository': { + 'name': 'repo_name', + 'slug': 'repo_name', + 'absolute_url': '~username/repo_name', + }, + 'commits': [ + { + 'author': 'Full Name', + 'raw_author': 'Full Name ', + 'node': 'short-hash-id', + 'raw_node': 'long-hash-id', + 'commit_url': 'https://hg.code.netlandish.com/~username/repo/rev/long-hash-id', + 'message': 'commit message', + }, + ... + ] + + } +""" +import os +import sys +import json +import requests +import datetime + +from mercurial.node import bin, short + + +# Return (bool) True for failure, False for success +OP_SUCCESS = False +OP_FAIL = True + + +def push(ui, repo, hooktype, node, **kwargs): + """ Return (bool) True for failure, False for success + """ + broker_url = ui.config(b'sendchanges', b'broker_url') + repo_url = ui.config(b'sendchanges', b'repo_url') + access_key = ui.config(b'sendchanges', b'access_key') + repo_name = ui.config(b'sendchanges', b'repo_name') + timeout = ui.config(b'sendchanges', b'timeout', default=5) + if not all([broker_url, repo_url]): + ui.warn(b'sendchanges: Missing required settings.') + return OP_FAIL + + broker_url = broker_url.decode('utf-8') + repo_url = repo_url.decode('utf-8') + + if access_key is not None: + access_key = access_key.decode('utf-8') + broker_url = f'{broker_url}?access_key={access_key}' + + if repo_name is not None: + repo_name = repo_name.decode('utf-8') + else: + repo_name = repo.url().decode('utf-8').split('/')[-1] + + if not isinstance(timeout, int): + timeout = int(timeout.decode('utf-8')) + + payload = { + 'cannon_url': repo_url, + 'repository': { + 'name': repo_name, + 'slug': repo_name, + 'absolute_url': repo_url, + }, + 'commits': [], + } + + n = bin(node) + start = repo.changelog.rev(n) + end = len(repo.changelog) + + for rev in range(start, end): + n = repo.changelog.node(rev) + ctx = repo[n] + rev_hash = ctx.hex().decode('utf-8') + user_full = ctx.user().decode('utf-8') + user = None + if ' <' in user_full: + user = user_full[:user_full.find(' <')] + payload['commits'].append({ + 'author': user or user_full, + 'raw_author': user_full, + 'node': short(ctx.node()).decode('utf-8'), + 'raw_node': rev_hash, + 'commit_url': f'{repo_url}/rev/{rev_hash}', + 'message': ctx.description().decode('utf-8'), + }) + + try: + r = requests.post( + broker_url, + data={'payload': json.dumps(payload)}, + timeout=timeout, + headers={ + 'X-Event-Key': 'repo:push', + #'Content-Type': 'application/json', + }, + ) + ui.status(b'sendchanges: Successfully pushed update to broker.') + except requests.exceptions.ReadTimeout: + ui.warn(str.encode( + f'sendchanges: Request timed out after {timeout} seconds.' + )) + return OP_FAIL + except requests.exceptions.RequestException as e: + ui.warn(str.encode(f'sendchanges: Error occurred: {e}')) + return OP_FAIL