summaryrefslogtreecommitdiff
path: root/packages/management
diff options
context:
space:
mode:
authorJohannes Krampf <johannes.krampf@gmail.com>2011-11-26 14:27:45 +0100
committerJohannes Krampf <johannes.krampf@gmail.com>2011-11-26 14:27:45 +0100
commit56c773b32fc68639eb55666b6cfaa32bc9618321 (patch)
treedcc047f0552224facb6d05cddf2fb72b973bd683 /packages/management
parentfbd23db51b7160a308cd88e407e676994eb08b10 (diff)
parent85657db05d7f65604340699cfcb9967c9e81a0ef (diff)
Merged with archweb trunk
Diffstat (limited to 'packages/management')
-rw-r--r--packages/management/__init__.py0
-rw-r--r--packages/management/commands/__init__.py0
-rw-r--r--packages/management/commands/populate_signoffs.py88
-rw-r--r--packages/management/commands/signoff_report.py125
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: