From 1d0edf473c06337d1f3f44a2ec73d8fdfbfd40b0 Mon Sep 17 00:00:00 2001 From: Jessica McKellar Date: Sun, 27 Oct 2013 19:34:25 -0400 Subject: [PATCH] Add a first pass at Mercurial changegroup (hg push) integration. (imported from commit 94c91aed6282a2fffcd7753a06dc68a298b7bdc7) --- integrations/hg/zulip-changegroup.py | 168 +++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100755 integrations/hg/zulip-changegroup.py diff --git a/integrations/hg/zulip-changegroup.py b/integrations/hg/zulip-changegroup.py new file mode 100755 index 0000000..4dcc27f --- /dev/null +++ b/integrations/hg/zulip-changegroup.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Zulip hook for Mercurial changeset pushes. +# Copyright © 2012-2013 Zulip, Inc. +# +# 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. +# +# +# This hook is called when changesets are pushed to the master repository (ie +# `hg push`). See https://zulip.com/integrations for installation instructions. + +import zulip + +VERSION = "0.9" + +def format_summary_line(web_url, user, base, tip, branch, node): + """ + Format the first line of the message, which contains summary + information about the changeset and links to the changelog if a + web URL has been configured: + + Jane Doe pushed 1 commit to master (170:e494a5be3393): + """ + revcount = tip - base + plural = "s" if revcount > 1 else "" + + if web_url: + shortlog_base_url = web_url.rstrip("/") + "/shortlog/" + summary_url = "{shortlog}{tip}?revcount={revcount}".format( + shortlog=shortlog_base_url, tip=tip - 1, revcount=revcount) + formatted_commit_count = "[{revcount} commit{s}]({url})".format( + revcount=revcount, s=plural, url=summary_url) + else: + formatted_commit_count = "{revcount} commit{s}".format( + revcount=revcount, s=plural) + + return u"**{user}** pushed {commits} to **{branch}** (`{tip}:{node}`):\n\n".format( + user=user, commits=formatted_commit_count, branch=branch, tip=tip, + node=node[:12]) + +def format_commit_lines(web_url, repo, base, tip): + """ + Format the per-commit information for the message, including the one-line + commit summary and a link to the diff if a web URL has been configured: + """ + if web_url: + rev_base_url = web_url.rstrip("/") + "/rev/" + + commit_summaries = [] + for rev in range(base, tip): + rev_node = repo.changelog.node(rev) + rev_ctx = repo.changectx(rev_node) + one_liner = rev_ctx.description().split("\n")[0] + + if web_url: + summary_url = rev_base_url + str(rev_ctx) + summary = "* [{summary}]({url})".format( + summary=one_liner, url=summary_url) + else: + summary = "* {summary}".format(summary=one_liner) + + commit_summaries.append(summary) + + return "\n".join(summary for summary in commit_summaries) + +def send_zulip(email, api_key, stream, subject, content): + """ + Send a message to Zulip using the provided credentials, which should be for + a bot in most cases. + """ + client = zulip.Client(email=email, api_key=api_key, + client="mercurial " + VERSION) + + message_data = { + "type": "stream", + "to": stream, + "subject": subject, + "content": content, + } + + client.send_message(message_data) + +def get_config(ui, item): + try: + # configlist returns everything in lists. + return ui.configlist('zulip', item)[0] + except IndexError: + return None + +def hook(ui, repo, **kwargs): + """ + Invoked by configuring a [hook] entry in .hg/hgrc. + """ + hooktype = kwargs["hooktype"] + node = kwargs["node"] + + ui.debug("Zulip: received {hooktype} event\n".format(hooktype=hooktype)) + + if hooktype != "changegroup": + ui.warn("Zulip: {hooktype} not supported\n".format(hooktype=hooktype)) + exit(1) + + ctx = repo.changectx(node) + branch = ctx.branch() + + # If `branches` isn't specified, notify on all branches. + branch_whitelist = get_config(ui, "branches") + branch_blacklist = get_config(ui, "ignore_branches") + + if branch_whitelist: + # Only send notifications on branches we are watching. + watched_branches = [b.lower().strip() for b in branch_whitelist.split(",")] + if branch.lower() not in watched_branches: + ui.debug("Zulip: ignoring event for {branch}\n".format(branch=branch)) + exit(0) + + if branch_blacklist: + # Don't send notifications for branches we've ignored. + ignored_branches = [b.lower().strip() for b in branch_blacklist.split(",")] + if branch.lower() in ignored_branches: + ui.debug("Zulip: ignoring event for {branch}\n".format(branch=branch)) + exit(0) + + # The first and final commits in the changeset. + base = repo[node].rev() + tip = len(repo) + + email = get_config(ui, "email") + api_key = get_config(ui, "api_key") + + if not (email and api_key): + ui.warn("Zulip: missing email or api_key configurations\n") + ui.warn("in the [zulip] section of your .hg/hgrc.\n") + exit(1) + + stream = get_config(ui, "stream") + # Give a default stream if one isn't provided. + if not stream: + stream = "commits" + + web_url = get_config(ui, "web_url") + user = ctx.user() + content = format_summary_line(web_url, user, base, tip, branch, node) + content += format_commit_lines(web_url, repo, base, tip) + + subject = branch + + ui.debug("Sending to Zulip:\n") + ui.debug(content + "\n") + + send_zulip(email, api_key, stream, subject, content)