Commits

jason kirtland committed 022cb04

Initial commit.

Comments (0)

Files changed (2)

+Mercurial commit hook for flowdock.com
+======================================
+
+Notifies one or more Flowdock flows on push.  May be used as a changegroup or
+incoming hook::
+
+  [plugins]
+  hgflowdock = /path/to/hgflowdock.py
+  
+  [hooks]
+  changegroup.hgflowdock = python:hgflowdock.hook
+  # or
+  # incoming.hgflowdock = python:hgflowdock.hook
+  
+  [fisheye]
+  # required: API token- find this in your flow config.  may supply multiple,
+  # separated by commas
+  token = 
+  
+  # comma-separated list of tags to attach to all commits
+  tags = 
+  
+  # boolean; tag commits with branch names if true
+  tag_branches = true
+  
+  # comma-separated list of branches to skip when tagging
+  uninteresting_branches = default
+  
+  # format for commit messages; see hg help templates
+  template = {desc}
+  
+  # timeout for API connections (python 2.6 or higher)
+  timeout = 15
+  
+  # style for changeset links.  may be 'hgweb' or 'fisheye'
+  #link_style = hgweb
+  
+  # base url for links, e.g. http://selenic.com/hg
+  #link_base =
+  
+  # override name of repository for link generation. default is guessed.
+  #name = repo_name
+  
+  #api = https://api.flowdock.com/v1/mercurial/
+
+---
+
+Copyright (c) Jason Kirtland <jek@discorporate.us> and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+"""Mercurial commit hook for flowdock.com
+======================================
+
+Notifies one or more Flowdock flows on push.  May be used as a changegroup or
+incoming hook::
+
+  [plugins]
+  hgflowdock = /path/to/hgflowdock.py
+  
+  [hooks]
+  changegroup.hgflowdock = python:hgflowdock.hook
+  # or
+  # incoming.hgflowdock = python:hgflowdock.hook
+  
+  [fisheye]
+  # required: API token- find this in your flow config.  may supply multiple,
+  # separated by commas
+  token = 
+  
+  # comma-separated list of tags to attach to all commits
+  tags = 
+  
+  # boolean; tag commits with branch names if true
+  tag_branches = true
+  
+  # comma-separated list of branches to skip when tagging
+  uninteresting_branches = default
+  
+  # format for commit messages; see hg help templates
+  template = {desc}
+  
+  # timeout for API connections (python 2.6 or higher)
+  timeout = 15
+  
+  # style for changeset links.  may be 'hgweb' or 'fisheye'
+  #link_style = hgweb
+  
+  # base url for links, e.g. http://selenic.com/hg
+  #link_base =
+  
+  # override name of repository for link generation. default is guessed.
+  #name = repo_name
+  
+  #api = https://api.flowdock.com/v1/mercurial/
+
+---
+
+Copyright (c) Jason Kirtland <jek@discorporate.us> and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+"""
+from datetime import datetime
+from json import dumps
+from os.path import basename
+import sys
+from urllib import urlencode
+from urllib2 import Request, URLError, urlopen
+
+from mercurial import templater
+from mercurial.cmdutil import changeset_templater
+from mercurial.node import bin, short
+
+
+API = 'https://api.flowdock.com/v1/mercurial/'
+python_26 = sys.version_info >= (2, 6)
+
+
+link_styles = {
+    'hgweb': ('{url}/shortlog/',
+              '{url}/rev/{short}'),
+    'fisheye': ('{url}/changelog/{reponame}',
+                '{url}/changelog/{reponame}?cs={node}')
+    }
+
+
+class Reporter(object):
+    def __init__(self, ui, repo):
+        self.ui = ui
+        self.repo = repo
+
+        self.repo_name = ui.config('flowdock', 'name')
+        if not self.repo_name:
+            self.repo_name = basename(repo.root)
+
+        # formatting for inbox message
+        template = ui.config('flowdock', 'template', '{desc}')
+        self.message_templater = self._make_changeset_templater(template)
+
+        # formatting for hyperlinks
+        try:
+            link_style = ui.config('flowdock', 'link_style')
+            website_t, changeset_t = link_styles[link_style]
+            self.website_template = website_t
+            self.link_templater = self._make_changeset_templater(changeset_t)
+        except KeyError:
+            self.website_template = None
+            self.link_templater = None
+
+    def flowdock_payload(self, ctxs):
+        info = self.envelope()
+        branches = set()
+        for ctx in ctxs:
+            changeset_info = self.changeset_info(ctx)
+            info['commits'].append(changeset_info)
+            branches.add(changeset_info['branch'])
+        payload = dumps(info, sort_keys=True, indent=4)
+        if isinstance(payload, unicode):
+            payload = payload.encode('utf8')
+        return payload, branches
+
+    def envelope(self):
+        repo_name = self.repo_name
+        info = {
+            'repository': {
+                'name': repo_name,
+                'slug': repo_name,
+                'website': '',
+                'absolute_url': '',
+                'owner': '',
+                },
+            'commits': [],
+            'user': self.ui.username(),
+            }
+
+        if self.website_template:
+            url = self.ui.config('flowdock', 'link_base', '')
+            website = self.website_template.replace('{url}', url)
+            website = website.replace('{reponame}', repo_name)
+            repo = info['repository']
+            repo['website'] = repo['absolute_url'] = website
+        return info
+
+    def changeset_info(self, ctx):
+        node = ctx.node()
+        url = self.ui.config('flowdock', 'link_base', '')
+        link = self._render_ctx(self.link_templater, ctx,
+                                url=url, reponame=self.repo_name)
+        ts = datetime.utcfromtimestamp(ctx.date()[0]).isoformat()
+        message = self._render_ctx(self.message_templater,
+                                   ctx, changes=ctx.changeset())
+
+        info = {
+            'author': ctx.user(),
+            'branch': ctx.branch(),
+            'files': [],
+            'link': link,
+            'message': message,
+            'node': short(node),
+            'parents': [short(p.node()) for p in ctx.parents()],
+            'raw_author': ctx.user(),
+            'raw_node': node.encode('hex'),
+            'revision': ctx.rev(),
+            'size': 0,
+            'timestamp': ts,
+            }
+
+        stat = self.repo.status(ctx.parents()[0].node(), ctx.node())
+        for idx, action in enumerate(('modified', 'added', 'removed')):
+            for path in stat[idx]:
+                info['files'].append({'type': action, 'file': path})
+        return info
+
+    def _render_ctx(self, renderer, ctx, **kwargs):
+        if not renderer:
+            return ''
+        self.ui.pushbuffer()
+        renderer.show(ctx, **kwargs)
+        return self.ui.popbuffer()
+
+    def _make_changeset_templater(self, template):
+        parsed = templater.parsestring(template, quoted=False)
+        ct = changeset_templater(self.ui, self.repo, False, None, None, False)
+        ct.use_template(parsed)
+        return ct
+
+
+def deliver(ui, payload, branches):
+    url = ui.config('flowdock', 'api', API)
+    tokens = ui.configlist('flowdock', 'token')
+    tags = ui.configlist('flowdock', 'tags')
+
+    if ui.configbool('flowdock', 'tag_branches', True):
+        keep = set(branches)
+        drop = ui.configlist('flowdock', 'uninteresting_branches', 'default')
+        for skip in drop:
+            keep.discard(skip)
+        tags.extend(sorted(keep))
+
+    try:
+        timeout = int(ui.config('flowdock', 'timeout', 15))
+    except ValueError:
+        timeout = 15
+
+    if ui.debugflag:
+        ui.debug("Prepared Flowdock payload:")
+        ui.debug(payload)
+
+    for token in tokens:
+        endpoint = url + token
+        if tags:
+            endpoint += '+' + '+'.join(tags)
+
+        data = urlencode({'payload': payload})
+        req = Request(endpoint, data)
+        try:
+            if python_26:
+                urlopen(req, timeout=timeout)
+            else:
+                urlopen(req)
+        except URLError, exc:
+            ui.warn(str(exc))
+            break
+
+
+def hook(ui, repo, hooktype, node=None, **kwargs):
+    if node is None:
+        return
+
+    if not ui.config('flowdock', 'token'):
+        ui.warn("No API token configured for Flowdock\n")
+        return
+
+    node = bin(node)
+    log = repo.changelog
+    if hooktype == 'changegroup':
+        start, end = log.rev(node), len(log)
+        ctxs = [repo.changectx(log.node(rev)) for rev in xrange(start, end)]
+    else:
+        ctxs = [repo.changectx(node)]
+
+    reporter = Reporter(ui, repo)
+    payload, branches = reporter.flowdock_payload(ctxs)
+    deliver(ui, payload, branches)