diff options
author | Johannes Krampf <johannes.krampf@gmail.com> | 2011-11-26 14:27:45 +0100 |
---|---|---|
committer | Johannes Krampf <johannes.krampf@gmail.com> | 2011-11-26 14:27:45 +0100 |
commit | 56c773b32fc68639eb55666b6cfaa32bc9618321 (patch) | |
tree | dcc047f0552224facb6d05cddf2fb72b973bd683 /packages/management | |
parent | fbd23db51b7160a308cd88e407e676994eb08b10 (diff) | |
parent | 85657db05d7f65604340699cfcb9967c9e81a0ef (diff) |
Merged with archweb trunk
Diffstat (limited to 'packages/management')
-rw-r--r-- | packages/management/__init__.py | 0 | ||||
-rw-r--r-- | packages/management/commands/__init__.py | 0 | ||||
-rw-r--r-- | packages/management/commands/populate_signoffs.py | 88 | ||||
-rw-r--r-- | packages/management/commands/signoff_report.py | 125 |
4 files changed, 213 insertions, 0 deletions
diff --git a/packages/management/__init__.py b/packages/management/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/packages/management/__init__.py diff --git a/packages/management/commands/__init__.py b/packages/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/packages/management/commands/__init__.py diff --git a/packages/management/commands/populate_signoffs.py b/packages/management/commands/populate_signoffs.py new file mode 100644 index 00000000..ce5ec734 --- /dev/null +++ b/packages/management/commands/populate_signoffs.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +""" +populate_signoffs command + +Pull the latest commit message from SVN for a given package that is +signoff-eligible and does not have an existing comment attached. + +Usage: ./manage.py populate_signoffs +""" + +from datetime import datetime +import logging +import subprocess +import sys +from xml.etree.ElementTree import XML + +from django.conf import settings +from django.contrib.auth.models import User +from django.core.management.base import NoArgsCommand + +from ...models import SignoffSpecification +from ...utils import get_signoff_groups +from devel.utils import UserFinder + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s -> %(levelname)s: %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + stream=sys.stderr) +logger = logging.getLogger() + +class Command(NoArgsCommand): + help = "Pull the latest commit message from SVN for a given package that is signoff-eligible and does not have an existing comment attached" + + def handle_noargs(self, **options): + v = int(options.get('verbosity', None)) + if v == 0: + logger.level = logging.ERROR + elif v == 1: + logger.level = logging.INFO + elif v == 2: + logger.level = logging.DEBUG + + return add_signoff_comments() + +def svn_log(pkgbase, repo): + path = '%s%s/%s/trunk/' % (settings.SVN_BASE_URL, repo.svn_root, pkgbase) + cmd = ['svn', 'log', '--limit=1', '--xml', path] + log_data = subprocess.check_output(cmd) + # the XML format is very very simple, especially with only one revision + xml = XML(log_data) + revision = int(xml.find('logentry').get('revision')) + date = datetime.strptime(xml.findtext('logentry/date'), + '%Y-%m-%dT%H:%M:%S.%fZ') + return { + 'revision': revision, + 'date': date, + 'author': xml.findtext('logentry/author'), + 'message': xml.findtext('logentry/msg'), + } + +def create_specification(package, log, finder): + trimmed_message = log['message'].strip() + spec = SignoffSpecification(pkgbase=package.pkgbase, + pkgver=package.pkgver, pkgrel=package.pkgrel, + epoch=package.epoch, arch=package.arch, repo=package.repo, + comments=trimmed_message) + spec.user = finder.find_by_username(log['author']) + return spec + +def add_signoff_comments(): + logger.info("getting all signoff groups") + groups = get_signoff_groups() + logger.info("%d signoff groups found", len(groups)) + + finder = UserFinder() + + for group in groups: + if not group.default_spec: + continue + + logger.debug("getting SVN log for %s (%s)", group.pkgbase, group.repo) + log = svn_log(group.pkgbase, group.repo) + logger.info("creating spec with SVN message for %s", group.pkgbase) + spec = create_specification(group.packages[0], log, finder) + spec.save() + +# vim: set ts=4 sw=4 et: diff --git a/packages/management/commands/signoff_report.py b/packages/management/commands/signoff_report.py new file mode 100644 index 00000000..3b67f518 --- /dev/null +++ b/packages/management/commands/signoff_report.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +""" +signoff_report command + +Send an email summarizing the state of outstanding signoffs for the given +repository. + +Usage: ./manage.py signoff_report <email> <repository> +""" + +from django.core.mail import send_mail +from django.core.urlresolvers import reverse +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth.models import User +from django.contrib.sites.models import Site +from django.db.models import Count +from django.template import loader, Context + +from collections import namedtuple +from datetime import datetime, timedelta +import logging +from operator import attrgetter +import sys + +from main.models import Repo +from packages.models import Signoff +from packages.utils import get_signoff_groups + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s -> %(levelname)s: %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + stream=sys.stderr) +logger = logging.getLogger() + +class Command(BaseCommand): + args = "<email> <repository>" + help = "Send a signoff report for the given repository." + + def handle(self, *args, **options): + v = int(options.get('verbosity', None)) + if v == 0: + logger.level = logging.ERROR + elif v == 1: + logger.level = logging.INFO + elif v == 2: + logger.level = logging.DEBUG + + if len(args) != 2: + raise CommandError("email and repository must be provided") + + return generate_report(args[0], args[1]) + +def generate_report(email, repo_name): + repo = Repo.objects.get(name__iexact=repo_name) + # Collect all existing signoffs for these packages + signoff_groups = sorted(get_signoff_groups([repo]), + key=attrgetter('target_repo', 'arch', 'pkgbase')) + disabled = [] + bad = [] + complete = [] + incomplete = [] + new = [] + old = [] + + new_hours = 24 + old_days = 14 + now = datetime.utcnow() + new_cutoff = now - timedelta(hours=new_hours) + old_cutoff = now - timedelta(days=old_days) + + if len(signoff_groups) == 0: + # no need to send an email at all + return + + for group in signoff_groups: + spec = group.specification + if spec.known_bad: + bad.append(group) + elif not spec.enabled: + disabled.append(group) + elif group.approved(): + complete.append(group) + else: + incomplete.append(group) + + if group.package.last_update > new_cutoff: + new.append(group) + if group.package.last_update < old_cutoff: + old.append(group) + + old.sort(key=attrgetter('last_update')) + + proto = 'https' + domain = Site.objects.get_current().domain + signoffs_url = '%s://%s%s' % (proto, domain, reverse('package-signoffs')) + + # and the fun bit + Leader = namedtuple('Leader', ['user', 'count']) + leaders = Signoff.objects.filter(created__gt=new_cutoff, + revoked__isnull=True).values_list('user').annotate( + signoff_count=Count('pk')).order_by('-signoff_count')[:5] + users = User.objects.in_bulk([l[0] for l in leaders]) + leaders = (Leader(users[l[0]], l[1]) for l in leaders) + + subject = 'Signoff report for [%s]' % repo.name.lower() + t = loader.get_template('packages/signoff_report.txt') + c = Context({ + 'repo': repo, + 'signoffs_url': signoffs_url, + 'disabled': disabled, + 'bad': bad, + 'all': signoff_groups, + 'incomplete': incomplete, + 'complete': complete, + 'new': new, + 'new_hours': new_hours, + 'old': old, + 'old_days': old_days, + 'leaders': leaders, + }) + from_addr = 'Arch Website Notification <nobody@archlinux.org>' + send_mail(subject, t.render(c), from_addr, [email]) + +# vim: set ts=4 sw=4 et: |