Initial commit. Adds sendchanges.py hg hook.
3 files changed, 166 insertions(+), 0 deletions(-)

A => .hgignore
A => README.md
A => sendchanges.py
A => .hgignore +23 -0
@@ 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

          
A => README.md +16 -0
@@ 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

          
A => sendchanges.py +127 -0
@@ 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