@@ 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
@@ 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 <user@email.com>',
+ '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