[distgit/pagure] hotfix the default hook to clean the PR merge status async

This is necessary because of the way our dist-git is deployed, the git hook
only have a read-only access to the database so they can't reset the cached
merge status of the open PRs in the same process so we need to do this via
an async process which is basically what this hotfix does.

Signed-off-by: Pierre-Yves Chibon <pingou@pingoured.fr>
This commit is contained in:
Pierre-Yves Chibon 2019-02-27 17:15:22 +01:00
parent 7e76690649
commit 89fce190b9
2 changed files with 368 additions and 0 deletions

View file

@ -0,0 +1,358 @@
# -*- 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__)
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:
print(" - to fedmsg")
import fedmsg
config = fedmsg.config.load_config([], None)
config["active"] = True
config["endpoints"]["relay_inbound"] = config["relay_inbound"]
fedmsg.init(name="relay_inbound", **config)
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_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,
)
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"]):
print(
"Deleting a reference/branch, so we won't run the "
"pagure hook"
)
return
elif set(oldrev) == set(["0"]):
oldrev = "^%s" % oldrev
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
pagure.lib.tasks.refresh_pr_cache(
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

View file

@ -380,3 +380,13 @@
tags:
- pagure
- logrotate
- name: hotfix the default hook so it updates pending PR async
copy: >
src=hotfix-20190227-hooks-default.py
dest=/usr/lib/python2.7/site-packages/pagure/hooks/default.py
owner=root group=root mode=0644
tags:
- hotfix
- pagure