pagure_ticket_hook.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. # -*- coding: utf-8 -*-
  2. """
  3. (c) 2014-2016 - Copyright Red Hat Inc
  4. Authors:
  5. Pierre-Yves Chibon <pingou@pingoured.fr>
  6. """
  7. from __future__ import absolute_import, unicode_literals
  8. import os
  9. import sqlalchemy as sa
  10. import wtforms
  11. try:
  12. from flask_wtf import FlaskForm
  13. except ImportError:
  14. from flask_wtf import Form as FlaskForm
  15. from sqlalchemy.orm import backref, relation
  16. import pagure.lib.git
  17. import pagure.lib.tasks_services
  18. from pagure.config import config as pagure_config
  19. from pagure.exceptions import FileNotFoundException
  20. from pagure.hooks import BaseHook, BaseRunner
  21. from pagure.lib.model import BASE, Project
  22. class PagureTicketsTable(BASE):
  23. """Stores information about the pagure tickets hook deployed on a project.
  24. Table -- hook_pagure_tickets
  25. """
  26. __tablename__ = "hook_pagure_tickets"
  27. id = sa.Column(sa.Integer, primary_key=True)
  28. project_id = sa.Column(
  29. sa.Integer,
  30. sa.ForeignKey("projects.id", onupdate="CASCADE", ondelete="CASCADE"),
  31. nullable=False,
  32. unique=True,
  33. index=True,
  34. )
  35. active = sa.Column(sa.Boolean, nullable=False, default=False)
  36. project = relation(
  37. "Project",
  38. remote_side=[Project.id],
  39. backref=backref(
  40. "pagure_hook_tickets",
  41. cascade="delete, delete-orphan",
  42. single_parent=True,
  43. uselist=False,
  44. ),
  45. )
  46. class PagureTicketRunner(BaseRunner):
  47. """Runner for the git hook updating the DB of tickets on push."""
  48. @staticmethod
  49. def post_receive(session, username, project, repotype, repodir, changes):
  50. """Run the post-receive tasks of a hook.
  51. For args, see BaseRunner.runhook.
  52. """
  53. if repotype != "tickets":
  54. print("The ticket hook only runs on the ticket git repository.")
  55. return
  56. if username == "pagure":
  57. # This was an update from inside the UI. Do not trigger further
  58. # database updates, as this has already been done
  59. return
  60. for refname in changes:
  61. (oldrev, newrev) = changes[refname]
  62. if set(newrev) == set(["0"]):
  63. print(
  64. "Deleting a reference/branch, so we won't run the "
  65. "pagure hook"
  66. )
  67. return
  68. commits = pagure.lib.git.get_revs_between(
  69. oldrev, newrev, repodir, refname
  70. )
  71. pagure.lib.tasks_services.load_json_commits_to_db.delay(
  72. name=project.name,
  73. commits=commits,
  74. abspath=repodir,
  75. data_type="ticket",
  76. agent=username,
  77. namespace=project.namespace,
  78. username=project.user.user if project.is_fork else None,
  79. )
  80. class PagureTicketsForm(FlaskForm):
  81. """Form to configure the pagure hook."""
  82. active = wtforms.BooleanField("Active", [wtforms.validators.Optional()])
  83. class PagureTicketHook(BaseHook):
  84. """Pagure ticket hook."""
  85. name = "Pagure tickets"
  86. description = (
  87. "Pagure specific hook to update tickets stored in the "
  88. "database based on the information pushed in the tickets git "
  89. "repository."
  90. )
  91. form = PagureTicketsForm
  92. db_object = PagureTicketsTable
  93. backref = "pagure_hook_tickets"
  94. form_fields = ["active"]
  95. runner = PagureTicketRunner
  96. @classmethod
  97. def set_up(cls, project):
  98. """Install the generic post-receive hook that allow us to call
  99. multiple post-receive hooks as set per plugin.
  100. """
  101. repopath = os.path.join(pagure_config["TICKETS_FOLDER"], project.path)
  102. if not os.path.exists(repopath):
  103. raise FileNotFoundException("No such file: %s" % repopath)
  104. hook_files = os.path.join(
  105. os.path.dirname(os.path.realpath(__file__)), "files"
  106. )
  107. # Make sure the hooks folder exists
  108. hookfolder = os.path.join(repopath, "hooks")
  109. if not os.path.exists(hookfolder):
  110. os.makedirs(hookfolder)
  111. # Install the main post-receive file
  112. postreceive = os.path.join(hookfolder, "post-receive")
  113. hook_file = os.path.join(hook_files, "post-receive")
  114. if not os.path.exists(postreceive):
  115. os.symlink(hook_file, postreceive)
  116. @classmethod
  117. def install(cls, project, dbobj):
  118. """Method called to install the hook for a project.
  119. :arg project: a ``pagure.model.Project`` object to which the hook
  120. should be installed
  121. """
  122. repopaths = [
  123. os.path.join(pagure_config["TICKETS_FOLDER"], project.path)
  124. ]
  125. cls.base_install(
  126. repopaths, dbobj, "pagure-ticket", "pagure_hook_tickets.py"
  127. )
  128. @classmethod
  129. def remove(cls, project):
  130. """Method called to remove the hook of a project.
  131. :arg project: a ``pagure.model.Project`` object to which the hook
  132. should be installed
  133. """
  134. repopaths = [
  135. os.path.join(pagure_config["TICKETS_FOLDER"], project.path)
  136. ]
  137. cls.base_remove(repopaths, "pagure-ticket")