diff options
Diffstat (limited to 'deployment/mgagit/templates/git-post-receive-hook')
| -rwxr-xr-x | deployment/mgagit/templates/git-post-receive-hook | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/deployment/mgagit/templates/git-post-receive-hook b/deployment/mgagit/templates/git-post-receive-hook new file mode 100755 index 00000000..68da3200 --- /dev/null +++ b/deployment/mgagit/templates/git-post-receive-hook @@ -0,0 +1,314 @@ +#!/usr/bin/python3 + +import configparser +import os +import re +import sys +import urllib.error +import urllib.parse +import urllib.request +import xmlrpc.client +from dataclasses import dataclass + +LIBDIR = '<%= @gitolite_commonhooksdir %>' +sys.path.insert(0, LIBDIR) + +import bugz.settings + +import git_multimail + +# When editing this list, remember to edit the same list in +# modules/cgit/templates/filter.commit-links.sh +BUG_REFS = { + 'Mageia': { 're': re.compile('mga#([0-9]+)'), 'replace': 'https://bugs.mageia.org/%s' }, + 'Red Hat': { 're': re.compile('rhbz#([0-9]+)'), 'replace': 'https://bugzilla.redhat.com/show_bug.cgi?id=%s' }, + 'Free Desktop': { 're': re.compile('fdo#([0-9]+)'), 'replace': 'https://bugs.freedesktop.org/show_bug.cgi?id=%s' }, + 'KDE': { 're': re.compile('(?:bko|kde)#([0-9]+)'), 'replace': 'https://bugs.kde.org/show_bug.cgi?id=%s' }, + 'GNOME': { 're': re.compile('(?:bgo|gnome)#([0-9]+)'), 'replace': 'https://bugzilla.gnome.org/show_bug.cgi?id=%s' }, + 'Launchpad': { 're': re.compile('lp#([0-9]+)'), 'replace': 'https://launchpad.net/bugs/%s' }, +} + +COMMIT_RE = re.compile('^commit ([a-f0-9]{40})') +COMMIT_REPLACE = 'https://gitweb.mageia.org/%s/commit/?id=%s' + +MAGEIA_BUGZILLA_URL = 'https://bugs.mageia.org/xmlrpc.cgi' +MAGEIA_BUGZILLA_PASSWORD_FILE = '.gitzilla-password' +MAGEIA_BUGZILLA_APIKEY_FILE = '.gitzilla-apikey' # this holds a Bugzilla API key +GITWEB_UPDATE_URL = 'http://gitweb.mageia.org:8000' + +# Bugzilla user +USER_LOGIN = 'bot' + +# Recipient of i18n notifications +I18N_MAIL = 'i18n-reports@ml.mageia.org' + +# Set to 1..3 for debug logging (WARNING: this will show passwords to git committers!) +DEBUG = 0 + +git_multimail.FOOTER_TEMPLATE = """\ + +-- \n\ +Mageia Git Monkeys. +""" +git_multimail.REVISION_FOOTER_TEMPLATE = git_multimail.FOOTER_TEMPLATE + +I18N_REVISION_HEADER_TEMPLATE = """\ +Date: %(send_date)s +To: %(recipients)s +Subject: %(emailprefix)s%(oneline)s +MIME-Version: 1.0 +Content-Type: text/plain; charset=%(charset)s +Content-Transfer-Encoding: 8bit +From: %(fromaddr)s +Reply-To: %(reply_to)s +In-Reply-To: %(reply_to_msgid)s +References: %(reply_to_msgid)s +X-Git-Host: %(fqdn)s +X-Git-Repo: %(repo_shortname)s +X-Git-Refname: %(refname)s +X-Git-Reftype: %(refname_type)s +X-Git-Rev: %(rev)s +Auto-Submitted: auto-generated +""" + + +REPO_NAME_RE = re.compile(r'^/git/(?P<name>.+?)(?:\.git)?$') + + +# Log a debug message when logging is enabled +def debug(s, *a): + if DEBUG > 0: + print(s % a) + + +def repo_shortname(): + basename = os.path.abspath(git_multimail.get_git_dir()) + m = REPO_NAME_RE.match(basename) + if m: + return m.group('name') + else: + return basename + + +# Override the Environment class to generate an appropriate short name which is +# used in git links and as an email prefix +class MageiaEnvironment(git_multimail.Environment): + def get_repo_shortname(self): + return repo_shortname() + + +git_multimail.Environment = MageiaEnvironment + + +# Override the Revision class to inject gitweb/cgit links and any referenced +# bug URLs +class MageiaLinksRevision(git_multimail.Revision): + def __init__(self, reference_change, rev, num, tot): + super().__init__(reference_change, rev, num, tot) + self.bz = None + + def bugzilla_init(self): + if not self.bz: + tokenfile = os.path.join(os.environ['HOME'], MAGEIA_BUGZILLA_APIKEY_FILE) + token = None + try: + token = open(tokenfile, 'r').readline().rstrip() + except IOError: + # Assume username/password will be used instead + pass + + passwordfile = os.path.join(os.environ['HOME'], MAGEIA_BUGZILLA_PASSWORD_FILE) + pword = None + try: + pword = open(passwordfile, 'r').readline().rstrip() + except IOError: + print('Error: no Bugzilla credentials available; trying anyway') + # There's no real point in continuing, but why not + + class ConfigSettings: + pass + + cfg = ConfigSettings() + cfg.connection = 'Mageia' + if token: + # If an API key is found, that will be used instead of user/password + cfg.key = token + cfg.user = USER_LOGIN + cfg.password = pword + cfg.base = MAGEIA_BUGZILLA_URL + cfg.debug = DEBUG + + cfile = configparser.ConfigParser() + cfile.add_section(cfg.connection) + self.bz = bugz.settings.Settings(cfg, cfile) + + def generate_email_body(self, push): + """Show this revision.""" + output = git_multimail.read_git_lines( + ['log'] + self.environment.commitlogopts + ['-1', self.rev.sha1], + keepends=True, + ) + bugs = {} + commit = None + idx = 0 + # Modify the mail output on-the-fly to add links; this is sensitive to + # the mail format produced by git_multimail. Also, update Mageia + # Bugzilla if a bug reference is found. + for line in output: + idx += 1 + if line == "---\n": + if commit and COMMIT_REPLACE: + output.insert(idx, "\n") + output.insert(idx, " %s\n" % (COMMIT_REPLACE % (self.environment.get_repo_shortname(), commit))) + output.insert(idx, " Commit Link:\n") + idx += 3 + if bugs: + output.insert(idx, " Bug links:\n") + idx += 1 + for tracker, bugnos in bugs.items(): + output.insert(idx, " %s\n" % tracker) + idx += 1 + for bugno in bugnos: + output.insert(idx, " %s\n" % (BUG_REFS[tracker]['replace'] % bugno)) + idx += 1 + output.insert(idx, "\n") + idx += 1 + + # Attempt to modify Bugzilla + if "Mageia" in bugs: + try: + self.bugzilla_init() + + # Mask email address + comment = None + # Suppress the "Bug links:" section if only one bug + # is referenced + if len(bugs) == 1 and len(bugs['Mageia']) == 1: + comment = output[0:idx-4] + else: + comment = output[0:idx] + comment[1] = re.sub(r'^(Author: [^@]*)@.*(>)?', r'\1@...>', comment[1]) + comment = "".join(comment) + + params = {} + params['ids'] = bugs['Mageia'] + params['comment'] = { 'body': comment } + self.bz.call_bz(self.bz.bz.Bug.update, params) + print("Updated bugzilla bugs: %s" % ", ".join(bugs['Mageia'])) + except: + print("Unable to post to bugzilla bugs: %s :(" % ", ".join(bugs['Mageia'])) + print(sys.exc_info()[1]) + + break + + m = COMMIT_RE.search(line) + if m: + commit = m.group(1) + for tracker in BUG_REFS.keys(): + foundbugs = BUG_REFS[tracker]['re'].findall(line) + if foundbugs: + if tracker not in bugs: + bugs[tracker] = foundbugs + else: + bugs[tracker] = list(set(bugs[tracker] + foundbugs)) + + return output + + +# Override the Revision class to inject gitweb/cgit links and any referenced +# bug URLs +class MageiaI18NRevision(git_multimail.Revision): + """A Change consisting of a single git commit.""" + + def __init__(self, reference_change, rev, num, tot): + super().__init__(reference_change, rev, num, tot) + + # Don't send to any of the normal recipients + self.recipients = False + self.cc_recipients = '' + + i18n_folders = [] + # Check files and find i18n folders + for line in git_multimail.read_git_lines(['ls-tree', '-rd', self.rev.sha1]): + (modetypesha1, name) = line.split("\t", 1) + if name.endswith("/.tx"): + i18n_folders.append(os.path.dirname(name)) + + if i18n_folders: + self.output = git_multimail.read_git_lines( + ['log', '-C', '--stat', '-p', '--no-walk', self.rev.sha1, '--'] + i18n_folders, + keepends=True, + ) + if self.output: + # We have some output so let's send the mail... + self.recipients = I18N_MAIL + print(f'Sending i8n notification to {self.recipients}') + + + def generate_email_body(self, push): + """Show this revision.""" + + return self.output + + +def main(): + # Attempt to write a last-updated file for cgit cosmetics + git_dir = git_multimail.get_git_dir() + infowebdir = os.path.join(git_dir, 'info', 'web') + lastupdated = git_multimail.read_git_output( + ['for-each-ref', '--sort=-committerdate', "--format=%(committerdate:iso8601)", '--count=1', 'refs/heads'], + ) + try: + if not os.path.exists(infowebdir): + os.makedirs(infowebdir) + with open(os.path.join(infowebdir, 'last-modified'), 'w') as modfile: + modfile.write(lastupdated) + except Exception: + debug('Warning: could not update git last-modified date: %s', sys.exc_info()[1]) + + # Contact the on-the-pull service on the gitweb server with the updated repo + try: + req = urllib.request.Request(GITWEB_UPDATE_URL, (repo_shortname() + '.git').encode('utf-8')) + req.add_header('Content-Type', 'x-git/repo') + fp = urllib.request.urlopen(req, timeout=5) + fp.close() + except Exception: + debug('Warning: could not contact gitweb server: %s', sys.exc_info()[1]) + + config = git_multimail.Config('multimailhook') + + try: + environment = git_multimail.choose_environment( + config, osenv=os.environ, + ) + + mailer = git_multimail.choose_mailer(config, environment) + # For testing...send mail to stdout only + #mailer = git_multimail.OutputMailer(sys.stdout) + + changes = [] + for line in sys.stdin: + (oldrev, newrev, refname) = line.strip().split(' ', 2) + changes.append( + git_multimail.ReferenceChange.create(environment, oldrev, newrev, refname) + ) + push = git_multimail.Push(environment, changes) + + # First pass - regular commit mails + git_multimail.Revision = MageiaLinksRevision + push.send_emails(mailer, body_filter=environment.filter_body) + + # Second pass - i18n commit mails + git_multimail.REVISION_HEADER_TEMPLATE = I18N_REVISION_HEADER_TEMPLATE + git_multimail.Revision = MageiaI18NRevision + # Don't send the summary email, so nuke the change recipients + for change in push.changes: + change.recipients = False + push.send_emails(mailer, body_filter=environment.filter_body) + + except git_multimail.ConfigurationException as e: + sys.exit(str(e)) + + +if __name__ == '__main__': + main() |
