2020-04-02 09:59:28 -04:00
|
|
|
#!/usr/bin/env python3
|
2020-03-23 00:55:21 -04:00
|
|
|
|
2013-10-27 19:34:25 -04:00
|
|
|
# Zulip hook for Mercurial changeset pushes.
|
|
|
|
#
|
|
|
|
# This hook is called when changesets are pushed to the master repository (ie
|
2020-06-08 17:03:27 -04:00
|
|
|
# `hg push`). See https://zulip.com/integrations for installation instructions.
|
2013-10-27 19:34:25 -04:00
|
|
|
|
2018-01-07 12:08:26 -05:00
|
|
|
import sys
|
2021-05-28 05:00:04 -04:00
|
|
|
|
|
|
|
from mercurial import repository as repo
|
|
|
|
from mercurial import ui
|
|
|
|
|
|
|
|
import zulip
|
2013-10-27 19:34:25 -04:00
|
|
|
|
|
|
|
VERSION = "0.9"
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
|
|
|
def format_summary_line(
|
2021-05-28 07:13:13 -04:00
|
|
|
web_url: str, user: str, base: int, tip: int, branch: str, node: str
|
|
|
|
) -> str:
|
2013-10-27 19:34:25 -04:00
|
|
|
"""
|
|
|
|
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 <jane@example.com> 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(
|
2021-05-28 05:03:46 -04:00
|
|
|
shortlog=shortlog_base_url, tip=tip - 1, revcount=revcount
|
|
|
|
)
|
2013-10-27 19:34:25 -04:00
|
|
|
formatted_commit_count = "[{revcount} commit{s}]({url})".format(
|
2021-05-28 05:03:46 -04:00
|
|
|
revcount=revcount, s=plural, url=summary_url
|
|
|
|
)
|
2013-10-27 19:34:25 -04:00
|
|
|
else:
|
2021-05-28 07:19:40 -04:00
|
|
|
formatted_commit_count = f"{revcount} commit{plural}"
|
2013-10-27 19:34:25 -04:00
|
|
|
|
2020-04-09 20:14:01 -04:00
|
|
|
return "**{user}** pushed {commits} to **{branch}** (`{tip}:{node}`):\n\n".format(
|
2021-05-28 05:03:46 -04:00
|
|
|
user=user, commits=formatted_commit_count, branch=branch, tip=tip, node=node[:12]
|
|
|
|
)
|
|
|
|
|
2013-10-27 19:34:25 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def format_commit_lines(web_url: str, repo: repo, base: int, tip: int) -> str:
|
2013-10-27 19:34:25 -04:00
|
|
|
"""
|
|
|
|
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)
|
2018-05-18 15:04:01 -04:00
|
|
|
rev_ctx = repo[rev_node]
|
2013-10-27 19:34:25 -04:00
|
|
|
one_liner = rev_ctx.description().split("\n")[0]
|
|
|
|
|
|
|
|
if web_url:
|
|
|
|
summary_url = rev_base_url + str(rev_ctx)
|
2021-05-28 07:19:40 -04:00
|
|
|
summary = f"* [{one_liner}]({summary_url})"
|
2013-10-27 19:34:25 -04:00
|
|
|
else:
|
2021-05-28 07:19:40 -04:00
|
|
|
summary = f"* {one_liner}"
|
2013-10-27 19:34:25 -04:00
|
|
|
|
|
|
|
commit_summaries.append(summary)
|
|
|
|
|
|
|
|
return "\n".join(summary for summary in commit_summaries)
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
|
|
|
def send_zulip(
|
2021-05-28 07:13:13 -04:00
|
|
|
email: str, api_key: str, site: str, stream: str, subject: str, content: str
|
2021-05-28 05:03:46 -04:00
|
|
|
) -> None:
|
2013-10-27 19:34:25 -04:00
|
|
|
"""
|
|
|
|
Send a message to Zulip using the provided credentials, which should be for
|
|
|
|
a bot in most cases.
|
|
|
|
"""
|
2021-05-28 05:03:46 -04:00
|
|
|
client = zulip.Client(
|
|
|
|
email=email, api_key=api_key, site=site, client="ZulipMercurial/" + VERSION
|
|
|
|
)
|
2013-10-27 19:34:25 -04:00
|
|
|
|
|
|
|
message_data = {
|
|
|
|
"type": "stream",
|
|
|
|
"to": stream,
|
|
|
|
"subject": subject,
|
|
|
|
"content": content,
|
|
|
|
}
|
|
|
|
|
|
|
|
client.send_message(message_data)
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def get_config(ui: ui, item: str) -> str:
|
2013-10-27 19:34:25 -04:00
|
|
|
try:
|
2018-05-18 15:04:01 -04:00
|
|
|
# config returns configuration value.
|
2021-05-28 05:05:11 -04:00
|
|
|
return ui.config("zulip", item)
|
2013-10-27 19:34:25 -04:00
|
|
|
except IndexError:
|
2021-05-28 07:19:40 -04:00
|
|
|
ui.warn(f"Zulip: Could not find required item {item} in hg config.")
|
2018-01-07 12:08:26 -05:00
|
|
|
sys.exit(1)
|
2013-10-27 19:34:25 -04:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2021-05-28 07:13:13 -04:00
|
|
|
def hook(ui: ui, repo: repo, **kwargs: str) -> None:
|
2013-10-27 19:34:25 -04:00
|
|
|
"""
|
|
|
|
Invoked by configuring a [hook] entry in .hg/hgrc.
|
|
|
|
"""
|
|
|
|
hooktype = kwargs["hooktype"]
|
|
|
|
node = kwargs["node"]
|
|
|
|
|
2021-05-28 07:19:40 -04:00
|
|
|
ui.debug(f"Zulip: received {hooktype} event\n")
|
2013-10-27 19:34:25 -04:00
|
|
|
|
|
|
|
if hooktype != "changegroup":
|
2021-05-28 07:19:40 -04:00
|
|
|
ui.warn(f"Zulip: {hooktype} not supported\n")
|
2018-01-07 12:08:26 -05:00
|
|
|
sys.exit(1)
|
2013-10-27 19:34:25 -04:00
|
|
|
|
2018-05-18 15:04:01 -04:00
|
|
|
ctx = repo[node]
|
2013-10-27 19:34:25 -04:00
|
|
|
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:
|
2021-05-28 07:19:40 -04:00
|
|
|
ui.debug(f"Zulip: ignoring event for {branch}\n")
|
2018-01-07 12:08:26 -05:00
|
|
|
sys.exit(0)
|
2013-10-27 19:34:25 -04:00
|
|
|
|
|
|
|
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:
|
2021-05-28 07:19:40 -04:00
|
|
|
ui.debug(f"Zulip: ignoring event for {branch}\n")
|
2018-01-07 12:08:26 -05:00
|
|
|
sys.exit(0)
|
2013-10-27 19:34:25 -04:00
|
|
|
|
|
|
|
# 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")
|
2013-12-04 16:15:49 -05:00
|
|
|
site = get_config(ui, "site")
|
2013-10-27 19:34:25 -04:00
|
|
|
|
|
|
|
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")
|
2018-01-07 12:08:26 -05:00
|
|
|
sys.exit(1)
|
2013-10-27 19:34:25 -04:00
|
|
|
|
|
|
|
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")
|
|
|
|
|
2013-12-04 16:15:49 -05:00
|
|
|
send_zulip(email, api_key, site, stream, subject, content)
|