# -*- coding: utf-8 -*-
"""
(c) 2016-2017 - Copyright Red Hat Inc
Authors:
Pierre-Yves Chibon <pingou@pingoured.fr>
"""
from __future__ import unicode_literals, print_function, absolute_import
import logging
import pygit2
import six
import pagure.config
import pagure.exceptions
import pagure.lib.query
import pagure.lib.tasks
import pagure.lib.tasks_services
import pagure.utils
from pagure.hooks import BaseHook, BaseRunner
_config = pagure.config.reload_config()
_log = logging.getLogger(__name__)
FEDMSG_INIT = False
def send_fedmsg_notifications(project, topic, msg):
""" If the user or admin asked for fedmsg notifications on commit, this will
do it.
"""
fedmsg_hook = pagure.lib.plugins.get_plugin("Fedmsg")
fedmsg_hook.db_object()
always_fedmsg = _config.get("ALWAYS_FEDMSG_ON_COMMITS") or None
# Send fedmsg and fedora-messaging notification
# (if fedmsg and fedora-messaging are there and set-up)
if always_fedmsg or (project.fedmsg_hook and project.fedmsg_hook.active):
if _config.get("FEDMSG_NOTIFICATIONS", True):
try:
global FEDMSG_INIT
print(" - to fedmsg")
import fedmsg
config = fedmsg.config.load_config([], None)
config["active"] = True
config["endpoints"]["relay_inbound"] = config["relay_inbound"]
if not FEDMSG_INIT:
fedmsg.init(name="relay_inbound", **config)
FEDMSG_INIT = True
pagure.lib.notify.fedmsg_publish(topic=topic, msg=msg)
except Exception:
_log.exception(
"Error sending fedmsg notifications on commit push"
)
if _config.get("FEDORA_MESSAGING_NOTIFICATIONS", False):
try:
print(" - to fedora-message")
pagure.lib.notify.fedora_messaging_publish(topic, msg)
except Exception:
_log.exception(
"Error sending fedora-messaging notifications on "
"commit push"
)
def send_stomp_notifications(project, topic, msg):
""" If the user or admin asked for stomp notifications on commit, this will
do it.
"""
always_stomp = _config.get("ALWAYS_STOMP_ON_COMMITS") or None
# Send stomp notification (if stomp is there and set-up)
if always_stomp or (project.fedmsg_hook and project.fedmsg_hook.active):
try:
print(" - to stomp")
pagure.lib.notify.stomp_publish(topic, msg)
except Exception:
_log.exception("Error sending stomp notifications on commit push")
def send_mqtt_notifications(project, topic, msg):
""" If the user or admin asked for mqtt notifications on commit, this will
do it.
"""
always_mqtt = _config.get("ALWAYS_MQTT_ON_COMMITS") or None
# Send mqtt notification (if mqtt is there and set-up)
if always_mqtt or (project.fedmsg_hook and project.fedmsg_hook.active):
try:
print(" - to mqtt")
pagure.lib.notify.mqtt_publish(topic, msg)
except Exception:
_log.exception("Error sending stomp notifications on commit push")
def send_webhook_notifications(project, topic, msg):
""" If the user asked for webhook notifications on commit, this will
do it.
"""
if project.settings.get("Web-hooks"):
try:
print(" - to web-hooks")
pagure.lib.tasks_services.webhook_notification.delay(
topic=topic,
msg=msg,
namespace=project.namespace,
name=project.name,
user=project.user.username if project.is_fork else None,
)
except Exception:
_log.exception(
"Error sending web-hook notifications on commit push"
)
def send_action_notification(
session, subject, action, project, repodir, user, refname, rev
):
""" Send out-going notifications about the branch that was just deleted.
"""
email = pagure.lib.git.get_author_email(rev, repodir)
name = pagure.lib.git.get_author(rev, repodir)
author = pagure.lib.query.search_user(session, email=email)
if author:
author = author.to_json(public=True)
else:
author = name
topic = "git.%s.%s" % (subject, action)
msg = dict(
authors=[author],
agent=user,
repo=project.to_json(public=True)
if not isinstance(project, six.string_types)
else project,
)
if subject == "branch":
msg["branch"] = refname
elif subject == "tag":
msg["tag"] = refname
# Send blink notification to any 3rd party plugins, if there are any
pagure.lib.notify.blinker_publish(topic, msg)
if not project.private:
send_fedmsg_notifications(project, topic, msg)
send_stomp_notifications(project, topic, msg)
send_mqtt_notifications(project, topic, msg)
send_webhook_notifications(project, topic, msg)
def send_notifications(session, project, repodir, user, refname, revs, forced):
""" Send out-going notifications about the commits that have just been
pushed.
"""
auths = set()
for rev in revs:
email = pagure.lib.git.get_author_email(rev, repodir)
name = pagure.lib.git.get_author(rev, repodir)
author = pagure.lib.query.search_user(session, email=email) or name
auths.add(author)
authors = []
for author in auths:
if not isinstance(author, six.string_types):
author = author.to_json(public=True)
authors.append(author)
if revs:
revs.reverse()
print("* Publishing information for %i commits" % len(revs))
topic = "git.receive"
msg = dict(
total_commits=len(revs),
start_commit=revs[0],
end_commit=revs[-1],
branch=refname,
forced=forced,
authors=list(authors),
agent=user,
repo=project.to_json(public=True)
if not isinstance(project, six.string_types)
else project,
)
# Send blink notification to any 3rd party plugins, if there are any
pagure.lib.notify.blinker_publish(topic, msg)
if not project.private:
send_fedmsg_notifications(project, topic, msg)
send_stomp_notifications(project, topic, msg)
send_mqtt_notifications(project, topic, msg)
send_webhook_notifications(project, topic, msg)
if (
_config.get("PAGURE_CI_SERVICES")
and project.ci_hook
and project.ci_hook.active_commit
and not project.private
):
pagure.lib.tasks_services.trigger_ci_build.delay(
project_name=project.fullname,
cause=revs[-1],
branch=refname,
ci_type=project.ci_hook.ci_type,
branch_to=None,
)
def inform_pull_request_urls(
session, project, commits, refname, default_branch
):
""" Inform the user about the URLs to open a new pull-request or visit
the existing one.
"""
target_repo = project
if project.is_fork:
target_repo = project.parent
pr_uids = []
if (
commits
and refname != default_branch
and target_repo.settings.get("pull_requests", True)
):
print()
prs = pagure.lib.query.search_pull_requests(
session,
project_id_from=target_repo.id,
status="Open",
branch_from=refname,
)
if project.id != target_repo.id:
prs.extend(
pagure.lib.query.search_pull_requests(
session,
project_id_from=project.id,
status="Open",
branch_from=refname,
)
)
# Link to existing PRs if there are any
seen = len(prs) != 0
for pr in prs:
# Refresh the PR in the db and everywhere else where needed
pagure.lib.tasks.update_pull_request.delay(pr.uid)
# Link tickets with pull-requests if the commit mentions it
pagure.lib.tasks.link_pr_to_ticket.delay(pr.uid)
# Inform the user about the PR
print("View pull-request for %s" % refname)
print(
" %s/%s/pull-request/%s"
% (_config["APP_URL"].rstrip("/"), pr.project.url_path, pr.id)
)
pr_uids.append(pr.uid)
# If no existing PRs, provide the link to open one
if not seen:
print("Create a pull-request for %s" % refname)
print(
" %s/%s/diff/%s..%s"
% (
_config["APP_URL"].rstrip("/"),
project.url_path,
default_branch,
refname,
)
)
print()
return pr_uids
class DefaultRunner(BaseRunner):
""" Runner for the default hook."""
@staticmethod
def post_receive(session, username, project, repotype, repodir, changes):
""" Run the default post-receive hook.
For args, see BaseRunner.runhook.
"""
if repotype != "main":
if _config.get("HOOK_DEBUG", False):
print("Default hook only runs on the main project repository")
return
if changes:
# Retrieve the default branch
repo_obj = pygit2.Repository(repodir)
default_branch = None
if not repo_obj.is_empty and not repo_obj.head_is_unborn:
default_branch = repo_obj.head.shorthand
pr_uids = []
for refname in changes:
(oldrev, newrev) = changes[refname]
forced = False
if set(newrev) == set(["0"]):
if refname.startswith("refs/tags"):
refname = refname.replace("refs/tags/", "")
send_action_notification(
session,
"tag",
"deletion",
project,
repodir,
username,
refname,
oldrev,
)
print("Deleting a tag, so we won't run the " "pagure hook")
elif refname.startswith("refs/heads/"):
refname = refname.replace("refs/heads/", "")
send_action_notification(
session,
"branch",
"deletion",
project,
repodir,
username,
refname,
oldrev,
)
print(
"Deleting a branch, so we won't run the " "pagure hook"
)
else:
print(
"Deleting %s, so we wont run the pagure hook nor "
"send notifications"
)
continue
elif set(oldrev) == set(["0"]):
oldrev = "^%s" % oldrev
if refname.startswith("refs/tags"):
refname = refname.replace("refs/tags/", "")
send_action_notification(
session,
"tag",
"creation",
project,
repodir,
username,
refname,
newrev,
)
elif pagure.lib.git.is_forced_push(oldrev, newrev, repodir):
forced = True
base = pagure.lib.git.get_base_revision(
oldrev, newrev, repodir
)
if base:
oldrev = base[0]
refname = refname.replace("refs/heads/", "")
commits = pagure.lib.git.get_revs_between(
oldrev, newrev, repodir, refname
)
log_all = _config.get("LOG_ALL_COMMITS", False)
if log_all or refname == default_branch:
print(
"Sending to redis to log activity and send commit "
"notification emails"
)
else:
print("Sending to redis to send commit notification emails")
# This is logging the commit to the log table in the DB so we can
# render commits in the calendar heatmap.
# It is also sending emails about commits to people using the
# 'watch' feature to be made aware of new commits.
pagure.lib.tasks_services.log_commit_send_notifications.delay(
name=project.name,
commits=commits,
abspath=repodir,
branch=refname,
default_branch=default_branch,
namespace=project.namespace,
username=project.user.user if project.is_fork else None,
)
# This one is sending fedmsg and web-hook notifications for project
# that set them up
send_notifications(
session, project, repodir, username, refname, commits, forced
)
# Now display to the user if this isn't the default branch links to
# open a new pr or review the existing one
pr_uids.extend(
inform_pull_request_urls(
session, project, commits, refname, default_branch
)
)
# Refresh of all opened PRs
parent = project.parent or project
if _config.get("GIT_HOOK_DB_RO", False):
pagure.lib.tasks.refresh_pr_cache(
parent.name,
parent.namespace,
parent.user.user if parent.is_fork else None,
but_uids=pr_uids,
)
else:
pagure.lib.tasks.refresh_pr_cache.delay(
parent.name,
parent.namespace,
parent.user.user if parent.is_fork else None,
but_uids=pr_uids,
)
if not project.is_on_repospanner and _config.get(
"GIT_GARBAGE_COLLECT", False
):
pagure.lib.tasks.git_garbage_collect.delay(
project.repopath("main")
)
class Default(BaseHook):
""" Default hooks. """
name = "default"
description = (
"Default hooks that should be enabled for each and every project."
)
runner = DefaultRunner
@classmethod
def is_enabled_for(cls, project):
return True