From: Jeremy Kerr Date: Sun, 24 May 2015 08:57:33 +0000 (+0800) Subject: Move to a more recent django project structure X-Git-Url: http://git.ozlabs.org/?a=commitdiff_plain;h=ad2762cf775a8dde508de47164d6429f3fd724f1;p=patchwork Move to a more recent django project structure This change updates patchwor to the newer project struture: we've moved the actual application out of the apps/ directory, and the patchwork-specific templates to under the patchwork application. This gives us the manage.py script in the top-level now. Signed-off-by: Jeremy Kerr --- diff --git a/apps/__init__.py b/apps/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/manage.py b/apps/manage.py deleted file mode 100755 index 04eac77..0000000 --- a/apps/manage.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "patchwork.settings.prod") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff --git a/apps/patchwork/__init__.py b/apps/patchwork/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/patchwork/admin.py b/apps/patchwork/admin.py deleted file mode 100644 index 5297903..0000000 --- a/apps/patchwork/admin.py +++ /dev/null @@ -1,50 +0,0 @@ -from django.contrib import admin -from patchwork.models import Project, Person, UserProfile, State, Patch, \ - Comment, Bundle - -class ProjectAdmin(admin.ModelAdmin): - list_display = ('name', 'linkname','listid', 'listemail') -admin.site.register(Project, ProjectAdmin) - -class PersonAdmin(admin.ModelAdmin): - list_display = ('__unicode__', 'has_account') - search_fields = ('name', 'email') - def has_account(self, person): - return bool(person.user) - has_account.boolean = True - has_account.admin_order_field = 'user' - has_account.short_description = 'Account' -admin.site.register(Person, PersonAdmin) - -class UserProfileAdmin(admin.ModelAdmin): - search_fields = ('user__username', 'user__first_name', 'user__last_name') -admin.site.register(UserProfile, UserProfileAdmin) - -class StateAdmin(admin.ModelAdmin): - list_display = ('name', 'action_required') -admin.site.register(State, StateAdmin) - -class PatchAdmin(admin.ModelAdmin): - list_display = ('name', 'submitter', 'project', 'state', 'date', - 'archived', 'is_pull_request') - list_filter = ('project', 'state', 'archived') - search_fields = ('name', 'submitter__name', 'submitter__email') - date_hierarchy = 'date' - def is_pull_request(self, patch): - return bool(patch.pull_url) - is_pull_request.boolean = True - is_pull_request.admin_order_field = 'pull_url' - is_pull_request.short_description = 'Pull' -admin.site.register(Patch, PatchAdmin) - -class CommentAdmin(admin.ModelAdmin): - list_display = ('patch', 'submitter', 'date') - search_fields = ('patch__name', 'submitter__name', 'submitter__email') - date_hierarchy = 'date' -admin.site.register(Comment, CommentAdmin) - -class BundleAdmin(admin.ModelAdmin): - list_display = ('name', 'owner', 'project', 'public') - list_filter = ('public', 'project') - search_fields = ('name', 'owner') -admin.site.register(Bundle, BundleAdmin) diff --git a/apps/patchwork/bin/__init__.py b/apps/patchwork/bin/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/patchwork/bin/bash_completion b/apps/patchwork/bin/bash_completion deleted file mode 100644 index a120a76..0000000 --- a/apps/patchwork/bin/bash_completion +++ /dev/null @@ -1,29 +0,0 @@ -# Autocompletion for bash. - -_pwclient() { - local cur prev words cword split - - if declare -f _init_completion >/dev/null; then - _init_completion -s || return - else - cur=$(_get_cword) - prev=${COMP_WORDS[COMP_CWORD-1]} - fi - - case "${COMP_CWORD}" in - 0|1) return 0;; - esac - - projects="$(sed -r -e '/\[options\]/d;' \ - -e '/^\[(.+)\]$/!d;' \ - -e 's//\1/;' ~/.pwclientrc 2>/dev/null)" - - case "${prev}" in - -p) COMPREPLY=( $(compgen -W "${projects}" -- "${cur}" ) );; - esac - - return 0 -} -complete -F _pwclient pwclient - -# vim: ft=sh diff --git a/apps/patchwork/bin/parsemail-batch.sh b/apps/patchwork/bin/parsemail-batch.sh deleted file mode 100755 index 31ef4f0..0000000 --- a/apps/patchwork/bin/parsemail-batch.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/sh -# -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -PATCHWORK_BINDIR=`dirname $0` - -if [ $# -ne 1 ] -then - echo "usage: $0 " >&2 - exit 1 -fi - -mail_dir="$1" - -echo "dir: $mail_dir" - -if [ ! -d "$mail_dir" ] -then - echo "$mail_dir should be a directory"? >&2 - exit 1 -fi - -ls -1rt "$mail_dir" | -while read line; -do - echo $line - $PATCHWORK_BINDIR/parsemail.sh < "$mail_dir/$line" -done diff --git a/apps/patchwork/bin/parsemail.py b/apps/patchwork/bin/parsemail.py deleted file mode 100755 index 19e6e57..0000000 --- a/apps/patchwork/bin/parsemail.py +++ /dev/null @@ -1,455 +0,0 @@ -#!/usr/bin/env python -# -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import sys -import re -import datetime -import time -import operator -import codecs -from email import message_from_file -try: - from email.header import Header, decode_header - from email.utils import parsedate_tz, mktime_tz -except ImportError: - # Python 2.4 compatibility - from email.Header import Header, decode_header - from email.Utils import parsedate_tz, mktime_tz - -from patchwork.parser import parse_patch -from patchwork.models import Patch, Project, Person, Comment, State, \ - get_default_initial_patch_state -from django.contrib.auth.models import User - -list_id_headers = ['List-ID', 'X-Mailing-List', 'X-list'] - -whitespace_re = re.compile('\s+') -def normalise_space(str): - return whitespace_re.sub(' ', str).strip() - -def clean_header(header): - """ Decode (possibly non-ascii) headers """ - - def decode(fragment): - (frag_str, frag_encoding) = fragment - if frag_encoding: - return frag_str.decode(frag_encoding) - return frag_str.decode() - - fragments = map(decode, decode_header(header)) - - return normalise_space(u' '.join(fragments)) - -def find_project(mail): - project = None - listid_res = [re.compile('.*<([^>]+)>.*', re.S), - re.compile('^([\S]+)$', re.S)] - - for header in list_id_headers: - if header in mail: - - for listid_re in listid_res: - match = listid_re.match(mail.get(header)) - if match: - break - - if not match: - continue - - listid = match.group(1) - - try: - project = Project.objects.get(listid = listid) - break - except: - pass - - return project - -def find_author(mail): - - from_header = clean_header(mail.get('From')) - (name, email) = (None, None) - - # tuple of (regex, fn) - # - where fn returns a (name, email) tuple from the match groups resulting - # from re.match().groups() - from_res = [ - # for "Firstname Lastname" style addresses - (re.compile('"?(.*?)"?\s*<([^>]+)>'), (lambda g: (g[0], g[1]))), - - # for example@example.com (Firstname Lastname) style addresses - (re.compile('"?(.*?)"?\s*\(([^\)]+)\)'), (lambda g: (g[1], g[0]))), - - # everything else - (re.compile('(.*)'), (lambda g: (None, g[0]))), - ] - - for regex, fn in from_res: - match = regex.match(from_header) - if match: - (name, email) = fn(match.groups()) - break - - if email is None: - raise Exception("Could not parse From: header") - - email = email.strip() - if name is not None: - name = name.strip() - - new_person = False - - try: - person = Person.objects.get(email__iexact = email) - except Person.DoesNotExist: - person = Person(name = name, email = email) - new_person = True - - return (person, new_person) - -def mail_date(mail): - t = parsedate_tz(mail.get('Date', '')) - if not t: - return datetime.datetime.utcnow() - return datetime.datetime.utcfromtimestamp(mktime_tz(t)) - -def mail_headers(mail): - return reduce(operator.__concat__, - ['%s: %s\n' % (k, Header(v, header_name = k, \ - continuation_ws = '\t').encode()) \ - for (k, v) in mail.items()]) - -def find_pull_request(content): - git_re = re.compile('^The following changes since commit.*' + - '^are available in the git repository at:\n' - '^\s*([\S]+://[^\n]+)$', - re.DOTALL | re.MULTILINE) - match = git_re.search(content) - if match: - return match.group(1) - return None - -def try_decode(payload, charset): - try: - payload = unicode(payload, charset) - except UnicodeDecodeError: - return None - return payload - -def find_content(project, mail): - patchbuf = None - commentbuf = '' - pullurl = None - - for part in mail.walk(): - if part.get_content_maintype() != 'text': - continue - - payload = part.get_payload(decode=True) - subtype = part.get_content_subtype() - - if not isinstance(payload, unicode): - charset = part.get_content_charset() - - # Check that we have a charset that we understand. Otherwise, - # ignore it and fallback to our standard set. - if charset is not None: - try: - codec = codecs.lookup(charset) - except LookupError: - charset = None - - # If there is no charset or if it is unknown, then try some common - # charsets before we fail. - if charset is None: - try_charsets = ['utf-8', 'windows-1252', 'iso-8859-1'] - else: - try_charsets = [charset] - - for cset in try_charsets: - decoded_payload = try_decode(payload, cset) - if decoded_payload is not None: - break - payload = decoded_payload - - # Could not find a valid decoded payload. Fail. - if payload is None: - return (None, None) - - if subtype in ['x-patch', 'x-diff']: - patchbuf = payload - - elif subtype == 'plain': - c = payload - - if not patchbuf: - (patchbuf, c) = parse_patch(payload) - - if not pullurl: - pullurl = find_pull_request(payload) - - if c is not None: - commentbuf += c.strip() + '\n' - - patch = None - comment = None - - if pullurl or patchbuf: - name = clean_subject(mail.get('Subject'), [project.linkname]) - patch = Patch(name = name, pull_url = pullurl, content = patchbuf, - date = mail_date(mail), headers = mail_headers(mail)) - - if commentbuf: - if patch: - cpatch = patch - else: - cpatch = find_patch_for_comment(project, mail) - if not cpatch: - return (None, None) - comment = Comment(patch = cpatch, date = mail_date(mail), - content = clean_content(commentbuf), - headers = mail_headers(mail)) - - return (patch, comment) - -def find_patch_for_comment(project, mail): - # construct a list of possible reply message ids - refs = [] - if 'In-Reply-To' in mail: - refs.append(mail.get('In-Reply-To')) - - if 'References' in mail: - rs = mail.get('References').split() - rs.reverse() - for r in rs: - if r not in refs: - refs.append(r) - - for ref in refs: - patch = None - - # first, check for a direct reply - try: - patch = Patch.objects.get(project = project, msgid = ref) - return patch - except Patch.DoesNotExist: - pass - - # see if we have comments that refer to a patch - try: - comment = Comment.objects.get(patch__project = project, msgid = ref) - return comment.patch - except Comment.DoesNotExist: - pass - - - return None - -split_re = re.compile('[,\s]+') - -def split_prefixes(prefix): - """ Turn a prefix string into a list of prefix tokens - - >>> split_prefixes('PATCH') - ['PATCH'] - >>> split_prefixes('PATCH,RFC') - ['PATCH', 'RFC'] - >>> split_prefixes('') - [] - >>> split_prefixes('PATCH,') - ['PATCH'] - >>> split_prefixes('PATCH ') - ['PATCH'] - >>> split_prefixes('PATCH,RFC') - ['PATCH', 'RFC'] - >>> split_prefixes('PATCH 1/2') - ['PATCH', '1/2'] - """ - matches = split_re.split(prefix) - return [ s for s in matches if s != '' ] - -re_re = re.compile('^(re|fwd?)[:\s]\s*', re.I) -prefix_re = re.compile('^\[([^\]]*)\]\s*(.*)$') - -def clean_subject(subject, drop_prefixes = None): - """ Clean a Subject: header from an incoming patch. - - Removes Re: and Fwd: strings, as well as [PATCH]-style prefixes. By - default, only [PATCH] is removed, and we keep any other bracketed data - in the subject. If drop_prefixes is provided, remove those too, - comparing case-insensitively. - - >>> clean_subject('meep') - 'meep' - >>> clean_subject('Re: meep') - 'meep' - >>> clean_subject('[PATCH] meep') - 'meep' - >>> clean_subject('[PATCH] meep \\n meep') - 'meep meep' - >>> clean_subject('[PATCH RFC] meep') - '[RFC] meep' - >>> clean_subject('[PATCH,RFC] meep') - '[RFC] meep' - >>> clean_subject('[PATCH,1/2] meep') - '[1/2] meep' - >>> clean_subject('[PATCH RFC 1/2] meep') - '[RFC,1/2] meep' - >>> clean_subject('[PATCH] [RFC] meep') - '[RFC] meep' - >>> clean_subject('[PATCH] [RFC,1/2] meep') - '[RFC,1/2] meep' - >>> clean_subject('[PATCH] [RFC] [1/2] meep') - '[RFC,1/2] meep' - >>> clean_subject('[PATCH] rewrite [a-z] regexes') - 'rewrite [a-z] regexes' - >>> clean_subject('[PATCH] [RFC] rewrite [a-z] regexes') - '[RFC] rewrite [a-z] regexes' - >>> clean_subject('[foo] [bar] meep', ['foo']) - '[bar] meep' - >>> clean_subject('[FOO] [bar] meep', ['foo']) - '[bar] meep' - """ - - subject = clean_header(subject) - - if drop_prefixes is None: - drop_prefixes = [] - else: - drop_prefixes = [ s.lower() for s in drop_prefixes ] - - drop_prefixes.append('patch') - - # remove Re:, Fwd:, etc - subject = re_re.sub(' ', subject) - - subject = normalise_space(subject) - - prefixes = [] - - match = prefix_re.match(subject) - - while match: - prefix_str = match.group(1) - prefixes += [ p for p in split_prefixes(prefix_str) \ - if p.lower() not in drop_prefixes] - - subject = match.group(2) - match = prefix_re.match(subject) - - subject = normalise_space(subject) - - subject = subject.strip() - if prefixes: - subject = '[%s] %s' % (','.join(prefixes), subject) - - return subject - -sig_re = re.compile('^(-- |_+)\n.*', re.S | re.M) -def clean_content(str): - """ Try to remove signature (-- ) and list footer (_____) cruft """ - str = sig_re.sub('', str) - return str.strip() - -def get_state(state_name): - """ Return the state with the given name or the default State """ - if state_name: - try: - return State.objects.get(name__iexact=state_name) - except State.DoesNotExist: - pass - return get_default_initial_patch_state() - -def get_delegate(delegate_email): - """ Return the delegate with the given email or None """ - if delegate_email: - try: - return User.objects.get(email__iexact=delegate_email) - except User.DoesNotExist: - pass - return None - -def parse_mail(mail): - - # some basic sanity checks - if 'From' not in mail: - return 0 - - if 'Subject' not in mail: - return 0 - - if 'Message-Id' not in mail: - return 0 - - hint = mail.get('X-Patchwork-Hint', '').lower() - if hint == 'ignore': - return 0; - - project = find_project(mail) - if project is None: - print "no project found" - return 0 - - msgid = mail.get('Message-Id').strip() - - (author, save_required) = find_author(mail) - - (patch, comment) = find_content(project, mail) - - if patch: - # we delay the saving until we know we have a patch. - if save_required: - author.save() - save_required = False - patch.submitter = author - patch.msgid = msgid - patch.project = project - patch.state = get_state(mail.get('X-Patchwork-State', '').strip()) - patch.delegate = get_delegate( - mail.get('X-Patchwork-Delegate', '').strip()) - try: - patch.save() - except Exception, ex: - print str(ex) - - if comment: - if save_required: - author.save() - # looks like the original constructor for Comment takes the pk - # when the Comment is created. reset it here. - if patch: - comment.patch = patch - comment.submitter = author - comment.msgid = msgid - try: - comment.save() - except Exception, ex: - print str(ex) - - return 0 - -def main(args): - mail = message_from_file(sys.stdin) - return parse_mail(mail) - -if __name__ == '__main__': - sys.exit(main(sys.argv)) diff --git a/apps/patchwork/bin/parsemail.sh b/apps/patchwork/bin/parsemail.sh deleted file mode 100755 index 246c2a1..0000000 --- a/apps/patchwork/bin/parsemail.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh -# -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -BIN_DIR=`dirname $0` -PATCHWORK_BASE=`readlink -e $BIN_DIR/../../..` - -PYTHONPATH="$PATCHWORK_BASE/apps":"$PATCHWORK_BASE/lib/python:$PYTHONPATH" \ - DJANGO_SETTINGS_MODULE=settings \ - "$PATCHWORK_BASE/apps/patchwork/bin/parsemail.py" - -exit 0 diff --git a/apps/patchwork/bin/patchwork-cron.py b/apps/patchwork/bin/patchwork-cron.py deleted file mode 100755 index 148e97c..0000000 --- a/apps/patchwork/bin/patchwork-cron.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python - -import sys -from patchwork.utils import send_notifications, do_expiry - -def main(args): - errors = send_notifications() - for (recipient, error) in errors: - print "Failed sending to %s: %s" % (recipient.email, ex) - - do_expiry() - -if __name__ == '__main__': - sys.exit(main(sys.argv)) - diff --git a/apps/patchwork/bin/pwclient b/apps/patchwork/bin/pwclient deleted file mode 100755 index 8d1f476..0000000 --- a/apps/patchwork/bin/pwclient +++ /dev/null @@ -1,744 +0,0 @@ -#!/usr/bin/env python -# -# Patchwork command line client -# Copyright (C) 2008 Nate Case -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import os -import sys -import xmlrpclib -import argparse -import string -import tempfile -import subprocess -import base64 -import ConfigParser -import shutil -import re - -# Default Patchwork remote XML-RPC server URL -# This script will check the PW_XMLRPC_URL environment variable -# for the URL to access. If that is unspecified, it will fallback to -# the hardcoded default value specified here. -DEFAULT_URL = "http://patchwork/xmlrpc/" -CONFIG_FILE = os.path.expanduser('~/.pwclientrc') - -class Filter: - """Filter for selecting patches.""" - def __init__(self): - # These fields refer to specific objects, so they are special - # because we have to resolve them to IDs before passing the - # filter to the server - self.state = "" - self.project = "" - - # The dictionary that gets passed to via XML-RPC - self.d = {} - - def add(self, field, value): - if field == 'state': - self.state = value - elif field == 'project': - self.project = value - else: - # OK to add directly - self.d[field] = value - - def resolve_ids(self, rpc): - """Resolve State, Project, and Person IDs based on filter strings.""" - if self.state != "": - id = state_id_by_name(rpc, self.state) - if id == 0: - sys.stderr.write("Note: No State found matching %s*, " \ - "ignoring filter\n" % self.state) - else: - self.d['state_id'] = id - - if self.project != None: - id = project_id_by_name(rpc, self.project) - if id == 0: - sys.stderr.write("Note: No Project found matching %s, " \ - "ignoring filter\n" % self.project) - else: - self.d['project_id'] = id - - def __str__(self): - """Return human-readable description of the filter.""" - return str(self.d) - -class BasicHTTPAuthTransport(xmlrpclib.SafeTransport): - - def __init__(self, username = None, password = None, use_https = False): - self.username = username - self.password = password - self.use_https = use_https - xmlrpclib.SafeTransport.__init__(self) - - def authenticated(self): - return self.username != None and self.password != None - - def send_host(self, connection, host): - xmlrpclib.Transport.send_host(self, connection, host) - if not self.authenticated(): - return - credentials = '%s:%s' % (self.username, self.password) - auth = 'Basic ' + base64.encodestring(credentials).strip() - connection.putheader('Authorization', auth) - - def make_connection(self, host): - if self.use_https: - fn = xmlrpclib.SafeTransport.make_connection - else: - fn = xmlrpclib.Transport.make_connection - return fn(self, host) - -def project_id_by_name(rpc, linkname): - """Given a project short name, look up the Project ID.""" - if len(linkname) == 0: - return 0 - projects = rpc.project_list(linkname, 0) - for project in projects: - if project['linkname'] == linkname: - return project['id'] - return 0 - -def state_id_by_name(rpc, name): - """Given a partial state name, look up the state ID.""" - if len(name) == 0: - return 0 - states = rpc.state_list(name, 0) - for state in states: - if state['name'].lower().startswith(name.lower()): - return state['id'] - return 0 - -def person_ids_by_name(rpc, name): - """Given a partial name or email address, return a list of the - person IDs that match.""" - if len(name) == 0: - return [] - people = rpc.person_list(name, 0) - return map(lambda x: x['id'], people) - -def list_patches(patches, format_str=None): - """Dump a list of patches to stdout.""" - if format_str: - format_field_re = re.compile("%{([a-z0-9_]+)}") - - def patch_field(matchobj): - fieldname = matchobj.group(1) - - if fieldname == "_msgid_": - # naive way to strip < and > from message-id - val = string.strip(str(patch["msgid"]), "<>") - else: - val = str(patch[fieldname]) - - return val - - for patch in patches: - print(format_field_re.sub(patch_field, format_str)) - else: - print("%-7s %-12s %s" % ("ID", "State", "Name")) - print("%-7s %-12s %s" % ("--", "-----", "----")) - for patch in patches: - print("%-7d %-12s %s" % (patch['id'], patch['state'], patch['name'])) - -def action_list(rpc, filter, submitter_str, delegate_str, format_str=None): - filter.resolve_ids(rpc) - - if submitter_str != None: - ids = person_ids_by_name(rpc, submitter_str) - if len(ids) == 0: - sys.stderr.write("Note: Nobody found matching *%s*\n" % \ - submitter_str) - else: - for id in ids: - person = rpc.person_get(id) - print "Patches submitted by %s <%s>:" % \ - (unicode(person['name']).encode("utf-8"), \ - unicode(person['email']).encode("utf-8")) - f = filter - f.add("submitter_id", id) - patches = rpc.patch_list(f.d) - list_patches(patches, format_str) - return - - if delegate_str != None: - ids = person_ids_by_name(rpc, delegate_str) - if len(ids) == 0: - sys.stderr.write("Note: Nobody found matching *%s*\n" % \ - delegate_str) - else: - for id in ids: - person = rpc.person_get(id) - print "Patches delegated to %s <%s>:" % \ - (person['name'], person['email']) - f = filter - f.add("delegate_id", id) - patches = rpc.patch_list(f.d) - list_patches(patches, format_str) - return - - patches = rpc.patch_list(filter.d) - list_patches(patches, format_str) - -def action_projects(rpc): - projects = rpc.project_list("", 0) - print("%-5s %-24s %s" % ("ID", "Name", "Description")) - print("%-5s %-24s %s" % ("--", "----", "-----------")) - for project in projects: - print("%-5d %-24s %s" % (project['id'], \ - project['linkname'], \ - project['name'])) - -def action_states(rpc): - states = rpc.state_list("", 0) - print("%-5s %s" % ("ID", "Name")) - print("%-5s %s" % ("--", "----")) - for state in states: - print("%-5d %s" % (state['id'], state['name'])) - -def action_info(rpc, patch_id): - patch = rpc.patch_get(patch_id) - s = "Information for patch id %d" % (patch_id) - print(s) - print('-' * len(s)) - for key, value in sorted(patch.iteritems()): - print("- %- 14s: %s" % (key, unicode(value).encode("utf-8"))) - -def action_get(rpc, patch_id): - patch = rpc.patch_get(patch_id) - s = rpc.patch_get_mbox(patch_id) - - if patch == {} or len(s) == 0: - sys.stderr.write("Unable to get patch %d\n" % patch_id) - sys.exit(1) - - base_fname = fname = os.path.basename(patch['filename']) - i = 0 - while os.path.exists(fname): - fname = "%s.%d" % (base_fname, i) - i += 1 - - try: - f = open(fname, "w") - except: - sys.stderr.write("Unable to open %s for writing\n" % fname) - sys.exit(1) - - try: - f.write(unicode(s).encode("utf-8")) - f.close() - print "Saved patch to %s" % fname - except: - sys.stderr.write("Failed to write to %s\n" % fname) - sys.exit(1) - -def action_apply(rpc, patch_id, apply_cmd=None): - patch = rpc.patch_get(patch_id) - if patch == {}: - sys.stderr.write("Error getting information on patch ID %d\n" % \ - patch_id) - sys.exit(1) - - if apply_cmd is None: - print "Applying patch #%d to current directory" % patch_id - apply_cmd = ['patch', '-p1'] - else: - print "Applying patch #%d using %s" % ( - patch_id, repr(' '.join(apply_cmd))) - - print "Description: %s" % patch['name'] - s = rpc.patch_get_mbox(patch_id) - if len(s) > 0: - proc = subprocess.Popen(apply_cmd, stdin = subprocess.PIPE) - proc.communicate(unicode(s).encode('utf-8')) - return proc.returncode - else: - sys.stderr.write("Error: No patch content found\n") - sys.exit(1) - -def action_update_patch(rpc, patch_id, state = None, archived = None, commit = None): - patch = rpc.patch_get(patch_id) - if patch == {}: - sys.stderr.write("Error getting information on patch ID %d\n" % \ - patch_id) - sys.exit(1) - - params = {} - - if state: - state_id = state_id_by_name(rpc, state) - if state_id == 0: - sys.stderr.write("Error: No State found matching %s*\n" % state) - sys.exit(1) - params['state'] = state_id - - if commit: - params['commit_ref'] = commit - - if archived: - params['archived'] = archived == 'yes' - - success = False - try: - success = rpc.patch_set(patch_id, params) - except xmlrpclib.Fault, f: - sys.stderr.write("Error updating patch: %s\n" % f.faultString) - - if not success: - sys.stderr.write("Patch not updated\n") - -def patch_id_from_hash(rpc, project, hash): - try: - patch = rpc.patch_get_by_project_hash(project, hash) - except xmlrpclib.Fault: - # the server may not have the newer patch_get_by_project_hash function, - # so fall back to hash-only. - patch = rpc.patch_get_by_hash(hash) - - if patch == {}: - sys.stderr.write("No patch has the hash provided\n") - sys.exit(1) - - patch_id = patch['id'] - # be super paranoid - try: - patch_id = int(patch_id) - except: - sys.stderr.write("Invalid patch ID obtained from server\n") - sys.exit(1) - return patch_id - -auth_actions = ['update'] - -# unfortunately we currently have to revert to this ugly hack.. -class _RecursiveHelpAction(argparse._HelpAction): - - def __call__(self, parser, namespace, values, option_string=None): - parser.print_help() - print - - subparsers_actions = [ - action for action in parser._actions - if isinstance(action, argparse._SubParsersAction) - ] - hash_n_id_actions = set(['hash', 'id', 'help']) - for subparsers_action in subparsers_actions: - for choice, subparser in subparsers_action.choices.items(): - # gross but the whole thing is.. - if (len(subparser._actions) == 3 \ - and set([a.dest for a in subparser._actions]) \ - == hash_n_id_actions) \ - or len(subparser._actions) == 0: - continue - print("command '{}'".format(choice)) - print(subparser.format_help()) - - parser.exit() - -def main(): - hash_parser = argparse.ArgumentParser(add_help=False, version=False) - hash_parser.add_argument( - '-h', metavar='HASH', dest='hash', action='store', - help='''Lookup by patch hash''' - ) - hash_parser.add_argument( - 'id', metavar='ID', nargs='*', action='store', type=int, - help='Patch ID', - ) - hash_parser.add_argument( - '-p', metavar='PROJECT', - help='''Lookup patch in project''' - ) - - filter_parser = argparse.ArgumentParser(add_help=False, version=False) - filter_parser.add_argument( - '-s', metavar='STATE', - help='''Filter by patch state (e.g., 'New', 'Accepted', etc.)''' - ) - filter_parser.add_argument( - '-a', choices=['yes','no'], - help='''Filter by patch archived state''' - ) - filter_parser.add_argument( - '-p', metavar='PROJECT', - help='''Filter by project name (see 'projects' for list)''' - ) - filter_parser.add_argument( - '-w', metavar='WHO', - help='''Filter by submitter (name, e-mail substring search)''' - ) - filter_parser.add_argument( - '-d', metavar='WHO', - help='''Filter by delegate (name, e-mail substring search)''' - ) - filter_parser.add_argument( - '-n', metavar='MAX#', - type=int, - help='''Restrict number of results''' - ) - filter_parser.add_argument( - '-m', metavar='MESSAGEID', - help='''Filter by Message-Id''' - ) - filter_parser.add_argument( - '-f', metavar='FORMAT', - help='''Print output in the given format. You can use tags matching ''' - '''fields, e.g. %%{id}, %%{state}, or %%{msgid}.''' - ) - filter_parser.add_argument( - 'patch_name', metavar='STR', nargs='?', - help='substring to search for patches by name', - ) - help_parser = argparse.ArgumentParser(add_help=False, version=False) - help_parser.add_argument( - '--help', action='help', help=argparse.SUPPRESS, - #help='''show this help message and exit''' - ) - - action_parser = argparse.ArgumentParser( - prog='pwclient', - add_help=False, - version=False, - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog='''(apply | get | info | view | update) (-h HASH | ID [ID ...])''', - ) - action_parser.add_argument( - '--help', - #action='help', - action=_RecursiveHelpAction, - help='''Print this help text''' - ) - - subparsers = action_parser.add_subparsers( - title='Commands', - metavar='' - ) - apply_parser = subparsers.add_parser( - 'apply', parents=[hash_parser, help_parser], - add_help=False, - help='''Apply a patch (in the current dir, using -p1)''' - ) - apply_parser.set_defaults(subcmd='apply') - git_am_parser = subparsers.add_parser( - 'git-am', parents=[hash_parser, help_parser], - add_help=False, - help='''Apply a patch to current git branch using "git am".''' - ) - git_am_parser.set_defaults(subcmd='git_am') - git_am_parser.add_argument( - '-s', '--signoff', - action='store_true', - help='''pass --signoff to git-am''' - ) - get_parser = subparsers.add_parser( - 'get', parents=[hash_parser, help_parser], - add_help=False, - help='''Download a patch and save it locally''' - ) - get_parser.set_defaults(subcmd='get') - info_parser = subparsers.add_parser( - 'info', parents=[hash_parser, help_parser], - add_help=False, - help='''Display patchwork info about a given patch ID''' - ) - info_parser.set_defaults(subcmd='info') - projects_parser = subparsers.add_parser( - 'projects', - add_help=False, - help='''List all projects''' - ) - projects_parser.set_defaults(subcmd='projects') - states_parser = subparsers.add_parser( - 'states', - add_help=False, - help='''Show list of potential patch states''' - ) - states_parser.set_defaults(subcmd='states') - view_parser = subparsers.add_parser( - 'view', parents=[hash_parser, help_parser], - add_help=False, - help='''View a patch''' - ) - view_parser.set_defaults(subcmd='view') - update_parser = subparsers.add_parser( - 'update', parents=[hash_parser, help_parser], - add_help=False, - help='''Update patch''', - epilog='''Using a COMMIT-REF allows for only one ID to be specified''', - ) - update_parser.add_argument( - '-c', metavar='COMMIT-REF', - help='''commit reference hash''' - ) - update_parser.add_argument( - '-s', metavar='STATE', - required=True, - help='''Set patch state (e.g., 'Accepted', 'Superseded' etc.)''' - ) - update_parser.add_argument( - '-a', choices=['yes', 'no'], - help='''Set patch archived state''' - ) - update_parser.set_defaults(subcmd='update') - list_parser = subparsers.add_parser("list", - add_help=False, - #aliases=['search'], - parents=[filter_parser, help_parser], - help='''List patches, using the optional filters specified - below and an optional substring to search for patches - by name''' - ) - list_parser.set_defaults(subcmd='list') - search_parser = subparsers.add_parser("search", - add_help=False, - parents=[filter_parser, help_parser], - help='''Alias for "list"''' - ) - # Poor man's argparse aliases: - # We register the "search" parser but effectively use "list" for the - # help-text. - search_parser.set_defaults(subcmd='list') - if len(sys.argv) < 2: - action_parser.print_help() - sys.exit(0) - - args = action_parser.parse_args() - args = dict(vars(args)) - action = args.get('subcmd') - - if args.get('hash') and len(args.get('id')): - # mimic mutual exclusive group - sys.stderr.write("Error: [-h HASH] and [ID [ID ...]] " + - "are mutually exlusive\n") - locals()[action + '_parser'].print_help() - sys.exit(1) - - # set defaults - filt = Filter() - commit_str = None - url = DEFAULT_URL - - archived_str = args.get('a') - state_str = args.get('s') - project_str = args.get('p') - submitter_str = args.get('w') - delegate_str = args.get('d') - format_str = args.get('f') - hash_str = args.get('hash') - patch_ids = args.get('id') - msgid_str = args.get('m') - if args.get('c'): - # update multiple IDs with a single commit-hash does not make sense - if action == 'update' and patch_ids and len(patch_ids) > 1: - sys.stderr.write( - "Declining update with COMMIT-REF on multiple IDs\n" - ) - update_parser.print_help() - sys.exit(1) - commit_str = args.get('c') - - if args.get('n') != None: - try: - filt.add("max_count", args.get('n')) - except: - sys.stderr.write("Invalid maximum count '%s'\n" % args.get('n')) - action_parser.print_help() - sys.exit(1) - - do_signoff = args.get('signoff') - - # grab settings from config files - config = ConfigParser.ConfigParser() - config.read([CONFIG_FILE]) - - if not config.has_section('options'): - sys.stderr.write('~/.pwclientrc is in the old format. Migrating it...') - - old_project = config.get('base','project') - - new_config = ConfigParser.ConfigParser() - new_config.add_section('options') - - new_config.set('options','default',old_project) - new_config.add_section(old_project) - - new_config.set(old_project,'url',config.get('base','url')) - if config.has_option('auth', 'username'): - new_config.set(old_project,'username',config.get('auth','username')) - if config.has_option('auth', 'password'): - new_config.set(old_project,'password',config.get('auth','password')) - - old_config_file = CONFIG_FILE + '.orig' - shutil.copy2(CONFIG_FILE,old_config_file) - - with open(CONFIG_FILE, 'wb') as fd: - new_config.write(fd) - - sys.stderr.write(' Done.\n') - sys.stderr.write('Your old ~/.pwclientrc was saved to %s\n' % old_config_file) - sys.stderr.write('and was converted to the new format. You may want to\n') - sys.stderr.write('inspect it before continuing.\n') - sys.exit(1) - - if not project_str: - try: - project_str = config.get('options', 'default') - except: - sys.stderr.write("No default project configured in ~/.pwclientrc\n") - action_parser.print_help() - sys.exit(1) - - if not config.has_section(project_str): - sys.stderr.write("No section for project %s\n" % project_str) - sys.exit(1) - if not config.has_option(project_str, 'url'): - sys.stderr.write("No URL for project %s\n" % project_str) - sys.exit(1) - if not do_signoff and config.has_option('options', 'signoff'): - do_signoff = config.getboolean('options', 'signoff') - if not do_signoff and config.has_option(project_str, 'signoff'): - do_signoff = config.getboolean(project_str, 'signoff') - - url = config.get(project_str, 'url') - - transport = None - if action in auth_actions: - if config.has_option(project_str, 'username') and \ - config.has_option(project_str, 'password'): - - use_https = url.startswith('https') - - transport = BasicHTTPAuthTransport( \ - config.get(project_str, 'username'), - config.get(project_str, 'password'), - use_https) - - else: - sys.stderr.write(("The %s action requires authentication, " - "but no username or password\nis configured\n") % action) - sys.exit(1) - - if project_str: - filt.add("project", project_str) - - if state_str: - filt.add("state", state_str) - - if archived_str: - filt.add("archived", archived_str == 'yes') - - if msgid_str: - filt.add("msgid", msgid_str) - - try: - rpc = xmlrpclib.Server(url, transport = transport) - except: - sys.stderr.write("Unable to connect to %s\n" % url) - sys.exit(1) - - # It should be safe to assume hash_str is not zero, but who knows.. - if hash_str != None: - patch_ids = [patch_id_from_hash(rpc, project_str, hash_str)] - - # helper for non_empty() to print correct helptext - h = locals()[action + '_parser'] - - # Require either hash_str or IDs for - def non_empty(h, patch_ids): - """Error out if no patch IDs were specified""" - if patch_ids == None or len(patch_ids) < 1: - sys.stderr.write("Error: Missing Argument! " + - "Either [-h HASH] or [ID [ID ...]] are required\n") - if h: - h.print_help() - sys.exit(1) - return patch_ids - - if action == 'list' or action == 'search': - if args.get('patch_name') != None: - filt.add("name__icontains", args.get('patch_name')) - action_list(rpc, filt, submitter_str, delegate_str, format_str) - - elif action.startswith('project'): - action_projects(rpc) - - elif action.startswith('state'): - action_states(rpc) - - elif action == 'view': - pager = os.environ.get('PAGER') - if pager: - pager = subprocess.Popen( - pager.split(), stdin=subprocess.PIPE - ) - if pager: - i = list() - for patch_id in non_empty(h, patch_ids): - s = rpc.patch_get_mbox(patch_id) - if len(s) > 0: - i.append(unicode(s).encode("utf-8")) - if len(i) > 0: - pager.communicate(input="\n".join(i)) - pager.stdin.close() - else: - for patch_id in non_empty(h, patch_ids): - s = rpc.patch_get_mbox(patch_id) - if len(s) > 0: - print unicode(s).encode("utf-8") - - elif action == 'info': - for patch_id in non_empty(h, patch_ids): - action_info(rpc, patch_id) - - elif action == 'get': - for patch_id in non_empty(h, patch_ids): - action_get(rpc, patch_id) - - elif action == 'apply': - for patch_id in non_empty(h, patch_ids): - ret = action_apply(rpc, patch_id) - if ret: - sys.stderr.write("Apply failed with exit status %d\n" % ret) - sys.exit(1) - - elif action == 'git_am': - cmd = ['git', 'am'] - if do_signoff: - cmd.append('-s') - for patch_id in non_empty(h, patch_ids): - ret = action_apply(rpc, patch_id, cmd) - if ret: - sys.stderr.write("'git am' failed with exit status %d\n" % ret) - sys.exit(1) - - elif action == 'update': - for patch_id in non_empty(h, patch_ids): - action_update_patch(rpc, patch_id, state = state_str, - archived = archived_str, commit = commit_str - ) - - else: - sys.stderr.write("Unknown action '%s'\n" % action) - action_parser.print_help() - sys.exit(1) - -if __name__ == "__main__": - main() diff --git a/apps/patchwork/bin/rehash.py b/apps/patchwork/bin/rehash.py deleted file mode 100755 index c44e49b..0000000 --- a/apps/patchwork/bin/rehash.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -# -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from patchwork.models import Patch -import sys - -if __name__ == '__main__': - if len(sys.argv) > 1: - patches = Patch.objects.filter(id__in = sys.argv[1:]) - else: - patches = Patch.objects.all() - - for patch in patches: - print patch.id, patch.name - patch.hash = None - patch.save() diff --git a/apps/patchwork/bin/update-patchwork-status.py b/apps/patchwork/bin/update-patchwork-status.py deleted file mode 100755 index 2da5d23..0000000 --- a/apps/patchwork/bin/update-patchwork-status.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python -# -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -import sys -import subprocess -from optparse import OptionParser - -def commits(options, revlist): - cmd = ['git', 'rev-list', revlist] - proc = subprocess.Popen(cmd, stdout = subprocess.PIPE, cwd = options.repodir) - - revs = [] - - for line in proc.stdout.readlines(): - revs.append(line.strip()) - - return revs - -def commit(options, rev): - cmd = ['git', 'diff', '%(rev)s^..%(rev)s' % {'rev': rev}] - proc = subprocess.Popen(cmd, stdout = subprocess.PIPE, cwd = options.repodir) - - buf = proc.communicate()[0] - - return buf - - -def main(args): - parser = OptionParser(usage = '%prog [options] revspec') - parser.add_option("-p", "--project", dest = "project", action = 'store', - help="use project PROJECT", metavar="PROJECT") - parser.add_option("-d", "--dir", dest = "repodir", action = 'store', - help="use git repo in DIR", metavar="DIR") - - (options, args) = parser.parse_args(args[1:]) - - if len(args) != 1: - parser.error("incorrect number of arguments") - - revspec = args[0] - revs = commits(options, revspec) - - for rev in revs: - print rev - print commit(options, rev) - - -if __name__ == '__main__': - sys.exit(main(sys.argv)) - - diff --git a/apps/patchwork/context_processors.py b/apps/patchwork/context_processors.py deleted file mode 100644 index f4ab5a9..0000000 --- a/apps/patchwork/context_processors.py +++ /dev/null @@ -1,32 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -from patchwork.models import Bundle -from patchwork.utils import order_map, get_order - -def bundle(request): - user = request.user - if not user.is_authenticated(): - return {} - return {'bundles': Bundle.objects.filter(owner = user)} - - -def patchlists(request): - diff --git a/apps/patchwork/filters.py b/apps/patchwork/filters.py deleted file mode 100644 index 8c9690e..0000000 --- a/apps/patchwork/filters.py +++ /dev/null @@ -1,471 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -from patchwork.models import Person, State -from django.utils.safestring import mark_safe -from django.utils.html import escape -from django.contrib.auth.models import User -from urllib import quote - -class Filter(object): - def __init__(self, filters): - self.filters = filters - self.applied = False - self.forced = False - - def name(self): - """The 'name' of the filter, to be displayed in the filter UI""" - return self.name - - def condition(self): - """The current condition of the filter, to be displayed in the - filter UI""" - return self.key - - def key(self): - """The key for this filter, to appear in the querystring. A key of - None will remove the param=ley pair from the querystring.""" - return None - - def set_status(self, *kwargs): - """Views can call this to force a specific filter status. For example, - a user's todo page needs to setup the delegate filter to show - that user's delegated patches""" - pass - - def parse(self, dict): - if self.param not in dict.keys(): - return - self._set_key(dict[self.param]) - - def url_without_me(self): - return self.filters.querystring_without_filter(self) - - def form_function(self): - return 'function(form) { return "unimplemented" }' - - def form(self): - if self.forced: - return mark_safe('%s' % (self.param, - self.condition())) - return self.condition() - return self._form() - - def kwargs(self): - return {} - - def __str__(self): - return '%s: %s' % (self.name, self.kwargs()) - - -class SubmitterFilter(Filter): - param = 'submitter' - def __init__(self, filters): - super(SubmitterFilter, self).__init__(filters) - self.name = 'Submitter' - self.person = None - self.person_match = None - - def _set_key(self, str): - self.person = None - self.person_match = None - submitter_id = None - try: - submitter_id = int(str) - except ValueError: - pass - except: - return - - if submitter_id: - self.person = Person.objects.get(id = int(str)) - self.applied = True - return - - - people = Person.objects.filter(name__icontains = str) - - if not people: - return - - self.person_match = str - self.applied = True - - def kwargs(self): - if self.person: - user = self.person.user - if user: - return {'submitter__in': - Person.objects.filter(user = user).values('pk').query} - return {'submitter': self.person} - - if self.person_match: - return {'submitter__name__icontains': self.person_match} - return {} - - def condition(self): - if self.person: - return self.person.name - elif self.person_match: - return self.person_match - return '' - - def _form(self): - name = '' - if self.person: - name = self.person.name - return mark_safe((' ' % escape(name)) + - '') - - def key(self): - if self.person: - return self.person.id - return self.person_match - -class StateFilter(Filter): - param = 'state' - any_key = '*' - action_req_str = 'Action Required' - - def __init__(self, filters): - super(StateFilter, self).__init__(filters) - self.name = 'State' - self.state = None - self.applied = True - - def _set_key(self, str): - self.state = None - - if str == self.any_key: - self.applied = False - return - - try: - self.state = State.objects.get(id=int(str)) - except: - return - - self.applied = True - - def kwargs(self): - if self.state is not None: - return {'state': self.state} - else: - return {'state__in': \ - State.objects.filter(action_required = True) \ - .values('pk').query} - - def condition(self): - if self.state: - return self.state.name - return self.action_req_str - - def key(self): - if self.state is not None: - return self.state.id - if not self.applied: - return '*' - return None - - def _form(self): - str = '' - return mark_safe(str); - - def form_function(self): - return 'function(form) { return form.x.value }' - - def url_without_me(self): - qs = self.filters.querystring_without_filter(self) - if qs != '?': - qs += '&' - return qs + '%s=%s' % (self.param, self.any_key) - -class SearchFilter(Filter): - param = 'q' - def __init__(self, filters): - super(SearchFilter, self).__init__(filters) - self.name = 'Search' - self.param = 'q' - self.search = None - - def _set_key(self, str): - str = str.strip() - if str == '': - return - self.search = str - self.applied = True - - def kwargs(self): - return {'name__icontains': self.search} - - def condition(self): - return self.search - - def key(self): - return self.search - - def _form(self): - value = '' - if self.search: - value = escape(self.search) - return mark_safe('' %\ - (self.param, value)) - - def form_function(self): - return mark_safe('function(form) { return form.x.value }') - -class ArchiveFilter(Filter): - param = 'archive' - def __init__(self, filters): - super(ArchiveFilter, self).__init__(filters) - self.name = 'Archived' - self.archive_state = False - self.applied = True - self.param_map = { - True: 'true', - False: '', - None: 'both' - } - self.description_map = { - True: 'Yes', - False: 'No', - None: 'Both' - } - - def _set_key(self, str): - self.archive_state = False - self.applied = True - for (k, v) in self.param_map.iteritems(): - if str == v: - self.archive_state = k - if self.archive_state == None: - self.applied = False - - def kwargs(self): - if self.archive_state == None: - return {} - return {'archived': self.archive_state} - - def condition(self): - return self.description_map[self.archive_state] - - def key(self): - if self.archive_state == False: - return None - return self.param_map[self.archive_state] - - def _form(self): - s = '' - for b in [False, True, None]: - label = self.description_map[b] - selected = '' - if self.archive_state == b: - selected = 'checked="true"' - s += ('%(label)s' + \ - '    ') % \ - {'label': label, - 'param': self.param, - 'selected': selected, - 'value': self.param_map[b] - } - return mark_safe(s) - - def url_without_me(self): - qs = self.filters.querystring_without_filter(self) - if qs != '?': - qs += '&' - return qs + 'archive=both' - - -class DelegateFilter(Filter): - param = 'delegate' - no_delegate_key = '-' - no_delegate_str = 'Nobody' - AnyDelegate = 1 - - def __init__(self, filters): - super(DelegateFilter, self).__init__(filters) - self.name = 'Delegate' - self.param = 'delegate' - self.delegate = None - - def _set_key(self, str): - if str == self.no_delegate_key: - self.applied = True - self.delegate = None - return - - applied = False - try: - self.delegate = User.objects.get(id = str) - self.applied = True - except: - pass - - def kwargs(self): - if not self.applied: - return {} - return {'delegate': self.delegate} - - def condition(self): - if self.delegate: - return self.delegate.profile.name() - return self.no_delegate_str - - def _form(self): - delegates = User.objects.filter(profile__maintainer_projects = - self.filters.project) - - str = '' - - return mark_safe(str) - - def key(self): - if self.delegate: - return self.delegate.id - if self.applied: - return self.no_delegate_key - return None - - def set_status(self, *args, **kwargs): - if 'delegate' in kwargs: - self.applied = self.forced = True - self.delegate = kwargs['delegate'] - if self.AnyDelegate in args: - self.applied = False - self.forced = True - -filterclasses = [SubmitterFilter, \ - StateFilter, - SearchFilter, - ArchiveFilter, - DelegateFilter] - -class Filters: - - def __init__(self, request): - self._filters = map(lambda c: c(self), filterclasses) - self.dict = request.GET - self.project = None - - for f in self._filters: - f.parse(self.dict) - - def set_project(self, project): - self.project = project - - def filter_conditions(self): - kwargs = {} - for f in self._filters: - if f.applied: - kwargs.update(f.kwargs()) - return kwargs - - def apply(self, queryset): - kwargs = self.filter_conditions() - if not kwargs: - return queryset - return queryset.filter(**kwargs) - - def params(self): - return [ (f.param, f.key()) for f in self._filters \ - if f.key() is not None ] - - def querystring(self, remove = None): - params = dict(self.params()) - - for (k, v) in self.dict.iteritems(): - if k not in params: - params[k] = v - - if remove is not None: - if remove.param in params.keys(): - del params[remove.param] - - pairs = params.iteritems() - - def sanitise(s): - if not isinstance(s, basestring): - s = unicode(s) - return quote(s.encode('utf-8')) - - return '?' + '&'.join(['%s=%s' % (sanitise(k), sanitise(v)) - for (k, v) in pairs]) - - def querystring_without_filter(self, filter): - return self.querystring(filter) - - def applied_filters(self): - return filter(lambda x: x.applied, self._filters) - - def available_filters(self): - return self._filters - - def set_status(self, filterclass, *args, **kwargs): - for f in self._filters: - if isinstance(f, filterclass): - f.set_status(*args, **kwargs) - return diff --git a/apps/patchwork/fixtures/default_projects.xml b/apps/patchwork/fixtures/default_projects.xml deleted file mode 100644 index c67fa56..0000000 --- a/apps/patchwork/fixtures/default_projects.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - cbe-oss-dev - Cell Broadband Engine development - cbe-oss-dev.ozlabs.org - cbe-oss-dev@ozlabs.org - - - linuxppc-dev - Linux PPC development - linuxppc-dev.ozlabs.org - linuxppc-dev@ozlabs.org - - - diff --git a/apps/patchwork/fixtures/initial_data.xml b/apps/patchwork/fixtures/initial_data.xml deleted file mode 100644 index 86e1105..0000000 --- a/apps/patchwork/fixtures/initial_data.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - New - 0 - True - - - Under Review - 1 - True - - - Accepted - 2 - False - - - Rejected - 3 - False - - - RFC - 4 - False - - - Not Applicable - 5 - False - - - Changes Requested - 6 - False - - - Awaiting Upstream - 7 - False - - - Superseded - 8 - False - - - Deferred - 9 - False - - diff --git a/apps/patchwork/forms.py b/apps/patchwork/forms.py deleted file mode 100644 index 0327958..0000000 --- a/apps/patchwork/forms.py +++ /dev/null @@ -1,237 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -from django.contrib.auth.models import User -from django import forms - -from patchwork.models import Patch, State, Bundle, UserProfile - -class RegistrationForm(forms.Form): - first_name = forms.CharField(max_length = 30, required = False) - last_name = forms.CharField(max_length = 30, required = False) - username = forms.RegexField(regex = r'^\w+$', max_length=30, - label=u'Username') - email = forms.EmailField(max_length=100, label=u'Email address') - password = forms.CharField(widget=forms.PasswordInput(), - label='Password') - - def clean_username(self): - value = self.cleaned_data['username'] - try: - user = User.objects.get(username__iexact = value) - except User.DoesNotExist: - return self.cleaned_data['username'] - raise forms.ValidationError('This username is already taken. ' + \ - 'Please choose another.') - - def clean_email(self): - value = self.cleaned_data['email'] - try: - user = User.objects.get(email__iexact = value) - except User.DoesNotExist: - return self.cleaned_data['email'] - raise forms.ValidationError('This email address is already in use ' + \ - 'for the account "%s".\n' % user.username) - - def clean(self): - return self.cleaned_data - -class LoginForm(forms.Form): - username = forms.CharField(max_length = 30) - password = forms.CharField(widget = forms.PasswordInput) - -class BundleForm(forms.ModelForm): - name = forms.RegexField(regex = r'^[^/]+$', max_length=50, label=u'Name', - error_messages = {'invalid': 'Bundle names can\'t contain slashes'}) - - class Meta: - model = Bundle - fields = ['name', 'public'] - -class CreateBundleForm(BundleForm): - def __init__(self, *args, **kwargs): - super(CreateBundleForm, self).__init__(*args, **kwargs) - - class Meta: - model = Bundle - fields = ['name'] - - def clean_name(self): - name = self.cleaned_data['name'] - count = Bundle.objects.filter(owner = self.instance.owner, \ - name = name).count() - if count > 0: - raise forms.ValidationError('A bundle called %s already exists' \ - % name) - return name - -class DeleteBundleForm(forms.Form): - name = 'deletebundleform' - form_name = forms.CharField(initial = name, widget = forms.HiddenInput) - bundle_id = forms.IntegerField(widget = forms.HiddenInput) - -class DelegateField(forms.ModelChoiceField): - def __init__(self, project, *args, **kwargs): - queryset = User.objects.filter(profile__in = \ - UserProfile.objects \ - .filter(maintainer_projects = project) \ - .values('pk').query) - super(DelegateField, self).__init__(queryset, *args, **kwargs) - - -class PatchForm(forms.ModelForm): - def __init__(self, instance = None, project = None, *args, **kwargs): - if (not project) and instance: - project = instance.project - if not project: - raise Exception("meep") - super(PatchForm, self).__init__(instance = instance, *args, **kwargs) - self.fields['delegate'] = DelegateField(project, required = False) - - class Meta: - model = Patch - fields = ['state', 'archived', 'delegate'] - -class UserProfileForm(forms.ModelForm): - class Meta: - model = UserProfile - fields = ['primary_project', 'patches_per_page'] - -class OptionalDelegateField(DelegateField): - no_change_choice = ('*', 'no change') - to_field_name = None - - def __init__(self, no_change_choice = None, *args, **kwargs): - self.filter = None - if (no_change_choice): - self.no_change_choice = no_change_choice - super(OptionalDelegateField, self). \ - __init__(initial = self.no_change_choice[0], *args, **kwargs) - - def _get_choices(self): - choices = list( - super(OptionalDelegateField, self)._get_choices()) - choices.append(self.no_change_choice) - return choices - - choices = property(_get_choices, forms.ChoiceField._set_choices) - - def is_no_change(self, value): - return value == self.no_change_choice[0] - - def clean(self, value): - if value == self.no_change_choice[0]: - return value - return super(OptionalDelegateField, self).clean(value) - -class OptionalModelChoiceField(forms.ModelChoiceField): - no_change_choice = ('*', 'no change') - to_field_name = None - - def __init__(self, no_change_choice = None, *args, **kwargs): - self.filter = None - if (no_change_choice): - self.no_change_choice = no_change_choice - super(OptionalModelChoiceField, self). \ - __init__(initial = self.no_change_choice[0], *args, **kwargs) - - def _get_choices(self): - choices = list( - super(OptionalModelChoiceField, self)._get_choices()) - choices.append(self.no_change_choice) - return choices - - choices = property(_get_choices, forms.ChoiceField._set_choices) - - def is_no_change(self, value): - return value == self.no_change_choice[0] - - def clean(self, value): - if value == self.no_change_choice[0]: - return value - return super(OptionalModelChoiceField, self).clean(value) - -class MultipleBooleanField(forms.ChoiceField): - no_change_choice = ('*', 'no change') - def __init__(self, *args, **kwargs): - super(MultipleBooleanField, self).__init__(*args, **kwargs) - self.choices = [self.no_change_choice] + \ - [(True, 'Archived'), (False, 'Unarchived')] - - def is_no_change(self, value): - return value == self.no_change_choice[0] - - # TODO: Check whether it'd be worth to use a TypedChoiceField here; I - # think that'd allow us to get rid of the custom valid_value() and - # to_python() methods. - def valid_value(self, value): - if value in [v1 for (v1, v2) in self.choices]: - return True - return False - - def to_python(self, value): - if value is None or self.is_no_change(value): - return self.no_change_choice[0] - elif value == 'True': - return True - elif value == 'False': - return False - else: - raise ValueError('Unknown value: %s' % value) - -class MultiplePatchForm(forms.Form): - action = 'update' - state = OptionalModelChoiceField(queryset = State.objects.all()) - archived = MultipleBooleanField() - - def __init__(self, project, *args, **kwargs): - super(MultiplePatchForm, self).__init__(*args, **kwargs) - self.fields['delegate'] = OptionalDelegateField(project = project, - required = False) - - def save(self, instance, commit = True): - opts = instance.__class__._meta - if self.errors: - raise ValueError("The %s could not be changed because the data " - "didn't validate." % opts.object_name) - data = self.cleaned_data - # Update the instance - for f in opts.fields: - if not f.name in data: - continue - - field = self.fields.get(f.name, None) - if not field: - continue - - if field.is_no_change(data[f.name]): - continue - - setattr(instance, f.name, data[f.name]) - - if commit: - instance.save() - return instance - -class EmailForm(forms.Form): - email = forms.EmailField(max_length = 200) - -UserPersonLinkForm = EmailForm -OptinoutRequestForm = EmailForm diff --git a/apps/patchwork/models.py b/apps/patchwork/models.py deleted file mode 100644 index 54b8656..0000000 --- a/apps/patchwork/models.py +++ /dev/null @@ -1,386 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from django.db import models -from django.contrib.auth.models import User -from django.core.urlresolvers import reverse -from django.contrib.sites.models import Site -from django.conf import settings -from patchwork.parser import hash_patch - -import re -import datetime, time -import random - -class Person(models.Model): - email = models.CharField(max_length=255, unique = True) - name = models.CharField(max_length=255, null = True, blank = True) - user = models.ForeignKey(User, null = True, blank = True, - on_delete = models.SET_NULL) - - def __unicode__(self): - if self.name: - return u'%s <%s>' % (self.name, self.email) - else: - return self.email - - def link_to_user(self, user): - self.name = user.profile.name() - self.user = user - - class Meta: - verbose_name_plural = 'People' - -class Project(models.Model): - linkname = models.CharField(max_length=255, unique=True) - name = models.CharField(max_length=255, unique=True) - listid = models.CharField(max_length=255, unique=True) - listemail = models.CharField(max_length=200) - web_url = models.CharField(max_length=2000, blank=True) - scm_url = models.CharField(max_length=2000, blank=True) - webscm_url = models.CharField(max_length=2000, blank=True) - send_notifications = models.BooleanField(default=False) - - def __unicode__(self): - return self.name - - def is_editable(self, user): - if not user.is_authenticated(): - return False - return self in user.profile.maintainer_projects.all() - - class Meta: - ordering = ['linkname'] - - -class UserProfile(models.Model): - user = models.OneToOneField(User, unique = True, related_name='profile') - primary_project = models.ForeignKey(Project, null = True, blank = True) - maintainer_projects = models.ManyToManyField(Project, - related_name = 'maintainer_project') - send_email = models.BooleanField(default = False, - help_text = 'Selecting this option allows patchwork to send ' + - 'email on your behalf') - patches_per_page = models.PositiveIntegerField(default = 100, - null = False, blank = False, - help_text = 'Number of patches to display per page') - - def name(self): - if self.user.first_name or self.user.last_name: - names = filter(bool, [self.user.first_name, self.user.last_name]) - return u' '.join(names) - return self.user.username - - def contributor_projects(self): - submitters = Person.objects.filter(user = self.user) - return Project.objects.filter(id__in = - Patch.objects.filter( - submitter__in = submitters) - .values('project_id').query) - - def sync_person(self): - pass - - def n_todo_patches(self): - return self.todo_patches().count() - - def todo_patches(self, project = None): - - # filter on project, if necessary - if project: - qs = Patch.objects.filter(project = project) - else: - qs = Patch.objects - - qs = qs.filter(archived = False) \ - .filter(delegate = self.user) \ - .filter(state__in = - State.objects.filter(action_required = True) - .values('pk').query) - return qs - - def __unicode__(self): - return self.name() - -def _user_saved_callback(sender, created, instance, **kwargs): - try: - profile = instance.profile - except UserProfile.DoesNotExist: - profile = UserProfile(user = instance) - profile.save() - -models.signals.post_save.connect(_user_saved_callback, sender = User) - -class State(models.Model): - name = models.CharField(max_length = 100) - ordering = models.IntegerField(unique = True) - action_required = models.BooleanField(default = True) - - def __unicode__(self): - return self.name - - class Meta: - ordering = ['ordering'] - -class HashField(models.CharField): - __metaclass__ = models.SubfieldBase - - def __init__(self, algorithm = 'sha1', *args, **kwargs): - self.algorithm = algorithm - try: - import hashlib - def _construct(string = ''): - return hashlib.new(self.algorithm, string) - self.construct = _construct - self.n_bytes = len(hashlib.new(self.algorithm).hexdigest()) - except ImportError: - modules = { 'sha1': 'sha', 'md5': 'md5'} - - if algorithm not in modules.keys(): - raise NameError("Unknown algorithm '%s'" % algorithm) - - self.construct = __import__(modules[algorithm]).new - - self.n_bytes = len(self.construct().hexdigest()) - - kwargs['max_length'] = self.n_bytes - super(HashField, self).__init__(*args, **kwargs) - - def db_type(self, connection=None): - return 'char(%d)' % self.n_bytes - -def get_default_initial_patch_state(): - return State.objects.get(ordering=0) - -class Patch(models.Model): - project = models.ForeignKey(Project) - msgid = models.CharField(max_length=255) - name = models.CharField(max_length=255) - date = models.DateTimeField(default=datetime.datetime.now) - submitter = models.ForeignKey(Person) - delegate = models.ForeignKey(User, blank = True, null = True) - state = models.ForeignKey(State, default=get_default_initial_patch_state) - archived = models.BooleanField(default = False) - headers = models.TextField(blank = True) - content = models.TextField(null = True, blank = True) - pull_url = models.CharField(max_length=255, null = True, blank = True) - commit_ref = models.CharField(max_length=255, null = True, blank = True) - hash = HashField(null = True, blank = True) - - def __unicode__(self): - return self.name - - def comments(self): - return Comment.objects.filter(patch = self) - - def save(self): - try: - s = self.state - except: - self.state = State.objects.get(ordering = 0) - - if self.hash is None and self.content is not None: - self.hash = hash_patch(self.content).hexdigest() - - super(Patch, self).save() - - def is_editable(self, user): - if not user.is_authenticated(): - return False - - if self.submitter.user == user or self.delegate == user: - return True - - return self.project.is_editable(user) - - def filename(self): - fname_re = re.compile('[^-_A-Za-z0-9\.]+') - str = fname_re.sub('-', self.name) - return str.strip('-') + '.patch' - - @models.permalink - def get_absolute_url(self): - return ('patchwork.views.patch.patch', (), {'patch_id': self.id}) - - class Meta: - verbose_name_plural = 'Patches' - ordering = ['date'] - unique_together = [('msgid', 'project')] - -class Comment(models.Model): - patch = models.ForeignKey(Patch) - msgid = models.CharField(max_length=255) - submitter = models.ForeignKey(Person) - date = models.DateTimeField(default = datetime.datetime.now) - headers = models.TextField(blank = True) - content = models.TextField() - - response_re = re.compile( \ - '^(Tested|Reviewed|Acked|Signed-off|Nacked|Reported)-by: .*$', - re.M | re.I) - - def patch_responses(self): - return ''.join([ match.group(0) + '\n' for match in - self.response_re.finditer(self.content)]) - - class Meta: - ordering = ['date'] - unique_together = [('msgid', 'patch')] - -class Bundle(models.Model): - owner = models.ForeignKey(User) - project = models.ForeignKey(Project) - name = models.CharField(max_length = 50, null = False, blank = False) - patches = models.ManyToManyField(Patch, through = 'BundlePatch') - public = models.BooleanField(default = False) - - def n_patches(self): - return self.patches.all().count() - - def ordered_patches(self): - return self.patches.order_by('bundlepatch__order') - - def append_patch(self, patch): - # todo: use the aggregate queries in django 1.1 - orders = BundlePatch.objects.filter(bundle = self).order_by('-order') \ - .values('order') - - if len(orders) > 0: - max_order = orders[0]['order'] - else: - max_order = 0 - - # see if the patch is already in this bundle - if BundlePatch.objects.filter(bundle = self, patch = patch).count(): - raise Exception("patch is already in bundle") - - bp = BundlePatch.objects.create(bundle = self, patch = patch, - order = max_order + 1) - bp.save() - - class Meta: - unique_together = [('owner', 'name')] - - def public_url(self): - if not self.public: - return None - site = Site.objects.get_current() - return 'http://%s%s' % (site.domain, - reverse('patchwork.views.bundle.bundle', - kwargs = { - 'username': self.owner.username, - 'bundlename': self.name - })) - - @models.permalink - def get_absolute_url(self): - return ('patchwork.views.bundle.bundle', (), { - 'username': self.owner.username, - 'bundlename': self.name, - }) - -class BundlePatch(models.Model): - patch = models.ForeignKey(Patch) - bundle = models.ForeignKey(Bundle) - order = models.IntegerField() - - class Meta: - unique_together = [('bundle', 'patch')] - ordering = ['order'] - -class EmailConfirmation(models.Model): - validity = datetime.timedelta(days = settings.CONFIRMATION_VALIDITY_DAYS) - type = models.CharField(max_length = 20, choices = [ - ('userperson', 'User-Person association'), - ('registration', 'Registration'), - ('optout', 'Email opt-out'), - ]) - email = models.CharField(max_length = 200) - user = models.ForeignKey(User, null = True) - key = HashField() - date = models.DateTimeField(default = datetime.datetime.now) - active = models.BooleanField(default = True) - - def deactivate(self): - self.active = False - self.save() - - def is_valid(self): - return self.date + self.validity > datetime.datetime.now() - - def save(self): - max = 1 << 32 - if self.key == '': - str = '%s%s%d' % (self.user, self.email, random.randint(0, max)) - self.key = self._meta.get_field('key').construct(str).hexdigest() - super(EmailConfirmation, self).save() - -class EmailOptout(models.Model): - email = models.CharField(max_length = 200, primary_key = True) - - def __unicode__(self): - return self.email - - @classmethod - def is_optout(cls, email): - email = email.lower().strip() - return cls.objects.filter(email = email).count() > 0 - -class PatchChangeNotification(models.Model): - patch = models.ForeignKey(Patch, primary_key = True) - last_modified = models.DateTimeField(default = datetime.datetime.now) - orig_state = models.ForeignKey(State) - -def _patch_change_callback(sender, instance, **kwargs): - # we only want notification of modified patches - if instance.pk is None: - return - - if instance.project is None or not instance.project.send_notifications: - return - - try: - orig_patch = Patch.objects.get(pk = instance.pk) - except Patch.DoesNotExist: - return - - # If there's no interesting changes, abort without creating the - # notification - if orig_patch.state == instance.state: - return - - notification = None - try: - notification = PatchChangeNotification.objects.get(patch = instance) - except PatchChangeNotification.DoesNotExist: - pass - - if notification is None: - notification = PatchChangeNotification(patch = instance, - orig_state = orig_patch.state) - - elif notification.orig_state == instance.state: - # If we're back at the original state, there is no need to notify - notification.delete() - return - - notification.last_modified = datetime.datetime.now() - notification.save() - -models.signals.pre_save.connect(_patch_change_callback, sender = Patch) diff --git a/apps/patchwork/paginator.py b/apps/patchwork/paginator.py deleted file mode 100644 index 31c0190..0000000 --- a/apps/patchwork/paginator.py +++ /dev/null @@ -1,88 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -from django.core import paginator -from django.conf import settings - -DEFAULT_PATCHES_PER_PAGE = 100 -LONG_PAGE_THRESHOLD = 30 -LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 10 -LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 8 -NUM_PAGES_OUTSIDE_RANGE = 2 -ADJACENT_PAGES = 4 - -# parts from: -# http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/ - -class Paginator(paginator.Paginator): - def __init__(self, request, objects): - - patches_per_page = settings.DEFAULT_PATCHES_PER_PAGE - - if request.user.is_authenticated(): - patches_per_page = request.user.profile.patches_per_page - - n = request.META.get('ppp') - if n: - try: - patches_per_page = int(n) - except ValueError: - pass - - super(Paginator, self).__init__(objects, patches_per_page) - - try: - page_no = int(request.GET.get('page')) - self.current_page = self.page(int(page_no)) - except Exception: - page_no = 1 - self.current_page = self.page(page_no) - - self.leading_set = self.trailing_set = [] - - pages = self.num_pages - - if pages <= LEADING_PAGE_RANGE_DISPLAYED: - self.adjacent_set = [n for n in range(1, pages + 1) \ - if n > 0 and n <= pages] - elif page_no <= LEADING_PAGE_RANGE: - self.adjacent_set = [n for n in \ - range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) \ - if n > 0 and n <= pages] - self.leading_set = [n + pages for n in \ - range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)] - elif page_no > pages - TRAILING_PAGE_RANGE: - self.adjacent_set = [n for n in \ - range(pages - TRAILING_PAGE_RANGE_DISPLAYED + 1, \ - pages + 1) if n > 0 and n <= pages] - self.trailing_set = [n + 1 for n in range(0, \ - NUM_PAGES_OUTSIDE_RANGE)] - else: - self.adjacent_set = [n for n in range(page_no - ADJACENT_PAGES, \ - page_no + ADJACENT_PAGES + 1) if n > 0 and n <= pages] - self.leading_set = [n + pages for n in \ - range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)] - self.trailing_set = [n + 1 for n in \ - range(0, NUM_PAGES_OUTSIDE_RANGE)] - - - self.leading_set.reverse() - self.long_page = \ - len(self.current_page.object_list) >= LONG_PAGE_THRESHOLD diff --git a/apps/patchwork/parser.py b/apps/patchwork/parser.py deleted file mode 100644 index a51a7b6..0000000 --- a/apps/patchwork/parser.py +++ /dev/null @@ -1,267 +0,0 @@ -#!/usr/bin/env python -# -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -import re - -try: - import hashlib - sha1_hash = hashlib.sha1 -except ImportError: - import sha - sha1_hash = sha.sha - -_hunk_re = re.compile('^\@\@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? \@\@') -_filename_re = re.compile('^(---|\+\+\+) (\S+)') - -def parse_patch(text): - patchbuf = '' - commentbuf = '' - buf = '' - - # state specified the line we just saw, and what to expect next - state = 0 - # 0: text - # 1: suspected patch header (diff, ====, Index:) - # 2: patch header line 1 (---) - # 3: patch header line 2 (+++) - # 4: patch hunk header line (@@ line) - # 5: patch hunk content - # 6: patch meta header (rename from/rename to) - # - # valid transitions: - # 0 -> 1 (diff, ===, Index:) - # 0 -> 2 (---) - # 1 -> 2 (---) - # 2 -> 3 (+++) - # 3 -> 4 (@@ line) - # 4 -> 5 (patch content) - # 5 -> 1 (run out of lines from @@-specifed count) - # 1 -> 6 (rename from / rename to) - # 6 -> 2 (---) - # 6 -> 1 (other text) - # - # Suspected patch header is stored into buf, and appended to - # patchbuf if we find a following hunk. Otherwise, append to - # comment after parsing. - - # line counts while parsing a patch hunk - lc = (0, 0) - hunk = 0 - - - for line in text.split('\n'): - line += '\n' - - if state == 0: - if line.startswith('diff ') or line.startswith('===') \ - or line.startswith('Index: '): - state = 1 - buf += line - - elif line.startswith('--- '): - state = 2 - buf += line - - else: - commentbuf += line - - elif state == 1: - buf += line - if line.startswith('--- '): - state = 2 - - if line.startswith('rename from ') or line.startswith('rename to '): - state = 6 - - elif state == 2: - if line.startswith('+++ '): - state = 3 - buf += line - - elif hunk: - state = 1 - buf += line - - else: - state = 0 - commentbuf += buf + line - buf = '' - - elif state == 3: - match = _hunk_re.match(line) - if match: - - def fn(x): - if not x: - return 1 - return int(x) - - lc = map(fn, match.groups()) - - state = 4 - patchbuf += buf + line - buf = '' - - elif line.startswith('--- '): - patchbuf += buf + line - buf = '' - state = 2 - - elif hunk and line.startswith('\ No newline at end of file'): - # If we had a hunk and now we see this, it's part of the patch, - # and we're still expecting another @@ line. - patchbuf += line - - elif hunk: - state = 1 - buf += line - - else: - state = 0 - commentbuf += buf + line - buf = '' - - elif state == 4 or state == 5: - if line.startswith('-'): - lc[0] -= 1 - elif line.startswith('+'): - lc[1] -= 1 - elif line.startswith('\ No newline at end of file'): - # Special case: Not included as part of the hunk's line count - pass - else: - lc[0] -= 1 - lc[1] -= 1 - - patchbuf += line - - if lc[0] <= 0 and lc[1] <= 0: - state = 3 - hunk += 1 - else: - state = 5 - - elif state == 6: - if line.startswith('rename to ') or line.startswith('rename from '): - patchbuf += buf + line - buf = '' - - elif line.startswith('--- '): - patchbuf += buf + line - buf = '' - state = 2 - - else: - buf += line - state = 1 - - else: - raise Exception("Unknown state %d! (line '%s')" % (state, line)) - - commentbuf += buf - - if patchbuf == '': - patchbuf = None - - if commentbuf == '': - commentbuf = None - - return (patchbuf, commentbuf) - -def hash_patch(str): - # normalise spaces - str = str.replace('\r', '') - str = str.strip() + '\n' - - prefixes = ['-', '+', ' '] - hash = sha1_hash() - - for line in str.split('\n'): - - if len(line) <= 0: - continue - - hunk_match = _hunk_re.match(line) - filename_match = _filename_re.match(line) - - if filename_match: - # normalise -p1 top-directories - if filename_match.group(1) == '---': - filename = 'a/' - else: - filename = 'b/' - filename += '/'.join(filename_match.group(2).split('/')[1:]) - - line = filename_match.group(1) + ' ' + filename - - elif hunk_match: - # remove line numbers, but leave line counts - def fn(x): - if not x: - return 1 - return int(x) - line_nos = map(fn, hunk_match.groups()) - line = '@@ -%d +%d @@' % tuple(line_nos) - - elif line[0] in prefixes: - # if we have a +, - or context line, leave as-is - pass - - else: - # other lines are ignored - continue - - hash.update(line.encode('utf-8') + '\n') - - return hash - - -def main(args): - from optparse import OptionParser - - parser = OptionParser() - parser.add_option('-p', '--patch', action = 'store_true', - dest = 'print_patch', help = 'print parsed patch') - parser.add_option('-c', '--comment', action = 'store_true', - dest = 'print_comment', help = 'print parsed comment') - parser.add_option('-#', '--hash', action = 'store_true', - dest = 'print_hash', help = 'print patch hash') - - (options, args) = parser.parse_args() - - # decode from (assumed) UTF-8 - content = sys.stdin.read().decode('utf-8') - - (patch, comment) = parse_patch(content) - - if options.print_hash and patch: - print hash_patch(patch).hexdigest() - - if options.print_patch and patch: - print "Patch: ------\n" + patch - - if options.print_comment and comment: - print "Comment: ----\n" + comment - -if __name__ == '__main__': - import sys - sys.exit(main(sys.argv)) diff --git a/apps/patchwork/requestcontext.py b/apps/patchwork/requestcontext.py deleted file mode 100644 index 3b1afaf..0000000 --- a/apps/patchwork/requestcontext.py +++ /dev/null @@ -1,89 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from django.template import RequestContext -from django.utils.html import escape -from django.contrib.sites.models import Site -from django.conf import settings -from patchwork.filters import Filters -from patchwork.models import Bundle, Project - -def bundle(request): - user = request.user - if not user.is_authenticated(): - return {} - return {'bundles': Bundle.objects.filter(owner = user)} - -def _params_as_qs(params): - return '&'.join([ '%s=%s' % (escape(k), escape(v)) for k, v in params ]) - -def _params_as_hidden_fields(params): - return '\n'.join([ '' % \ - (escape(k), escape(v)) for k, v in params ]) - -class PatchworkRequestContext(RequestContext): - def __init__(self, request, project = None, - dict = None, processors = None, - list_view = None, list_view_params = {}): - self._project = project - self.filters = Filters(request) - if processors is None: - processors = [] - processors.append(bundle) - super(PatchworkRequestContext, self). \ - __init__(request, dict, processors); - - self.update({ - 'filters': self.filters, - 'messages': [], - }) - if list_view: - params = self.filters.params() - for param in ['order', 'page']: - value = request.REQUEST.get(param, None) - if value: - params.append((param, value)) - self.update({ - 'list_view': { - 'view': list_view, - 'view_params': list_view_params, - 'params': params - }}) - - self.projects = Project.objects.all() - - self.update({ - 'project': self.project, - 'site': Site.objects.get_current(), - 'settings': settings, - 'other_projects': len(self.projects) > 1 - }) - - def _set_project(self, project): - self._project = project - self.filters.set_project(project) - self.update({'project': self._project}) - - def _get_project(self): - return self._project - - project = property(_get_project, _set_project) - - def add_message(self, message): - self['messages'].append(message) diff --git a/apps/patchwork/settings/__init__.py b/apps/patchwork/settings/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/patchwork/settings/base.py b/apps/patchwork/settings/base.py deleted file mode 100644 index 5440de6..0000000 --- a/apps/patchwork/settings/base.py +++ /dev/null @@ -1,115 +0,0 @@ -""" -Base settings for patchwork project. -""" - -import os - -import django - -ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), - os.pardir, os.pardir, os.pardir) - -# -# Core settings -# https://docs.djangoproject.com/en/1.6/ref/settings/#core-settings -# - -# Models - -INSTALLED_APPS = [ - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.admin', - 'django.contrib.staticfiles', - 'patchwork', -] - -# HTTP - -MIDDLEWARE_CLASSES = [ - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', -] - -if django.VERSION < (1, 7): - MIDDLEWARE_CLASSES.append('django.middleware.doc.XViewMiddleware') -else: - MIDDLEWARE_CLASSES.append( - 'django.contrib.admindocs.middleware.XViewMiddleware') - -# Globalization - -TIME_ZONE = 'Australia/Canberra' - -LANGUAGE_CODE = 'en-au' - -USE_I18N = True - -# URLs - -ROOT_URLCONF = 'patchwork.urls' - -# Templates - -TEMPLATE_DIRS = ( - os.path.join(ROOT_DIR, 'templates'), -) - - -# -# Auth settings -# https://docs.djangoproject.com/en/1.6/ref/settings/#auth -# - -LOGIN_URL = '/user/login/' -LOGIN_REDIRECT_URL = '/user/' - - -# -# Sites settings -# https://docs.djangoproject.com/en/1.6/ref/settings/#sites -# - -SITE_ID = 1 - - -# -# Static files settings -# https://docs.djangoproject.com/en/1.6/ref/settings/#static-files -# - -STATIC_URL = '/static/' - -STATICFILES_DIRS = [ - os.path.join(ROOT_DIR, 'htdocs'), -] - - -# -# Patchwork settings -# - -DEFAULT_PATCHES_PER_PAGE = 100 -DEFAULT_FROM_EMAIL = 'Patchwork ' - -CONFIRMATION_VALIDITY_DAYS = 7 - -NOTIFICATION_DELAY_MINUTES = 10 -NOTIFICATION_FROM_EMAIL = DEFAULT_FROM_EMAIL - -# Set to True to enable the Patchwork XML-RPC interface -ENABLE_XMLRPC = False - -# Set to True to enable redirections or URLs from previous versions -# of patchwork -COMPAT_REDIR = True - -# Set to True to always generate https:// links instead of guessing -# the scheme based on current access. This is useful if SSL protocol -# is terminated upstream of the server (e.g. at the load balancer) -FORCE_HTTPS_LINKS = False diff --git a/apps/patchwork/settings/dev.py b/apps/patchwork/settings/dev.py deleted file mode 100644 index 6e373cc..0000000 --- a/apps/patchwork/settings/dev.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -Development settings for patchwork project. - -These are also used in unit tests. - -Design based on: - http://www.revsys.com/blog/2014/nov/21/recommended-django-project-layout/ -""" - -import django - -from base import * - -# -# Core settings -# https://docs.djangoproject.com/en/1.6/ref/settings/#core-settings -# - -# Security - -SECRET_KEY = '00000000000000000000000000000000000000000000000000' - -# Debugging - -DEBUG = True - -# Templates - -TEMPLATE_DEBUG = True - -# Database - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'HOST': 'localhost', - 'PORT': '', - 'USER': os.environ['PW_TEST_DB_USER'], - 'PASSWORD': os.environ['PW_TEST_DB_PASS'], - 'NAME': 'patchwork', - 'TEST_CHARSET': 'utf8', - }, -} - -if django.VERSION >= (1, 7): - TEST_RUNNER = 'django.test.runner.DiscoverRunner' - -# -# Patchwork settings -# - -ENABLE_XMLRPC = True diff --git a/apps/patchwork/settings/prod.py b/apps/patchwork/settings/prod.py deleted file mode 100644 index d71f3df..0000000 --- a/apps/patchwork/settings/prod.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -Sample production-ready settings for patchwork project. - -Most of these are commented out as they will be installation dependent. - -Design based on: - http://www.revsys.com/blog/2014/nov/21/recommended-django-project-layout/ -""" - -from base import * - -# -# Core settings -# https://docs.djangoproject.com/en/1.6/ref/settings/#core-settings -# - -# Security - -# SECRET_KEY = '00000000000000000000000000000000000000000000000000' - -# Email - -# ADMINS = ( -# ('Jeremy Kerr', 'jk@ozlabs.org'), -# ) - -# Database - -# DATABASES = { -# 'default': { -# 'ENGINE': 'django.db.backends.postgresql_psycopg2', -# 'NAME': 'patchwork', -# }, -# } - -# File Uploads - -# MEDIA_ROOT = os.path.join( -# ROOT_DIR, 'lib', 'python', 'django', 'contrib', 'admin', 'media') - - -# -# Static files settings -# https://docs.djangoproject.com/en/1.6/ref/settings/#static-files -# - -# STATIC_ROOT = '/srv/patchwork/htdocs' - - -# -# Custom user overrides (for legacy) -# - -try: - from local_settings import * -except ImportError, ex: - import sys - sys.stderr.write(\ - ("settings.py: error importing local settings file:\n" + \ - "\t%s\n" + \ - "Do you have a local_settings.py module?\n") % str(ex)) - raise diff --git a/apps/patchwork/templatetags/__init__.py b/apps/patchwork/templatetags/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/patchwork/templatetags/filter.py b/apps/patchwork/templatetags/filter.py deleted file mode 100644 index 7a5d9df..0000000 --- a/apps/patchwork/templatetags/filter.py +++ /dev/null @@ -1,36 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from django import template -from django.utils.html import escape - -import re - - -register = template.Library() - -@register.filter -def personify(person): - if person.name: - linktext = escape(person.name) - else: - linktext = escape(person.email) - - return '%s' % (escape(person.email), linktext) - diff --git a/apps/patchwork/templatetags/listurl.py b/apps/patchwork/templatetags/listurl.py deleted file mode 100644 index 5fe03e4..0000000 --- a/apps/patchwork/templatetags/listurl.py +++ /dev/null @@ -1,136 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from django import template -from django.utils.html import escape -from django.utils.safestring import mark_safe -from django.utils.encoding import smart_str -from patchwork.filters import filterclasses -from django.conf import settings -from django.core.urlresolvers import reverse, NoReverseMatch -import re - -register = template.Library() - -# params to preserve across views -list_params = [ c.param for c in filterclasses ] + ['order', 'page'] - -class ListURLNode(template.defaulttags.URLNode): - def __init__(self, kwargs): - super(ListURLNode, self).__init__(None, [], {}, False) - self.params = {} - for (k, v) in kwargs.iteritems(): - if k in list_params: - self.params[k] = v - - def render(self, context): - view_name = template.Variable('list_view.view').resolve(context) - kwargs = template.Variable('list_view.view_params') \ - .resolve(context) - - str = None - try: - str = reverse(view_name, args=[], kwargs=kwargs) - except NoReverseMatch: - try: - project_name = settings.SETTINGS_MODULE.split('.')[0] - str = reverse(project_name + '.' + view_name, - args=[], kwargs=kwargs) - except NoReverseMatch: - raise - - if str is None: - return '' - - params = [] - try: - qs_var = template.Variable('list_view.params') - params = dict(qs_var.resolve(context)) - except Exception: - pass - - for (k, v) in self.params.iteritems(): - params[smart_str(k,'ascii')] = v.resolve(context) - - if not params: - return str - - return str + '?' + '&'.join(['%s=%s' % (k, escape(v)) \ - for (k, v) in params.iteritems()]) - -@register.tag -def listurl(parser, token): - bits = token.contents.split(' ', 1) - if len(bits) < 1: - raise TemplateSyntaxError("'%s' takes at least one argument" - " (path to a view)" % bits[0]) - kwargs = {} - if len(bits) > 1: - for arg in bits[1].split(','): - if '=' in arg: - k, v = arg.split('=', 1) - k = k.strip() - kwargs[k] = parser.compile_filter(v) - else: - raise TemplateSyntaxError("'%s' requires name=value params" \ - % bits[0]) - return ListURLNode(kwargs) - -class ListFieldsNode(template.Node): - def __init__(self, params): - self.params = params - - def render(self, context): - self.view_name = template.Variable('list_view.view').resolve(context) - try: - qs_var = template.Variable('list_view.params') - params = dict(qs_var.resolve(context)) - except Exception: - pass - - params.update(self.params) - - if not params: - return '' - - str = '' - for (k, v) in params.iteritems(): - str += '' % \ - (k, escape(v)) - - return mark_safe(str) - -@register.tag -def listfields(parser, token): - bits = token.contents.split(' ', 1) - if len(bits) < 1: - raise TemplateSyntaxError("'%s' takes at least one argument" - " (path to a view)" % bits[0]) - params = {} - if len(bits) > 2: - for arg in bits[2].split(','): - if '=' in arg: - k, v = arg.split('=', 1) - k = k.strip() - params[k] = parser.compile_filter(v) - else: - raise TemplateSyntaxError("'%s' requires name=value params" \ - % bits[0]) - return ListFieldsNode(bits[1], params) - diff --git a/apps/patchwork/templatetags/order.py b/apps/patchwork/templatetags/order.py deleted file mode 100644 index e392f03..0000000 --- a/apps/patchwork/templatetags/order.py +++ /dev/null @@ -1,66 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -from django import template -import re - -register = template.Library() - -@register.tag(name = 'ifpatcheditable') -def do_patch_is_editable(parser, token): - try: - tag_name, name, cur_order = token.split_contents() - except ValueError: - raise template.TemplateSyntaxError("%r tag requires two arguments" \ - % token.contents.split()[0]) - - end_tag = 'endifpatcheditable' - nodelist_true = parser.parse([end_tag, 'else']) - - token = parser.next_token() - if token.contents == 'else': - nodelist_false = parser.parse([end_tag]) - parser.delete_first_token() - else: - nodelist_false = template.NodeList() - - return EditablePatchNode(patch_var, nodelist_true, nodelist_false) - -class EditablePatchNode(template.Node): - def __init__(self, patch_var, nodelist_true, nodelist_false): - self.nodelist_true = nodelist_true - self.nodelist_false = nodelist_false - self.patch_var = template.Variable(patch_var) - self.user_var = template.Variable('user') - - def render(self, context): - try: - patch = self.patch_var.resolve(context) - user = self.user_var.resolve(context) - except template.VariableDoesNotExist: - return '' - - if not user.is_authenticated(): - return self.nodelist_false.render(context) - - if not patch.is_editable(user): - return self.nodelist_false.render(context) - - return self.nodelist_true.render(context) diff --git a/apps/patchwork/templatetags/patch.py b/apps/patchwork/templatetags/patch.py deleted file mode 100644 index bec0cab..0000000 --- a/apps/patchwork/templatetags/patch.py +++ /dev/null @@ -1,65 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from django import template -import re - -register = template.Library() - -@register.tag(name = 'ifpatcheditable') -def do_patch_is_editable(parser, token): - try: - tag_name, patch_var = token.split_contents() - except ValueError: - raise template.TemplateSyntaxError("%r tag requires one argument" \ - % token.contents.split()[0]) - - end_tag = 'endifpatcheditable' - nodelist_true = parser.parse([end_tag, 'else']) - - token = parser.next_token() - if token.contents == 'else': - nodelist_false = parser.parse([end_tag]) - parser.delete_first_token() - else: - nodelist_false = template.NodeList() - - return EditablePatchNode(patch_var, nodelist_true, nodelist_false) - -class EditablePatchNode(template.Node): - def __init__(self, patch_var, nodelist_true, nodelist_false): - self.nodelist_true = nodelist_true - self.nodelist_false = nodelist_false - self.patch_var = template.Variable(patch_var) - self.user_var = template.Variable('user') - - def render(self, context): - try: - patch = self.patch_var.resolve(context) - user = self.user_var.resolve(context) - except template.VariableDoesNotExist: - return '' - - if not user.is_authenticated(): - return self.nodelist_false.render(context) - - if not patch.is_editable(user): - return self.nodelist_false.render(context) - - return self.nodelist_true.render(context) diff --git a/apps/patchwork/templatetags/person.py b/apps/patchwork/templatetags/person.py deleted file mode 100644 index c337c74..0000000 --- a/apps/patchwork/templatetags/person.py +++ /dev/null @@ -1,43 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from django import template -from django.utils.html import escape -from django.utils.safestring import mark_safe -from django.core.urlresolvers import reverse -from patchwork.filters import SubmitterFilter -import re - -register = template.Library() - -@register.filter -def personify(person, project): - - if person.name: - linktext = escape(person.name) - else: - linktext = escape(person.email) - - url = reverse('patchwork.views.patch.list', kwargs = {'project_id' : project.linkname}) - str = '%s' % \ - (url, SubmitterFilter.param, escape(person.id), linktext) - - return mark_safe(str) - - diff --git a/apps/patchwork/templatetags/pwurl.py b/apps/patchwork/templatetags/pwurl.py deleted file mode 100644 index 98bc1ca..0000000 --- a/apps/patchwork/templatetags/pwurl.py +++ /dev/null @@ -1,76 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from django import template -from django.utils.html import escape -from django.utils.safestring import mark_safe -from patchwork.filters import filterclasses -import re - -register = template.Library() - -# params to preserve across views -list_params = [ c.param for c in filterclasses ] + ['order', 'page'] - -class ListURLNode(template.defaulttags.URLNode): - def __init__(self, *args, **kwargs): - super(ListURLNode, self).__init__(*args, **kwargs) - self.params = {} - for (k, v) in kwargs: - if k in list_params: - self.params[k] = v - - def render(self, context): - self.view_name = template.Variable('list_view.view') - str = super(ListURLNode, self).render(context) - if str == '': - return str - params = [] - try: - qs_var = template.Variable('list_view.params') - params = dict(qs_var.resolve(context)) - except Exception: - pass - - params.update(self.params) - - if not params: - return str - - return str + '?' + '&'.join(['%s=%s' % (k, escape(v)) \ - for (k, v) in params.iteritems()]) - -@register.tag -def listurl(parser, token): - bits = token.contents.split(' ', 1) - if len(bits) < 1: - raise TemplateSyntaxError("'%s' takes at least one argument" - " (path to a view)" % bits[0]) - args = [''] - kwargs = {} - if len(bits) > 1: - for arg in bits[2].split(','): - if '=' in arg: - k, v = arg.split('=', 1) - k = k.strip() - kwargs[k] = parser.compile_filter(v) - else: - args.append(parser.compile_filter(arg)) - return PatchworkURLNode(bits[1], args, kwargs) - diff --git a/apps/patchwork/templatetags/syntax.py b/apps/patchwork/templatetags/syntax.py deleted file mode 100644 index abdbb4d..0000000 --- a/apps/patchwork/templatetags/syntax.py +++ /dev/null @@ -1,75 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from django import template -from django.utils.html import escape -from django.utils.safestring import mark_safe -import re - -register = template.Library() - -def _compile(t): - (r, str) = t - return (re.compile(r, re.M | re.I), str) - -_patch_span_res = map(_compile, [ - ('^(Index:?|diff|\-\-\-|\+\+\+|\*\*\*) .*$', 'p_header'), - ('^\+.*$', 'p_add'), - ('^-.*$', 'p_del'), - ('^!.*$', 'p_mod'), - ]) - -_patch_chunk_re = \ - re.compile('^(@@ \-\d+(?:,\d+)? \+\d+(?:,\d+)? @@)(.*)$', re.M | re.I) - -_comment_span_res = map(_compile, [ - ('^\s*Signed-off-by: .*$', 'signed-off-by'), - ('^\s*Acked-by: .*$', 'acked-by'), - ('^\s*Nacked-by: .*$', 'nacked-by'), - ('^\s*Tested-by: .*$', 'tested-by'), - ('^\s*Reviewed-by: .*$', 'reviewed-by'), - ('^\s*From: .*$', 'from'), - ('^\s*>.*$', 'quote'), - ]) - -_span = '%s' - -@register.filter -def patchsyntax(patch): - content = escape(patch.content) - - for (r,cls) in _patch_span_res: - content = r.sub(lambda x: _span % (cls, x.group(0)), content) - - content = _patch_chunk_re.sub( \ - lambda x: \ - _span % ('p_chunk', x.group(1)) + ' ' + \ - _span % ('p_context', x.group(2)), \ - content) - - return mark_safe(content) - -@register.filter -def commentsyntax(comment): - content = escape(comment.content) - - for (r,cls) in _comment_span_res: - content = r.sub(lambda x: _span % (cls, x.group(0)), content) - - return mark_safe(content) diff --git a/apps/patchwork/tests/__init__.py b/apps/patchwork/tests/__init__.py deleted file mode 100644 index 85200bd..0000000 --- a/apps/patchwork/tests/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from patchwork.tests.test_patchparser import * -from patchwork.tests.test_encodings import * -from patchwork.tests.test_bundles import * -from patchwork.tests.test_mboxviews import * -from patchwork.tests.test_updates import * -from patchwork.tests.test_filters import * -from patchwork.tests.test_confirm import * -from patchwork.tests.test_registration import * -from patchwork.tests.test_user import * -from patchwork.tests.test_mail_settings import * -from patchwork.tests.test_notifications import * -from patchwork.tests.test_list import * -from patchwork.tests.test_person import * -from patchwork.tests.test_expiry import * -from patchwork.tests.test_xmlrpc import * diff --git a/apps/patchwork/tests/mail/0001-git-pull-request.mbox b/apps/patchwork/tests/mail/0001-git-pull-request.mbox deleted file mode 100644 index 0dbedbe..0000000 --- a/apps/patchwork/tests/mail/0001-git-pull-request.mbox +++ /dev/null @@ -1,348 +0,0 @@ -From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010 -Return-Path: -X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on bilbo.ozlabs.org -X-Spam-Level: -X-Spam-Status: No, score=0.0 required=3.0 tests=none autolearn=disabled - version=3.3.1 -X-Original-To: jk@ozlabs.org -Delivered-To: jk@ozlabs.org -Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) - by ozlabs.org (Postfix) with ESMTP id ED4B3100937 - for ; Fri, 22 Oct 2010 14:51:54 +1100 (EST) -Received: by ozlabs.org (Postfix) - id BF799B70CB; Fri, 22 Oct 2010 14:51:50 +1100 (EST) -Delivered-To: linuxppc-dev@ozlabs.org -Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) - (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) - (Client did not present a certificate) - by ozlabs.org (Postfix) with ESMTPS id 94629B7043 - for ; Fri, 22 Oct 2010 14:51:49 +1100 (EST) -Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1]) - by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o9M3p3SP018234; - Thu, 21 Oct 2010 22:51:04 -0500 -Subject: [git pull] Please pull powerpc.git next branch -From: Benjamin Herrenschmidt -To: Linus Torvalds -Date: Fri, 22 Oct 2010 14:51:02 +1100 -Message-ID: <1287719462.2198.37.camel@pasglop> -Mime-Version: 1.0 -X-Mailer: Evolution 2.30.3 -Cc: linuxppc-dev list , - Andrew Morton , - Linux Kernel list -X-BeenThere: linuxppc-dev@lists.ozlabs.org -X-Mailman-Version: 2.1.13 -Precedence: list -List-Id: Linux on PowerPC Developers Mail List -List-Unsubscribe: , - -List-Archive: -List-Post: -List-Help: -List-Subscribe: , - -Content-Type: text/plain; - charset="us-ascii" -Content-Transfer-Encoding: 7bit -Sender: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org -Errors-To: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org -X-UID: 11446 -X-Length: 16781 -Status: R -X-Status: N -X-KMail-EncryptionState: -X-KMail-SignatureState: -X-KMail-MDN-Sent: - -Hi Linus ! - -Here's powerpc's batch for this merge window. Mostly bits and pieces, -such as Anton doing some performance tuning left and right, and the -usual churn. One hilight is the support for the new Freescale e5500 core -(64-bit BookE). Another one is that we now wire up the whole lot of -socket calls as direct syscalls in addition to the old style indirect -method. - -Cheers, -Ben. - -The following changes since commit e10117d36ef758da0690c95ecffc09d5dd7da479: - Linus Torvalds (1): - Merge branch 'upstream-linus' of git://git.kernel.org/.../jgarzik/libata-dev - -are available in the git repository at: - - git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git next - -Andreas Schwab (1): - powerpc: Remove fpscr use from [kvm_]cvt_{fd,df} - -Anton Blanchard (5): - powerpc: Optimise 64bit csum_partial - powerpc: Optimise 64bit csum_partial_copy_generic and add csum_and_copy_from_user - powerpc: Add 64bit csum_and_copy_to_user - powerpc: Feature nop out reservation clear when stcx checks address - powerpc: Check end of stack canary at oops time - -Arnd Bergmann (1): - powerpc/spufs: Use llseek in all file operations - -Benjamin Herrenschmidt (4): - powerpc/dma: Add optional platform override of dma_set_mask() - powerpc/dart_iommu: Support for 64-bit iommu bypass window on PCIe - Merge remote branch 'kumar/merge' into next - Merge remote branch 'jwb/next' into next - -Denis Kirjanov (1): - powerpc: Use is_32bit_task() helper to test 32-bit binary - -Harninder Rai (1): - powerpc/85xx: add cache-sram support - -Ian Munsie (1): - powerpc: Wire up direct socket system calls - -Ilya Yanok (1): - powerpc/mpc83xx: Support for MPC8308 P1M board - -Joe Perches (2): - powerpc: Use static const char arrays - powerpc: Remove pr_ uses of KERN_ - -Josh Boyer (1): - powerpc/44x: Update ppc44x_defconfig - -Julia Lawall (7): - powerpc/via-pmu-led.c: Add of_node_put to avoid memory leak - powerpc/maple: Add of_node_put to avoid memory leak - powerpc/powermac/pfunc_core.c: Add of_node_put to avoid memory leak - powerpc/cell: Add of_node_put to avoid memory leak - powerpc/chrp/nvram.c: Add of_node_put to avoid memory leak - powerpc/irq.c: Add of_node_put to avoid memory leak - i2c/i2c-pasemi.c: Fix unsigned return type - -Kumar Gala (11): - powerpc/ppc64e: Fix link problem when building ppc64e_defconfig - powerpc/fsl-pci: Fix MSI support on 83xx platforms - powerpc/mpc8xxx_gpio: Add support for 'qoriq-gpio' controllers - powerpc/fsl-booke: Add PCI device ids for P2040/P3041/P5010/P5020 QoirQ chips - powerpc/fsl-booke: Add p3041 DS board support - powerpc: Fix compile error with paca code on ppc64e - powerpc/fsl-booke: Add support for FSL 64-bit e5500 core - powerpc/fsl-booke: Add support for FSL Arch v1.0 MMU in setup_page_sizes - powerpc/fsl-booke64: Use TLB CAMs to cover linear mapping on FSL 64-bit chips - powerpc/fsl-booke: Add p5020 DS board support - powerpc/fsl-booke: Add e55xx (64-bit) smp defconfig - -Matthew McClintock (7): - powerpc/mm: Assume first cpu is boot_cpuid not 0 - powerpc/kexec: make masking/disabling interrupts generic - powerpc/85xx: Remove call to mpic_teardown_this_cpu in kexec - powerpc/85xx: Minor fixups for kexec on 85xx - powerpc/85xx: flush dcache before resetting cores - powerpc/fsl_soc: Search all global-utilities nodes for rstccr - powerpc/fsl_booke: Add support to boot from core other than 0 - -Michael Neuling (1): - powerpc: Move arch_sd_sibling_asym_packing() to smp.c - -Nathan Fontenot (3): - powerpc/pseries: Export device tree updating routines - powerpc/pseries: Export rtas_ibm_suspend_me() - powerpc/pseries: Partition migration in the kernel - -Nishanth Aravamudan (8): - powerpc/pci: Fix return type of BUID_{HI,LO} macros - powerpc/dma: Fix dma_iommu_dma_supported compare - powerpc/dma: Fix check for direct DMA support - powerpc/vio: Use put_device() on device_register failure - powerpc/viobus: Free TCE table on device release - powerpc/pseries: Use kmemdup - powerpc/pci: Cleanup device dma setup code - powerpc/pseries/xics: Use cpu_possible_mask rather than cpu_all_mask - -Paul Gortmaker (1): - powerpc: Fix invalid page flags in create TLB CAM path for PTE_64BIT - -Paul Mackerras (5): - powerpc: Abstract indexing of lppaca structs - powerpc: Dynamically allocate most lppaca structs - powerpc: Account time using timebase rather than PURR - powerpc/pseries: Re-enable dispatch trace log userspace interface - powerpc/perf: Fix sampling enable for PPC970 - -Scott Wood (1): - oprofile/fsl emb: Don't set MSR[PMM] until after clearing the interrupt. - -Sean MacLennan (2): - powerpc: Fix incorrect .stabs entry for copy_32.S - powerpc: mtmsrd not defined - -Shaohui Xie (1): - fsl_rio: Add comments for sRIO registers. - -Stephen Rothwell (1): - powerpc: define a compat_sys_recv cond_syscall - -Timur Tabi (5): - powerpc: export ppc_proc_freq and ppc_tb_freq as GPL symbols - powerpc/watchdog: Allow the Book-E driver to be compiled as a module - powerpc/p1022: Add probing for individual DMA channels - powerpc/85xx: add ngPIXIS FPGA device tree node to the P1022DS board - powerpc/watchdog: Make default timeout for Book-E watchdog a Kconfig option - -Tirumala Marri (1): - powerpc/44x: Add support for the AMCC APM821xx SoC - -matt mooney (1): - powerpc/Makefiles: Change to new flag variables - - arch/powerpc/boot/addnote.c | 4 +- - arch/powerpc/boot/dts/bluestone.dts | 254 +++++++++++++ - arch/powerpc/boot/dts/mpc8308_p1m.dts | 332 ++++++++++++++++ - arch/powerpc/boot/dts/p1022ds.dts | 11 + - arch/powerpc/configs/44x/bluestone_defconfig | 68 ++++ - arch/powerpc/configs/e55xx_smp_defconfig | 84 ++++ - arch/powerpc/configs/ppc44x_defconfig | 9 +- - arch/powerpc/configs/ppc64e_defconfig | 4 +- - arch/powerpc/include/asm/checksum.h | 10 + - arch/powerpc/include/asm/compat.h | 4 +- - arch/powerpc/include/asm/cputable.h | 14 +- - arch/powerpc/include/asm/dma-mapping.h | 14 +- - arch/powerpc/include/asm/elf.h | 2 +- - arch/powerpc/include/asm/exception-64s.h | 3 +- - arch/powerpc/include/asm/fsl_85xx_cache_sram.h | 48 +++ - arch/powerpc/include/asm/kexec.h | 1 + - arch/powerpc/include/asm/kvm_fpu.h | 4 +- - arch/powerpc/include/asm/lppaca.h | 29 ++ - arch/powerpc/include/asm/machdep.h | 3 + - arch/powerpc/include/asm/mmu-book3e.h | 15 + - arch/powerpc/include/asm/paca.h | 10 +- - arch/powerpc/include/asm/page_64.h | 4 +- - arch/powerpc/include/asm/ppc-pci.h | 4 +- - arch/powerpc/include/asm/ppc_asm.h | 50 ++- - arch/powerpc/include/asm/processor.h | 4 +- - arch/powerpc/include/asm/pte-common.h | 7 + - arch/powerpc/include/asm/rtas.h | 1 + - arch/powerpc/include/asm/systbl.h | 19 + - arch/powerpc/include/asm/system.h | 4 +- - arch/powerpc/include/asm/time.h | 5 - - arch/powerpc/include/asm/unistd.h | 21 +- - arch/powerpc/kernel/Makefile | 4 +- - arch/powerpc/kernel/align.c | 4 +- - arch/powerpc/kernel/asm-offsets.c | 12 +- - arch/powerpc/kernel/cpu_setup_44x.S | 1 + - arch/powerpc/kernel/cpu_setup_fsl_booke.S | 15 + - arch/powerpc/kernel/cputable.c | 43 ++- - arch/powerpc/kernel/crash.c | 13 +- - arch/powerpc/kernel/dma-iommu.c | 21 +- - arch/powerpc/kernel/dma.c | 20 +- - arch/powerpc/kernel/entry_64.S | 40 ++ - arch/powerpc/kernel/fpu.S | 10 - - arch/powerpc/kernel/head_fsl_booke.S | 10 +- - arch/powerpc/kernel/irq.c | 6 +- - arch/powerpc/kernel/lparcfg.c | 14 +- - arch/powerpc/kernel/machine_kexec.c | 24 ++ - arch/powerpc/kernel/machine_kexec_32.c | 4 + - arch/powerpc/kernel/paca.c | 70 ++++- - arch/powerpc/kernel/pci-common.c | 4 +- - arch/powerpc/kernel/ppc970-pmu.c | 2 + - arch/powerpc/kernel/process.c | 12 - - arch/powerpc/kernel/ptrace.c | 2 +- - arch/powerpc/kernel/rtas.c | 4 +- - arch/powerpc/kernel/setup_32.c | 2 +- - arch/powerpc/kernel/smp.c | 14 +- - arch/powerpc/kernel/time.c | 275 +++++++------- - arch/powerpc/kernel/traps.c | 5 + - arch/powerpc/kernel/vdso.c | 6 +- - arch/powerpc/kernel/vdso32/Makefile | 6 +- - arch/powerpc/kernel/vdso64/Makefile | 6 +- - arch/powerpc/kernel/vio.c | 10 +- - arch/powerpc/kvm/Makefile | 2 +- - arch/powerpc/kvm/book3s_paired_singles.c | 44 +-- - arch/powerpc/kvm/emulate.c | 4 +- - arch/powerpc/kvm/fpu.S | 8 - - arch/powerpc/lib/Makefile | 7 +- - arch/powerpc/lib/checksum_64.S | 482 +++++++++++++++++------- - arch/powerpc/lib/checksum_wrappers_64.c | 102 +++++ - arch/powerpc/lib/copy_32.S | 2 +- - arch/powerpc/lib/ldstfp.S | 36 +- - arch/powerpc/lib/locks.c | 4 +- - arch/powerpc/lib/sstep.c | 8 + - arch/powerpc/math-emu/Makefile | 2 +- - arch/powerpc/mm/Makefile | 6 +- - arch/powerpc/mm/fault.c | 6 + - arch/powerpc/mm/fsl_booke_mmu.c | 15 +- - arch/powerpc/mm/mmu_context_nohash.c | 6 +- - arch/powerpc/mm/mmu_decl.h | 5 +- - arch/powerpc/mm/tlb_nohash.c | 56 +++- - arch/powerpc/mm/tlb_nohash_low.S | 2 +- - arch/powerpc/oprofile/Makefile | 4 +- - arch/powerpc/oprofile/backtrace.c | 2 +- - arch/powerpc/oprofile/op_model_fsl_emb.c | 15 +- - arch/powerpc/platforms/44x/Kconfig | 16 + - arch/powerpc/platforms/44x/ppc44x_simple.c | 1 + - arch/powerpc/platforms/83xx/Kconfig | 4 +- - arch/powerpc/platforms/83xx/mpc830x_rdb.c | 3 +- - arch/powerpc/platforms/85xx/Kconfig | 28 ++- - arch/powerpc/platforms/85xx/Makefile | 2 + - arch/powerpc/platforms/85xx/p1022_ds.c | 2 + - arch/powerpc/platforms/85xx/p3041_ds.c | 64 ++++ - arch/powerpc/platforms/85xx/p5020_ds.c | 69 ++++ - arch/powerpc/platforms/85xx/smp.c | 83 ++++- - arch/powerpc/platforms/Kconfig.cputype | 8 +- - arch/powerpc/platforms/cell/ras.c | 4 +- - arch/powerpc/platforms/cell/spider-pic.c | 4 +- - arch/powerpc/platforms/cell/spufs/file.c | 18 + - arch/powerpc/platforms/chrp/nvram.c | 4 +- - arch/powerpc/platforms/iseries/Makefile | 2 +- - arch/powerpc/platforms/iseries/dt.c | 4 +- - arch/powerpc/platforms/iseries/smp.c | 2 +- - arch/powerpc/platforms/maple/setup.c | 1 + - arch/powerpc/platforms/powermac/pfunc_core.c | 9 +- - arch/powerpc/platforms/pseries/Makefile | 13 +- - arch/powerpc/platforms/pseries/dlpar.c | 7 +- - arch/powerpc/platforms/pseries/dtl.c | 224 +++++++++--- - arch/powerpc/platforms/pseries/lpar.c | 25 ++- - arch/powerpc/platforms/pseries/mobility.c | 362 ++++++++++++++++++ - arch/powerpc/platforms/pseries/pseries.h | 9 + - arch/powerpc/platforms/pseries/setup.c | 52 +++ - arch/powerpc/platforms/pseries/xics.c | 2 +- - arch/powerpc/sysdev/Makefile | 5 +- - arch/powerpc/sysdev/dart_iommu.c | 74 ++++- - arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h | 101 +++++ - arch/powerpc/sysdev/fsl_85xx_cache_sram.c | 159 ++++++++ - arch/powerpc/sysdev/fsl_85xx_l2ctlr.c | 231 +++++++++++ - arch/powerpc/sysdev/fsl_msi.c | 9 +- - arch/powerpc/sysdev/fsl_pci.c | 60 +++- - arch/powerpc/sysdev/fsl_pci.h | 1 + - arch/powerpc/sysdev/fsl_rio.c | 65 ++-- - arch/powerpc/sysdev/fsl_soc.c | 20 +- - arch/powerpc/sysdev/mpc8xxx_gpio.c | 3 + - arch/powerpc/sysdev/pmi.c | 2 +- - arch/powerpc/xmon/Makefile | 4 +- - drivers/i2c/busses/i2c-pasemi.c | 2 +- - drivers/macintosh/via-pmu-led.c | 4 +- - drivers/watchdog/Kconfig | 22 +- - drivers/watchdog/booke_wdt.c | 47 ++- - include/linux/pci_ids.h | 8 + - kernel/sys_ni.c | 1 + - 130 files changed, 3676 insertions(+), 683 deletions(-) - create mode 100644 arch/powerpc/boot/dts/bluestone.dts - create mode 100644 arch/powerpc/boot/dts/mpc8308_p1m.dts - create mode 100644 arch/powerpc/configs/44x/bluestone_defconfig - create mode 100644 arch/powerpc/configs/e55xx_smp_defconfig - create mode 100644 arch/powerpc/include/asm/fsl_85xx_cache_sram.h - create mode 100644 arch/powerpc/lib/checksum_wrappers_64.c - create mode 100644 arch/powerpc/platforms/85xx/p3041_ds.c - create mode 100644 arch/powerpc/platforms/85xx/p5020_ds.c - create mode 100644 arch/powerpc/platforms/pseries/mobility.c - create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h - create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_sram.c - create mode 100644 arch/powerpc/sysdev/fsl_85xx_l2ctlr.c - - -_______________________________________________ -Linuxppc-dev mailing list -Linuxppc-dev@lists.ozlabs.org -https://lists.ozlabs.org/listinfo/linuxppc-dev diff --git a/apps/patchwork/tests/mail/0002-git-pull-request-wrapped.mbox b/apps/patchwork/tests/mail/0002-git-pull-request-wrapped.mbox deleted file mode 100644 index d3ccee1..0000000 --- a/apps/patchwork/tests/mail/0002-git-pull-request-wrapped.mbox +++ /dev/null @@ -1,349 +0,0 @@ -From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010 -Return-Path: -X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on bilbo.ozlabs.org -X-Spam-Level: -X-Spam-Status: No, score=0.0 required=3.0 tests=none autolearn=disabled - version=3.3.1 -X-Original-To: jk@ozlabs.org -Delivered-To: jk@ozlabs.org -Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) - by ozlabs.org (Postfix) with ESMTP id ED4B3100937 - for ; Fri, 22 Oct 2010 14:51:54 +1100 (EST) -Received: by ozlabs.org (Postfix) - id BF799B70CB; Fri, 22 Oct 2010 14:51:50 +1100 (EST) -Delivered-To: linuxppc-dev@ozlabs.org -Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) - (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) - (Client did not present a certificate) - by ozlabs.org (Postfix) with ESMTPS id 94629B7043 - for ; Fri, 22 Oct 2010 14:51:49 +1100 (EST) -Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1]) - by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o9M3p3SP018234; - Thu, 21 Oct 2010 22:51:04 -0500 -Subject: [git pull] Please pull powerpc.git next branch -From: Benjamin Herrenschmidt -To: Linus Torvalds -Date: Fri, 22 Oct 2010 14:51:02 +1100 -Message-ID: <1287719462.2198.37.camel@pasglop> -Mime-Version: 1.0 -X-Mailer: Evolution 2.30.3 -Cc: linuxppc-dev list , - Andrew Morton , - Linux Kernel list -X-BeenThere: linuxppc-dev@lists.ozlabs.org -X-Mailman-Version: 2.1.13 -Precedence: list -List-Id: Linux on PowerPC Developers Mail List -List-Unsubscribe: , - -List-Archive: -List-Post: -List-Help: -List-Subscribe: , - -Content-Type: text/plain; - charset="us-ascii" -Content-Transfer-Encoding: 7bit -Sender: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org -Errors-To: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org -X-UID: 11446 -X-Length: 16781 -Status: R -X-Status: N -X-KMail-EncryptionState: -X-KMail-SignatureState: -X-KMail-MDN-Sent: - -Hi Linus ! - -Here's powerpc's batch for this merge window. Mostly bits and pieces, -such as Anton doing some performance tuning left and right, and the -usual churn. One hilight is the support for the new Freescale e5500 core -(64-bit BookE). Another one is that we now wire up the whole lot of -socket calls as direct syscalls in addition to the old style indirect -method. - -Cheers, -Ben. - -The following changes since commit -e10117d36ef758da0690c95ecffc09d5dd7da479: - Linus Torvalds (1): - Merge branch 'upstream-linus' of git://git.kernel.org/.../jgarzik/libata-dev - -are available in the git repository at: - - git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git next - -Andreas Schwab (1): - powerpc: Remove fpscr use from [kvm_]cvt_{fd,df} - -Anton Blanchard (5): - powerpc: Optimise 64bit csum_partial - powerpc: Optimise 64bit csum_partial_copy_generic and add csum_and_copy_from_user - powerpc: Add 64bit csum_and_copy_to_user - powerpc: Feature nop out reservation clear when stcx checks address - powerpc: Check end of stack canary at oops time - -Arnd Bergmann (1): - powerpc/spufs: Use llseek in all file operations - -Benjamin Herrenschmidt (4): - powerpc/dma: Add optional platform override of dma_set_mask() - powerpc/dart_iommu: Support for 64-bit iommu bypass window on PCIe - Merge remote branch 'kumar/merge' into next - Merge remote branch 'jwb/next' into next - -Denis Kirjanov (1): - powerpc: Use is_32bit_task() helper to test 32-bit binary - -Harninder Rai (1): - powerpc/85xx: add cache-sram support - -Ian Munsie (1): - powerpc: Wire up direct socket system calls - -Ilya Yanok (1): - powerpc/mpc83xx: Support for MPC8308 P1M board - -Joe Perches (2): - powerpc: Use static const char arrays - powerpc: Remove pr_ uses of KERN_ - -Josh Boyer (1): - powerpc/44x: Update ppc44x_defconfig - -Julia Lawall (7): - powerpc/via-pmu-led.c: Add of_node_put to avoid memory leak - powerpc/maple: Add of_node_put to avoid memory leak - powerpc/powermac/pfunc_core.c: Add of_node_put to avoid memory leak - powerpc/cell: Add of_node_put to avoid memory leak - powerpc/chrp/nvram.c: Add of_node_put to avoid memory leak - powerpc/irq.c: Add of_node_put to avoid memory leak - i2c/i2c-pasemi.c: Fix unsigned return type - -Kumar Gala (11): - powerpc/ppc64e: Fix link problem when building ppc64e_defconfig - powerpc/fsl-pci: Fix MSI support on 83xx platforms - powerpc/mpc8xxx_gpio: Add support for 'qoriq-gpio' controllers - powerpc/fsl-booke: Add PCI device ids for P2040/P3041/P5010/P5020 QoirQ chips - powerpc/fsl-booke: Add p3041 DS board support - powerpc: Fix compile error with paca code on ppc64e - powerpc/fsl-booke: Add support for FSL 64-bit e5500 core - powerpc/fsl-booke: Add support for FSL Arch v1.0 MMU in setup_page_sizes - powerpc/fsl-booke64: Use TLB CAMs to cover linear mapping on FSL 64-bit chips - powerpc/fsl-booke: Add p5020 DS board support - powerpc/fsl-booke: Add e55xx (64-bit) smp defconfig - -Matthew McClintock (7): - powerpc/mm: Assume first cpu is boot_cpuid not 0 - powerpc/kexec: make masking/disabling interrupts generic - powerpc/85xx: Remove call to mpic_teardown_this_cpu in kexec - powerpc/85xx: Minor fixups for kexec on 85xx - powerpc/85xx: flush dcache before resetting cores - powerpc/fsl_soc: Search all global-utilities nodes for rstccr - powerpc/fsl_booke: Add support to boot from core other than 0 - -Michael Neuling (1): - powerpc: Move arch_sd_sibling_asym_packing() to smp.c - -Nathan Fontenot (3): - powerpc/pseries: Export device tree updating routines - powerpc/pseries: Export rtas_ibm_suspend_me() - powerpc/pseries: Partition migration in the kernel - -Nishanth Aravamudan (8): - powerpc/pci: Fix return type of BUID_{HI,LO} macros - powerpc/dma: Fix dma_iommu_dma_supported compare - powerpc/dma: Fix check for direct DMA support - powerpc/vio: Use put_device() on device_register failure - powerpc/viobus: Free TCE table on device release - powerpc/pseries: Use kmemdup - powerpc/pci: Cleanup device dma setup code - powerpc/pseries/xics: Use cpu_possible_mask rather than cpu_all_mask - -Paul Gortmaker (1): - powerpc: Fix invalid page flags in create TLB CAM path for PTE_64BIT - -Paul Mackerras (5): - powerpc: Abstract indexing of lppaca structs - powerpc: Dynamically allocate most lppaca structs - powerpc: Account time using timebase rather than PURR - powerpc/pseries: Re-enable dispatch trace log userspace interface - powerpc/perf: Fix sampling enable for PPC970 - -Scott Wood (1): - oprofile/fsl emb: Don't set MSR[PMM] until after clearing the interrupt. - -Sean MacLennan (2): - powerpc: Fix incorrect .stabs entry for copy_32.S - powerpc: mtmsrd not defined - -Shaohui Xie (1): - fsl_rio: Add comments for sRIO registers. - -Stephen Rothwell (1): - powerpc: define a compat_sys_recv cond_syscall - -Timur Tabi (5): - powerpc: export ppc_proc_freq and ppc_tb_freq as GPL symbols - powerpc/watchdog: Allow the Book-E driver to be compiled as a module - powerpc/p1022: Add probing for individual DMA channels - powerpc/85xx: add ngPIXIS FPGA device tree node to the P1022DS board - powerpc/watchdog: Make default timeout for Book-E watchdog a Kconfig option - -Tirumala Marri (1): - powerpc/44x: Add support for the AMCC APM821xx SoC - -matt mooney (1): - powerpc/Makefiles: Change to new flag variables - - arch/powerpc/boot/addnote.c | 4 +- - arch/powerpc/boot/dts/bluestone.dts | 254 +++++++++++++ - arch/powerpc/boot/dts/mpc8308_p1m.dts | 332 ++++++++++++++++ - arch/powerpc/boot/dts/p1022ds.dts | 11 + - arch/powerpc/configs/44x/bluestone_defconfig | 68 ++++ - arch/powerpc/configs/e55xx_smp_defconfig | 84 ++++ - arch/powerpc/configs/ppc44x_defconfig | 9 +- - arch/powerpc/configs/ppc64e_defconfig | 4 +- - arch/powerpc/include/asm/checksum.h | 10 + - arch/powerpc/include/asm/compat.h | 4 +- - arch/powerpc/include/asm/cputable.h | 14 +- - arch/powerpc/include/asm/dma-mapping.h | 14 +- - arch/powerpc/include/asm/elf.h | 2 +- - arch/powerpc/include/asm/exception-64s.h | 3 +- - arch/powerpc/include/asm/fsl_85xx_cache_sram.h | 48 +++ - arch/powerpc/include/asm/kexec.h | 1 + - arch/powerpc/include/asm/kvm_fpu.h | 4 +- - arch/powerpc/include/asm/lppaca.h | 29 ++ - arch/powerpc/include/asm/machdep.h | 3 + - arch/powerpc/include/asm/mmu-book3e.h | 15 + - arch/powerpc/include/asm/paca.h | 10 +- - arch/powerpc/include/asm/page_64.h | 4 +- - arch/powerpc/include/asm/ppc-pci.h | 4 +- - arch/powerpc/include/asm/ppc_asm.h | 50 ++- - arch/powerpc/include/asm/processor.h | 4 +- - arch/powerpc/include/asm/pte-common.h | 7 + - arch/powerpc/include/asm/rtas.h | 1 + - arch/powerpc/include/asm/systbl.h | 19 + - arch/powerpc/include/asm/system.h | 4 +- - arch/powerpc/include/asm/time.h | 5 - - arch/powerpc/include/asm/unistd.h | 21 +- - arch/powerpc/kernel/Makefile | 4 +- - arch/powerpc/kernel/align.c | 4 +- - arch/powerpc/kernel/asm-offsets.c | 12 +- - arch/powerpc/kernel/cpu_setup_44x.S | 1 + - arch/powerpc/kernel/cpu_setup_fsl_booke.S | 15 + - arch/powerpc/kernel/cputable.c | 43 ++- - arch/powerpc/kernel/crash.c | 13 +- - arch/powerpc/kernel/dma-iommu.c | 21 +- - arch/powerpc/kernel/dma.c | 20 +- - arch/powerpc/kernel/entry_64.S | 40 ++ - arch/powerpc/kernel/fpu.S | 10 - - arch/powerpc/kernel/head_fsl_booke.S | 10 +- - arch/powerpc/kernel/irq.c | 6 +- - arch/powerpc/kernel/lparcfg.c | 14 +- - arch/powerpc/kernel/machine_kexec.c | 24 ++ - arch/powerpc/kernel/machine_kexec_32.c | 4 + - arch/powerpc/kernel/paca.c | 70 ++++- - arch/powerpc/kernel/pci-common.c | 4 +- - arch/powerpc/kernel/ppc970-pmu.c | 2 + - arch/powerpc/kernel/process.c | 12 - - arch/powerpc/kernel/ptrace.c | 2 +- - arch/powerpc/kernel/rtas.c | 4 +- - arch/powerpc/kernel/setup_32.c | 2 +- - arch/powerpc/kernel/smp.c | 14 +- - arch/powerpc/kernel/time.c | 275 +++++++------- - arch/powerpc/kernel/traps.c | 5 + - arch/powerpc/kernel/vdso.c | 6 +- - arch/powerpc/kernel/vdso32/Makefile | 6 +- - arch/powerpc/kernel/vdso64/Makefile | 6 +- - arch/powerpc/kernel/vio.c | 10 +- - arch/powerpc/kvm/Makefile | 2 +- - arch/powerpc/kvm/book3s_paired_singles.c | 44 +-- - arch/powerpc/kvm/emulate.c | 4 +- - arch/powerpc/kvm/fpu.S | 8 - - arch/powerpc/lib/Makefile | 7 +- - arch/powerpc/lib/checksum_64.S | 482 +++++++++++++++++------- - arch/powerpc/lib/checksum_wrappers_64.c | 102 +++++ - arch/powerpc/lib/copy_32.S | 2 +- - arch/powerpc/lib/ldstfp.S | 36 +- - arch/powerpc/lib/locks.c | 4 +- - arch/powerpc/lib/sstep.c | 8 + - arch/powerpc/math-emu/Makefile | 2 +- - arch/powerpc/mm/Makefile | 6 +- - arch/powerpc/mm/fault.c | 6 + - arch/powerpc/mm/fsl_booke_mmu.c | 15 +- - arch/powerpc/mm/mmu_context_nohash.c | 6 +- - arch/powerpc/mm/mmu_decl.h | 5 +- - arch/powerpc/mm/tlb_nohash.c | 56 +++- - arch/powerpc/mm/tlb_nohash_low.S | 2 +- - arch/powerpc/oprofile/Makefile | 4 +- - arch/powerpc/oprofile/backtrace.c | 2 +- - arch/powerpc/oprofile/op_model_fsl_emb.c | 15 +- - arch/powerpc/platforms/44x/Kconfig | 16 + - arch/powerpc/platforms/44x/ppc44x_simple.c | 1 + - arch/powerpc/platforms/83xx/Kconfig | 4 +- - arch/powerpc/platforms/83xx/mpc830x_rdb.c | 3 +- - arch/powerpc/platforms/85xx/Kconfig | 28 ++- - arch/powerpc/platforms/85xx/Makefile | 2 + - arch/powerpc/platforms/85xx/p1022_ds.c | 2 + - arch/powerpc/platforms/85xx/p3041_ds.c | 64 ++++ - arch/powerpc/platforms/85xx/p5020_ds.c | 69 ++++ - arch/powerpc/platforms/85xx/smp.c | 83 ++++- - arch/powerpc/platforms/Kconfig.cputype | 8 +- - arch/powerpc/platforms/cell/ras.c | 4 +- - arch/powerpc/platforms/cell/spider-pic.c | 4 +- - arch/powerpc/platforms/cell/spufs/file.c | 18 + - arch/powerpc/platforms/chrp/nvram.c | 4 +- - arch/powerpc/platforms/iseries/Makefile | 2 +- - arch/powerpc/platforms/iseries/dt.c | 4 +- - arch/powerpc/platforms/iseries/smp.c | 2 +- - arch/powerpc/platforms/maple/setup.c | 1 + - arch/powerpc/platforms/powermac/pfunc_core.c | 9 +- - arch/powerpc/platforms/pseries/Makefile | 13 +- - arch/powerpc/platforms/pseries/dlpar.c | 7 +- - arch/powerpc/platforms/pseries/dtl.c | 224 +++++++++--- - arch/powerpc/platforms/pseries/lpar.c | 25 ++- - arch/powerpc/platforms/pseries/mobility.c | 362 ++++++++++++++++++ - arch/powerpc/platforms/pseries/pseries.h | 9 + - arch/powerpc/platforms/pseries/setup.c | 52 +++ - arch/powerpc/platforms/pseries/xics.c | 2 +- - arch/powerpc/sysdev/Makefile | 5 +- - arch/powerpc/sysdev/dart_iommu.c | 74 ++++- - arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h | 101 +++++ - arch/powerpc/sysdev/fsl_85xx_cache_sram.c | 159 ++++++++ - arch/powerpc/sysdev/fsl_85xx_l2ctlr.c | 231 +++++++++++ - arch/powerpc/sysdev/fsl_msi.c | 9 +- - arch/powerpc/sysdev/fsl_pci.c | 60 +++- - arch/powerpc/sysdev/fsl_pci.h | 1 + - arch/powerpc/sysdev/fsl_rio.c | 65 ++-- - arch/powerpc/sysdev/fsl_soc.c | 20 +- - arch/powerpc/sysdev/mpc8xxx_gpio.c | 3 + - arch/powerpc/sysdev/pmi.c | 2 +- - arch/powerpc/xmon/Makefile | 4 +- - drivers/i2c/busses/i2c-pasemi.c | 2 +- - drivers/macintosh/via-pmu-led.c | 4 +- - drivers/watchdog/Kconfig | 22 +- - drivers/watchdog/booke_wdt.c | 47 ++- - include/linux/pci_ids.h | 8 + - kernel/sys_ni.c | 1 + - 130 files changed, 3676 insertions(+), 683 deletions(-) - create mode 100644 arch/powerpc/boot/dts/bluestone.dts - create mode 100644 arch/powerpc/boot/dts/mpc8308_p1m.dts - create mode 100644 arch/powerpc/configs/44x/bluestone_defconfig - create mode 100644 arch/powerpc/configs/e55xx_smp_defconfig - create mode 100644 arch/powerpc/include/asm/fsl_85xx_cache_sram.h - create mode 100644 arch/powerpc/lib/checksum_wrappers_64.c - create mode 100644 arch/powerpc/platforms/85xx/p3041_ds.c - create mode 100644 arch/powerpc/platforms/85xx/p5020_ds.c - create mode 100644 arch/powerpc/platforms/pseries/mobility.c - create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h - create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_sram.c - create mode 100644 arch/powerpc/sysdev/fsl_85xx_l2ctlr.c - - -_______________________________________________ -Linuxppc-dev mailing list -Linuxppc-dev@lists.ozlabs.org -https://lists.ozlabs.org/listinfo/linuxppc-dev diff --git a/apps/patchwork/tests/mail/0003-git-pull-request-with-diff.mbox b/apps/patchwork/tests/mail/0003-git-pull-request-with-diff.mbox deleted file mode 100644 index b4d578c..0000000 --- a/apps/patchwork/tests/mail/0003-git-pull-request-with-diff.mbox +++ /dev/null @@ -1,141 +0,0 @@ -From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010 -Return-Path: -X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on bilbo.ozlabs.org -X-Spam-Level: -X-Spam-Status: No, score=0.0 required=3.0 tests=none autolearn=disabled - version=3.3.1 -X-Original-To: jk@ozlabs.org -Delivered-To: jk@ozlabs.org -Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) - by ozlabs.org (Postfix) with ESMTP id ED4B3100937 - for ; Fri, 22 Oct 2010 14:51:54 +1100 (EST) -Received: by ozlabs.org (Postfix) - id BF799B70CB; Fri, 22 Oct 2010 14:51:50 +1100 (EST) -Delivered-To: linuxppc-dev@ozlabs.org -Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) - (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) - (Client did not present a certificate) - by ozlabs.org (Postfix) with ESMTPS id 94629B7043 - for ; Fri, 22 Oct 2010 14:51:49 +1100 (EST) -Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1]) - by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o9M3p3SP018234; - Thu, 21 Oct 2010 22:51:04 -0500 -Subject: [git pull] Please pull powerpc.git next branch -From: Benjamin Herrenschmidt -To: Linus Torvalds -Date: Fri, 22 Oct 2010 14:51:02 +1100 -Message-ID: <1287719462.2198.37.camel@pasglop> -Mime-Version: 1.0 -X-Mailer: Evolution 2.30.3 -Cc: linuxppc-dev list , - Andrew Morton , - Linux Kernel list -X-BeenThere: linuxppc-dev@lists.ozlabs.org -X-Mailman-Version: 2.1.13 -Precedence: list -List-Id: Linux on PowerPC Developers Mail List -List-Unsubscribe: , - -List-Archive: -List-Post: -List-Help: -List-Subscribe: , - -Content-Type: text/plain; - charset="us-ascii" -Content-Transfer-Encoding: 7bit -Sender: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org -Errors-To: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org -X-UID: 11446 -X-Length: 16781 -Status: R -X-Status: N -X-KMail-EncryptionState: -X-KMail-SignatureState: -X-KMail-MDN-Sent: - -The following changes since commit e10117d36ef758da0690c95ecffc09d5dd7da479: - Linus Torvalds (1): - Merge branch 'upstream-linus' of git://git.kernel.org/.../jgarzik/libata-dev - -are available in the git repository at: - - git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip.git x86-fixes-for-linus - -------------------> -H. Peter Anvin (1): - x86-32: Make sure the stack is set up before we use it -Matthieu CASTET (1): - x86, nx: Don't force pages RW when setting NX bits - -Suresh Siddha (1): - x86, mtrr: Avoid MTRR reprogramming on BP during boot on UP platforms - - - arch/x86/include/asm/smp.h | 5 +---- - arch/x86/kernel/acpi/sleep.c | 2 +- - arch/x86/kernel/cpu/mtrr/main.c | 10 +++++++++- - arch/x86/kernel/head_32.S | 30 +++++++++++++----------------- - arch/x86/kernel/smpboot.c | 4 ++-- - arch/x86/mm/pageattr.c | 8 -------- - 6 files changed, 26 insertions(+), 33 deletions(-) -diff --git a/arch/x86/include/asm/smp.h b/arch/x86/include/asm/smp.h -index 4c2f63c..1f46951 100644 ---- a/arch/x86/include/asm/smp.h -+++ b/arch/x86/include/asm/smp.h -@@ -40,10 +40,7 @@ DECLARE_EARLY_PER_CPU(u16, x86_cpu_to_apicid); - DECLARE_EARLY_PER_CPU(u16, x86_bios_cpu_apicid); - - /* Static state in head.S used to set up a CPU */ --extern struct { -- void *sp; -- unsigned short ss; --} stack_start; -+extern unsigned long stack_start; /* Initial stack pointer address */ - - struct smp_ops { - void (*smp_prepare_boot_cpu)(void); -diff --git a/arch/x86/kernel/acpi/sleep.c b/arch/x86/kernel/acpi/sleep.c -index 69fd72a..4d9ebba 100644 ---- a/arch/x86/kernel/acpi/sleep.c -+++ b/arch/x86/kernel/acpi/sleep.c -@@ -100,7 +100,7 @@ int acpi_save_state_mem(void) - #else /* CONFIG_64BIT */ - header->trampoline_segment = setup_trampoline() >> 4; - #ifdef CONFIG_SMP -- stack_start.sp = temp_stack + sizeof(temp_stack); -+ stack_start = (unsigned long)temp_stack + sizeof(temp_stack); - early_gdt_descr.address = - (unsigned long)get_cpu_gdt_table(smp_processor_id()); - initial_gs = per_cpu_offset(smp_processor_id()); -diff --git a/arch/x86/kernel/cpu/mtrr/main.c b/arch/x86/kernel/cpu/mtrr/main.c -index 01c0f3e..bebabec 100644 ---- a/arch/x86/kernel/cpu/mtrr/main.c -+++ b/arch/x86/kernel/cpu/mtrr/main.c -@@ -793,13 +793,21 @@ void set_mtrr_aps_delayed_init(void) - } - - /* -- * MTRR initialization for all AP's -+ * Delayed MTRR initialization for all AP's - */ - void mtrr_aps_init(void) - { - if (!use_intel()) - return; - -+ /* -+ * Check if someone has requested the delay of AP MTRR initialization, -+ * by doing set_mtrr_aps_delayed_init(), prior to this point. If not, -+ * then we are done. -+ */ -+ if (!mtrr_aps_delayed_init) -+ return; -+ - set_mtrr(~0U, 0, 0, 0); - mtrr_aps_delayed_init = false; - } -_______________________________________________ -Linuxppc-dev mailing list -Linuxppc-dev@lists.ozlabs.org -https://lists.ozlabs.org/listinfo/linuxppc-dev diff --git a/apps/patchwork/tests/mail/0004-git-pull-request-git+ssh.mbox b/apps/patchwork/tests/mail/0004-git-pull-request-git+ssh.mbox deleted file mode 100644 index da96465..0000000 --- a/apps/patchwork/tests/mail/0004-git-pull-request-git+ssh.mbox +++ /dev/null @@ -1,348 +0,0 @@ -From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010 -Return-Path: -X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on bilbo.ozlabs.org -X-Spam-Level: -X-Spam-Status: No, score=0.0 required=3.0 tests=none autolearn=disabled - version=3.3.1 -X-Original-To: jk@ozlabs.org -Delivered-To: jk@ozlabs.org -Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) - by ozlabs.org (Postfix) with ESMTP id ED4B3100937 - for ; Fri, 22 Oct 2010 14:51:54 +1100 (EST) -Received: by ozlabs.org (Postfix) - id BF799B70CB; Fri, 22 Oct 2010 14:51:50 +1100 (EST) -Delivered-To: linuxppc-dev@ozlabs.org -Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) - (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) - (Client did not present a certificate) - by ozlabs.org (Postfix) with ESMTPS id 94629B7043 - for ; Fri, 22 Oct 2010 14:51:49 +1100 (EST) -Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1]) - by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o9M3p3SP018234; - Thu, 21 Oct 2010 22:51:04 -0500 -Subject: [git pull] Please pull powerpc.git next branch -From: Benjamin Herrenschmidt -To: Linus Torvalds -Date: Fri, 22 Oct 2010 14:51:02 +1100 -Message-ID: <1287719462.2198.37.camel@pasglop> -Mime-Version: 1.0 -X-Mailer: Evolution 2.30.3 -Cc: linuxppc-dev list , - Andrew Morton , - Linux Kernel list -X-BeenThere: linuxppc-dev@lists.ozlabs.org -X-Mailman-Version: 2.1.13 -Precedence: list -List-Id: Linux on PowerPC Developers Mail List -List-Unsubscribe: , - -List-Archive: -List-Post: -List-Help: -List-Subscribe: , - -Content-Type: text/plain; - charset="us-ascii" -Content-Transfer-Encoding: 7bit -Sender: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org -Errors-To: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org -X-UID: 11446 -X-Length: 16781 -Status: R -X-Status: N -X-KMail-EncryptionState: -X-KMail-SignatureState: -X-KMail-MDN-Sent: - -Hi Linus ! - -Here's powerpc's batch for this merge window. Mostly bits and pieces, -such as Anton doing some performance tuning left and right, and the -usual churn. One hilight is the support for the new Freescale e5500 core -(64-bit BookE). Another one is that we now wire up the whole lot of -socket calls as direct syscalls in addition to the old style indirect -method. - -Cheers, -Ben. - -The following changes since commit e10117d36ef758da0690c95ecffc09d5dd7da479: - Linus Torvalds (1): - Merge branch 'upstream-linus' of git://git.kernel.org/.../jgarzik/libata-dev - -are available in the git repository at: - - git+ssh://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git next - -Andreas Schwab (1): - powerpc: Remove fpscr use from [kvm_]cvt_{fd,df} - -Anton Blanchard (5): - powerpc: Optimise 64bit csum_partial - powerpc: Optimise 64bit csum_partial_copy_generic and add csum_and_copy_from_user - powerpc: Add 64bit csum_and_copy_to_user - powerpc: Feature nop out reservation clear when stcx checks address - powerpc: Check end of stack canary at oops time - -Arnd Bergmann (1): - powerpc/spufs: Use llseek in all file operations - -Benjamin Herrenschmidt (4): - powerpc/dma: Add optional platform override of dma_set_mask() - powerpc/dart_iommu: Support for 64-bit iommu bypass window on PCIe - Merge remote branch 'kumar/merge' into next - Merge remote branch 'jwb/next' into next - -Denis Kirjanov (1): - powerpc: Use is_32bit_task() helper to test 32-bit binary - -Harninder Rai (1): - powerpc/85xx: add cache-sram support - -Ian Munsie (1): - powerpc: Wire up direct socket system calls - -Ilya Yanok (1): - powerpc/mpc83xx: Support for MPC8308 P1M board - -Joe Perches (2): - powerpc: Use static const char arrays - powerpc: Remove pr_ uses of KERN_ - -Josh Boyer (1): - powerpc/44x: Update ppc44x_defconfig - -Julia Lawall (7): - powerpc/via-pmu-led.c: Add of_node_put to avoid memory leak - powerpc/maple: Add of_node_put to avoid memory leak - powerpc/powermac/pfunc_core.c: Add of_node_put to avoid memory leak - powerpc/cell: Add of_node_put to avoid memory leak - powerpc/chrp/nvram.c: Add of_node_put to avoid memory leak - powerpc/irq.c: Add of_node_put to avoid memory leak - i2c/i2c-pasemi.c: Fix unsigned return type - -Kumar Gala (11): - powerpc/ppc64e: Fix link problem when building ppc64e_defconfig - powerpc/fsl-pci: Fix MSI support on 83xx platforms - powerpc/mpc8xxx_gpio: Add support for 'qoriq-gpio' controllers - powerpc/fsl-booke: Add PCI device ids for P2040/P3041/P5010/P5020 QoirQ chips - powerpc/fsl-booke: Add p3041 DS board support - powerpc: Fix compile error with paca code on ppc64e - powerpc/fsl-booke: Add support for FSL 64-bit e5500 core - powerpc/fsl-booke: Add support for FSL Arch v1.0 MMU in setup_page_sizes - powerpc/fsl-booke64: Use TLB CAMs to cover linear mapping on FSL 64-bit chips - powerpc/fsl-booke: Add p5020 DS board support - powerpc/fsl-booke: Add e55xx (64-bit) smp defconfig - -Matthew McClintock (7): - powerpc/mm: Assume first cpu is boot_cpuid not 0 - powerpc/kexec: make masking/disabling interrupts generic - powerpc/85xx: Remove call to mpic_teardown_this_cpu in kexec - powerpc/85xx: Minor fixups for kexec on 85xx - powerpc/85xx: flush dcache before resetting cores - powerpc/fsl_soc: Search all global-utilities nodes for rstccr - powerpc/fsl_booke: Add support to boot from core other than 0 - -Michael Neuling (1): - powerpc: Move arch_sd_sibling_asym_packing() to smp.c - -Nathan Fontenot (3): - powerpc/pseries: Export device tree updating routines - powerpc/pseries: Export rtas_ibm_suspend_me() - powerpc/pseries: Partition migration in the kernel - -Nishanth Aravamudan (8): - powerpc/pci: Fix return type of BUID_{HI,LO} macros - powerpc/dma: Fix dma_iommu_dma_supported compare - powerpc/dma: Fix check for direct DMA support - powerpc/vio: Use put_device() on device_register failure - powerpc/viobus: Free TCE table on device release - powerpc/pseries: Use kmemdup - powerpc/pci: Cleanup device dma setup code - powerpc/pseries/xics: Use cpu_possible_mask rather than cpu_all_mask - -Paul Gortmaker (1): - powerpc: Fix invalid page flags in create TLB CAM path for PTE_64BIT - -Paul Mackerras (5): - powerpc: Abstract indexing of lppaca structs - powerpc: Dynamically allocate most lppaca structs - powerpc: Account time using timebase rather than PURR - powerpc/pseries: Re-enable dispatch trace log userspace interface - powerpc/perf: Fix sampling enable for PPC970 - -Scott Wood (1): - oprofile/fsl emb: Don't set MSR[PMM] until after clearing the interrupt. - -Sean MacLennan (2): - powerpc: Fix incorrect .stabs entry for copy_32.S - powerpc: mtmsrd not defined - -Shaohui Xie (1): - fsl_rio: Add comments for sRIO registers. - -Stephen Rothwell (1): - powerpc: define a compat_sys_recv cond_syscall - -Timur Tabi (5): - powerpc: export ppc_proc_freq and ppc_tb_freq as GPL symbols - powerpc/watchdog: Allow the Book-E driver to be compiled as a module - powerpc/p1022: Add probing for individual DMA channels - powerpc/85xx: add ngPIXIS FPGA device tree node to the P1022DS board - powerpc/watchdog: Make default timeout for Book-E watchdog a Kconfig option - -Tirumala Marri (1): - powerpc/44x: Add support for the AMCC APM821xx SoC - -matt mooney (1): - powerpc/Makefiles: Change to new flag variables - - arch/powerpc/boot/addnote.c | 4 +- - arch/powerpc/boot/dts/bluestone.dts | 254 +++++++++++++ - arch/powerpc/boot/dts/mpc8308_p1m.dts | 332 ++++++++++++++++ - arch/powerpc/boot/dts/p1022ds.dts | 11 + - arch/powerpc/configs/44x/bluestone_defconfig | 68 ++++ - arch/powerpc/configs/e55xx_smp_defconfig | 84 ++++ - arch/powerpc/configs/ppc44x_defconfig | 9 +- - arch/powerpc/configs/ppc64e_defconfig | 4 +- - arch/powerpc/include/asm/checksum.h | 10 + - arch/powerpc/include/asm/compat.h | 4 +- - arch/powerpc/include/asm/cputable.h | 14 +- - arch/powerpc/include/asm/dma-mapping.h | 14 +- - arch/powerpc/include/asm/elf.h | 2 +- - arch/powerpc/include/asm/exception-64s.h | 3 +- - arch/powerpc/include/asm/fsl_85xx_cache_sram.h | 48 +++ - arch/powerpc/include/asm/kexec.h | 1 + - arch/powerpc/include/asm/kvm_fpu.h | 4 +- - arch/powerpc/include/asm/lppaca.h | 29 ++ - arch/powerpc/include/asm/machdep.h | 3 + - arch/powerpc/include/asm/mmu-book3e.h | 15 + - arch/powerpc/include/asm/paca.h | 10 +- - arch/powerpc/include/asm/page_64.h | 4 +- - arch/powerpc/include/asm/ppc-pci.h | 4 +- - arch/powerpc/include/asm/ppc_asm.h | 50 ++- - arch/powerpc/include/asm/processor.h | 4 +- - arch/powerpc/include/asm/pte-common.h | 7 + - arch/powerpc/include/asm/rtas.h | 1 + - arch/powerpc/include/asm/systbl.h | 19 + - arch/powerpc/include/asm/system.h | 4 +- - arch/powerpc/include/asm/time.h | 5 - - arch/powerpc/include/asm/unistd.h | 21 +- - arch/powerpc/kernel/Makefile | 4 +- - arch/powerpc/kernel/align.c | 4 +- - arch/powerpc/kernel/asm-offsets.c | 12 +- - arch/powerpc/kernel/cpu_setup_44x.S | 1 + - arch/powerpc/kernel/cpu_setup_fsl_booke.S | 15 + - arch/powerpc/kernel/cputable.c | 43 ++- - arch/powerpc/kernel/crash.c | 13 +- - arch/powerpc/kernel/dma-iommu.c | 21 +- - arch/powerpc/kernel/dma.c | 20 +- - arch/powerpc/kernel/entry_64.S | 40 ++ - arch/powerpc/kernel/fpu.S | 10 - - arch/powerpc/kernel/head_fsl_booke.S | 10 +- - arch/powerpc/kernel/irq.c | 6 +- - arch/powerpc/kernel/lparcfg.c | 14 +- - arch/powerpc/kernel/machine_kexec.c | 24 ++ - arch/powerpc/kernel/machine_kexec_32.c | 4 + - arch/powerpc/kernel/paca.c | 70 ++++- - arch/powerpc/kernel/pci-common.c | 4 +- - arch/powerpc/kernel/ppc970-pmu.c | 2 + - arch/powerpc/kernel/process.c | 12 - - arch/powerpc/kernel/ptrace.c | 2 +- - arch/powerpc/kernel/rtas.c | 4 +- - arch/powerpc/kernel/setup_32.c | 2 +- - arch/powerpc/kernel/smp.c | 14 +- - arch/powerpc/kernel/time.c | 275 +++++++------- - arch/powerpc/kernel/traps.c | 5 + - arch/powerpc/kernel/vdso.c | 6 +- - arch/powerpc/kernel/vdso32/Makefile | 6 +- - arch/powerpc/kernel/vdso64/Makefile | 6 +- - arch/powerpc/kernel/vio.c | 10 +- - arch/powerpc/kvm/Makefile | 2 +- - arch/powerpc/kvm/book3s_paired_singles.c | 44 +-- - arch/powerpc/kvm/emulate.c | 4 +- - arch/powerpc/kvm/fpu.S | 8 - - arch/powerpc/lib/Makefile | 7 +- - arch/powerpc/lib/checksum_64.S | 482 +++++++++++++++++------- - arch/powerpc/lib/checksum_wrappers_64.c | 102 +++++ - arch/powerpc/lib/copy_32.S | 2 +- - arch/powerpc/lib/ldstfp.S | 36 +- - arch/powerpc/lib/locks.c | 4 +- - arch/powerpc/lib/sstep.c | 8 + - arch/powerpc/math-emu/Makefile | 2 +- - arch/powerpc/mm/Makefile | 6 +- - arch/powerpc/mm/fault.c | 6 + - arch/powerpc/mm/fsl_booke_mmu.c | 15 +- - arch/powerpc/mm/mmu_context_nohash.c | 6 +- - arch/powerpc/mm/mmu_decl.h | 5 +- - arch/powerpc/mm/tlb_nohash.c | 56 +++- - arch/powerpc/mm/tlb_nohash_low.S | 2 +- - arch/powerpc/oprofile/Makefile | 4 +- - arch/powerpc/oprofile/backtrace.c | 2 +- - arch/powerpc/oprofile/op_model_fsl_emb.c | 15 +- - arch/powerpc/platforms/44x/Kconfig | 16 + - arch/powerpc/platforms/44x/ppc44x_simple.c | 1 + - arch/powerpc/platforms/83xx/Kconfig | 4 +- - arch/powerpc/platforms/83xx/mpc830x_rdb.c | 3 +- - arch/powerpc/platforms/85xx/Kconfig | 28 ++- - arch/powerpc/platforms/85xx/Makefile | 2 + - arch/powerpc/platforms/85xx/p1022_ds.c | 2 + - arch/powerpc/platforms/85xx/p3041_ds.c | 64 ++++ - arch/powerpc/platforms/85xx/p5020_ds.c | 69 ++++ - arch/powerpc/platforms/85xx/smp.c | 83 ++++- - arch/powerpc/platforms/Kconfig.cputype | 8 +- - arch/powerpc/platforms/cell/ras.c | 4 +- - arch/powerpc/platforms/cell/spider-pic.c | 4 +- - arch/powerpc/platforms/cell/spufs/file.c | 18 + - arch/powerpc/platforms/chrp/nvram.c | 4 +- - arch/powerpc/platforms/iseries/Makefile | 2 +- - arch/powerpc/platforms/iseries/dt.c | 4 +- - arch/powerpc/platforms/iseries/smp.c | 2 +- - arch/powerpc/platforms/maple/setup.c | 1 + - arch/powerpc/platforms/powermac/pfunc_core.c | 9 +- - arch/powerpc/platforms/pseries/Makefile | 13 +- - arch/powerpc/platforms/pseries/dlpar.c | 7 +- - arch/powerpc/platforms/pseries/dtl.c | 224 +++++++++--- - arch/powerpc/platforms/pseries/lpar.c | 25 ++- - arch/powerpc/platforms/pseries/mobility.c | 362 ++++++++++++++++++ - arch/powerpc/platforms/pseries/pseries.h | 9 + - arch/powerpc/platforms/pseries/setup.c | 52 +++ - arch/powerpc/platforms/pseries/xics.c | 2 +- - arch/powerpc/sysdev/Makefile | 5 +- - arch/powerpc/sysdev/dart_iommu.c | 74 ++++- - arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h | 101 +++++ - arch/powerpc/sysdev/fsl_85xx_cache_sram.c | 159 ++++++++ - arch/powerpc/sysdev/fsl_85xx_l2ctlr.c | 231 +++++++++++ - arch/powerpc/sysdev/fsl_msi.c | 9 +- - arch/powerpc/sysdev/fsl_pci.c | 60 +++- - arch/powerpc/sysdev/fsl_pci.h | 1 + - arch/powerpc/sysdev/fsl_rio.c | 65 ++-- - arch/powerpc/sysdev/fsl_soc.c | 20 +- - arch/powerpc/sysdev/mpc8xxx_gpio.c | 3 + - arch/powerpc/sysdev/pmi.c | 2 +- - arch/powerpc/xmon/Makefile | 4 +- - drivers/i2c/busses/i2c-pasemi.c | 2 +- - drivers/macintosh/via-pmu-led.c | 4 +- - drivers/watchdog/Kconfig | 22 +- - drivers/watchdog/booke_wdt.c | 47 ++- - include/linux/pci_ids.h | 8 + - kernel/sys_ni.c | 1 + - 130 files changed, 3676 insertions(+), 683 deletions(-) - create mode 100644 arch/powerpc/boot/dts/bluestone.dts - create mode 100644 arch/powerpc/boot/dts/mpc8308_p1m.dts - create mode 100644 arch/powerpc/configs/44x/bluestone_defconfig - create mode 100644 arch/powerpc/configs/e55xx_smp_defconfig - create mode 100644 arch/powerpc/include/asm/fsl_85xx_cache_sram.h - create mode 100644 arch/powerpc/lib/checksum_wrappers_64.c - create mode 100644 arch/powerpc/platforms/85xx/p3041_ds.c - create mode 100644 arch/powerpc/platforms/85xx/p5020_ds.c - create mode 100644 arch/powerpc/platforms/pseries/mobility.c - create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h - create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_sram.c - create mode 100644 arch/powerpc/sysdev/fsl_85xx_l2ctlr.c - - -_______________________________________________ -Linuxppc-dev mailing list -Linuxppc-dev@lists.ozlabs.org -https://lists.ozlabs.org/listinfo/linuxppc-dev diff --git a/apps/patchwork/tests/mail/0005-git-pull-request-ssh.mbox b/apps/patchwork/tests/mail/0005-git-pull-request-ssh.mbox deleted file mode 100644 index 7f4c93e..0000000 --- a/apps/patchwork/tests/mail/0005-git-pull-request-ssh.mbox +++ /dev/null @@ -1,348 +0,0 @@ -From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010 -Return-Path: -X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on bilbo.ozlabs.org -X-Spam-Level: -X-Spam-Status: No, score=0.0 required=3.0 tests=none autolearn=disabled - version=3.3.1 -X-Original-To: jk@ozlabs.org -Delivered-To: jk@ozlabs.org -Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) - by ozlabs.org (Postfix) with ESMTP id ED4B3100937 - for ; Fri, 22 Oct 2010 14:51:54 +1100 (EST) -Received: by ozlabs.org (Postfix) - id BF799B70CB; Fri, 22 Oct 2010 14:51:50 +1100 (EST) -Delivered-To: linuxppc-dev@ozlabs.org -Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) - (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) - (Client did not present a certificate) - by ozlabs.org (Postfix) with ESMTPS id 94629B7043 - for ; Fri, 22 Oct 2010 14:51:49 +1100 (EST) -Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1]) - by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o9M3p3SP018234; - Thu, 21 Oct 2010 22:51:04 -0500 -Subject: [git pull] Please pull powerpc.git next branch -From: Benjamin Herrenschmidt -To: Linus Torvalds -Date: Fri, 22 Oct 2010 14:51:02 +1100 -Message-ID: <1287719462.2198.37.camel@pasglop> -Mime-Version: 1.0 -X-Mailer: Evolution 2.30.3 -Cc: linuxppc-dev list , - Andrew Morton , - Linux Kernel list -X-BeenThere: linuxppc-dev@lists.ozlabs.org -X-Mailman-Version: 2.1.13 -Precedence: list -List-Id: Linux on PowerPC Developers Mail List -List-Unsubscribe: , - -List-Archive: -List-Post: -List-Help: -List-Subscribe: , - -Content-Type: text/plain; - charset="us-ascii" -Content-Transfer-Encoding: 7bit -Sender: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org -Errors-To: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org -X-UID: 11446 -X-Length: 16781 -Status: R -X-Status: N -X-KMail-EncryptionState: -X-KMail-SignatureState: -X-KMail-MDN-Sent: - -Hi Linus ! - -Here's powerpc's batch for this merge window. Mostly bits and pieces, -such as Anton doing some performance tuning left and right, and the -usual churn. One hilight is the support for the new Freescale e5500 core -(64-bit BookE). Another one is that we now wire up the whole lot of -socket calls as direct syscalls in addition to the old style indirect -method. - -Cheers, -Ben. - -The following changes since commit e10117d36ef758da0690c95ecffc09d5dd7da479: - Linus Torvalds (1): - Merge branch 'upstream-linus' of git://git.kernel.org/.../jgarzik/libata-dev - -are available in the git repository at: - - ssh://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git next - -Andreas Schwab (1): - powerpc: Remove fpscr use from [kvm_]cvt_{fd,df} - -Anton Blanchard (5): - powerpc: Optimise 64bit csum_partial - powerpc: Optimise 64bit csum_partial_copy_generic and add csum_and_copy_from_user - powerpc: Add 64bit csum_and_copy_to_user - powerpc: Feature nop out reservation clear when stcx checks address - powerpc: Check end of stack canary at oops time - -Arnd Bergmann (1): - powerpc/spufs: Use llseek in all file operations - -Benjamin Herrenschmidt (4): - powerpc/dma: Add optional platform override of dma_set_mask() - powerpc/dart_iommu: Support for 64-bit iommu bypass window on PCIe - Merge remote branch 'kumar/merge' into next - Merge remote branch 'jwb/next' into next - -Denis Kirjanov (1): - powerpc: Use is_32bit_task() helper to test 32-bit binary - -Harninder Rai (1): - powerpc/85xx: add cache-sram support - -Ian Munsie (1): - powerpc: Wire up direct socket system calls - -Ilya Yanok (1): - powerpc/mpc83xx: Support for MPC8308 P1M board - -Joe Perches (2): - powerpc: Use static const char arrays - powerpc: Remove pr_ uses of KERN_ - -Josh Boyer (1): - powerpc/44x: Update ppc44x_defconfig - -Julia Lawall (7): - powerpc/via-pmu-led.c: Add of_node_put to avoid memory leak - powerpc/maple: Add of_node_put to avoid memory leak - powerpc/powermac/pfunc_core.c: Add of_node_put to avoid memory leak - powerpc/cell: Add of_node_put to avoid memory leak - powerpc/chrp/nvram.c: Add of_node_put to avoid memory leak - powerpc/irq.c: Add of_node_put to avoid memory leak - i2c/i2c-pasemi.c: Fix unsigned return type - -Kumar Gala (11): - powerpc/ppc64e: Fix link problem when building ppc64e_defconfig - powerpc/fsl-pci: Fix MSI support on 83xx platforms - powerpc/mpc8xxx_gpio: Add support for 'qoriq-gpio' controllers - powerpc/fsl-booke: Add PCI device ids for P2040/P3041/P5010/P5020 QoirQ chips - powerpc/fsl-booke: Add p3041 DS board support - powerpc: Fix compile error with paca code on ppc64e - powerpc/fsl-booke: Add support for FSL 64-bit e5500 core - powerpc/fsl-booke: Add support for FSL Arch v1.0 MMU in setup_page_sizes - powerpc/fsl-booke64: Use TLB CAMs to cover linear mapping on FSL 64-bit chips - powerpc/fsl-booke: Add p5020 DS board support - powerpc/fsl-booke: Add e55xx (64-bit) smp defconfig - -Matthew McClintock (7): - powerpc/mm: Assume first cpu is boot_cpuid not 0 - powerpc/kexec: make masking/disabling interrupts generic - powerpc/85xx: Remove call to mpic_teardown_this_cpu in kexec - powerpc/85xx: Minor fixups for kexec on 85xx - powerpc/85xx: flush dcache before resetting cores - powerpc/fsl_soc: Search all global-utilities nodes for rstccr - powerpc/fsl_booke: Add support to boot from core other than 0 - -Michael Neuling (1): - powerpc: Move arch_sd_sibling_asym_packing() to smp.c - -Nathan Fontenot (3): - powerpc/pseries: Export device tree updating routines - powerpc/pseries: Export rtas_ibm_suspend_me() - powerpc/pseries: Partition migration in the kernel - -Nishanth Aravamudan (8): - powerpc/pci: Fix return type of BUID_{HI,LO} macros - powerpc/dma: Fix dma_iommu_dma_supported compare - powerpc/dma: Fix check for direct DMA support - powerpc/vio: Use put_device() on device_register failure - powerpc/viobus: Free TCE table on device release - powerpc/pseries: Use kmemdup - powerpc/pci: Cleanup device dma setup code - powerpc/pseries/xics: Use cpu_possible_mask rather than cpu_all_mask - -Paul Gortmaker (1): - powerpc: Fix invalid page flags in create TLB CAM path for PTE_64BIT - -Paul Mackerras (5): - powerpc: Abstract indexing of lppaca structs - powerpc: Dynamically allocate most lppaca structs - powerpc: Account time using timebase rather than PURR - powerpc/pseries: Re-enable dispatch trace log userspace interface - powerpc/perf: Fix sampling enable for PPC970 - -Scott Wood (1): - oprofile/fsl emb: Don't set MSR[PMM] until after clearing the interrupt. - -Sean MacLennan (2): - powerpc: Fix incorrect .stabs entry for copy_32.S - powerpc: mtmsrd not defined - -Shaohui Xie (1): - fsl_rio: Add comments for sRIO registers. - -Stephen Rothwell (1): - powerpc: define a compat_sys_recv cond_syscall - -Timur Tabi (5): - powerpc: export ppc_proc_freq and ppc_tb_freq as GPL symbols - powerpc/watchdog: Allow the Book-E driver to be compiled as a module - powerpc/p1022: Add probing for individual DMA channels - powerpc/85xx: add ngPIXIS FPGA device tree node to the P1022DS board - powerpc/watchdog: Make default timeout for Book-E watchdog a Kconfig option - -Tirumala Marri (1): - powerpc/44x: Add support for the AMCC APM821xx SoC - -matt mooney (1): - powerpc/Makefiles: Change to new flag variables - - arch/powerpc/boot/addnote.c | 4 +- - arch/powerpc/boot/dts/bluestone.dts | 254 +++++++++++++ - arch/powerpc/boot/dts/mpc8308_p1m.dts | 332 ++++++++++++++++ - arch/powerpc/boot/dts/p1022ds.dts | 11 + - arch/powerpc/configs/44x/bluestone_defconfig | 68 ++++ - arch/powerpc/configs/e55xx_smp_defconfig | 84 ++++ - arch/powerpc/configs/ppc44x_defconfig | 9 +- - arch/powerpc/configs/ppc64e_defconfig | 4 +- - arch/powerpc/include/asm/checksum.h | 10 + - arch/powerpc/include/asm/compat.h | 4 +- - arch/powerpc/include/asm/cputable.h | 14 +- - arch/powerpc/include/asm/dma-mapping.h | 14 +- - arch/powerpc/include/asm/elf.h | 2 +- - arch/powerpc/include/asm/exception-64s.h | 3 +- - arch/powerpc/include/asm/fsl_85xx_cache_sram.h | 48 +++ - arch/powerpc/include/asm/kexec.h | 1 + - arch/powerpc/include/asm/kvm_fpu.h | 4 +- - arch/powerpc/include/asm/lppaca.h | 29 ++ - arch/powerpc/include/asm/machdep.h | 3 + - arch/powerpc/include/asm/mmu-book3e.h | 15 + - arch/powerpc/include/asm/paca.h | 10 +- - arch/powerpc/include/asm/page_64.h | 4 +- - arch/powerpc/include/asm/ppc-pci.h | 4 +- - arch/powerpc/include/asm/ppc_asm.h | 50 ++- - arch/powerpc/include/asm/processor.h | 4 +- - arch/powerpc/include/asm/pte-common.h | 7 + - arch/powerpc/include/asm/rtas.h | 1 + - arch/powerpc/include/asm/systbl.h | 19 + - arch/powerpc/include/asm/system.h | 4 +- - arch/powerpc/include/asm/time.h | 5 - - arch/powerpc/include/asm/unistd.h | 21 +- - arch/powerpc/kernel/Makefile | 4 +- - arch/powerpc/kernel/align.c | 4 +- - arch/powerpc/kernel/asm-offsets.c | 12 +- - arch/powerpc/kernel/cpu_setup_44x.S | 1 + - arch/powerpc/kernel/cpu_setup_fsl_booke.S | 15 + - arch/powerpc/kernel/cputable.c | 43 ++- - arch/powerpc/kernel/crash.c | 13 +- - arch/powerpc/kernel/dma-iommu.c | 21 +- - arch/powerpc/kernel/dma.c | 20 +- - arch/powerpc/kernel/entry_64.S | 40 ++ - arch/powerpc/kernel/fpu.S | 10 - - arch/powerpc/kernel/head_fsl_booke.S | 10 +- - arch/powerpc/kernel/irq.c | 6 +- - arch/powerpc/kernel/lparcfg.c | 14 +- - arch/powerpc/kernel/machine_kexec.c | 24 ++ - arch/powerpc/kernel/machine_kexec_32.c | 4 + - arch/powerpc/kernel/paca.c | 70 ++++- - arch/powerpc/kernel/pci-common.c | 4 +- - arch/powerpc/kernel/ppc970-pmu.c | 2 + - arch/powerpc/kernel/process.c | 12 - - arch/powerpc/kernel/ptrace.c | 2 +- - arch/powerpc/kernel/rtas.c | 4 +- - arch/powerpc/kernel/setup_32.c | 2 +- - arch/powerpc/kernel/smp.c | 14 +- - arch/powerpc/kernel/time.c | 275 +++++++------- - arch/powerpc/kernel/traps.c | 5 + - arch/powerpc/kernel/vdso.c | 6 +- - arch/powerpc/kernel/vdso32/Makefile | 6 +- - arch/powerpc/kernel/vdso64/Makefile | 6 +- - arch/powerpc/kernel/vio.c | 10 +- - arch/powerpc/kvm/Makefile | 2 +- - arch/powerpc/kvm/book3s_paired_singles.c | 44 +-- - arch/powerpc/kvm/emulate.c | 4 +- - arch/powerpc/kvm/fpu.S | 8 - - arch/powerpc/lib/Makefile | 7 +- - arch/powerpc/lib/checksum_64.S | 482 +++++++++++++++++------- - arch/powerpc/lib/checksum_wrappers_64.c | 102 +++++ - arch/powerpc/lib/copy_32.S | 2 +- - arch/powerpc/lib/ldstfp.S | 36 +- - arch/powerpc/lib/locks.c | 4 +- - arch/powerpc/lib/sstep.c | 8 + - arch/powerpc/math-emu/Makefile | 2 +- - arch/powerpc/mm/Makefile | 6 +- - arch/powerpc/mm/fault.c | 6 + - arch/powerpc/mm/fsl_booke_mmu.c | 15 +- - arch/powerpc/mm/mmu_context_nohash.c | 6 +- - arch/powerpc/mm/mmu_decl.h | 5 +- - arch/powerpc/mm/tlb_nohash.c | 56 +++- - arch/powerpc/mm/tlb_nohash_low.S | 2 +- - arch/powerpc/oprofile/Makefile | 4 +- - arch/powerpc/oprofile/backtrace.c | 2 +- - arch/powerpc/oprofile/op_model_fsl_emb.c | 15 +- - arch/powerpc/platforms/44x/Kconfig | 16 + - arch/powerpc/platforms/44x/ppc44x_simple.c | 1 + - arch/powerpc/platforms/83xx/Kconfig | 4 +- - arch/powerpc/platforms/83xx/mpc830x_rdb.c | 3 +- - arch/powerpc/platforms/85xx/Kconfig | 28 ++- - arch/powerpc/platforms/85xx/Makefile | 2 + - arch/powerpc/platforms/85xx/p1022_ds.c | 2 + - arch/powerpc/platforms/85xx/p3041_ds.c | 64 ++++ - arch/powerpc/platforms/85xx/p5020_ds.c | 69 ++++ - arch/powerpc/platforms/85xx/smp.c | 83 ++++- - arch/powerpc/platforms/Kconfig.cputype | 8 +- - arch/powerpc/platforms/cell/ras.c | 4 +- - arch/powerpc/platforms/cell/spider-pic.c | 4 +- - arch/powerpc/platforms/cell/spufs/file.c | 18 + - arch/powerpc/platforms/chrp/nvram.c | 4 +- - arch/powerpc/platforms/iseries/Makefile | 2 +- - arch/powerpc/platforms/iseries/dt.c | 4 +- - arch/powerpc/platforms/iseries/smp.c | 2 +- - arch/powerpc/platforms/maple/setup.c | 1 + - arch/powerpc/platforms/powermac/pfunc_core.c | 9 +- - arch/powerpc/platforms/pseries/Makefile | 13 +- - arch/powerpc/platforms/pseries/dlpar.c | 7 +- - arch/powerpc/platforms/pseries/dtl.c | 224 +++++++++--- - arch/powerpc/platforms/pseries/lpar.c | 25 ++- - arch/powerpc/platforms/pseries/mobility.c | 362 ++++++++++++++++++ - arch/powerpc/platforms/pseries/pseries.h | 9 + - arch/powerpc/platforms/pseries/setup.c | 52 +++ - arch/powerpc/platforms/pseries/xics.c | 2 +- - arch/powerpc/sysdev/Makefile | 5 +- - arch/powerpc/sysdev/dart_iommu.c | 74 ++++- - arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h | 101 +++++ - arch/powerpc/sysdev/fsl_85xx_cache_sram.c | 159 ++++++++ - arch/powerpc/sysdev/fsl_85xx_l2ctlr.c | 231 +++++++++++ - arch/powerpc/sysdev/fsl_msi.c | 9 +- - arch/powerpc/sysdev/fsl_pci.c | 60 +++- - arch/powerpc/sysdev/fsl_pci.h | 1 + - arch/powerpc/sysdev/fsl_rio.c | 65 ++-- - arch/powerpc/sysdev/fsl_soc.c | 20 +- - arch/powerpc/sysdev/mpc8xxx_gpio.c | 3 + - arch/powerpc/sysdev/pmi.c | 2 +- - arch/powerpc/xmon/Makefile | 4 +- - drivers/i2c/busses/i2c-pasemi.c | 2 +- - drivers/macintosh/via-pmu-led.c | 4 +- - drivers/watchdog/Kconfig | 22 +- - drivers/watchdog/booke_wdt.c | 47 ++- - include/linux/pci_ids.h | 8 + - kernel/sys_ni.c | 1 + - 130 files changed, 3676 insertions(+), 683 deletions(-) - create mode 100644 arch/powerpc/boot/dts/bluestone.dts - create mode 100644 arch/powerpc/boot/dts/mpc8308_p1m.dts - create mode 100644 arch/powerpc/configs/44x/bluestone_defconfig - create mode 100644 arch/powerpc/configs/e55xx_smp_defconfig - create mode 100644 arch/powerpc/include/asm/fsl_85xx_cache_sram.h - create mode 100644 arch/powerpc/lib/checksum_wrappers_64.c - create mode 100644 arch/powerpc/platforms/85xx/p3041_ds.c - create mode 100644 arch/powerpc/platforms/85xx/p5020_ds.c - create mode 100644 arch/powerpc/platforms/pseries/mobility.c - create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h - create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_sram.c - create mode 100644 arch/powerpc/sysdev/fsl_85xx_l2ctlr.c - - -_______________________________________________ -Linuxppc-dev mailing list -Linuxppc-dev@lists.ozlabs.org -https://lists.ozlabs.org/listinfo/linuxppc-dev diff --git a/apps/patchwork/tests/mail/0006-git-pull-request-http.mbox b/apps/patchwork/tests/mail/0006-git-pull-request-http.mbox deleted file mode 100644 index e4f9007..0000000 --- a/apps/patchwork/tests/mail/0006-git-pull-request-http.mbox +++ /dev/null @@ -1,348 +0,0 @@ -From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010 -Return-Path: -X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on bilbo.ozlabs.org -X-Spam-Level: -X-Spam-Status: No, score=0.0 required=3.0 tests=none autolearn=disabled - version=3.3.1 -X-Original-To: jk@ozlabs.org -Delivered-To: jk@ozlabs.org -Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) - by ozlabs.org (Postfix) with ESMTP id ED4B3100937 - for ; Fri, 22 Oct 2010 14:51:54 +1100 (EST) -Received: by ozlabs.org (Postfix) - id BF799B70CB; Fri, 22 Oct 2010 14:51:50 +1100 (EST) -Delivered-To: linuxppc-dev@ozlabs.org -Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) - (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) - (Client did not present a certificate) - by ozlabs.org (Postfix) with ESMTPS id 94629B7043 - for ; Fri, 22 Oct 2010 14:51:49 +1100 (EST) -Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1]) - by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o9M3p3SP018234; - Thu, 21 Oct 2010 22:51:04 -0500 -Subject: [git pull] Please pull powerpc.git next branch -From: Benjamin Herrenschmidt -To: Linus Torvalds -Date: Fri, 22 Oct 2010 14:51:02 +1100 -Message-ID: <1287719462.2198.37.camel@pasglop> -Mime-Version: 1.0 -X-Mailer: Evolution 2.30.3 -Cc: linuxppc-dev list , - Andrew Morton , - Linux Kernel list -X-BeenThere: linuxppc-dev@lists.ozlabs.org -X-Mailman-Version: 2.1.13 -Precedence: list -List-Id: Linux on PowerPC Developers Mail List -List-Unsubscribe: , - -List-Archive: -List-Post: -List-Help: -List-Subscribe: , - -Content-Type: text/plain; - charset="us-ascii" -Content-Transfer-Encoding: 7bit -Sender: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org -Errors-To: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org -X-UID: 11446 -X-Length: 16781 -Status: R -X-Status: N -X-KMail-EncryptionState: -X-KMail-SignatureState: -X-KMail-MDN-Sent: - -Hi Linus ! - -Here's powerpc's batch for this merge window. Mostly bits and pieces, -such as Anton doing some performance tuning left and right, and the -usual churn. One hilight is the support for the new Freescale e5500 core -(64-bit BookE). Another one is that we now wire up the whole lot of -socket calls as direct syscalls in addition to the old style indirect -method. - -Cheers, -Ben. - -The following changes since commit e10117d36ef758da0690c95ecffc09d5dd7da479: - Linus Torvalds (1): - Merge branch 'upstream-linus' of git://git.kernel.org/.../jgarzik/libata-dev - -are available in the git repository at: - - http://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git next - -Andreas Schwab (1): - powerpc: Remove fpscr use from [kvm_]cvt_{fd,df} - -Anton Blanchard (5): - powerpc: Optimise 64bit csum_partial - powerpc: Optimise 64bit csum_partial_copy_generic and add csum_and_copy_from_user - powerpc: Add 64bit csum_and_copy_to_user - powerpc: Feature nop out reservation clear when stcx checks address - powerpc: Check end of stack canary at oops time - -Arnd Bergmann (1): - powerpc/spufs: Use llseek in all file operations - -Benjamin Herrenschmidt (4): - powerpc/dma: Add optional platform override of dma_set_mask() - powerpc/dart_iommu: Support for 64-bit iommu bypass window on PCIe - Merge remote branch 'kumar/merge' into next - Merge remote branch 'jwb/next' into next - -Denis Kirjanov (1): - powerpc: Use is_32bit_task() helper to test 32-bit binary - -Harninder Rai (1): - powerpc/85xx: add cache-sram support - -Ian Munsie (1): - powerpc: Wire up direct socket system calls - -Ilya Yanok (1): - powerpc/mpc83xx: Support for MPC8308 P1M board - -Joe Perches (2): - powerpc: Use static const char arrays - powerpc: Remove pr_ uses of KERN_ - -Josh Boyer (1): - powerpc/44x: Update ppc44x_defconfig - -Julia Lawall (7): - powerpc/via-pmu-led.c: Add of_node_put to avoid memory leak - powerpc/maple: Add of_node_put to avoid memory leak - powerpc/powermac/pfunc_core.c: Add of_node_put to avoid memory leak - powerpc/cell: Add of_node_put to avoid memory leak - powerpc/chrp/nvram.c: Add of_node_put to avoid memory leak - powerpc/irq.c: Add of_node_put to avoid memory leak - i2c/i2c-pasemi.c: Fix unsigned return type - -Kumar Gala (11): - powerpc/ppc64e: Fix link problem when building ppc64e_defconfig - powerpc/fsl-pci: Fix MSI support on 83xx platforms - powerpc/mpc8xxx_gpio: Add support for 'qoriq-gpio' controllers - powerpc/fsl-booke: Add PCI device ids for P2040/P3041/P5010/P5020 QoirQ chips - powerpc/fsl-booke: Add p3041 DS board support - powerpc: Fix compile error with paca code on ppc64e - powerpc/fsl-booke: Add support for FSL 64-bit e5500 core - powerpc/fsl-booke: Add support for FSL Arch v1.0 MMU in setup_page_sizes - powerpc/fsl-booke64: Use TLB CAMs to cover linear mapping on FSL 64-bit chips - powerpc/fsl-booke: Add p5020 DS board support - powerpc/fsl-booke: Add e55xx (64-bit) smp defconfig - -Matthew McClintock (7): - powerpc/mm: Assume first cpu is boot_cpuid not 0 - powerpc/kexec: make masking/disabling interrupts generic - powerpc/85xx: Remove call to mpic_teardown_this_cpu in kexec - powerpc/85xx: Minor fixups for kexec on 85xx - powerpc/85xx: flush dcache before resetting cores - powerpc/fsl_soc: Search all global-utilities nodes for rstccr - powerpc/fsl_booke: Add support to boot from core other than 0 - -Michael Neuling (1): - powerpc: Move arch_sd_sibling_asym_packing() to smp.c - -Nathan Fontenot (3): - powerpc/pseries: Export device tree updating routines - powerpc/pseries: Export rtas_ibm_suspend_me() - powerpc/pseries: Partition migration in the kernel - -Nishanth Aravamudan (8): - powerpc/pci: Fix return type of BUID_{HI,LO} macros - powerpc/dma: Fix dma_iommu_dma_supported compare - powerpc/dma: Fix check for direct DMA support - powerpc/vio: Use put_device() on device_register failure - powerpc/viobus: Free TCE table on device release - powerpc/pseries: Use kmemdup - powerpc/pci: Cleanup device dma setup code - powerpc/pseries/xics: Use cpu_possible_mask rather than cpu_all_mask - -Paul Gortmaker (1): - powerpc: Fix invalid page flags in create TLB CAM path for PTE_64BIT - -Paul Mackerras (5): - powerpc: Abstract indexing of lppaca structs - powerpc: Dynamically allocate most lppaca structs - powerpc: Account time using timebase rather than PURR - powerpc/pseries: Re-enable dispatch trace log userspace interface - powerpc/perf: Fix sampling enable for PPC970 - -Scott Wood (1): - oprofile/fsl emb: Don't set MSR[PMM] until after clearing the interrupt. - -Sean MacLennan (2): - powerpc: Fix incorrect .stabs entry for copy_32.S - powerpc: mtmsrd not defined - -Shaohui Xie (1): - fsl_rio: Add comments for sRIO registers. - -Stephen Rothwell (1): - powerpc: define a compat_sys_recv cond_syscall - -Timur Tabi (5): - powerpc: export ppc_proc_freq and ppc_tb_freq as GPL symbols - powerpc/watchdog: Allow the Book-E driver to be compiled as a module - powerpc/p1022: Add probing for individual DMA channels - powerpc/85xx: add ngPIXIS FPGA device tree node to the P1022DS board - powerpc/watchdog: Make default timeout for Book-E watchdog a Kconfig option - -Tirumala Marri (1): - powerpc/44x: Add support for the AMCC APM821xx SoC - -matt mooney (1): - powerpc/Makefiles: Change to new flag variables - - arch/powerpc/boot/addnote.c | 4 +- - arch/powerpc/boot/dts/bluestone.dts | 254 +++++++++++++ - arch/powerpc/boot/dts/mpc8308_p1m.dts | 332 ++++++++++++++++ - arch/powerpc/boot/dts/p1022ds.dts | 11 + - arch/powerpc/configs/44x/bluestone_defconfig | 68 ++++ - arch/powerpc/configs/e55xx_smp_defconfig | 84 ++++ - arch/powerpc/configs/ppc44x_defconfig | 9 +- - arch/powerpc/configs/ppc64e_defconfig | 4 +- - arch/powerpc/include/asm/checksum.h | 10 + - arch/powerpc/include/asm/compat.h | 4 +- - arch/powerpc/include/asm/cputable.h | 14 +- - arch/powerpc/include/asm/dma-mapping.h | 14 +- - arch/powerpc/include/asm/elf.h | 2 +- - arch/powerpc/include/asm/exception-64s.h | 3 +- - arch/powerpc/include/asm/fsl_85xx_cache_sram.h | 48 +++ - arch/powerpc/include/asm/kexec.h | 1 + - arch/powerpc/include/asm/kvm_fpu.h | 4 +- - arch/powerpc/include/asm/lppaca.h | 29 ++ - arch/powerpc/include/asm/machdep.h | 3 + - arch/powerpc/include/asm/mmu-book3e.h | 15 + - arch/powerpc/include/asm/paca.h | 10 +- - arch/powerpc/include/asm/page_64.h | 4 +- - arch/powerpc/include/asm/ppc-pci.h | 4 +- - arch/powerpc/include/asm/ppc_asm.h | 50 ++- - arch/powerpc/include/asm/processor.h | 4 +- - arch/powerpc/include/asm/pte-common.h | 7 + - arch/powerpc/include/asm/rtas.h | 1 + - arch/powerpc/include/asm/systbl.h | 19 + - arch/powerpc/include/asm/system.h | 4 +- - arch/powerpc/include/asm/time.h | 5 - - arch/powerpc/include/asm/unistd.h | 21 +- - arch/powerpc/kernel/Makefile | 4 +- - arch/powerpc/kernel/align.c | 4 +- - arch/powerpc/kernel/asm-offsets.c | 12 +- - arch/powerpc/kernel/cpu_setup_44x.S | 1 + - arch/powerpc/kernel/cpu_setup_fsl_booke.S | 15 + - arch/powerpc/kernel/cputable.c | 43 ++- - arch/powerpc/kernel/crash.c | 13 +- - arch/powerpc/kernel/dma-iommu.c | 21 +- - arch/powerpc/kernel/dma.c | 20 +- - arch/powerpc/kernel/entry_64.S | 40 ++ - arch/powerpc/kernel/fpu.S | 10 - - arch/powerpc/kernel/head_fsl_booke.S | 10 +- - arch/powerpc/kernel/irq.c | 6 +- - arch/powerpc/kernel/lparcfg.c | 14 +- - arch/powerpc/kernel/machine_kexec.c | 24 ++ - arch/powerpc/kernel/machine_kexec_32.c | 4 + - arch/powerpc/kernel/paca.c | 70 ++++- - arch/powerpc/kernel/pci-common.c | 4 +- - arch/powerpc/kernel/ppc970-pmu.c | 2 + - arch/powerpc/kernel/process.c | 12 - - arch/powerpc/kernel/ptrace.c | 2 +- - arch/powerpc/kernel/rtas.c | 4 +- - arch/powerpc/kernel/setup_32.c | 2 +- - arch/powerpc/kernel/smp.c | 14 +- - arch/powerpc/kernel/time.c | 275 +++++++------- - arch/powerpc/kernel/traps.c | 5 + - arch/powerpc/kernel/vdso.c | 6 +- - arch/powerpc/kernel/vdso32/Makefile | 6 +- - arch/powerpc/kernel/vdso64/Makefile | 6 +- - arch/powerpc/kernel/vio.c | 10 +- - arch/powerpc/kvm/Makefile | 2 +- - arch/powerpc/kvm/book3s_paired_singles.c | 44 +-- - arch/powerpc/kvm/emulate.c | 4 +- - arch/powerpc/kvm/fpu.S | 8 - - arch/powerpc/lib/Makefile | 7 +- - arch/powerpc/lib/checksum_64.S | 482 +++++++++++++++++------- - arch/powerpc/lib/checksum_wrappers_64.c | 102 +++++ - arch/powerpc/lib/copy_32.S | 2 +- - arch/powerpc/lib/ldstfp.S | 36 +- - arch/powerpc/lib/locks.c | 4 +- - arch/powerpc/lib/sstep.c | 8 + - arch/powerpc/math-emu/Makefile | 2 +- - arch/powerpc/mm/Makefile | 6 +- - arch/powerpc/mm/fault.c | 6 + - arch/powerpc/mm/fsl_booke_mmu.c | 15 +- - arch/powerpc/mm/mmu_context_nohash.c | 6 +- - arch/powerpc/mm/mmu_decl.h | 5 +- - arch/powerpc/mm/tlb_nohash.c | 56 +++- - arch/powerpc/mm/tlb_nohash_low.S | 2 +- - arch/powerpc/oprofile/Makefile | 4 +- - arch/powerpc/oprofile/backtrace.c | 2 +- - arch/powerpc/oprofile/op_model_fsl_emb.c | 15 +- - arch/powerpc/platforms/44x/Kconfig | 16 + - arch/powerpc/platforms/44x/ppc44x_simple.c | 1 + - arch/powerpc/platforms/83xx/Kconfig | 4 +- - arch/powerpc/platforms/83xx/mpc830x_rdb.c | 3 +- - arch/powerpc/platforms/85xx/Kconfig | 28 ++- - arch/powerpc/platforms/85xx/Makefile | 2 + - arch/powerpc/platforms/85xx/p1022_ds.c | 2 + - arch/powerpc/platforms/85xx/p3041_ds.c | 64 ++++ - arch/powerpc/platforms/85xx/p5020_ds.c | 69 ++++ - arch/powerpc/platforms/85xx/smp.c | 83 ++++- - arch/powerpc/platforms/Kconfig.cputype | 8 +- - arch/powerpc/platforms/cell/ras.c | 4 +- - arch/powerpc/platforms/cell/spider-pic.c | 4 +- - arch/powerpc/platforms/cell/spufs/file.c | 18 + - arch/powerpc/platforms/chrp/nvram.c | 4 +- - arch/powerpc/platforms/iseries/Makefile | 2 +- - arch/powerpc/platforms/iseries/dt.c | 4 +- - arch/powerpc/platforms/iseries/smp.c | 2 +- - arch/powerpc/platforms/maple/setup.c | 1 + - arch/powerpc/platforms/powermac/pfunc_core.c | 9 +- - arch/powerpc/platforms/pseries/Makefile | 13 +- - arch/powerpc/platforms/pseries/dlpar.c | 7 +- - arch/powerpc/platforms/pseries/dtl.c | 224 +++++++++--- - arch/powerpc/platforms/pseries/lpar.c | 25 ++- - arch/powerpc/platforms/pseries/mobility.c | 362 ++++++++++++++++++ - arch/powerpc/platforms/pseries/pseries.h | 9 + - arch/powerpc/platforms/pseries/setup.c | 52 +++ - arch/powerpc/platforms/pseries/xics.c | 2 +- - arch/powerpc/sysdev/Makefile | 5 +- - arch/powerpc/sysdev/dart_iommu.c | 74 ++++- - arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h | 101 +++++ - arch/powerpc/sysdev/fsl_85xx_cache_sram.c | 159 ++++++++ - arch/powerpc/sysdev/fsl_85xx_l2ctlr.c | 231 +++++++++++ - arch/powerpc/sysdev/fsl_msi.c | 9 +- - arch/powerpc/sysdev/fsl_pci.c | 60 +++- - arch/powerpc/sysdev/fsl_pci.h | 1 + - arch/powerpc/sysdev/fsl_rio.c | 65 ++-- - arch/powerpc/sysdev/fsl_soc.c | 20 +- - arch/powerpc/sysdev/mpc8xxx_gpio.c | 3 + - arch/powerpc/sysdev/pmi.c | 2 +- - arch/powerpc/xmon/Makefile | 4 +- - drivers/i2c/busses/i2c-pasemi.c | 2 +- - drivers/macintosh/via-pmu-led.c | 4 +- - drivers/watchdog/Kconfig | 22 +- - drivers/watchdog/booke_wdt.c | 47 ++- - include/linux/pci_ids.h | 8 + - kernel/sys_ni.c | 1 + - 130 files changed, 3676 insertions(+), 683 deletions(-) - create mode 100644 arch/powerpc/boot/dts/bluestone.dts - create mode 100644 arch/powerpc/boot/dts/mpc8308_p1m.dts - create mode 100644 arch/powerpc/configs/44x/bluestone_defconfig - create mode 100644 arch/powerpc/configs/e55xx_smp_defconfig - create mode 100644 arch/powerpc/include/asm/fsl_85xx_cache_sram.h - create mode 100644 arch/powerpc/lib/checksum_wrappers_64.c - create mode 100644 arch/powerpc/platforms/85xx/p3041_ds.c - create mode 100644 arch/powerpc/platforms/85xx/p5020_ds.c - create mode 100644 arch/powerpc/platforms/pseries/mobility.c - create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h - create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_sram.c - create mode 100644 arch/powerpc/sysdev/fsl_85xx_l2ctlr.c - - -_______________________________________________ -Linuxppc-dev mailing list -Linuxppc-dev@lists.ozlabs.org -https://lists.ozlabs.org/listinfo/linuxppc-dev diff --git a/apps/patchwork/tests/mail/0007-cvs-format-diff.mbox b/apps/patchwork/tests/mail/0007-cvs-format-diff.mbox deleted file mode 100644 index 99735fa..0000000 --- a/apps/patchwork/tests/mail/0007-cvs-format-diff.mbox +++ /dev/null @@ -1,134 +0,0 @@ -Received: with ECARTIS (v1.0.0; list linux-mips); Tue, 06 Dec 2011 01:49:42 +0100 (CET) -Received: from mail3.caviumnetworks.com ([12.108.191.235]:14337 "EHLO - mail3.caviumnetworks.com" rhost-flags-OK-OK-OK-OK) - by eddie.linux-mips.org with ESMTP id S1903632Ab1LFAth (ORCPT - ); Tue, 6 Dec 2011 01:49:37 +0100 -Received: from caexch01.caveonetworks.com (Not Verified[192.168.16.9]) by mail3.caviumnetworks.com with MailMarshal (v6,7,2,8378) - id ; Mon, 05 Dec 2011 16:51:04 -0800 -Received: from caexch01.caveonetworks.com ([192.168.16.9]) by caexch01.caveonetworks.com with Microsoft SMTPSVC(6.0.3790.4675); - Mon, 5 Dec 2011 16:49:36 -0800 -Received: from dd1.caveonetworks.com ([64.2.3.195]) by caexch01.caveonetworks.com over TLS secured channel with Microsoft SMTPSVC(6.0.3790.4675); - Mon, 5 Dec 2011 16:49:35 -0800 -Message-ID: <4EDD669F.30207@cavium.com> -Date: Mon, 05 Dec 2011 16:49:35 -0800 -From: David Daney -User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.15) Gecko/20101027 Fedora/3.0.10-1.fc12 Thunderbird/3.0.10 -MIME-Version: 1.0 -To: binutils -CC: linux-mips , - Manuel Lauss , - Debian MIPS -Subject: [Patch]: Fix ld pr11138 FAILures on mips*. -Content-Type: multipart/mixed; - boundary="------------080709040708040308010506" -X-OriginalArrivalTime: 06 Dec 2011 00:49:35.0825 (UTC) FILETIME=[ECF8DC10:01CCB3B0] -Return-Path: -X-Envelope-To: <"|/home/ecartis/ecartis -s linux-mips"> (uid 0) -X-Orcpt: rfc822;linux-mips@linux-mips.org -Original-Recipient: rfc822;linux-mips@linux-mips.org -X-archive-position: 32041 -X-ecartis-version: Ecartis v1.0.0 -Sender: linux-mips-bounce@linux-mips.org -Errors-to: linux-mips-bounce@linux-mips.org -X-original-sender: david.daney@cavium.com -Precedence: bulk -X-list: linux-mips - -This is a multi-part message in MIME format. ---------------080709040708040308010506 -Content-Type: text/plain; charset=ISO-8859-1; format=flowed -Content-Transfer-Encoding: 7bit - -The pr11138 testcase links an executable with a version script. On -mips64-linux the presence of a version script was causing the -MIPS_RLD_MAP dynamic tag to be populated with a NULL value. When such -an executable was run ld.so would try to dereference this and receive -SIGSEGV, thus killing the process. - -The root cause of this is that the mips linker synthesizes a special -symbol "__RLD_MAP", and then sets MIPS_RLD_MAP to point to it. When a -version script is present, this symbol gets versioned along with all the -rest, and when it is time to take its address, the symbol can no longer -be found as it has had version information appended to its name. - -Since "__RLD_MAP" is really part of the ABI, we want to exclude it from -symbol versioning. To this end, I introduced a new symbol flag -'no_sym_version' to tag this type of symbol. When the "__RLD_MAP" -symbol is created, we set this flag. - -In _bfd_elf_link_assign_sym_version, we then skip all symbols that have -'no_sym_version' set, and everything now works. - -This problem has also been reported in the wild when linking the firefox -executable. - -Tested on mips64-linux-gnu and x86_64-linux-gnu - -Ok to commit? - -2011-12-05 David Daney - - * elf-bfd.h (elf_link_hash_entry): Add no_sym_version field. - * elflink.c (_bfd_elf_link_assign_sym_version): Don't assign a - version if no_sym_version is set. - * elfxx-mips.c (_bfd_mips_elf_create_dynamic_sections): Set - no_sym_version for "__RLD_MAP". - ---------------080709040708040308010506 -Content-Type: text/plain; - name="dd-2.patch" -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment; - filename="dd-2.patch" - -Index: bfd/elf-bfd.h -=================================================================== -RCS file: /cvs/src/src/bfd/elf-bfd.h,v -retrieving revision 1.329 -diff -u -p -r1.329 elf-bfd.h ---- bfd/elf-bfd.h 17 Aug 2011 00:39:38 -0000 1.329 -+++ bfd/elf-bfd.h 5 Dec 2011 20:15:49 -0000 -@@ -198,6 +198,8 @@ struct elf_link_hash_entry - unsigned int pointer_equality_needed : 1; - /* Symbol is a unique global symbol. */ - unsigned int unique_global : 1; -+ /* Symbol should not be versioned. It is part of the ABI */ -+ unsigned int no_sym_version : 1; - - /* String table index in .dynstr if this is a dynamic symbol. */ - unsigned long dynstr_index; -Index: bfd/elflink.c -=================================================================== -RCS file: /cvs/src/src/bfd/elflink.c,v -retrieving revision 1.430 -diff -u -p -r1.430 elflink.c ---- bfd/elflink.c 15 Nov 2011 11:33:57 -0000 1.430 -+++ bfd/elflink.c 5 Dec 2011 20:15:50 -0000 -@@ -1946,6 +1946,9 @@ _bfd_elf_link_assign_sym_version (struct - if (!h->def_regular) - return TRUE; - -+ if (h->no_sym_version) -+ return TRUE; -+ - bed = get_elf_backend_data (info->output_bfd); - p = strchr (h->root.root.string, ELF_VER_CHR); - if (p != NULL && h->verinfo.vertree == NULL) -Index: bfd/elfxx-mips.c -=================================================================== -RCS file: /cvs/src/src/bfd/elfxx-mips.c,v -retrieving revision 1.296 -diff -u -p -r1.296 elfxx-mips.c ---- bfd/elfxx-mips.c 29 Nov 2011 20:28:54 -0000 1.296 -+++ bfd/elfxx-mips.c 5 Dec 2011 20:15:50 -0000 -@@ -7260,6 +7260,7 @@ _bfd_mips_elf_create_dynamic_sections (b - h = (struct elf_link_hash_entry *) bh; - h->non_elf = 0; - h->def_regular = 1; -+ h->no_sym_version = 1; - h->type = STT_OBJECT; - - if (! bfd_elf_link_record_dynamic_symbol (info, h)) - ---------------080709040708040308010506-- - diff --git a/apps/patchwork/tests/mail/0008-git-rename.mbox b/apps/patchwork/tests/mail/0008-git-rename.mbox deleted file mode 100644 index 8277049..0000000 --- a/apps/patchwork/tests/mail/0008-git-rename.mbox +++ /dev/null @@ -1,24 +0,0 @@ -From: "Yann E. MORIN" -Subject: [Buildroot] [PATCH 01/11] package/rpi-userland: rename patches -Date: Tue, 8 Oct 2013 22:09:47 +0000 - -Rename patches to follow standard naming scheme. - -Signed-off-by: "Yann E. MORIN" ---- - ...d-pkgconfig-files.patch => rpi-userland-000-add-pkgconfig-files.patch} | 0 - ...erland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch} | 0 - 2 files changed, 0 insertions(+), 0 deletions(-) - rename package/rpi-userland/{rpi-userland-add-pkgconfig-files.patch => rpi-userland-000-add-pkgconfig-files.patch} (100%) - rename package/rpi-userland/{rpi-userland-makefiles-0001-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch => rpi-userland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch} (100%) - -diff --git a/package/rpi-userland/rpi-userland-add-pkgconfig-files.patch b/package/rpi-userland/rpi-userland-000-add-pkgconfig-files.patch -similarity index 100% -rename from package/rpi-userland/rpi-userland-add-pkgconfig-files.patch -rename to package/rpi-userland/rpi-userland-000-add-pkgconfig-files.patch -diff --git a/package/rpi-userland/rpi-userland-makefiles-0001-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch b/package/rpi-userland/rpi-userland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch -similarity index 100% -rename from package/rpi-userland/rpi-userland-makefiles-0001-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch -rename to package/rpi-userland/rpi-userland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch --- -1.8.1.2 diff --git a/apps/patchwork/tests/mail/0009-git-rename-with-diff.mbox b/apps/patchwork/tests/mail/0009-git-rename-with-diff.mbox deleted file mode 100644 index 761cfc1..0000000 --- a/apps/patchwork/tests/mail/0009-git-rename-with-diff.mbox +++ /dev/null @@ -1,32 +0,0 @@ -From: "Yann E. MORIN" -Subject: [Buildroot] [PATCH 01/11] package/rpi-userland: rename patches -Date: Tue, 8 Oct 2013 22:09:47 +0000 - -Rename patches to follow standard naming scheme. - -Signed-off-by: "Yann E. MORIN" ---- - ...d-pkgconfig-files.patch => rpi-userland-000-add-pkgconfig-files.patch} | 0 - ...erland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch} | 0 - 2 files changed, 0 insertions(+), 0 deletions(-) - rename package/rpi-userland/{rpi-userland-add-pkgconfig-files.patch => rpi-userland-000-add-pkgconfig-files.patch} (100%) - rename package/rpi-userland/{rpi-userland-makefiles-0001-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch => rpi-userland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch} (100%) - -diff --git a/package/rpi-userland/rpi-userland-add-pkgconfig-files.patch b/package/rpi-userland/rpi-userland-000-add-pkgconfig-files.patch -similarity index 100% -rename from package/rpi-userland/rpi-userland-add-pkgconfig-files.patch -rename to package/rpi-userland/rpi-userland-000-add-pkgconfig-files.patch -@@ -100,7 +100,7 @@ - a - a --a -+b - c - c - c -diff --git a/package/rpi-userland/rpi-userland-makefiles-0001-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch b/package/rpi-userland/rpi-userland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch -similarity index 100% -rename from package/rpi-userland/rpi-userland-makefiles-0001-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch -rename to package/rpi-userland/rpi-userland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch --- -1.8.1.2 diff --git a/apps/patchwork/tests/mail/0010-invalid-charset.mbox b/apps/patchwork/tests/mail/0010-invalid-charset.mbox deleted file mode 100644 index 10b369d..0000000 --- a/apps/patchwork/tests/mail/0010-invalid-charset.mbox +++ /dev/null @@ -1,90 +0,0 @@ -From libc-alpha-return-50517-siddhesh=redhat.com@sourceware.org Thu Jun 5 10:36:33 2014 -Received: (qmail 11948 invoked by alias); 4 Jun 2014 17:51:01 -0000 -Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm -List-Id: -Sender: libc-alpha-owner@sourceware.org -Date: Wed, 4 Jun 2014 17:50:46 +0000 -From: "Joseph S. Myers" -To: -Subject: Fix pow overflow in non-default rounding modes (bug 16315) -Message-ID: -MIME-Version: 1.0 -Content-Type: multipart/mixed; - boundary="-1152306461-1522705971-1401904246=:3719" -Content-Length: 24171 - ----1152306461-1522705971-1401904246=:3719 -Content-Type: text/plain; charset="none" -Content-Transfer-Encoding: QUOTED-PRINTABLE - -This patch, relative to a tree with - applied, -fixes bug 16315, bad pow handling of overflow/underflow in non-default -rounding modes. Tests of pow are duly converted to ALL_RM_TEST to run -all tests in all rounding modes. - -There are two main issues here. First, various implementations -compute a negative result by negating a positive result, but this -yields inappropriate overflow / underflow values for directed -rounding, so either overflow / underflow results need recomputing in -the correct sign, or the relevant overflowing / underflowing operation -needs to be made to have a result of the correct sign. Second, the -dbl-64 implementation sets FE_TONEAREST internally; in the overflow / -underflow case, the result needs recomputing in the original rounding -mode. - -Tested x86_64 and x86 and ulps updated accordingly. - -(auto-libm-test-out diffs omitted below.) - -2014-06-04 Joseph Myers - -=09[BZ #16315] -=09* sysdeps/i386/fpu/e_pow.S (__ieee754_pow): Ensure possibly -=09overflowing or underflowing operations take place with sign of -=09result. -=09* sysdeps/i386/fpu/e_powf.S (__ieee754_powf): Likewise. -=09* sysdeps/i386/fpu/e_powl.S (__ieee754_powl): Likewise. -=09* sysdeps/ieee754/dbl-64/e_pow.c: Include . -=09(__ieee754_pow): Recompute overflowing and underflowing results in -=09original rounding mode. -=09* sysdeps/x86/fpu/powl_helper.c: Include . -=09(__powl_helper): Allow negative argument X and scale negated value -=09as needed. Avoid passing value outside [-1, 1] to f2xm1. -=09* sysdeps/x86_64/fpu/e_powl.S (__ieee754_powl): Ensure possibly -=09overflowing or underflowing operations take place with sign of -=09result. -=09* sysdeps/x86_64/fpu/multiarch/e_pow.c [HAVE_FMA4_SUPPORT]: -=09Include . -=09* math/auto-libm-test-in: Add more tests of pow. -=09* math/auto-libm-test-out: Regenerated. -=09* math/libm-test.inc (pow_test): Use ALL_RM_TEST. -=09(pow_tonearest_test_data): Remove. -=09(pow_test_tonearest): Likewise. -=09(pow_towardzero_test_data): Likewise. -=09(pow_test_towardzero): Likewise. -=09(pow_downward_test_data): Likewise. -=09(pow_test_downward): Likewise. -=09(pow_upward_test_data): Likewise. -=09(pow_test_upward): Likewise. -=09(main): Don't call removed functions. -=09* sysdeps/i386/fpu/libm-test-ulps: Update. -=09* sysdeps/x86_64/fpu/libm-test-ulps: Likewise. - -diff --git a/sysdeps/x86_64/fpu/multiarch/e_pow.c b/sysdeps/x86_64/fpu/mult= -iarch/e_pow.c -index a740b6c..433cce0 100644 ---- a/sysdeps/x86_64/fpu/multiarch/e_pow.c -+++ b/sysdeps/x86_64/fpu/multiarch/e_pow.c -@@ -1,5 +1,6 @@ - #ifdef HAVE_FMA4_SUPPORT - # include -+# include - # include -=20 - extern double __ieee754_pow_sse2 (double, double); - ---=20 -Joseph S. Myers -joseph@codesourcery.com ----1152306461-1522705971-1401904246=:3719-- diff --git a/apps/patchwork/tests/mail/0011-no-newline-at-end-of-file.mbox b/apps/patchwork/tests/mail/0011-no-newline-at-end-of-file.mbox deleted file mode 100644 index 3ed0597..0000000 --- a/apps/patchwork/tests/mail/0011-no-newline-at-end-of-file.mbox +++ /dev/null @@ -1,45 +0,0 @@ -Subject: [PATCH v3 5/5] selftests, powerpc: Add test for VPHN -From: Greg Kurz -To: Michael Ellerman -Cc: Benjamin Herrenschmidt , - linuxppc-dev@lists.ozlabs.org -Date: Mon, 23 Feb 2015 16:14:44 +0100 -MIME-Version: 1.0 -Content-Type: text/plain; charset="utf-8" -Content-Transfer-Encoding: 8bit - -The goal is to verify vphn_unpack_associativity() parses VPHN numbers -correctly. We feed it with a variety of input values and compare with -expected results. - -diff --git a/tools/testing/selftests/powerpc/Makefile b/tools/testing/selftests/powerpc/Makefile -index 1d5e7ad..476b8dd 100644 ---- a/tools/testing/selftests/powerpc/Makefile -+++ b/tools/testing/selftests/powerpc/Makefile -@@ -13,7 +13,7 @@ CFLAGS := -Wall -O2 -flto -Wall -Werror -DGIT_VERSION='"$(GIT_VERSION)"' -I$(CUR - - export CC CFLAGS - --TARGETS = pmu copyloops mm tm primitives stringloops -+TARGETS = pmu copyloops mm tm primitives stringloops vphn - - endif - -diff --git a/tools/testing/selftests/powerpc/vphn/vphn.c b/tools/testing/selftests/powerpc/vphn/vphn.c -new file mode 120000 -index 0000000..186b906 ---- /dev/null -+++ b/tools/testing/selftests/powerpc/vphn/vphn.c -@@ -0,0 +1 @@ -+../../../../../arch/powerpc/mm/vphn.c -\ No newline at end of file -diff --git a/tools/testing/selftests/powerpc/vphn/vphn.h b/tools/testing/selftests/powerpc/vphn/vphn.h -new file mode 120000 -index 0000000..7131efe ---- /dev/null -+++ b/tools/testing/selftests/powerpc/vphn/vphn.h -@@ -0,0 +1 @@ -+../../../../../arch/powerpc/mm/vphn.h -\ No newline at end of file - - diff --git a/apps/patchwork/tests/patches/0001-add-line.patch b/apps/patchwork/tests/patches/0001-add-line.patch deleted file mode 100644 index c6cb9f1..0000000 --- a/apps/patchwork/tests/patches/0001-add-line.patch +++ /dev/null @@ -1,7 +0,0 @@ -diff --git a/meep.text b/meep.text -index 3d75d48..a57f4dd 100644 ---- a/meep.text -+++ b/meep.text -@@ -1,1 +1,2 @@ - meep -+meep diff --git a/apps/patchwork/tests/patches/0002-utf-8.patch b/apps/patchwork/tests/patches/0002-utf-8.patch deleted file mode 100644 index 71a2f24..0000000 --- a/apps/patchwork/tests/patches/0002-utf-8.patch +++ /dev/null @@ -1,7 +0,0 @@ -diff --git a/meep.text b/meep.text -index 3d75d48..a57f4dd 100644 ---- a/meep.text -+++ b/meep.text -@@ -1,1 +1,2 @@ - meep -+meëp diff --git a/apps/patchwork/tests/test_bundles.py b/apps/patchwork/tests/test_bundles.py deleted file mode 100644 index 38f3a2c..0000000 --- a/apps/patchwork/tests/test_bundles.py +++ /dev/null @@ -1,646 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2009 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import unittest -import datetime -from django.test import TestCase -from django.test.client import Client -from django.utils.http import urlencode -from django.conf import settings -from patchwork.models import Patch, Bundle, BundlePatch, Person -from patchwork.tests.utils import defaults, create_user, find_in_context - -def bundle_url(bundle): - return '/bundle/%s/%s/' % (bundle.owner.username, bundle.name) - -class BundleListTest(TestCase): - def setUp(self): - self.user = create_user() - self.client.login(username = self.user.username, - password = self.user.username) - - def testNoBundles(self): - response = self.client.get('/user/bundles/') - self.failUnlessEqual(response.status_code, 200) - self.failUnlessEqual( - len(find_in_context(response.context, 'bundles')), 0) - - def testSingleBundle(self): - defaults.project.save() - bundle = Bundle(owner = self.user, project = defaults.project) - bundle.save() - response = self.client.get('/user/bundles/') - self.failUnlessEqual(response.status_code, 200) - self.failUnlessEqual( - len(find_in_context(response.context, 'bundles')), 1) - - def tearDown(self): - self.user.delete() - -class BundleTestBase(TestCase): - def setUp(self, patch_count=3): - patch_names = ['testpatch%d' % (i) for i in range(1, patch_count+1)] - self.user = create_user() - self.client.login(username = self.user.username, - password = self.user.username) - defaults.project.save() - self.bundle = Bundle(owner = self.user, project = defaults.project, - name = 'testbundle') - self.bundle.save() - self.patches = [] - - for patch_name in patch_names: - patch = Patch(project = defaults.project, - msgid = patch_name, name = patch_name, - submitter = Person.objects.get(user = self.user), - content = '') - patch.save() - self.patches.append(patch) - - def tearDown(self): - for patch in self.patches: - patch.delete() - self.bundle.delete() - self.user.delete() - -class BundleViewTest(BundleTestBase): - - def testEmptyBundle(self): - response = self.client.get(bundle_url(self.bundle)) - self.failUnlessEqual(response.status_code, 200) - page = find_in_context(response.context, 'page') - self.failUnlessEqual(len(page.object_list), 0) - - def testNonEmptyBundle(self): - self.bundle.append_patch(self.patches[0]) - - response = self.client.get(bundle_url(self.bundle)) - self.failUnlessEqual(response.status_code, 200) - page = find_in_context(response.context, 'page') - self.failUnlessEqual(len(page.object_list), 1) - - def testBundleOrder(self): - for patch in self.patches: - self.bundle.append_patch(patch) - - response = self.client.get(bundle_url(self.bundle)) - - pos = 0 - for patch in self.patches: - next_pos = response.content.find(patch.name) - # ensure that this patch is after the previous - self.failUnless(next_pos > pos) - pos = next_pos - - # reorder and recheck - i = 0 - for patch in self.patches.__reversed__(): - bundlepatch = BundlePatch.objects.get(bundle = self.bundle, - patch = patch) - bundlepatch.order = i - bundlepatch.save() - i += 1 - - response = self.client.get(bundle_url(self.bundle)) - pos = len(response.content) - for patch in self.patches: - next_pos = response.content.find(patch.name) - # ensure that this patch is now *before* the previous - self.failUnless(next_pos < pos) - pos = next_pos - -class BundleUpdateTest(BundleTestBase): - - def setUp(self): - super(BundleUpdateTest, self).setUp() - self.newname = 'newbundlename' - - def checkPatchformErrors(self, response): - formname = 'patchform' - if not formname in response.context: - return - form = response.context[formname] - if not form: - return - self.assertEquals(form.errors, {}) - - def publicString(self, public): - if public: - return 'on' - return '' - - def testNoAction(self): - data = { - 'form': 'bundle', - 'name': self.newname, - 'public': self.publicString(not self.bundle.public) - } - response = self.client.post(bundle_url(self.bundle), data) - self.assertEqual(response.status_code, 200) - - bundle = Bundle.objects.get(pk = self.bundle.pk) - self.assertEqual(bundle.name, self.bundle.name) - self.assertEqual(bundle.public, self.bundle.public) - - def testUpdateName(self): - newname = 'newbundlename' - data = { - 'form': 'bundle', - 'action': 'update', - 'name': newname, - 'public': self.publicString(self.bundle.public) - } - response = self.client.post(bundle_url(self.bundle), data) - bundle = Bundle.objects.get(pk = self.bundle.pk) - self.assertRedirects(response, bundle_url(bundle)) - self.assertEqual(bundle.name, newname) - self.assertEqual(bundle.public, self.bundle.public) - - def testUpdatePublic(self): - newname = 'newbundlename' - data = { - 'form': 'bundle', - 'action': 'update', - 'name': self.bundle.name, - 'public': self.publicString(not self.bundle.public) - } - response = self.client.post(bundle_url(self.bundle), data) - self.assertEqual(response.status_code, 200) - bundle = Bundle.objects.get(pk = self.bundle.pk) - self.assertEqual(bundle.name, self.bundle.name) - self.assertEqual(bundle.public, not self.bundle.public) - - # check other forms for errors - self.checkPatchformErrors(response) - -class BundleMaintainerUpdateTest(BundleUpdateTest): - - def setUp(self): - super(BundleMaintainerUpdateTest, self).setUp() - profile = self.user.profile - profile.maintainer_projects.add(defaults.project) - profile.save() - -class BundlePublicViewTest(BundleTestBase): - - def setUp(self): - super(BundlePublicViewTest, self).setUp() - self.client.logout() - self.bundle.append_patch(self.patches[0]) - self.url = bundle_url(self.bundle) - - def testPublicBundle(self): - self.bundle.public = True - self.bundle.save() - response = self.client.get(self.url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, self.patches[0].name) - - def testPrivateBundle(self): - self.bundle.public = False - self.bundle.save() - response = self.client.get(self.url) - self.assertEqual(response.status_code, 404) - -class BundlePublicViewMboxTest(BundlePublicViewTest): - def setUp(self): - super(BundlePublicViewMboxTest, self).setUp() - self.url = bundle_url(self.bundle) + "mbox/" - -class BundlePublicModifyTest(BundleTestBase): - """Ensure that non-owners can't modify bundles""" - - def setUp(self): - super(BundlePublicModifyTest, self).setUp() - self.bundle.public = True - self.bundle.save() - self.other_user = create_user() - - def testBundleFormPresence(self): - """Check for presence of the modify form on the bundle""" - self.client.login(username = self.other_user.username, - password = self.other_user.username) - response = self.client.get(bundle_url(self.bundle)) - self.assertNotContains(response, 'name="form" value="bundle"') - self.assertNotContains(response, 'Change order') - - def testBundleFormSubmission(self): - oldname = 'oldbundlename' - newname = 'newbundlename' - data = { - 'form': 'bundle', - 'action': 'update', - 'name': newname, - } - self.bundle.name = oldname - self.bundle.save() - - # first, check that we can modify with the owner - self.client.login(username = self.user.username, - password = self.user.username) - response = self.client.post(bundle_url(self.bundle), data) - self.bundle = Bundle.objects.get(pk = self.bundle.pk) - self.assertEqual(self.bundle.name, newname) - - # reset bundle name - self.bundle.name = oldname - self.bundle.save() - - # log in with a different user, and check that we can no longer modify - self.client.login(username = self.other_user.username, - password = self.other_user.username) - response = self.client.post(bundle_url(self.bundle), data) - self.bundle = Bundle.objects.get(pk = self.bundle.pk) - self.assertNotEqual(self.bundle.name, newname) - -class BundleCreateFromListTest(BundleTestBase): - def testCreateEmptyBundle(self): - newbundlename = 'testbundle-new' - params = {'form': 'patchlistform', - 'bundle_name': newbundlename, - 'action': 'Create', - 'project': defaults.project.id} - - response = self.client.post( - '/project/%s/list/' % defaults.project.linkname, - params) - - self.assertContains(response, 'Bundle %s created' % newbundlename) - - def testCreateNonEmptyBundle(self): - newbundlename = 'testbundle-new' - patch = self.patches[0] - - params = {'form': 'patchlistform', - 'bundle_name': newbundlename, - 'action': 'Create', - 'project': defaults.project.id, - 'patch_id:%d' % patch.id: 'checked'} - - response = self.client.post( - '/project/%s/list/' % defaults.project.linkname, - params) - - self.assertContains(response, 'Bundle %s created' % newbundlename) - self.assertContains(response, 'added to bundle %s' % newbundlename, - count = 1) - - bundle = Bundle.objects.get(name = newbundlename) - self.failUnlessEqual(bundle.patches.count(), 1) - self.failUnlessEqual(bundle.patches.all()[0], patch) - - def testCreateNonEmptyBundleEmptyName(self): - newbundlename = 'testbundle-new' - patch = self.patches[0] - - n_bundles = Bundle.objects.count() - - params = {'form': 'patchlistform', - 'bundle_name': '', - 'action': 'Create', - 'project': defaults.project.id, - 'patch_id:%d' % patch.id: 'checked'} - - response = self.client.post( - '/project/%s/list/' % defaults.project.linkname, - params) - - self.assertContains(response, 'No bundle name was specified', - status_code = 200) - - # test that no new bundles are present - self.failUnlessEqual(n_bundles, Bundle.objects.count()) - - def testCreateDuplicateName(self): - newbundlename = 'testbundle-dup' - patch = self.patches[0] - - params = {'form': 'patchlistform', - 'bundle_name': newbundlename, - 'action': 'Create', - 'project': defaults.project.id, - 'patch_id:%d' % patch.id: 'checked'} - - response = self.client.post( - '/project/%s/list/' % defaults.project.linkname, - params) - - n_bundles = Bundle.objects.count() - self.assertContains(response, 'Bundle %s created' % newbundlename) - self.assertContains(response, 'added to bundle %s' % newbundlename, - count = 1) - - bundle = Bundle.objects.get(name = newbundlename) - self.failUnlessEqual(bundle.patches.count(), 1) - self.failUnlessEqual(bundle.patches.all()[0], patch) - - response = self.client.post( - '/project/%s/list/' % defaults.project.linkname, - params) - - self.assertNotContains(response, 'Bundle %s created' % newbundlename) - self.assertContains(response, 'You already have a bundle called') - self.assertEqual(Bundle.objects.count(), n_bundles) - self.assertEqual(bundle.patches.count(), 1) - -class BundleCreateFromPatchTest(BundleTestBase): - def testCreateNonEmptyBundle(self): - newbundlename = 'testbundle-new' - patch = self.patches[0] - - params = {'name': newbundlename, - 'action': 'createbundle'} - - response = self.client.post('/patch/%d/' % patch.id, params) - - self.assertContains(response, - 'Bundle %s created' % newbundlename) - - bundle = Bundle.objects.get(name = newbundlename) - self.failUnlessEqual(bundle.patches.count(), 1) - self.failUnlessEqual(bundle.patches.all()[0], patch) - - def testCreateWithExistingName(self): - newbundlename = self.bundle.name - patch = self.patches[0] - - params = {'name': newbundlename, - 'action': 'createbundle'} - - response = self.client.post('/patch/%d/' % patch.id, params) - - self.assertContains(response, - 'A bundle called %s already exists' % newbundlename) - - count = Bundle.objects.count() - self.failUnlessEqual(Bundle.objects.count(), 1) - -class BundleAddFromListTest(BundleTestBase): - def testAddToEmptyBundle(self): - patch = self.patches[0] - params = {'form': 'patchlistform', - 'action': 'Add', - 'project': defaults.project.id, - 'bundle_id': self.bundle.id, - 'patch_id:%d' % patch.id: 'checked'} - - response = self.client.post( - '/project/%s/list/' % defaults.project.linkname, - params) - - self.assertContains(response, 'added to bundle %s' % self.bundle.name, - count = 1) - - self.failUnlessEqual(self.bundle.patches.count(), 1) - self.failUnlessEqual(self.bundle.patches.all()[0], patch) - - def testAddToNonEmptyBundle(self): - self.bundle.append_patch(self.patches[0]) - patch = self.patches[1] - params = {'form': 'patchlistform', - 'action': 'Add', - 'project': defaults.project.id, - 'bundle_id': self.bundle.id, - 'patch_id:%d' % patch.id: 'checked'} - - response = self.client.post( - '/project/%s/list/' % defaults.project.linkname, - params) - - self.assertContains(response, 'added to bundle %s' % self.bundle.name, - count = 1) - - self.failUnlessEqual(self.bundle.patches.count(), 2) - self.failUnless(self.patches[0] in self.bundle.patches.all()) - self.failUnless(self.patches[1] in self.bundle.patches.all()) - - # check order - bps = [ BundlePatch.objects.get(bundle = self.bundle, - patch = self.patches[i]) \ - for i in [0, 1] ] - self.failUnless(bps[0].order < bps[1].order) - - def testAddDuplicate(self): - self.bundle.append_patch(self.patches[0]) - count = self.bundle.patches.count() - patch = self.patches[0] - - params = {'form': 'patchlistform', - 'action': 'Add', - 'project': defaults.project.id, - 'bundle_id': self.bundle.id, - 'patch_id:%d' % patch.id: 'checked'} - - response = self.client.post( - '/project/%s/list/' % defaults.project.linkname, - params) - - self.assertContains(response, 'Patch '%s' already in bundle' \ - % patch.name, count = 1, status_code = 200) - - self.assertEquals(count, self.bundle.patches.count()) - - def testAddNewAndDuplicate(self): - self.bundle.append_patch(self.patches[0]) - count = self.bundle.patches.count() - patch = self.patches[0] - - params = {'form': 'patchlistform', - 'action': 'Add', - 'project': defaults.project.id, - 'bundle_id': self.bundle.id, - 'patch_id:%d' % patch.id: 'checked', - 'patch_id:%d' % self.patches[1].id: 'checked'} - - response = self.client.post( - '/project/%s/list/' % defaults.project.linkname, - params) - - self.assertContains(response, 'Patch '%s' already in bundle' \ - % patch.name, count = 1, status_code = 200) - self.assertContains(response, 'Patch '%s' added to bundle' \ - % self.patches[1].name, count = 1, - status_code = 200) - self.assertEquals(count + 1, self.bundle.patches.count()) - -class BundleAddFromPatchTest(BundleTestBase): - def testAddToEmptyBundle(self): - patch = self.patches[0] - params = {'action': 'addtobundle', - 'bundle_id': self.bundle.id} - - response = self.client.post('/patch/%d/' % patch.id, params) - - self.assertContains(response, - 'added to bundle "%s"' % self.bundle.name, - count = 1) - - self.failUnlessEqual(self.bundle.patches.count(), 1) - self.failUnlessEqual(self.bundle.patches.all()[0], patch) - - def testAddToNonEmptyBundle(self): - self.bundle.append_patch(self.patches[0]) - patch = self.patches[1] - params = {'action': 'addtobundle', - 'bundle_id': self.bundle.id} - - response = self.client.post('/patch/%d/' % patch.id, params) - - self.assertContains(response, - 'added to bundle "%s"' % self.bundle.name, - count = 1) - - self.failUnlessEqual(self.bundle.patches.count(), 2) - self.failUnless(self.patches[0] in self.bundle.patches.all()) - self.failUnless(self.patches[1] in self.bundle.patches.all()) - - # check order - bps = [ BundlePatch.objects.get(bundle = self.bundle, - patch = self.patches[i]) \ - for i in [0, 1] ] - self.failUnless(bps[0].order < bps[1].order) - -class BundleInitialOrderTest(BundleTestBase): - """When creating bundles from a patch list, ensure that the patches in the - bundle are ordered by date""" - - def setUp(self): - super(BundleInitialOrderTest, self).setUp(5) - - # put patches in an arbitrary order - idxs = [2, 4, 3, 1, 0] - self.patches = [ self.patches[i] for i in idxs ] - - # set dates to be sequential - last_patch = self.patches[0] - for patch in self.patches[1:]: - patch.date = last_patch.date + datetime.timedelta(0, 1) - patch.save() - last_patch = patch - - def _testOrder(self, ids, expected_order): - newbundlename = 'testbundle-new' - - # need to define our querystring explicity to enforce ordering - params = {'form': 'patchlistform', - 'bundle_name': newbundlename, - 'action': 'Create', - 'project': defaults.project.id, - } - - data = urlencode(params) + \ - ''.join([ '&patch_id:%d=checked' % i for i in ids ]) - - response = self.client.post( - '/project/%s/list/' % defaults.project.linkname, - data = data, - content_type = 'application/x-www-form-urlencoded', - ) - - self.assertContains(response, 'Bundle %s created' % newbundlename) - self.assertContains(response, 'added to bundle %s' % newbundlename, - count = 5) - - bundle = Bundle.objects.get(name = newbundlename) - - # BundlePatches should be sorted by .order by default - bps = BundlePatch.objects.filter(bundle = bundle) - - for (bp, p) in zip(bps, expected_order): - self.assertEqual(bp.patch.pk, p.pk) - - bundle.delete() - - def testBundleForwardOrder(self): - ids = map(lambda p: p.id, self.patches) - self._testOrder(ids, self.patches) - - def testBundleReverseOrder(self): - ids = map(lambda p: p.id, self.patches) - ids.reverse() - self._testOrder(ids, self.patches) - -class BundleReorderTest(BundleTestBase): - def setUp(self): - super(BundleReorderTest, self).setUp(5) - for i in range(5): - self.bundle.append_patch(self.patches[i]) - - def checkReordering(self, neworder, start, end): - neworder_ids = [ self.patches[i].id for i in neworder ] - - firstpatch = BundlePatch.objects.get(bundle = self.bundle, - patch = self.patches[start]).patch - - slice_ids = neworder_ids[start:end] - params = {'form': 'reorderform', - 'order_start': firstpatch.id, - 'neworder': slice_ids} - - response = self.client.post(bundle_url(self.bundle), params) - - self.failUnlessEqual(response.status_code, 200) - - bps = BundlePatch.objects.filter(bundle = self.bundle) \ - .order_by('order') - - # check if patch IDs are in the expected order: - bundle_ids = [ bp.patch.id for bp in bps ] - self.failUnlessEqual(neworder_ids, bundle_ids) - - # check if order field is still sequential: - order_numbers = [ bp.order for bp in bps ] - expected_order = range(1, len(neworder)+1) # [1 ... len(neworder)] - self.failUnlessEqual(order_numbers, expected_order) - - def testBundleReorderAll(self): - # reorder all patches: - self.checkReordering([2,1,4,0,3], 0, 5) - - def testBundleReorderEnd(self): - # reorder only the last three patches - self.checkReordering([0,1,3,2,4], 2, 5) - - def testBundleReorderBegin(self): - # reorder only the first three patches - self.checkReordering([2,0,1,3,4], 0, 3) - - def testBundleReorderMiddle(self): - # reorder only 2nd, 3rd, and 4th patches - self.checkReordering([0,2,3,1,4], 1, 4) - -class BundleRedirTest(BundleTestBase): - # old URL: private bundles used to be under /user/bundle/ - - def setUp(self): - super(BundleRedirTest, self).setUp() - - @unittest.skipIf(not settings.COMPAT_REDIR, "compat redirections disabled") - def testBundleRedir(self): - url = '/user/bundle/%d/' % self.bundle.id - response = self.client.get(url) - self.assertRedirects(response, bundle_url(self.bundle)) - - @unittest.skipIf(not settings.COMPAT_REDIR, "compat redirections disabled") - def testMboxRedir(self): - url = '/user/bundle/%d/mbox/' % self.bundle.id - response = self.client.get(url) - self.assertRedirects(response,'/bundle/%s/%s/mbox/' % - (self.bundle.owner.username, - self.bundle.name)) diff --git a/apps/patchwork/tests/test_confirm.py b/apps/patchwork/tests/test_confirm.py deleted file mode 100644 index fad5125..0000000 --- a/apps/patchwork/tests/test_confirm.py +++ /dev/null @@ -1,67 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2011 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import unittest -from django.test import TestCase -from django.contrib.auth.models import User -from django.core.urlresolvers import reverse -from patchwork.models import EmailConfirmation, Person - -def _confirmation_url(conf): - return reverse('patchwork.views.confirm', kwargs = {'key': conf.key}) - -class TestUser(object): - username = 'testuser' - email = 'test@example.com' - secondary_email = 'test2@example.com' - password = None - - def __init__(self): - self.password = User.objects.make_random_password() - self.user = User.objects.create_user(self.username, - self.email, self.password) - -class InvalidConfirmationTest(TestCase): - def setUp(self): - EmailConfirmation.objects.all().delete() - Person.objects.all().delete() - self.user = TestUser() - self.conf = EmailConfirmation(type = 'userperson', - email = self.user.secondary_email, - user = self.user.user) - self.conf.save() - - def testInactiveConfirmation(self): - self.conf.active = False - self.conf.save() - response = self.client.get(_confirmation_url(self.conf)) - self.assertEquals(response.status_code, 200) - self.assertTemplateUsed(response, 'patchwork/confirm-error.html') - self.assertEqual(response.context['error'], 'inactive') - self.assertEqual(response.context['conf'], self.conf) - - def testExpiredConfirmation(self): - self.conf.date -= self.conf.validity - self.conf.save() - response = self.client.get(_confirmation_url(self.conf)) - self.assertEquals(response.status_code, 200) - self.assertTemplateUsed(response, 'patchwork/confirm-error.html') - self.assertEqual(response.context['error'], 'expired') - self.assertEqual(response.context['conf'], self.conf) - diff --git a/apps/patchwork/tests/test_encodings.py b/apps/patchwork/tests/test_encodings.py deleted file mode 100644 index b9032bb..0000000 --- a/apps/patchwork/tests/test_encodings.py +++ /dev/null @@ -1,87 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import unittest -import os -import time -from patchwork.models import Patch, Person -from patchwork.tests.utils import defaults, read_patch -from django.test import TestCase -from django.test.client import Client - -class UTF8PatchViewTest(TestCase): - patch_filename = '0002-utf-8.patch' - patch_encoding = 'utf-8' - - def setUp(self): - defaults.project.save() - defaults.patch_author_person.save() - self.patch_content = read_patch(self.patch_filename, - encoding = self.patch_encoding) - self.patch = Patch(project = defaults.project, - msgid = 'x', name = defaults.patch_name, - submitter = defaults.patch_author_person, - content = self.patch_content) - self.patch.save() - self.client = Client() - - def testPatchView(self): - response = self.client.get('/patch/%d/' % self.patch.id) - self.assertContains(response, self.patch.name) - - def testMboxView(self): - response = self.client.get('/patch/%d/mbox/' % self.patch.id) - self.assertEquals(response.status_code, 200) - self.assertTrue(self.patch.content in \ - response.content.decode(self.patch_encoding)) - - def testRawView(self): - response = self.client.get('/patch/%d/raw/' % self.patch.id) - self.assertEquals(response.status_code, 200) - self.assertEquals(response.content.decode(self.patch_encoding), - self.patch.content) - - def tearDown(self): - self.patch.delete() - defaults.patch_author_person.delete() - defaults.project.delete() - -class UTF8HeaderPatchViewTest(UTF8PatchViewTest): - patch_filename = '0002-utf-8.patch' - patch_encoding = 'utf-8' - patch_author_name = u'P\xe4tch Author' - - def setUp(self): - defaults.project.save() - self.patch_author = Person(name = self.patch_author_name, - email = defaults.patch_author_person.email) - self.patch_author.save() - self.patch_content = read_patch(self.patch_filename, - encoding = self.patch_encoding) - self.patch = Patch(project = defaults.project, - msgid = 'x', name = defaults.patch_name, - submitter = self.patch_author, - content = self.patch_content) - self.patch.save() - self.client = Client() - - def tearDown(self): - self.patch.delete() - self.patch_author.delete() - defaults.project.delete() diff --git a/apps/patchwork/tests/test_expiry.py b/apps/patchwork/tests/test_expiry.py deleted file mode 100644 index 844ed4b..0000000 --- a/apps/patchwork/tests/test_expiry.py +++ /dev/null @@ -1,121 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2014 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import unittest -import datetime -from django.test import TestCase -from django.contrib.auth.models import User -from patchwork.models import EmailConfirmation, Person, Patch -from patchwork.tests.utils import create_user, defaults -from patchwork.utils import do_expiry - -class TestRegistrationExpiry(TestCase): - - def register(self, date): - user = create_user() - user.is_active = False - user.date_joined = user.last_login = date - user.save() - - conf = EmailConfirmation(type='registration', user=user, - email=user.email) - conf.date = date - conf.save() - - return (user, conf) - - def testOldRegistrationExpiry(self): - date = ((datetime.datetime.now() - EmailConfirmation.validity) - - datetime.timedelta(hours = 1)) - (user, conf) = self.register(date) - - do_expiry() - - self.assertFalse(User.objects.filter(pk = user.pk).exists()) - self.assertFalse(EmailConfirmation.objects.filter(pk = conf.pk) - .exists()) - - - def testRecentRegistrationExpiry(self): - date = ((datetime.datetime.now() - EmailConfirmation.validity) + - datetime.timedelta(hours = 1)) - (user, conf) = self.register(date) - - do_expiry() - - self.assertTrue(User.objects.filter(pk = user.pk).exists()) - self.assertTrue(EmailConfirmation.objects.filter(pk = conf.pk) - .exists()) - - def testInactiveRegistrationExpiry(self): - (user, conf) = self.register(datetime.datetime.now()) - - # confirm registration - conf.user.is_active = True - conf.user.save() - conf.deactivate() - - do_expiry() - - self.assertTrue(User.objects.filter(pk = user.pk).exists()) - self.assertFalse(EmailConfirmation.objects.filter(pk = conf.pk) - .exists()) - - def testPatchSubmitterExpiry(self): - defaults.project.save() - defaults.patch_author_person.save() - - # someone submits a patch... - patch = Patch(project = defaults.project, - msgid = 'test@example.com', name = 'test patch', - submitter = defaults.patch_author_person, - content = defaults.patch) - patch.save() - - # ... then starts registration... - date = ((datetime.datetime.now() - EmailConfirmation.validity) - - datetime.timedelta(hours = 1)) - userid = 'test-user' - user = User.objects.create_user(userid, - defaults.patch_author_person.email, userid) - user.is_active = False - user.date_joined = user.last_login = date - user.save() - - self.assertEqual(user.email, patch.submitter.email) - - conf = EmailConfirmation(type='registration', user=user, - email=user.email) - conf.date = date - conf.save() - - # ... which expires - do_expiry() - - # we should see no matching user - self.assertFalse(User.objects.filter(email = patch.submitter.email) - .exists()) - # but the patch and person should still be present - self.assertTrue(Person.objects.filter( - pk = defaults.patch_author_person.pk).exists()) - self.assertTrue(Patch.objects.filter(pk = patch.pk).exists()) - - # and there should be no user associated with the person - self.assertEqual(Person.objects.get(pk = - defaults.patch_author_person.pk).user, None) diff --git a/apps/patchwork/tests/test_filters.py b/apps/patchwork/tests/test_filters.py deleted file mode 100644 index 2c464e5..0000000 --- a/apps/patchwork/tests/test_filters.py +++ /dev/null @@ -1,45 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2011 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import unittest -from django.test import TestCase -from django.test.client import Client -from patchwork.tests.utils import defaults, create_user, find_in_context - -class FilterQueryStringTest(TestCase): - def testFilterQSEscaping(self): - """test that filter fragments in a query string are properly escaped, - and stray ampersands don't get reflected back in the filter - links""" - project = defaults.project - defaults.project.save() - url = '/project/%s/list/?submitter=a%%26b=c' % project.linkname - response = self.client.get(url) - self.failUnlessEqual(response.status_code, 200) - self.failIf('submitter=a&b=c' in response.content) - self.failIf('submitter=a&b=c' in response.content) - - def testUTF8QSHandling(self): - """test that non-ascii characters can be handled by the filter - code""" - project = defaults.project - defaults.project.save() - url = '/project/%s/list/?submitter=%%E2%%98%%83' % project.linkname - response = self.client.get(url) - self.failUnlessEqual(response.status_code, 200) diff --git a/apps/patchwork/tests/test_list.py b/apps/patchwork/tests/test_list.py deleted file mode 100644 index a795a5f..0000000 --- a/apps/patchwork/tests/test_list.py +++ /dev/null @@ -1,116 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2012 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import unittest -import random -import datetime -import string -import re -from django.test import TestCase -from django.test.client import Client -from patchwork.tests.utils import defaults, create_user, find_in_context -from patchwork.models import Person, Patch -from django.core.urlresolvers import reverse - -class EmptyPatchListTest(TestCase): - - def testEmptyPatchList(self): - """test that we don't output an empty table when there are no - patches present""" - project = defaults.project - defaults.project.save() - url = reverse('patchwork.views.patch.list', - kwargs={'project_id': project.linkname}) - response = self.client.get(url) - self.assertContains(response, 'No patches to display') - self.assertNotContains(response, 'tbody') - -class PatchOrderTest(TestCase): - - d = datetime.datetime - patchmeta = [ - ('AlCMyjOsx', 'AlxMyjOsx@nRbqkQV.wBw', d(2014,3,16,13, 4,50, 155643)), - ('MMZnrcDjT', 'MMmnrcDjT@qGaIfOl.tbk', d(2014,1,25,13, 4,50, 162814)), - ('WGirwRXgK', 'WGSrwRXgK@TriIETY.GhE', d(2014,2,14,13, 4,50, 169305)), - ('isjNIuiAc', 'issNIuiAc@OsEirYx.EJh', d(2014,3,15,13, 4,50, 176264)), - ('XkAQpYGws', 'XkFQpYGws@hzntTcm.JSE', d(2014,1,18,13, 4,50, 182493)), - ('uJuCPWMvi', 'uJACPWMvi@AVRBOBl.ecy', d(2014,3,12,13, 4,50, 189554)), - ('TyQmWtcbg', 'TylmWtcbg@DzrNeNH.JuB', d(2014,2, 3,13, 4,50, 195685)), - ('FpvAhWRdX', 'FpKAhWRdX@agxnCAI.wFO', d(2014,3,15,13, 4,50, 201398)), - ('bmoYvnyWa', 'bmdYvnyWa@aeoPnlX.juy', d(2014,3, 4,13, 4,50, 206800)), - ('CiReUQsAq', 'CiieUQsAq@DnOYRuf.TTI', d(2014,3,28,13, 4,50, 212169)), - ] - - def setUp(self): - defaults.project.save() - - for (name, email, date) in self.patchmeta: - patch_name = 'testpatch' + name - person = Person(name = name, email = email) - person.save() - patch = Patch(project = defaults.project, msgid = patch_name, - submitter = person, content = '', date = date) - patch.save() - - def _extract_patch_ids(self, response): - id_re = re.compile(' -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import unittest -import re -from django.test import TestCase -from django.test.client import Client -from django.core import mail -from django.core.urlresolvers import reverse -from django.contrib.auth.models import User -from patchwork.models import EmailOptout, EmailConfirmation, Person -from patchwork.tests.utils import create_user, error_strings - -class MailSettingsTest(TestCase): - view = 'patchwork.views.mail.settings' - url = reverse(view) - - def testMailSettingsGET(self): - response = self.client.get(self.url) - self.assertEquals(response.status_code, 200) - self.assertTrue(response.context['form']) - - def testMailSettingsPOST(self): - email = u'foo@example.com' - response = self.client.post(self.url, {'email': email}) - self.assertEquals(response.status_code, 200) - self.assertTemplateUsed(response, 'patchwork/mail-settings.html') - self.assertEquals(response.context['email'], email) - - def testMailSettingsPOSTEmpty(self): - response = self.client.post(self.url, {'email': ''}) - self.assertEquals(response.status_code, 200) - self.assertTemplateUsed(response, 'patchwork/mail-form.html') - self.assertFormError(response, 'form', 'email', - 'This field is required.') - - def testMailSettingsPOSTInvalid(self): - response = self.client.post(self.url, {'email': 'foo'}) - self.assertEquals(response.status_code, 200) - self.assertTemplateUsed(response, 'patchwork/mail-form.html') - self.assertFormError(response, 'form', 'email', error_strings['email']) - - def testMailSettingsPOSTOptedIn(self): - email = u'foo@example.com' - response = self.client.post(self.url, {'email': email}) - self.assertEquals(response.status_code, 200) - self.assertTemplateUsed(response, 'patchwork/mail-settings.html') - self.assertEquals(response.context['is_optout'], False) - self.assertTrue('may' in response.content) - optout_url = reverse('patchwork.views.mail.optout') - self.assertTrue(('action="%s"' % optout_url) in response.content) - - def testMailSettingsPOSTOptedOut(self): - email = u'foo@example.com' - EmailOptout(email = email).save() - response = self.client.post(self.url, {'email': email}) - self.assertEquals(response.status_code, 200) - self.assertTemplateUsed(response, 'patchwork/mail-settings.html') - self.assertEquals(response.context['is_optout'], True) - self.assertTrue('may not' in response.content) - optin_url = reverse('patchwork.views.mail.optin') - self.assertTrue(('action="%s"' % optin_url) in response.content) - -class OptoutRequestTest(TestCase): - view = 'patchwork.views.mail.optout' - url = reverse(view) - - def testOptOutRequestGET(self): - response = self.client.get(self.url) - self.assertRedirects(response, reverse('patchwork.views.mail.settings')) - - def testOptoutRequestValidPOST(self): - email = u'foo@example.com' - response = self.client.post(self.url, {'email': email}) - - # check for a confirmation object - self.assertEquals(EmailConfirmation.objects.count(), 1) - conf = EmailConfirmation.objects.get(email = email) - - # check confirmation page - self.assertEquals(response.status_code, 200) - self.assertEquals(response.context['confirmation'], conf) - self.assertTrue(email in response.content) - - # check email - url = reverse('patchwork.views.confirm', kwargs = {'key': conf.key}) - self.assertEquals(len(mail.outbox), 1) - msg = mail.outbox[0] - self.assertEquals(msg.to, [email]) - self.assertEquals(msg.subject, 'Patchwork opt-out confirmation') - self.assertTrue(url in msg.body) - - def testOptoutRequestInvalidPOSTEmpty(self): - response = self.client.post(self.url, {'email': ''}) - self.assertEquals(response.status_code, 200) - self.assertFormError(response, 'form', 'email', - 'This field is required.') - self.assertTrue(response.context['error']) - self.assertTrue('email_sent' not in response.context) - self.assertEquals(len(mail.outbox), 0) - - def testOptoutRequestInvalidPOSTNonEmail(self): - response = self.client.post(self.url, {'email': 'foo'}) - self.assertEquals(response.status_code, 200) - self.assertFormError(response, 'form', 'email', error_strings['email']) - self.assertTrue(response.context['error']) - self.assertTrue('email_sent' not in response.context) - self.assertEquals(len(mail.outbox), 0) - -class OptoutTest(TestCase): - view = 'patchwork.views.mail.optout' - url = reverse(view) - - def setUp(self): - self.email = u'foo@example.com' - self.conf = EmailConfirmation(type = 'optout', email = self.email) - self.conf.save() - - def testOptoutValidHash(self): - url = reverse('patchwork.views.confirm', - kwargs = {'key': self.conf.key}) - response = self.client.get(url) - - self.assertEquals(response.status_code, 200) - self.assertTemplateUsed(response, 'patchwork/optout.html') - self.assertTrue(self.email in response.content) - - # check that we've got an optout in the list - self.assertEquals(EmailOptout.objects.count(), 1) - self.assertEquals(EmailOptout.objects.all()[0].email, self.email) - - # check that the confirmation is now inactive - self.assertFalse(EmailConfirmation.objects.get( - pk = self.conf.pk).active) - - -class OptoutPreexistingTest(OptoutTest): - """Test that a duplicated opt-out behaves the same as the initial one""" - def setUp(self): - super(OptoutPreexistingTest, self).setUp() - EmailOptout(email = self.email).save() - -class OptinRequestTest(TestCase): - view = 'patchwork.views.mail.optin' - url = reverse(view) - - def setUp(self): - self.email = u'foo@example.com' - EmailOptout(email = self.email).save() - - def testOptInRequestGET(self): - response = self.client.get(self.url) - self.assertRedirects(response, reverse('patchwork.views.mail.settings')) - - def testOptInRequestValidPOST(self): - response = self.client.post(self.url, {'email': self.email}) - - # check for a confirmation object - self.assertEquals(EmailConfirmation.objects.count(), 1) - conf = EmailConfirmation.objects.get(email = self.email) - - # check confirmation page - self.assertEquals(response.status_code, 200) - self.assertEquals(response.context['confirmation'], conf) - self.assertTrue(self.email in response.content) - - # check email - url = reverse('patchwork.views.confirm', kwargs = {'key': conf.key}) - self.assertEquals(len(mail.outbox), 1) - msg = mail.outbox[0] - self.assertEquals(msg.to, [self.email]) - self.assertEquals(msg.subject, 'Patchwork opt-in confirmation') - self.assertTrue(url in msg.body) - - def testOptoutRequestInvalidPOSTEmpty(self): - response = self.client.post(self.url, {'email': ''}) - self.assertEquals(response.status_code, 200) - self.assertFormError(response, 'form', 'email', - 'This field is required.') - self.assertTrue(response.context['error']) - self.assertTrue('email_sent' not in response.context) - self.assertEquals(len(mail.outbox), 0) - - def testOptoutRequestInvalidPOSTNonEmail(self): - response = self.client.post(self.url, {'email': 'foo'}) - self.assertEquals(response.status_code, 200) - self.assertFormError(response, 'form', 'email', error_strings['email']) - self.assertTrue(response.context['error']) - self.assertTrue('email_sent' not in response.context) - self.assertEquals(len(mail.outbox), 0) - -class OptinTest(TestCase): - - def setUp(self): - self.email = u'foo@example.com' - self.optout = EmailOptout(email = self.email) - self.optout.save() - self.conf = EmailConfirmation(type = 'optin', email = self.email) - self.conf.save() - - def testOptinValidHash(self): - url = reverse('patchwork.views.confirm', - kwargs = {'key': self.conf.key}) - response = self.client.get(url) - - self.assertEquals(response.status_code, 200) - self.assertTemplateUsed(response, 'patchwork/optin.html') - self.assertTrue(self.email in response.content) - - # check that there's no optout remaining - self.assertEquals(EmailOptout.objects.count(), 0) - - # check that the confirmation is now inactive - self.assertFalse(EmailConfirmation.objects.get( - pk = self.conf.pk).active) - -class OptinWithoutOptoutTest(TestCase): - """Test an opt-in with no existing opt-out""" - view = 'patchwork.views.mail.optin' - url = reverse(view) - - def testOptInWithoutOptout(self): - email = u'foo@example.com' - response = self.client.post(self.url, {'email': email}) - - # check for an error message - self.assertEquals(response.status_code, 200) - self.assertTrue(bool(response.context['error'])) - self.assertTrue('not on the patchwork opt-out list' in response.content) - -class UserProfileOptoutFormTest(TestCase): - """Test that the correct optin/optout forms appear on the user profile - page, for logged-in users""" - - view = 'patchwork.views.user.profile' - url = reverse(view) - optout_url = reverse('patchwork.views.mail.optout') - optin_url = reverse('patchwork.views.mail.optin') - form_re_template = (']*action="%(url)s"[^>]*>' - '.*?]*value="%(email)s"[^>]*>.*?' - '') - secondary_email = 'test2@example.com' - - def setUp(self): - self.user = create_user() - self.client.login(username = self.user.username, - password = self.user.username) - - def _form_re(self, url, email): - return re.compile(self.form_re_template % {'url': url, 'email': email}, - re.DOTALL) - - def testMainEmailOptoutForm(self): - form_re = self._form_re(self.optout_url, self.user.email) - response = self.client.get(self.url) - self.assertEquals(response.status_code, 200) - self.assertTrue(form_re.search(response.content) is not None) - - def testMainEmailOptinForm(self): - EmailOptout(email = self.user.email).save() - form_re = self._form_re(self.optin_url, self.user.email) - response = self.client.get(self.url) - self.assertEquals(response.status_code, 200) - self.assertTrue(form_re.search(response.content) is not None) - - def testSecondaryEmailOptoutForm(self): - p = Person(email = self.secondary_email, user = self.user) - p.save() - - form_re = self._form_re(self.optout_url, p.email) - response = self.client.get(self.url) - self.assertEquals(response.status_code, 200) - self.assertTrue(form_re.search(response.content) is not None) - - def testSecondaryEmailOptinForm(self): - p = Person(email = self.secondary_email, user = self.user) - p.save() - EmailOptout(email = p.email).save() - - form_re = self._form_re(self.optin_url, p.email) - response = self.client.get(self.url) - self.assertEquals(response.status_code, 200) - self.assertTrue(form_re.search(response.content) is not None) diff --git a/apps/patchwork/tests/test_mboxviews.py b/apps/patchwork/tests/test_mboxviews.py deleted file mode 100644 index 0e57f42..0000000 --- a/apps/patchwork/tests/test_mboxviews.py +++ /dev/null @@ -1,209 +0,0 @@ -# vim: set fileencoding=utf-8 : -# -# Patchwork - automated patch tracking system -# Copyright (C) 2009 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import unittest -import email -import datetime -import dateutil.parser, dateutil.tz -from django.test import TestCase -from django.test.client import Client -from patchwork.models import Patch, Comment, Person -from patchwork.tests.utils import defaults, create_user, find_in_context - -class MboxPatchResponseTest(TestCase): - """ Test that the mbox view appends the Acked-by from a patch comment """ - def setUp(self): - defaults.project.save() - - self.person = defaults.patch_author_person - self.person.save() - - self.patch = Patch(project = defaults.project, - msgid = 'p1', name = 'testpatch', - submitter = self.person, content = '') - self.patch.save() - comment = Comment(patch = self.patch, msgid = 'p1', - submitter = self.person, - content = 'comment 1 text\nAcked-by: 1\n') - comment.save() - - comment = Comment(patch = self.patch, msgid = 'p2', - submitter = self.person, - content = 'comment 2 text\nAcked-by: 2\n') - comment.save() - - def testPatchResponse(self): - response = self.client.get('/patch/%d/mbox/' % self.patch.id) - self.assertContains(response, - 'Acked-by: 1\nAcked-by: 2\n') - -class MboxPatchSplitResponseTest(TestCase): - """ Test that the mbox view appends the Acked-by from a patch comment, - and places it before an '---' update line. """ - def setUp(self): - defaults.project.save() - - self.person = defaults.patch_author_person - self.person.save() - - self.patch = Patch(project = defaults.project, - msgid = 'p1', name = 'testpatch', - submitter = self.person, content = '') - self.patch.save() - comment = Comment(patch = self.patch, msgid = 'p1', - submitter = self.person, - content = 'comment 1 text\nAcked-by: 1\n---\nupdate\n') - comment.save() - - comment = Comment(patch = self.patch, msgid = 'p2', - submitter = self.person, - content = 'comment 2 text\nAcked-by: 2\n') - comment.save() - - def testPatchResponse(self): - response = self.client.get('/patch/%d/mbox/' % self.patch.id) - self.assertContains(response, - 'Acked-by: 1\nAcked-by: 2\n') - -class MboxPassThroughHeaderTest(TestCase): - """ Test that we see 'Cc' and 'To' headers passed through from original - message to mbox view """ - - def setUp(self): - defaults.project.save() - self.person = defaults.patch_author_person - self.person.save() - - self.cc_header = 'Cc: CC Person ' - self.to_header = 'To: To Person ' - self.date_header = 'Date: Fri, 7 Jun 2013 15:42:54 +1000' - - self.patch = Patch(project = defaults.project, - msgid = 'p1', name = 'testpatch', - submitter = self.person, content = '') - - def testCCHeader(self): - self.patch.headers = self.cc_header + '\n' - self.patch.save() - - response = self.client.get('/patch/%d/mbox/' % self.patch.id) - self.assertContains(response, self.cc_header) - - def testToHeader(self): - self.patch.headers = self.to_header + '\n' - self.patch.save() - - response = self.client.get('/patch/%d/mbox/' % self.patch.id) - self.assertContains(response, self.to_header) - - def testDateHeader(self): - self.patch.headers = self.date_header + '\n' - self.patch.save() - - response = self.client.get('/patch/%d/mbox/' % self.patch.id) - self.assertContains(response, self.date_header) - -class MboxBrokenFromHeaderTest(TestCase): - """ Test that a person with characters outside ASCII in his name do - produce correct From header. As RFC 2822 state we must retain the - format for the mail while the name part may be coded - in some ways. """ - - def setUp(self): - defaults.project.save() - self.person = defaults.patch_author_person - self.person.name = u'©ool guÅ·' - self.person.save() - - self.patch = Patch(project = defaults.project, - msgid = 'p1', name = 'testpatch', - submitter = self.person, content = '') - - def testFromHeader(self): - self.patch.save() - from_email = '<' + self.person.email + '>' - - response = self.client.get('/patch/%d/mbox/' % self.patch.id) - self.assertContains(response, from_email) - -class MboxDateHeaderTest(TestCase): - """ Test that the date provided in the patch mail view is correct """ - - def setUp(self): - defaults.project.save() - self.person = defaults.patch_author_person - self.person.save() - - self.patch = Patch(project = defaults.project, - msgid = 'p1', name = 'testpatch', - submitter = self.person, content = '') - self.patch.save() - - def testDateHeader(self): - response = self.client.get('/patch/%d/mbox/' % self.patch.id) - mail = email.message_from_string(response.content) - mail_date = dateutil.parser.parse(mail['Date']) - # patch dates are all in UTC - patch_date = self.patch.date.replace(tzinfo=dateutil.tz.tzutc(), - microsecond=0) - self.assertEqual(mail_date, patch_date) - - def testSuppliedDateHeader(self): - hour_offset = 3 - tz = dateutil.tz.tzoffset(None, hour_offset * 60 * 60) - date = datetime.datetime.utcnow() - datetime.timedelta(days = 1) - date = date.replace(tzinfo=tz, microsecond=0) - - self.patch.headers = 'Date: %s\n' % date.strftime("%a, %d %b %Y %T %z") - self.patch.save() - - response = self.client.get('/patch/%d/mbox/' % self.patch.id) - mail = email.message_from_string(response.content) - mail_date = dateutil.parser.parse(mail['Date']) - self.assertEqual(mail_date, date) - -class MboxCommentPostcriptUnchangedTest(TestCase): - """ Test that the mbox view doesn't change the postscript part of a mail. - There where always a missing blank right after the postscript - delimiter '---' and an additional newline right before. """ - def setUp(self): - defaults.project.save() - - self.person = defaults.patch_author_person - self.person.save() - - self.patch = Patch(project = defaults.project, - msgid = 'p1', name = 'testpatch', - submitter = self.person, content = '') - self.patch.save() - - self.txt = 'some comment\n---\n some/file | 1 +\n' - - comment = Comment(patch = self.patch, msgid = 'p1', - submitter = self.person, - content = self.txt) - comment.save() - - def testCommentUnchanged(self): - response = self.client.get('/patch/%d/mbox/' % self.patch.id) - self.assertContains(response, self.txt) - self.txt += "\n" - self.assertNotContains(response, self.txt) diff --git a/apps/patchwork/tests/test_notifications.py b/apps/patchwork/tests/test_notifications.py deleted file mode 100644 index ed35140..0000000 --- a/apps/patchwork/tests/test_notifications.py +++ /dev/null @@ -1,255 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2011 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import datetime -from django.test import TestCase -from django.core.urlresolvers import reverse -from django.core import mail -from django.conf import settings -from django.db.utils import IntegrityError -from patchwork.models import Patch, State, PatchChangeNotification, EmailOptout -from patchwork.tests.utils import defaults, create_maintainer -from patchwork.utils import send_notifications - -class PatchNotificationModelTest(TestCase): - """Tests for the creation & update of the PatchChangeNotification model""" - - def setUp(self): - self.project = defaults.project - self.project.send_notifications = True - self.project.save() - self.submitter = defaults.patch_author_person - self.submitter.save() - self.patch = Patch(project = self.project, msgid = 'testpatch', - name = 'testpatch', content = '', - submitter = self.submitter) - - def tearDown(self): - self.patch.delete() - self.submitter.delete() - self.project.delete() - - def testPatchCreation(self): - """Ensure we don't get a notification on create""" - self.patch.save() - self.assertEqual(PatchChangeNotification.objects.count(), 0) - - def testPatchUninterestingChange(self): - """Ensure we don't get a notification for "uninteresting" changes""" - self.patch.save() - self.patch.archived = True - self.patch.save() - self.assertEqual(PatchChangeNotification.objects.count(), 0) - - def testPatchChange(self): - """Ensure we get a notification for interesting patch changes""" - self.patch.save() - oldstate = self.patch.state - state = State.objects.exclude(pk = oldstate.pk)[0] - - self.patch.state = state - self.patch.save() - self.assertEqual(PatchChangeNotification.objects.count(), 1) - notification = PatchChangeNotification.objects.all()[0] - self.assertEqual(notification.patch, self.patch) - self.assertEqual(notification.orig_state, oldstate) - - def testNotificationCancelled(self): - """Ensure we cancel notifications that are no longer valid""" - self.patch.save() - oldstate = self.patch.state - state = State.objects.exclude(pk = oldstate.pk)[0] - - self.patch.state = state - self.patch.save() - self.assertEqual(PatchChangeNotification.objects.count(), 1) - - self.patch.state = oldstate - self.patch.save() - self.assertEqual(PatchChangeNotification.objects.count(), 0) - - def testNotificationUpdated(self): - """Ensure we update notifications when the patch has a second change, - but keep the original patch details""" - self.patch.save() - oldstate = self.patch.state - newstates = State.objects.exclude(pk = oldstate.pk)[:2] - - self.patch.state = newstates[0] - self.patch.save() - self.assertEqual(PatchChangeNotification.objects.count(), 1) - notification = PatchChangeNotification.objects.all()[0] - self.assertEqual(notification.orig_state, oldstate) - orig_timestamp = notification.last_modified - - self.patch.state = newstates[1] - self.patch.save() - self.assertEqual(PatchChangeNotification.objects.count(), 1) - notification = PatchChangeNotification.objects.all()[0] - self.assertEqual(notification.orig_state, oldstate) - self.assertTrue(notification.last_modified >= orig_timestamp) - - def testProjectNotificationsDisabled(self): - """Ensure we don't see notifications created when a project is - configured not to send them""" - self.project.send_notifications = False - self.project.save() - - self.patch.save() - oldstate = self.patch.state - state = State.objects.exclude(pk = oldstate.pk)[0] - - self.patch.state = state - self.patch.save() - self.assertEqual(PatchChangeNotification.objects.count(), 0) - -class PatchNotificationEmailTest(TestCase): - - def setUp(self): - self.project = defaults.project - self.project.send_notifications = True - self.project.save() - self.submitter = defaults.patch_author_person - self.submitter.save() - self.patch = Patch(project = self.project, msgid = 'testpatch', - name = 'testpatch', content = '', - submitter = self.submitter) - self.patch.save() - - def tearDown(self): - self.patch.delete() - self.submitter.delete() - self.project.delete() - - def _expireNotifications(self, **kwargs): - timestamp = datetime.datetime.now() - \ - datetime.timedelta(minutes = - settings.NOTIFICATION_DELAY_MINUTES + 1) - - qs = PatchChangeNotification.objects.all() - if kwargs: - qs = qs.filter(**kwargs) - - qs.update(last_modified = timestamp) - - def testNoNotifications(self): - self.assertEquals(send_notifications(), []) - - def testNoReadyNotifications(self): - """ We shouldn't see immediate notifications""" - PatchChangeNotification(patch = self.patch, - orig_state = self.patch.state).save() - - errors = send_notifications() - self.assertEquals(errors, []) - self.assertEquals(len(mail.outbox), 0) - - def testNotifications(self): - PatchChangeNotification(patch = self.patch, - orig_state = self.patch.state).save() - self._expireNotifications() - - errors = send_notifications() - self.assertEquals(errors, []) - self.assertEquals(len(mail.outbox), 1) - msg = mail.outbox[0] - self.assertEquals(msg.to, [self.submitter.email]) - self.assertTrue(self.patch.get_absolute_url() in msg.body) - - def testNotificationEscaping(self): - self.patch.name = 'Patch name with " character' - self.patch.save() - PatchChangeNotification(patch = self.patch, - orig_state = self.patch.state).save() - self._expireNotifications() - - errors = send_notifications() - self.assertEquals(errors, []) - self.assertEquals(len(mail.outbox), 1) - msg = mail.outbox[0] - self.assertEquals(msg.to, [self.submitter.email]) - self.assertFalse('"' in msg.body) - - def testNotificationOptout(self): - """ensure opt-out addresses don't get notifications""" - PatchChangeNotification(patch = self.patch, - orig_state = self.patch.state).save() - self._expireNotifications() - - EmailOptout(email = self.submitter.email).save() - - errors = send_notifications() - self.assertEquals(errors, []) - self.assertEquals(len(mail.outbox), 0) - - def testNotificationMerge(self): - patches = [self.patch, - Patch(project = self.project, msgid = 'testpatch-2', - name = 'testpatch 2', content = '', - submitter = self.submitter)] - - for patch in patches: - patch.save() - PatchChangeNotification(patch = patch, - orig_state = patch.state).save() - - self.assertEquals(PatchChangeNotification.objects.count(), len(patches)) - self._expireNotifications() - errors = send_notifications() - self.assertEquals(errors, []) - self.assertEquals(len(mail.outbox), 1) - msg = mail.outbox[0] - self.assertTrue(patches[0].get_absolute_url() in msg.body) - self.assertTrue(patches[1].get_absolute_url() in msg.body) - - def testUnexpiredNotificationMerge(self): - """Test that when there are multiple pending notifications, with - at least one within the notification delay, that other notifications - are held""" - patches = [self.patch, - Patch(project = self.project, msgid = 'testpatch-2', - name = 'testpatch 2', content = '', - submitter = self.submitter)] - - for patch in patches: - patch.save() - PatchChangeNotification(patch = patch, - orig_state = patch.state).save() - - self.assertEquals(PatchChangeNotification.objects.count(), len(patches)) - self._expireNotifications() - - # update one notification, to bring it out of the notification delay - patches[0].state = State.objects.exclude(pk = patches[0].state.pk)[0] - patches[0].save() - - # the updated notification should prevent the other from being sent - errors = send_notifications() - self.assertEquals(errors, []) - self.assertEquals(len(mail.outbox), 0) - - # expire the updated notification - self._expireNotifications() - - errors = send_notifications() - self.assertEquals(errors, []) - self.assertEquals(len(mail.outbox), 1) - msg = mail.outbox[0] - self.assertTrue(patches[0].get_absolute_url() in msg.body) - self.assertTrue(patches[1].get_absolute_url() in msg.body) diff --git a/apps/patchwork/tests/test_patchparser.py b/apps/patchwork/tests/test_patchparser.py deleted file mode 100644 index 119936a..0000000 --- a/apps/patchwork/tests/test_patchparser.py +++ /dev/null @@ -1,554 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import os -from email import message_from_string -from django.test import TestCase -from patchwork.models import Project, Person, Patch, Comment, State, \ - get_default_initial_patch_state -from patchwork.tests.utils import read_patch, read_mail, create_email, \ - defaults, create_user - -try: - from email.mime.text import MIMEText -except ImportError: - # Python 2.4 compatibility - from email.MIMEText import MIMEText - -class PatchTest(TestCase): - default_sender = defaults.sender - default_subject = defaults.subject - project = defaults.project - -from patchwork.bin.parsemail import find_content, find_author, find_project, \ - parse_mail - -class InlinePatchTest(PatchTest): - patch_filename = '0001-add-line.patch' - test_comment = 'Test for attached patch' - - def setUp(self): - self.orig_patch = read_patch(self.patch_filename) - email = create_email(self.test_comment + '\n' + self.orig_patch) - (self.patch, self.comment) = find_content(self.project, email) - - def testPatchPresence(self): - self.assertTrue(self.patch is not None) - - def testPatchContent(self): - self.assertEquals(self.patch.content, self.orig_patch) - - def testCommentPresence(self): - self.assertTrue(self.comment is not None) - - def testCommentContent(self): - self.assertEquals(self.comment.content, self.test_comment) - - -class AttachmentPatchTest(InlinePatchTest): - patch_filename = '0001-add-line.patch' - test_comment = 'Test for attached patch' - content_subtype = 'x-patch' - - def setUp(self): - self.orig_patch = read_patch(self.patch_filename) - email = create_email(self.test_comment, multipart = True) - attachment = MIMEText(self.orig_patch, _subtype = self.content_subtype) - email.attach(attachment) - (self.patch, self.comment) = find_content(self.project, email) - -class AttachmentXDiffPatchTest(AttachmentPatchTest): - content_subtype = 'x-diff' - -class UTF8InlinePatchTest(InlinePatchTest): - patch_filename = '0002-utf-8.patch' - patch_encoding = 'utf-8' - - def setUp(self): - self.orig_patch = read_patch(self.patch_filename, self.patch_encoding) - email = create_email(self.test_comment + '\n' + self.orig_patch, - content_encoding = self.patch_encoding) - (self.patch, self.comment) = find_content(self.project, email) - -class NoCharsetInlinePatchTest(InlinePatchTest): - """ Test mails with no content-type or content-encoding header """ - patch_filename = '0001-add-line.patch' - - def setUp(self): - self.orig_patch = read_patch(self.patch_filename) - email = create_email(self.test_comment + '\n' + self.orig_patch) - del email['Content-Type'] - del email['Content-Transfer-Encoding'] - (self.patch, self.comment) = find_content(self.project, email) - -class SignatureCommentTest(InlinePatchTest): - patch_filename = '0001-add-line.patch' - test_comment = 'Test comment\nmore comment' - - def setUp(self): - self.orig_patch = read_patch(self.patch_filename) - email = create_email( \ - self.test_comment + '\n' + \ - '-- \nsig\n' + self.orig_patch) - (self.patch, self.comment) = find_content(self.project, email) - - -class ListFooterTest(InlinePatchTest): - patch_filename = '0001-add-line.patch' - test_comment = 'Test comment\nmore comment' - - def setUp(self): - self.orig_patch = read_patch(self.patch_filename) - email = create_email( \ - self.test_comment + '\n' + \ - '_______________________________________________\n' + \ - 'Linuxppc-dev mailing list\n' + \ - self.orig_patch) - (self.patch, self.comment) = find_content(self.project, email) - - -class DiffWordInCommentTest(InlinePatchTest): - test_comment = 'Lines can start with words beginning in "diff"\n' + \ - 'difficult\nDifferent' - - -class UpdateCommentTest(InlinePatchTest): - """ Test for '---\nUpdate: v2' style comments to patches. """ - patch_filename = '0001-add-line.patch' - test_comment = 'Test comment\nmore comment\n---\nUpdate: test update' - -class UpdateSigCommentTest(SignatureCommentTest): - """ Test for '---\nUpdate: v2' style comments to patches, with a sig """ - patch_filename = '0001-add-line.patch' - test_comment = 'Test comment\nmore comment\n---\nUpdate: test update' - -class SenderEncodingTest(TestCase): - sender_name = u'example user' - sender_email = 'user@example.com' - from_header = 'example user ' - - def setUp(self): - mail = 'From: %s\n' % self.from_header + \ - 'Subject: test\n\n' + \ - 'test' - self.email = message_from_string(mail) - (self.person, new) = find_author(self.email) - self.person.save() - - def tearDown(self): - self.person.delete() - - def testName(self): - self.assertEquals(self.person.name, self.sender_name) - - def testEmail(self): - self.assertEquals(self.person.email, self.sender_email) - - def testDBQueryName(self): - db_person = Person.objects.get(name = self.sender_name) - self.assertEquals(self.person, db_person) - - def testDBQueryEmail(self): - db_person = Person.objects.get(email = self.sender_email) - self.assertEquals(self.person, db_person) - - -class SenderUTF8QPEncodingTest(SenderEncodingTest): - sender_name = u'\xe9xample user' - from_header = '=?utf-8?q?=C3=A9xample=20user?= ' - -class SenderUTF8QPSplitEncodingTest(SenderEncodingTest): - sender_name = u'\xe9xample user' - from_header = '=?utf-8?q?=C3=A9xample?= user ' - -class SenderUTF8B64EncodingTest(SenderUTF8QPEncodingTest): - from_header = '=?utf-8?B?w6l4YW1wbGUgdXNlcg==?= ' - -class SubjectEncodingTest(PatchTest): - sender = 'example user ' - subject = 'test subject' - subject_header = 'test subject' - - def setUp(self): - mail = 'From: %s\n' % self.sender + \ - 'Subject: %s\n\n' % self.subject_header + \ - 'test\n\n' + defaults.patch - self.projects = defaults.project - self.email = message_from_string(mail) - - def testSubjectEncoding(self): - (patch, comment) = find_content(self.project, self.email) - self.assertEquals(patch.name, self.subject) - -class SubjectUTF8QPEncodingTest(SubjectEncodingTest): - subject = u'test s\xfcbject' - subject_header = '=?utf-8?q?test=20s=c3=bcbject?=' - -class SubjectUTF8QPMultipleEncodingTest(SubjectEncodingTest): - subject = u'test s\xfcbject' - subject_header = 'test =?utf-8?q?s=c3=bcbject?=' - -class SenderCorrelationTest(TestCase): - existing_sender = 'Existing Sender ' - non_existing_sender = 'Non-existing Sender ' - - def mail(self, sender): - return message_from_string('From: %s\nSubject: Test\n\ntest\n' % sender) - - def setUp(self): - self.existing_sender_mail = self.mail(self.existing_sender) - self.non_existing_sender_mail = self.mail(self.non_existing_sender) - (self.person, new) = find_author(self.existing_sender_mail) - self.person.save() - - def testExisingSender(self): - (person, new) = find_author(self.existing_sender_mail) - self.assertEqual(new, False) - self.assertEqual(person.id, self.person.id) - - def testNonExisingSender(self): - (person, new) = find_author(self.non_existing_sender_mail) - self.assertEqual(new, True) - self.assertEqual(person.id, None) - - def testExistingDifferentFormat(self): - mail = self.mail('existing@example.com') - (person, new) = find_author(mail) - self.assertEqual(new, False) - self.assertEqual(person.id, self.person.id) - - def testExistingDifferentCase(self): - mail = self.mail(self.existing_sender.upper()) - (person, new) = find_author(mail) - self.assertEqual(new, False) - self.assertEqual(person.id, self.person.id) - - def tearDown(self): - self.person.delete() - -class MultipleProjectPatchTest(TestCase): - """ Test that patches sent to multiple patchwork projects are - handled correctly """ - - test_comment = 'Test Comment' - patch_filename = '0001-add-line.patch' - msgid = '<1@example.com>' - - def setUp(self): - self.p1 = Project(linkname = 'test-project-1', name = 'Project 1', - listid = '1.example.com', listemail='1@example.com') - self.p2 = Project(linkname = 'test-project-2', name = 'Project 2', - listid = '2.example.com', listemail='2@example.com') - - self.p1.save() - self.p2.save() - - patch = read_patch(self.patch_filename) - email = create_email(self.test_comment + '\n' + patch) - email['Message-Id'] = self.msgid - - del email['List-ID'] - email['List-ID'] = '<' + self.p1.listid + '>' - parse_mail(email) - - del email['List-ID'] - email['List-ID'] = '<' + self.p2.listid + '>' - parse_mail(email) - - def testParsedProjects(self): - self.assertEquals(Patch.objects.filter(project = self.p1).count(), 1) - self.assertEquals(Patch.objects.filter(project = self.p2).count(), 1) - - def tearDown(self): - self.p1.delete() - self.p2.delete() - - -class MultipleProjectPatchCommentTest(MultipleProjectPatchTest): - """ Test that followups to multiple-project patches end up on the - correct patch """ - - comment_msgid = '<2@example.com>' - comment_content = 'test comment' - - def setUp(self): - super(MultipleProjectPatchCommentTest, self).setUp() - - for project in [self.p1, self.p2]: - email = MIMEText(self.comment_content) - email['From'] = defaults.sender - email['Subject'] = defaults.subject - email['Message-Id'] = self.comment_msgid - email['List-ID'] = '<' + project.listid + '>' - email['In-Reply-To'] = self.msgid - parse_mail(email) - - def testParsedComment(self): - for project in [self.p1, self.p2]: - patch = Patch.objects.filter(project = project)[0] - # we should see two comments now - the original mail with the patch, - # and the one we parsed in setUp() - self.assertEquals(Comment.objects.filter(patch = patch).count(), 2) - -class ListIdHeaderTest(TestCase): - """ Test that we parse List-Id headers from mails correctly """ - def setUp(self): - self.project = Project(linkname = 'test-project-1', name = 'Project 1', - listid = '1.example.com', listemail='1@example.com') - self.project.save() - - def testNoListId(self): - email = MIMEText('') - project = find_project(email) - self.assertEquals(project, None) - - def testBlankListId(self): - email = MIMEText('') - email['List-Id'] = '' - project = find_project(email) - self.assertEquals(project, None) - - def testWhitespaceListId(self): - email = MIMEText('') - email['List-Id'] = ' ' - project = find_project(email) - self.assertEquals(project, None) - - def testSubstringListId(self): - email = MIMEText('') - email['List-Id'] = 'example.com' - project = find_project(email) - self.assertEquals(project, None) - - def testShortListId(self): - """ Some mailing lists have List-Id headers in short formats, where it - is only the list ID itself (without enclosing angle-brackets). """ - email = MIMEText('') - email['List-Id'] = self.project.listid - project = find_project(email) - self.assertEquals(project, self.project) - - def testLongListId(self): - email = MIMEText('') - email['List-Id'] = 'Test text <%s>' % self.project.listid - project = find_project(email) - self.assertEquals(project, self.project) - - def tearDown(self): - self.project.delete() - -class MBoxPatchTest(PatchTest): - def setUp(self): - self.mail = read_mail(self.mail_file, project = self.project) - -class GitPullTest(MBoxPatchTest): - mail_file = '0001-git-pull-request.mbox' - - def testGitPullRequest(self): - (patch, comment) = find_content(self.project, self.mail) - self.assertTrue(patch is not None) - self.assertTrue(patch.pull_url is not None) - self.assertTrue(patch.content is None) - self.assertTrue(comment is not None) - -class GitPullWrappedTest(GitPullTest): - mail_file = '0002-git-pull-request-wrapped.mbox' - -class GitPullWithDiffTest(MBoxPatchTest): - mail_file = '0003-git-pull-request-with-diff.mbox' - - def testGitPullWithDiff(self): - (patch, comment) = find_content(self.project, self.mail) - self.assertTrue(patch is not None) - self.assertEqual('git://git.kernel.org/pub/scm/linux/kernel/git/tip/' + - 'linux-2.6-tip.git x86-fixes-for-linus', patch.pull_url) - self.assertTrue( - patch.content.startswith('diff --git a/arch/x86/include/asm/smp.h'), - patch.content) - self.assertTrue(comment is not None) - -class GitPullGitSSHUrlTest(GitPullTest): - mail_file = '0004-git-pull-request-git+ssh.mbox' - -class GitPullSSHUrlTest(GitPullTest): - mail_file = '0005-git-pull-request-ssh.mbox' - -class GitPullHTTPUrlTest(GitPullTest): - mail_file = '0006-git-pull-request-http.mbox' - -class GitRenameOnlyTest(MBoxPatchTest): - mail_file = '0008-git-rename.mbox' - - def testGitRename(self): - (patch, comment) = find_content(self.project, self.mail) - self.assertTrue(patch is not None) - self.assertTrue(comment is not None) - self.assertEqual(patch.content.count("\nrename from "), 2) - self.assertEqual(patch.content.count("\nrename to "), 2) - -class GitRenameWithDiffTest(MBoxPatchTest): - mail_file = '0009-git-rename-with-diff.mbox' - - def testGitRename(self): - (patch, comment) = find_content(self.project, self.mail) - self.assertTrue(patch is not None) - self.assertTrue(comment is not None) - self.assertEqual(patch.content.count("\nrename from "), 2) - self.assertEqual(patch.content.count("\nrename to "), 2) - self.assertEqual(patch.content.count('\n-a\n+b'), 1) - -class CVSFormatPatchTest(MBoxPatchTest): - mail_file = '0007-cvs-format-diff.mbox' - - def testPatch(self): - (patch, comment) = find_content(self.project, self.mail) - self.assertTrue(patch is not None) - self.assertTrue(comment is not None) - self.assertTrue(patch.content.startswith('Index')) - -class CharsetFallbackPatchTest(MBoxPatchTest): - """ Test mail with and invalid charset name, and check that we can parse - with one of the fallback encodings""" - - mail_file = '0010-invalid-charset.mbox' - - def testPatch(self): - (patch, comment) = find_content(self.project, self.mail) - self.assertTrue(patch is not None) - self.assertTrue(comment is not None) - -class NoNewlineAtEndOfFilePatchTest(MBoxPatchTest): - mail_file = '0011-no-newline-at-end-of-file.mbox' - - def testPatch(self): - (patch, comment) = find_content(self.project, self.mail) - self.assertTrue(patch is not None) - self.assertTrue(comment is not None) - self.assertTrue(patch.content.startswith('diff --git a/tools/testing/selftests/powerpc/Makefile')) - # Confirm the trailing no newline marker doesn't end up in the comment - self.assertFalse(comment.content.rstrip().endswith('\ No newline at end of file')) - # Confirm it's instead at the bottom of the patch - self.assertTrue(patch.content.rstrip().endswith('\ No newline at end of file')) - # Confirm we got both markers - self.assertEqual(2, patch.content.count('\ No newline at end of file')) - -class DelegateRequestTest(TestCase): - patch_filename = '0001-add-line.patch' - msgid = '<1@example.com>' - invalid_delegate_email = "nobody" - - def setUp(self): - self.patch = read_patch(self.patch_filename) - self.user = create_user() - self.p1 = Project(linkname = 'test-project-1', name = 'Project 1', - listid = '1.example.com', listemail='1@example.com') - self.p1.save() - - def get_email(self): - email = create_email(self.patch) - del email['List-ID'] - email['List-ID'] = '<' + self.p1.listid + '>' - email['Message-Id'] = self.msgid - return email - - def _assertDelegate(self, delegate): - query = Patch.objects.filter(project=self.p1) - self.assertEquals(query.count(), 1) - self.assertEquals(query[0].delegate, delegate) - - def testDelegate(self): - email = self.get_email() - email['X-Patchwork-Delegate'] = self.user.email - parse_mail(email) - self._assertDelegate(self.user) - - def testNoDelegate(self): - email = self.get_email() - parse_mail(email) - self._assertDelegate(None) - - def testInvalidDelegateFallsBackToNoDelegate(self): - email = self.get_email() - email['X-Patchwork-Delegate'] = self.invalid_delegate_email - parse_mail(email) - self._assertDelegate(None) - - def tearDown(self): - self.p1.delete() - self.user.delete() - -class InitialPatchStateTest(TestCase): - patch_filename = '0001-add-line.patch' - msgid = '<1@example.com>' - invalid_state_name = "Nonexistent Test State" - - def setUp(self): - self.patch = read_patch(self.patch_filename) - self.user = create_user() - self.p1 = Project(linkname = 'test-project-1', name = 'Project 1', - listid = '1.example.com', listemail='1@example.com') - self.p1.save() - self.default_state = get_default_initial_patch_state() - self.nondefault_state = State.objects.get(name="Accepted") - - def get_email(self): - email = create_email(self.patch) - del email['List-ID'] - email['List-ID'] = '<' + self.p1.listid + '>' - email['Message-Id'] = self.msgid - return email - - def _assertState(self, state): - query = Patch.objects.filter(project=self.p1) - self.assertEquals(query.count(), 1) - self.assertEquals(query[0].state, state) - - def testNonDefaultStateIsActuallyNotTheDefaultState(self): - self.assertNotEqual(self.default_state, self.nondefault_state) - - def testExplicitNonDefaultStateRequest(self): - email = self.get_email() - email['X-Patchwork-State'] = self.nondefault_state.name - parse_mail(email) - self._assertState(self.nondefault_state) - - def testExplicitDefaultStateRequest(self): - email = self.get_email() - email['X-Patchwork-State'] = self.default_state.name - parse_mail(email) - self._assertState(self.default_state) - - def testImplicitDefaultStateRequest(self): - email = self.get_email() - parse_mail(email) - self._assertState(self.default_state) - - def testInvalidTestStateDoesNotExist(self): - with self.assertRaises(State.DoesNotExist): - State.objects.get(name=self.invalid_state_name) - - def testInvalidStateRequestFallsBackToDefaultState(self): - email = self.get_email() - email['X-Patchwork-State'] = self.invalid_state_name - parse_mail(email) - self._assertState(self.default_state) - - def tearDown(self): - self.p1.delete() - self.user.delete() diff --git a/apps/patchwork/tests/test_person.py b/apps/patchwork/tests/test_person.py deleted file mode 100644 index d948096..0000000 --- a/apps/patchwork/tests/test_person.py +++ /dev/null @@ -1,55 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2013 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import unittest -from django.test import TestCase -from django.test.client import Client -from patchwork.models import EmailConfirmation, Person, Bundle -import json - -class SubmitterCompletionTest(TestCase): - def setUp(self): - self.people = [ - Person(name = "Test Name", email = "test1@example.com"), - Person(email = "test2@example.com"), - ] - map(lambda p: p.save(), self.people) - - def testNameComplete(self): - response = self.client.get('/submitter/', {'q': 'name'}) - self.assertEquals(response.status_code, 200) - data = json.loads(response.content) - self.assertEquals(len(data), 1) - self.assertEquals(data[0]['fields']['name'], 'Test Name') - - def testEmailComplete(self): - response = self.client.get('/submitter/', {'q': 'test2'}) - self.assertEquals(response.status_code, 200) - data = json.loads(response.content) - self.assertEquals(len(data), 1) - self.assertEquals(data[0]['fields']['email'], 'test2@example.com') - - def testCompleteLimit(self): - for i in range(3,10): - person = Person(email = 'test%d@example.com' % i) - person.save() - response = self.client.get('/submitter/', {'q': 'test', 'l': 5}) - self.assertEquals(response.status_code, 200) - data = json.loads(response.content) - self.assertEquals(len(data), 5) diff --git a/apps/patchwork/tests/test_registration.py b/apps/patchwork/tests/test_registration.py deleted file mode 100644 index 845b60b..0000000 --- a/apps/patchwork/tests/test_registration.py +++ /dev/null @@ -1,210 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2010 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import unittest -from django.test import TestCase -from django.test.client import Client -from django.core import mail -from django.core.urlresolvers import reverse -from django.contrib.auth.models import User -from patchwork.models import EmailConfirmation, Person -from patchwork.tests.utils import create_user - -def _confirmation_url(conf): - return reverse('patchwork.views.confirm', kwargs = {'key': conf.key}) - -class TestUser(object): - firstname = 'Test' - lastname = 'User' - username = 'testuser' - email = 'test@example.com' - password = 'foobar' - -class RegistrationTest(TestCase): - def setUp(self): - self.user = TestUser() - self.client = Client() - self.default_data = {'username': self.user.username, - 'first_name': self.user.firstname, - 'last_name': self.user.lastname, - 'email': self.user.email, - 'password': self.user.password} - self.required_error = 'This field is required.' - self.invalid_error = 'Enter a valid value.' - - def testRegistrationForm(self): - response = self.client.get('/register/') - self.assertEquals(response.status_code, 200) - self.assertTemplateUsed(response, 'patchwork/registration_form.html') - - def testBlankFields(self): - for field in ['username', 'email', 'password']: - data = self.default_data.copy() - del data[field] - response = self.client.post('/register/', data) - self.assertEquals(response.status_code, 200) - self.assertFormError(response, 'form', field, self.required_error) - - def testInvalidUsername(self): - data = self.default_data.copy() - data['username'] = 'invalid user' - response = self.client.post('/register/', data) - self.assertEquals(response.status_code, 200) - self.assertFormError(response, 'form', 'username', self.invalid_error) - - def testExistingUsername(self): - user = create_user() - data = self.default_data.copy() - data['username'] = user.username - response = self.client.post('/register/', data) - self.assertEquals(response.status_code, 200) - self.assertFormError(response, 'form', 'username', - 'This username is already taken. Please choose another.') - - def testExistingEmail(self): - user = create_user() - data = self.default_data.copy() - data['email'] = user.email - response = self.client.post('/register/', data) - self.assertEquals(response.status_code, 200) - self.assertFormError(response, 'form', 'email', - 'This email address is already in use ' + \ - 'for the account "%s".\n' % user.username) - - def testValidRegistration(self): - response = self.client.post('/register/', self.default_data) - self.assertEquals(response.status_code, 200) - self.assertContains(response, 'confirmation email has been sent') - - # check for presence of an inactive user object - users = User.objects.filter(username = self.user.username) - self.assertEquals(users.count(), 1) - user = users[0] - self.assertEquals(user.username, self.user.username) - self.assertEquals(user.email, self.user.email) - self.assertEquals(user.is_active, False) - - # check for confirmation object - confs = EmailConfirmation.objects.filter(user = user, - type = 'registration') - self.assertEquals(len(confs), 1) - conf = confs[0] - self.assertEquals(conf.email, self.user.email) - - # check for a sent mail - self.assertEquals(len(mail.outbox), 1) - msg = mail.outbox[0] - self.assertEquals(msg.subject, 'Patchwork account confirmation') - self.assertTrue(self.user.email in msg.to) - self.assertTrue(_confirmation_url(conf) in msg.body) - - # ...and that the URL is valid - response = self.client.get(_confirmation_url(conf)) - self.assertEquals(response.status_code, 200) - -class RegistrationConfirmationTest(TestCase): - - def setUp(self): - self.user = TestUser() - self.default_data = {'username': self.user.username, - 'first_name': self.user.firstname, - 'last_name': self.user.lastname, - 'email': self.user.email, - 'password': self.user.password} - - def testRegistrationConfirmation(self): - self.assertEqual(EmailConfirmation.objects.count(), 0) - response = self.client.post('/register/', self.default_data) - self.assertEquals(response.status_code, 200) - self.assertContains(response, 'confirmation email has been sent') - - self.assertEqual(EmailConfirmation.objects.count(), 1) - conf = EmailConfirmation.objects.filter()[0] - self.assertFalse(conf.user.is_active) - self.assertTrue(conf.active) - - response = self.client.get(_confirmation_url(conf)) - self.assertEquals(response.status_code, 200) - self.assertTemplateUsed(response, 'patchwork/registration-confirm.html') - - conf = EmailConfirmation.objects.get(pk = conf.pk) - self.assertTrue(conf.user.is_active) - self.assertFalse(conf.active) - - def testRegistrationNewPersonSetup(self): - """ Check that the person object created after registration has the - correct details """ - - # register - self.assertEqual(EmailConfirmation.objects.count(), 0) - response = self.client.post('/register/', self.default_data) - self.assertEquals(response.status_code, 200) - self.assertFalse(Person.objects.exists()) - - # confirm - conf = EmailConfirmation.objects.filter()[0] - response = self.client.get(_confirmation_url(conf)) - self.assertEquals(response.status_code, 200) - - qs = Person.objects.filter(email = self.user.email) - self.assertTrue(qs.exists()) - person = Person.objects.get(email = self.user.email) - - self.assertEquals(person.name, - self.user.firstname + ' ' + self.user.lastname) - - def testRegistrationExistingPersonSetup(self): - """ Check that the person object created after registration has the - correct details """ - - fullname = self.user.firstname + ' ' + self.user.lastname - person = Person(name = fullname, email = self.user.email) - person.save() - - # register - self.assertEqual(EmailConfirmation.objects.count(), 0) - response = self.client.post('/register/', self.default_data) - self.assertEquals(response.status_code, 200) - - # confirm - conf = EmailConfirmation.objects.filter()[0] - response = self.client.get(_confirmation_url(conf)) - self.assertEquals(response.status_code, 200) - - person = Person.objects.get(email = self.user.email) - - self.assertEquals(person.name, fullname) - - def testRegistrationExistingPersonUnmodified(self): - """ Check that an unconfirmed registration can't modify an existing - Person object""" - - fullname = self.user.firstname + ' ' + self.user.lastname - person = Person(name = fullname, email = self.user.email) - person.save() - - # register - data = self.default_data.copy() - data['first_name'] = 'invalid' - data['last_name'] = 'invalid' - self.assertEquals(data['email'], person.email) - response = self.client.post('/register/', data) - self.assertEquals(response.status_code, 200) - - self.assertEquals(Person.objects.get(pk = person.pk).name, fullname) diff --git a/apps/patchwork/tests/test_updates.py b/apps/patchwork/tests/test_updates.py deleted file mode 100644 index 177ee78..0000000 --- a/apps/patchwork/tests/test_updates.py +++ /dev/null @@ -1,118 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2010 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from django.test import TestCase -from django.core.urlresolvers import reverse -from patchwork.models import Patch, Person, State -from patchwork.tests.utils import defaults, create_maintainer - -class MultipleUpdateTest(TestCase): - def setUp(self): - defaults.project.save() - self.user = create_maintainer(defaults.project) - self.client.login(username = self.user.username, - password = self.user.username) - self.properties_form_id = 'patchform-properties' - self.url = reverse( - 'patchwork.views.patch.list', args = [defaults.project.linkname]) - self.base_data = { - 'action': 'Update', 'project': str(defaults.project.id), - 'form': 'patchlistform', 'archived': '*', 'delegate': '*', - 'state': '*'} - self.patches = [] - for name in ['patch one', 'patch two', 'patch three']: - patch = Patch(project = defaults.project, msgid = name, - name = name, content = '', - submitter = Person.objects.get(user = self.user)) - patch.save() - self.patches.append(patch) - - def _selectAllPatches(self, data): - for patch in self.patches: - data['patch_id:%d' % patch.id] = 'checked' - - def testArchivingPatches(self): - data = self.base_data.copy() - data.update({'archived': 'True'}) - self._selectAllPatches(data) - response = self.client.post(self.url, data) - self.assertContains(response, 'No patches to display', - status_code = 200) - for patch in [Patch.objects.get(pk = p.pk) for p in self.patches]: - self.assertTrue(patch.archived) - - def testUnArchivingPatches(self): - # Start with one patch archived and the remaining ones unarchived. - self.patches[0].archived = True - self.patches[0].save() - data = self.base_data.copy() - data.update({'archived': 'False'}) - self._selectAllPatches(data) - response = self.client.post(self.url, data) - self.assertContains(response, self.properties_form_id, - status_code = 200) - for patch in [Patch.objects.get(pk = p.pk) for p in self.patches]: - self.assertFalse(patch.archived) - - def _testStateChange(self, state): - data = self.base_data.copy() - data.update({'state': str(state)}) - self._selectAllPatches(data) - response = self.client.post(self.url, data) - self.assertContains(response, self.properties_form_id, - status_code = 200) - return response - - def testStateChangeValid(self): - states = [patch.state.pk for patch in self.patches] - state = State.objects.exclude(pk__in = states)[0] - self._testStateChange(state.pk) - for p in self.patches: - self.assertEquals(Patch.objects.get(pk = p.pk).state, state) - - def testStateChangeInvalid(self): - state = max(State.objects.all().values_list('id', flat = True)) + 1 - orig_states = [patch.state for patch in self.patches] - response = self._testStateChange(state) - self.assertEquals( \ - [Patch.objects.get(pk = p.pk).state for p in self.patches], - orig_states) - self.assertFormError(response, 'patchform', 'state', - 'Select a valid choice. That choice is not one ' + \ - 'of the available choices.') - - def _testDelegateChange(self, delegate_str): - data = self.base_data.copy() - data.update({'delegate': delegate_str}) - self._selectAllPatches(data) - response = self.client.post(self.url, data) - self.assertContains(response, self.properties_form_id, - status_code=200) - return response - - def testDelegateChangeValid(self): - delegate = create_maintainer(defaults.project) - response = self._testDelegateChange(str(delegate.pk)) - for p in self.patches: - self.assertEquals(Patch.objects.get(pk = p.pk).delegate, delegate) - - def testDelegateClear(self): - response = self._testDelegateChange('') - for p in self.patches: - self.assertEquals(Patch.objects.get(pk = p.pk).delegate, None) diff --git a/apps/patchwork/tests/test_user.py b/apps/patchwork/tests/test_user.py deleted file mode 100644 index 0faa970..0000000 --- a/apps/patchwork/tests/test_user.py +++ /dev/null @@ -1,195 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2010 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import unittest -from django.test import TestCase -from django.test.client import Client -from django.core import mail -from django.core.urlresolvers import reverse -from django.conf import settings -from django.contrib.auth.models import User -from patchwork.models import EmailConfirmation, Person, Bundle -from patchwork.tests.utils import defaults, error_strings - -def _confirmation_url(conf): - return reverse('patchwork.views.confirm', kwargs = {'key': conf.key}) - -class TestUser(object): - username = 'testuser' - email = 'test@example.com' - secondary_email = 'test2@example.com' - password = None - - def __init__(self): - self.password = User.objects.make_random_password() - self.user = User.objects.create_user(self.username, - self.email, self.password) - -class UserPersonRequestTest(TestCase): - def setUp(self): - self.user = TestUser() - self.client.login(username = self.user.username, - password = self.user.password) - EmailConfirmation.objects.all().delete() - - def testUserPersonRequestForm(self): - response = self.client.get('/user/link/') - self.assertEquals(response.status_code, 200) - self.assertTrue(response.context['linkform']) - - def testUserPersonRequestEmpty(self): - response = self.client.post('/user/link/', {'email': ''}) - self.assertEquals(response.status_code, 200) - self.assertTrue(response.context['linkform']) - self.assertFormError(response, 'linkform', 'email', - 'This field is required.') - - def testUserPersonRequestInvalid(self): - response = self.client.post('/user/link/', {'email': 'foo'}) - self.assertEquals(response.status_code, 200) - self.assertTrue(response.context['linkform']) - self.assertFormError(response, 'linkform', 'email', - error_strings['email']) - - def testUserPersonRequestValid(self): - response = self.client.post('/user/link/', - {'email': self.user.secondary_email}) - self.assertEquals(response.status_code, 200) - self.assertTrue(response.context['confirmation']) - - # check that we have a confirmation saved - self.assertEquals(EmailConfirmation.objects.count(), 1) - conf = EmailConfirmation.objects.all()[0] - self.assertEquals(conf.user, self.user.user) - self.assertEquals(conf.email, self.user.secondary_email) - self.assertEquals(conf.type, 'userperson') - - # check that an email has gone out... - self.assertEquals(len(mail.outbox), 1) - msg = mail.outbox[0] - self.assertEquals(msg.subject, 'Patchwork email address confirmation') - self.assertTrue(self.user.secondary_email in msg.to) - self.assertTrue(_confirmation_url(conf) in msg.body) - - # ...and that the URL is valid - response = self.client.get(_confirmation_url(conf)) - self.assertEquals(response.status_code, 200) - self.assertTemplateUsed(response, 'patchwork/user-link-confirm.html') - -class UserPersonConfirmTest(TestCase): - def setUp(self): - EmailConfirmation.objects.all().delete() - Person.objects.all().delete() - self.user = TestUser() - self.client.login(username = self.user.username, - password = self.user.password) - self.conf = EmailConfirmation(type = 'userperson', - email = self.user.secondary_email, - user = self.user.user) - self.conf.save() - - def testUserPersonConfirm(self): - self.assertEquals(Person.objects.count(), 0) - response = self.client.get(_confirmation_url(self.conf)) - self.assertEquals(response.status_code, 200) - - # check that the Person object has been created and linked - self.assertEquals(Person.objects.count(), 1) - person = Person.objects.get(email = self.user.secondary_email) - self.assertEquals(person.email, self.user.secondary_email) - self.assertEquals(person.user, self.user.user) - - # check that the confirmation has been marked as inactive. We - # need to reload the confirmation to check this. - conf = EmailConfirmation.objects.get(pk = self.conf.pk) - self.assertEquals(conf.active, False) - -class UserLoginRedirectTest(TestCase): - - def testUserLoginRedirect(self): - url = '/user/' - response = self.client.get(url) - self.assertRedirects(response, settings.LOGIN_URL + '?next=' + url) - -class UserProfileTest(TestCase): - - def setUp(self): - self.user = TestUser() - self.client.login(username = self.user.username, - password = self.user.password) - - def testUserProfile(self): - response = self.client.get('/user/') - self.assertContains(response, 'User Profile: %s' % self.user.username) - self.assertContains(response, 'User Profile: %s' % self.user.username) - - def testUserProfileNoBundles(self): - response = self.client.get('/user/') - self.assertContains(response, 'You have no bundles') - - def testUserProfileBundles(self): - project = defaults.project - project.save() - - bundle = Bundle(project = project, name = 'test-1', - owner = self.user.user) - bundle.save() - - response = self.client.get('/user/') - - self.assertContains(response, 'You have the following bundle') - self.assertContains(response, bundle.get_absolute_url()) - -class UserPasswordChangeTest(TestCase): - form_url = reverse('django.contrib.auth.views.password_change') - done_url = reverse('django.contrib.auth.views.password_change_done') - - def testPasswordChangeForm(self): - self.user = TestUser() - self.client.login(username = self.user.username, - password = self.user.password) - - response = self.client.get(self.form_url) - self.assertContains(response, 'Change my password') - - def testPasswordChange(self): - self.user = TestUser() - self.client.login(username = self.user.username, - password = self.user.password) - - old_password = self.user.password - new_password = User.objects.make_random_password() - - data = { - 'old_password': old_password, - 'new_password1': new_password, - 'new_password2': new_password, - } - - response = self.client.post(self.form_url, data) - self.assertRedirects(response, self.done_url) - - user = User.objects.get(id = self.user.user.id) - - self.assertFalse(user.check_password(old_password)) - self.assertTrue(user.check_password(new_password)) - - response = self.client.get(self.done_url) - self.assertContains(response, - "Your password has been changed sucessfully") diff --git a/apps/patchwork/tests/test_xmlrpc.py b/apps/patchwork/tests/test_xmlrpc.py deleted file mode 100644 index 2b459b2..0000000 --- a/apps/patchwork/tests/test_xmlrpc.py +++ /dev/null @@ -1,55 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2014 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import unittest -import xmlrpclib -from django.test import LiveServerTestCase -from django.core.urlresolvers import reverse -from django.conf import settings -from patchwork.models import Person, Patch -from patchwork.tests.utils import defaults - -@unittest.skipUnless(settings.ENABLE_XMLRPC, - "requires xmlrpc interface (use the ENABLE_XMLRPC setting)") -class XMLRPCTest(LiveServerTestCase): - - def setUp(self): - settings.STATIC_URL = '/' - self.url = (self.live_server_url + - reverse('patchwork.views.xmlrpc.xmlrpc')) - self.rpc = xmlrpclib.Server(self.url) - - def testGetRedirect(self): - response = self.client.get(self.url) - self.assertRedirects(response, - reverse('patchwork.views.help', - kwargs = {'path': 'pwclient/'})) - - def testList(self): - defaults.project.save() - defaults.patch_author_person.save() - patch = Patch(project = defaults.project, - submitter = defaults.patch_author_person, - msgid = defaults.patch_name, - content = defaults.patch) - patch.save() - - patches = self.rpc.patch_list() - self.assertEqual(len(patches), 1) - self.assertEqual(patches[0]['id'], patch.id) diff --git a/apps/patchwork/tests/utils.py b/apps/patchwork/tests/utils.py deleted file mode 100644 index 782ed36..0000000 --- a/apps/patchwork/tests/utils.py +++ /dev/null @@ -1,138 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import os -import codecs -from patchwork.models import Project, Person -from django.contrib.auth.models import User -from django.forms.fields import EmailField - -from email import message_from_file -try: - from email.mime.text import MIMEText - from email.mime.multipart import MIMEMultipart -except ImportError: - # Python 2.4 compatibility - from email.MIMEText import MIMEText - from email.MIMEMultipart import MIMEMultipart - -# helper functions for tests -_test_mail_dir = os.path.join(os.path.dirname(__file__), 'mail') -_test_patch_dir = os.path.join(os.path.dirname(__file__), 'patches') - -class defaults(object): - project = Project(linkname = 'test-project', name = 'Test Project', - listid = 'test.example.com') - - patch_author = 'Patch Author ' - patch_author_person = Person(name = 'Patch Author', - email = 'patch-author@example.com') - - comment_author = 'Comment Author ' - - sender = 'Test Author ' - - subject = 'Test Subject' - - patch_name = 'Test Patch' - - patch = """--- /dev/null 2011-01-01 00:00:00.000000000 +0800 -+++ a 2011-01-01 00:00:00.000000000 +0800 -@@ -0,0 +1 @@ -+a -""" - -error_strings = { - 'email': 'Enter a valid email address.', -} - -_user_idx = 1 -def create_user(): - global _user_idx - userid = 'test%d' % _user_idx - email = '%s@example.com' % userid - _user_idx += 1 - - user = User.objects.create_user(userid, email, userid) - user.save() - - person = Person(email = email, name = userid, user = user) - person.save() - - return user - -def create_maintainer(project): - user = create_user() - profile = user.profile - profile.maintainer_projects.add(project) - profile.save() - return user - -def find_in_context(context, key): - if isinstance(context, list): - for c in context: - v = find_in_context(c, key) - if v is not None: - return v - else: - if key in context: - return context[key] - return None - -def read_patch(filename, encoding = None): - file_path = os.path.join(_test_patch_dir, filename) - if encoding is not None: - f = codecs.open(file_path, encoding = encoding) - else: - f = file(file_path) - - return f.read() - -def read_mail(filename, project = None): - file_path = os.path.join(_test_mail_dir, filename) - mail = message_from_file(open(file_path)) - if project is not None: - mail['List-Id'] = project.listid - return mail - -def create_email(content, subject = None, sender = None, multipart = False, - project = None, content_encoding = None): - if subject is None: - subject = defaults.subject - if sender is None: - sender = defaults.sender - if project is None: - project = defaults.project - if content_encoding is None: - content_encoding = 'us-ascii' - - if multipart: - msg = MIMEMultipart() - body = MIMEText(content, _subtype = 'plain', - _charset = content_encoding) - msg.attach(body) - else: - msg = MIMEText(content, _charset = content_encoding) - - msg['Subject'] = subject - msg['From'] = sender - msg['List-Id'] = project.listid - - - return msg diff --git a/apps/patchwork/urls.py b/apps/patchwork/urls.py deleted file mode 100644 index b28eb90..0000000 --- a/apps/patchwork/urls.py +++ /dev/null @@ -1,103 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from django.conf.urls import patterns, url, include -from django.conf import settings -from django.contrib import admin -from django.contrib.auth import views as auth_views - -admin.autodiscover() - -urlpatterns = patterns('', - url(r'^admin/', include(admin.site.urls)), - - (r'^$', 'patchwork.views.projects'), - (r'^project/(?P[^/]+)/list/$', 'patchwork.views.patch.list'), - (r'^project/(?P[^/]+)/$', 'patchwork.views.project.project'), - - # patch views - (r'^patch/(?P\d+)/$', 'patchwork.views.patch.patch'), - (r'^patch/(?P\d+)/raw/$', 'patchwork.views.patch.content'), - (r'^patch/(?P\d+)/mbox/$', 'patchwork.views.patch.mbox'), - - # logged-in user stuff - (r'^user/$', 'patchwork.views.user.profile'), - (r'^user/todo/$', 'patchwork.views.user.todo_lists'), - (r'^user/todo/(?P[^/]+)/$', 'patchwork.views.user.todo_list'), - - (r'^user/bundles/$', - 'patchwork.views.bundle.bundles'), - - (r'^user/link/$', 'patchwork.views.user.link'), - (r'^user/unlink/(?P[^/]+)/$', 'patchwork.views.user.unlink'), - - # password change - url(r'^user/password-change/$', auth_views.password_change, - name='password_change'), - url(r'^user/password-change/done/$', auth_views.password_change_done, - name='password_change_done'), - - # login/logout - url(r'^user/login/$', auth_views.login, - {'template_name': 'patchwork/login.html'}, - name = 'auth_login'), - url(r'^user/logout/$', auth_views.logout, - {'template_name': 'patchwork/logout.html'}, - name = 'auth_logout'), - - # registration - (r'^register/', 'patchwork.views.user.register'), - - # public view for bundles - (r'^bundle/(?P[^/]*)/(?P[^/]*)/$', - 'patchwork.views.bundle.bundle'), - (r'^bundle/(?P[^/]*)/(?P[^/]*)/mbox/$', - 'patchwork.views.bundle.mbox'), - - (r'^confirm/(?P[0-9a-f]+)/$', 'patchwork.views.confirm'), - - # submitter autocomplete - (r'^submitter/$', 'patchwork.views.submitter_complete'), - - # email setup - (r'^mail/$', 'patchwork.views.mail.settings'), - (r'^mail/optout/$', 'patchwork.views.mail.optout'), - (r'^mail/optin/$', 'patchwork.views.mail.optin'), - - # help! - (r'^help/(?P.*)$', 'patchwork.views.help'), -) - -if settings.ENABLE_XMLRPC: - urlpatterns += patterns('', - (r'xmlrpc/$', 'patchwork.views.xmlrpc.xmlrpc'), - (r'^pwclient/$', 'patchwork.views.pwclient'), - (r'^project/(?P[^/]+)/pwclientrc/$', - 'patchwork.views.pwclientrc'), - ) - -# redirect from old urls -if settings.COMPAT_REDIR: - urlpatterns += patterns('', - (r'^user/bundle/(?P[^/]+)/$', - 'patchwork.views.bundle.bundle_redir'), - (r'^user/bundle/(?P[^/]+)/mbox/$', - 'patchwork.views.bundle.mbox_redir'), - ) - diff --git a/apps/patchwork/utils.py b/apps/patchwork/utils.py deleted file mode 100644 index 9ed9e41..0000000 --- a/apps/patchwork/utils.py +++ /dev/null @@ -1,248 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -import itertools -import datetime -from django.shortcuts import get_object_or_404 -from django.template.loader import render_to_string -from django.contrib.auth.models import User -from django.contrib.sites.models import Site -from django.conf import settings -from django.core.mail import EmailMessage -from django.db.models import Max, Q, F -from django.db.utils import IntegrityError -from patchwork.forms import MultiplePatchForm -from patchwork.models import Bundle, Project, BundlePatch, UserProfile, \ - PatchChangeNotification, EmailOptout, EmailConfirmation - -def get_patch_ids(d, prefix = 'patch_id'): - ids = [] - - for (k, v) in d.items(): - a = k.split(':') - if len(a) != 2: - continue - if a[0] != prefix: - continue - if not v: - continue - ids.append(a[1]) - - return ids - -class Order(object): - order_map = { - 'date': 'date', - 'name': 'name', - 'state': 'state__ordering', - 'submitter': 'submitter__name', - 'delegate': 'delegate__username', - } - default_order = ('date', True) - - def __init__(self, str = None, editable = False): - self.reversed = False - self.editable = editable - (self.order, self.reversed) = self.default_order - - if self.editable: - return - - if str is None or str == '': - return - - reversed = False - if str[0] == '-': - str = str[1:] - reversed = True - - if str not in self.order_map.keys(): - return - - self.order = str - self.reversed = reversed - - def __str__(self): - str = self.order - if self.reversed: - str = '-' + str - return str - - def name(self): - return self.order - - def reversed_name(self): - if self.reversed: - return self.order - else: - return '-' + self.order - - def apply(self, qs): - q = self.order_map[self.order] - if self.reversed: - q = '-' + q - - orders = [q] - - # if we're using a non-default order, add the default as a secondary - # ordering. We reverse the default if the primary is reversed. - (default_name, default_reverse) = self.default_order - if self.order != default_name: - q = self.order_map[default_name] - if self.reversed ^ default_reverse: - q = '-' + q - orders.append(q) - - return qs.order_by(*orders) - -bundle_actions = ['create', 'add', 'remove'] -def set_bundle(user, project, action, data, patches, context): - # set up the bundle - bundle = None - if action == 'create': - bundle_name = data['bundle_name'].strip() - if '/' in bundle_name: - return ['Bundle names can\'t contain slashes'] - - if not bundle_name: - return ['No bundle name was specified'] - - if Bundle.objects.filter(owner = user, name = bundle_name).count() > 0: - return ['You already have a bundle called "%s"' % bundle_name] - - bundle = Bundle(owner = user, project = project, - name = bundle_name) - bundle.save() - context.add_message("Bundle %s created" % bundle.name) - - elif action =='add': - bundle = get_object_or_404(Bundle, id = data['bundle_id']) - - elif action =='remove': - bundle = get_object_or_404(Bundle, id = data['removed_bundle_id']) - - if not bundle: - return ['no such bundle'] - - for patch in patches: - if action == 'create' or action == 'add': - bundlepatch_count = BundlePatch.objects.filter(bundle = bundle, - patch = patch).count() - if bundlepatch_count == 0: - bundle.append_patch(patch) - context.add_message("Patch '%s' added to bundle %s" % \ - (patch.name, bundle.name)) - else: - context.add_message("Patch '%s' already in bundle %s" % \ - (patch.name, bundle.name)) - - elif action == 'remove': - try: - bp = BundlePatch.objects.get(bundle = bundle, patch = patch) - bp.delete() - context.add_message("Patch '%s' removed from bundle %s\n" % \ - (patch.name, bundle.name)) - except Exception: - pass - - bundle.save() - - return [] - -def send_notifications(): - date_limit = datetime.datetime.now() - \ - datetime.timedelta(minutes = - settings.NOTIFICATION_DELAY_MINUTES) - - # This gets funky: we want to filter out any notifications that should - # be grouped with other notifications that aren't ready to go out yet. To - # do that, we join back onto PatchChangeNotification (PCN -> Patch -> - # Person -> Patch -> max(PCN.last_modified)), filtering out any maxima - # that are with the date_limit. - qs = PatchChangeNotification.objects \ - .annotate(m = Max('patch__submitter__patch__patchchangenotification' - '__last_modified')) \ - .filter(m__lt = date_limit) - - groups = itertools.groupby(qs.order_by('patch__submitter'), - lambda n: n.patch.submitter) - - errors = [] - - for (recipient, notifications) in groups: - notifications = list(notifications) - projects = set([ n.patch.project.linkname for n in notifications ]) - - def delete_notifications(): - pks = [ n.pk for n in notifications ] - PatchChangeNotification.objects.filter(pk__in = pks).delete() - - if EmailOptout.is_optout(recipient.email): - delete_notifications() - continue - - context = { - 'site': Site.objects.get_current(), - 'person': recipient, - 'notifications': notifications, - 'projects': projects, - } - - subject = render_to_string( - 'patchwork/patch-change-notification-subject.text', - context).strip() - content = render_to_string('patchwork/patch-change-notification.mail', - context) - - message = EmailMessage(subject = subject, body = content, - from_email = settings.NOTIFICATION_FROM_EMAIL, - to = [recipient.email], - headers = {'Precedence': 'bulk'}) - - try: - message.send() - except ex: - errors.append((recipient, ex)) - continue - - delete_notifications() - - return errors - -def do_expiry(): - # expire any pending confirmations - q = (Q(date__lt = datetime.datetime.now() - EmailConfirmation.validity) | - Q(active = False)) - EmailConfirmation.objects.filter(q).delete() - - # expire inactive users with no pending confirmation - pending_confs = EmailConfirmation.objects.values('user') - users = User.objects.filter( - is_active = False, - last_login = F('date_joined') - ).exclude( - id__in = pending_confs - ) - - # delete users - users.delete() - - - diff --git a/apps/patchwork/views/__init__.py b/apps/patchwork/views/__init__.py deleted file mode 100644 index dfca56d..0000000 --- a/apps/patchwork/views/__init__.py +++ /dev/null @@ -1,220 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -from base import * -from patchwork.utils import Order, get_patch_ids, bundle_actions, set_bundle -from patchwork.paginator import Paginator -from patchwork.forms import MultiplePatchForm -from patchwork.models import Comment -import re -import datetime - -try: - from email.mime.nonmultipart import MIMENonMultipart - from email.encoders import encode_7or8bit - from email.parser import HeaderParser - from email.header import Header - import email.utils -except ImportError: - # Python 2.4 compatibility - from email.MIMENonMultipart import MIMENonMultipart - from email.Encoders import encode_7or8bit - from email.Parser import HeaderParser - from email.Header import Header - import email.Utils - email.utils = email.Utils - -def generic_list(request, project, view, - view_args = {}, filter_settings = [], patches = None, - editable_order = False): - - context = PatchworkRequestContext(request, - list_view = view, - list_view_params = view_args) - - context.project = project - order = Order(request.REQUEST.get('order'), editable = editable_order) - - # Explicitly set data to None because request.POST will be an empty dict - # when the form is not submitted, but passing a non-None data argument to - # a forms.Form will make it bound and we don't want that to happen unless - # there's been a form submission. - data = None - if request.method == 'POST': - data = request.POST - user = request.user - properties_form = None - if project.is_editable(user): - - # we only pass the post data to the MultiplePatchForm if that was - # the actual form submitted - data_tmp = None - if data and data.get('form', '') == 'patchlistform': - data_tmp = data - - properties_form = MultiplePatchForm(project, data = data_tmp) - - if request.method == 'POST' and data.get('form') == 'patchlistform': - action = data.get('action', '').lower() - - # special case: the user may have hit enter in the 'create bundle' - # text field, so if non-empty, assume the create action: - if data.get('bundle_name', False): - action = 'create' - - ps = Patch.objects.filter(id__in = get_patch_ids(data)) - - if action in bundle_actions: - errors = set_bundle(user, project, action, data, ps, context) - - elif properties_form and action == properties_form.action: - errors = process_multiplepatch_form(properties_form, user, - action, ps, context) - else: - errors = [] - - if errors: - context['errors'] = errors - - for (filterclass, setting) in filter_settings: - if isinstance(setting, dict): - context.filters.set_status(filterclass, **setting) - elif isinstance(setting, list): - context.filters.set_status(filterclass, *setting) - else: - context.filters.set_status(filterclass, setting) - - if patches is None: - patches = Patch.objects.filter(project=project) - - patches = context.filters.apply(patches) - if not editable_order: - patches = order.apply(patches) - - # we don't need the content or headers for a list; they're text fields - # that can potentially contain a lot of data - patches = patches.defer('content', 'headers') - - # but we will need to follow the state and submitter relations for - # rendering the list template - patches = patches.select_related('state', 'submitter') - - paginator = Paginator(request, patches) - - context.update({ - 'page': paginator.current_page, - 'patchform': properties_form, - 'project': project, - 'order': order, - }) - - return context - - -def process_multiplepatch_form(form, user, action, patches, context): - errors = [] - if not form.is_valid() or action != form.action: - return ['The submitted form data was invalid'] - - if len(patches) == 0: - context.add_message("No patches selected; nothing updated") - return errors - - changed_patches = 0 - for patch in patches: - if not patch.is_editable(user): - errors.append("You don't have permissions to edit patch '%s'" - % patch.name) - continue - - changed_patches += 1 - form.save(patch) - - if changed_patches == 1: - context.add_message("1 patch updated") - elif changed_patches > 1: - context.add_message("%d patches updated" % changed_patches) - else: - context.add_message("No patches updated") - - return errors - -class PatchMbox(MIMENonMultipart): - patch_charset = 'utf-8' - def __init__(self, _text): - MIMENonMultipart.__init__(self, 'text', 'plain', - **{'charset': self.patch_charset}) - self.set_payload(_text.encode(self.patch_charset)) - encode_7or8bit(self) - -def patch_to_mbox(patch): - postscript_re = re.compile('\n-{2,3} ?\n') - - comment = None - try: - comment = Comment.objects.get(patch = patch, msgid = patch.msgid) - except Exception: - pass - - body = '' - if comment: - body = comment.content.strip() + "\n" - - parts = postscript_re.split(body, 1) - if len(parts) == 2: - (body, postscript) = parts - body = body.strip() + "\n" - postscript = postscript.rstrip() - else: - postscript = '' - - for comment in Comment.objects.filter(patch = patch) \ - .exclude(msgid = patch.msgid): - body += comment.patch_responses() - - if postscript: - body += '---\n' + postscript + '\n' - - if patch.content: - body += '\n' + patch.content - - delta = patch.date - datetime.datetime.utcfromtimestamp(0) - utc_timestamp = delta.seconds + delta.days*24*3600 - - mail = PatchMbox(body) - mail['Subject'] = patch.name - mail['From'] = email.utils.formataddr(( - str(Header(patch.submitter.name, mail.patch_charset)), - patch.submitter.email)) - mail['X-Patchwork-Id'] = str(patch.id) - mail['Message-Id'] = patch.msgid - mail.set_unixfrom('From patchwork ' + patch.date.ctime()) - - - copied_headers = ['To', 'Cc', 'Date'] - orig_headers = HeaderParser().parsestr(str(patch.headers)) - for header in copied_headers: - if header in orig_headers: - mail[header] = orig_headers[header] - - if 'Date' not in mail: - mail['Date'] = email.utils.formatdate(utc_timestamp) - - return mail diff --git a/apps/patchwork/views/base.py b/apps/patchwork/views/base.py deleted file mode 100644 index 6d7dd13..0000000 --- a/apps/patchwork/views/base.py +++ /dev/null @@ -1,122 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -from patchwork.models import Patch, Project, Person, EmailConfirmation -from django.shortcuts import render_to_response, get_object_or_404 -from django.http import HttpResponse, HttpResponseRedirect, Http404 -from patchwork.requestcontext import PatchworkRequestContext -from django.core import serializers, urlresolvers -from django.template.loader import render_to_string -from django.conf import settings -from django.db.models import Q - -def projects(request): - context = PatchworkRequestContext(request) - projects = Project.objects.all() - - if projects.count() == 1: - return HttpResponseRedirect( - urlresolvers.reverse('patchwork.views.patch.list', - kwargs = {'project_id': projects[0].linkname})) - - context['projects'] = projects - return render_to_response('patchwork/projects.html', context) - -def pwclientrc(request, project_id): - project = get_object_or_404(Project, linkname = project_id) - context = PatchworkRequestContext(request) - context.project = project - if settings.FORCE_HTTPS_LINKS or request.is_secure(): - context['scheme'] = 'https' - else: - context['scheme'] = 'http' - response = HttpResponse(content_type = "text/plain") - response['Content-Disposition'] = 'attachment; filename=.pwclientrc' - response.write(render_to_string('patchwork/pwclientrc', context)) - return response - -def pwclient(request): - context = PatchworkRequestContext(request) - response = HttpResponse(content_type = "text/x-python") - response['Content-Disposition'] = 'attachment; filename=pwclient' - response.write(render_to_string('patchwork/pwclient', context)) - return response - -def confirm(request, key): - import patchwork.views.user, patchwork.views.mail - views = { - 'userperson': patchwork.views.user.link_confirm, - 'registration': patchwork.views.user.register_confirm, - 'optout': patchwork.views.mail.optout_confirm, - 'optin': patchwork.views.mail.optin_confirm, - } - - conf = get_object_or_404(EmailConfirmation, key = key) - if conf.type not in views: - raise Http404 - - if conf.active and conf.is_valid(): - return views[conf.type](request, conf) - - context = PatchworkRequestContext(request) - context['conf'] = conf - if not conf.active: - context['error'] = 'inactive' - elif not conf.is_valid(): - context['error'] = 'expired' - - return render_to_response('patchwork/confirm-error.html', context) - -def submitter_complete(request): - search = request.GET.get('q', '') - limit = request.GET.get('l', None) - response = HttpResponse(content_type = "text/plain") - - if len(search) <= 3: - return response - - queryset = Person.objects.filter(Q(name__icontains = search) | - Q(email__icontains = search)) - if limit is not None: - try: - limit = int(limit) - except ValueError: - limit = None - - if limit is not None and limit > 0: - queryset = queryset[:limit] - - json_serializer = serializers.get_serializer("json")() - json_serializer.serialize(queryset, ensure_ascii=False, stream=response) - return response - -help_pages = {'': 'index.html', - 'about/': 'about.html', - } - -if settings.ENABLE_XMLRPC: - help_pages['pwclient/'] = 'pwclient.html' - -def help(request, path): - context = PatchworkRequestContext(request) - if path in help_pages: - return render_to_response('patchwork/help/' + help_pages[path], context) - raise Http404 - diff --git a/apps/patchwork/views/bundle.py b/apps/patchwork/views/bundle.py deleted file mode 100644 index 3fb47e2..0000000 --- a/apps/patchwork/views/bundle.py +++ /dev/null @@ -1,221 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User -from django.shortcuts import render_to_response, get_object_or_404 -from patchwork.requestcontext import PatchworkRequestContext -from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound -import django.core.urlresolvers -from patchwork.models import Patch, Bundle, BundlePatch, Project -from patchwork.utils import get_patch_ids -from patchwork.forms import BundleForm, DeleteBundleForm -from patchwork.views import generic_list, patch_to_mbox -from patchwork.filters import DelegateFilter - -@login_required -def setbundle(request): - context = PatchworkRequestContext(request) - - bundle = None - - if request.method == 'POST': - action = request.POST.get('action', None) - if action is None: - pass - elif action == 'create': - project = get_object_or_404(Project, - id = request.POST.get('project')) - bundle = Bundle(owner = request.user, project = project, - name = request.POST['name']) - bundle.save() - patch_id = request.POST.get('patch_id', None) - if patch_id: - patch = get_object_or_404(Patch, id = patch_id) - try: - bundle.append_patch(patch) - except Exception: - pass - bundle.save() - elif action == 'add': - bundle = get_object_or_404(Bundle, - owner = request.user, id = request.POST['id']) - bundle.save() - - patch_id = request.get('patch_id', None) - if patch_id: - patch_ids = patch_id - else: - patch_ids = get_patch_ids(request.POST) - - for id in patch_ids: - try: - patch = Patch.objects.get(id = id) - bundle.append_patch(patch) - except: - pass - - bundle.save() - elif action == 'delete': - try: - bundle = Bundle.objects.get(owner = request.user, - id = request.POST['id']) - bundle.delete() - except Exception: - pass - - bundle = None - - else: - bundle = get_object_or_404(Bundle, owner = request.user, - id = request.POST['bundle_id']) - - if 'error' in context: - pass - - if bundle: - return HttpResponseRedirect( - django.core.urlresolvers.reverse( - 'patchwork.views.bundle.bundle', - kwargs = {'bundle_id': bundle.id} - ) - ) - else: - return HttpResponseRedirect( - django.core.urlresolvers.reverse( - 'patchwork.views.bundle.list') - ) - -@login_required -def bundles(request): - context = PatchworkRequestContext(request) - - if request.method == 'POST': - form_name = request.POST.get('form_name', '') - - if form_name == DeleteBundleForm.name: - form = DeleteBundleForm(request.POST) - if form.is_valid(): - bundle = get_object_or_404(Bundle, - id = form.cleaned_data['bundle_id']) - bundle.delete() - - bundles = Bundle.objects.filter(owner = request.user) - for bundle in bundles: - bundle.delete_form = DeleteBundleForm(auto_id = False, - initial = {'bundle_id': bundle.id}) - - context['bundles'] = bundles - - return render_to_response('patchwork/bundles.html', context) - -def bundle(request, username, bundlename): - bundle = get_object_or_404(Bundle, owner__username = username, - name = bundlename) - filter_settings = [(DelegateFilter, DelegateFilter.AnyDelegate)] - - is_owner = request.user == bundle.owner - - if not (is_owner or bundle.public): - return HttpResponseNotFound() - - if is_owner: - if request.method == 'POST' and request.POST.get('form') == 'bundle': - action = request.POST.get('action', '').lower() - if action == 'delete': - bundle.delete() - return HttpResponseRedirect( - django.core.urlresolvers.reverse( - 'patchwork.views.user.profile') - ) - elif action == 'update': - form = BundleForm(request.POST, instance = bundle) - if form.is_valid(): - form.save() - - # if we've changed the bundle name, redirect to new URL - bundle = Bundle.objects.get(pk = bundle.pk) - if bundle.name != bundlename: - return HttpResponseRedirect(bundle.get_absolute_url()) - - else: - form = BundleForm(instance = bundle) - else: - form = BundleForm(instance = bundle) - - if request.method == 'POST' and \ - request.POST.get('form') == 'reorderform': - order = get_object_or_404(BundlePatch, bundle = bundle, - patch__id = request.POST.get('order_start')).order - - for patch_id in request.POST.getlist('neworder'): - bundlepatch = get_object_or_404(BundlePatch, - bundle = bundle, patch__id = patch_id) - bundlepatch.order = order - bundlepatch.save() - order += 1 - else: - form = None - - context = generic_list(request, bundle.project, - 'patchwork.views.bundle.bundle', - view_args = {'username': bundle.owner.username, - 'bundlename': bundle.name}, - filter_settings = filter_settings, - patches = bundle.ordered_patches(), - editable_order = is_owner) - - context['bundle'] = bundle - context['bundleform'] = form - - return render_to_response('patchwork/bundle.html', context) - -def mbox(request, username, bundlename): - bundle = get_object_or_404(Bundle, owner__username = username, - name = bundlename) - - if not (request.user == bundle.owner or bundle.public): - return HttpResponseNotFound() - - mbox = '\n'.join([patch_to_mbox(p).as_string(True) - for p in bundle.ordered_patches()]) - - response = HttpResponse(content_type='text/plain') - response['Content-Disposition'] = \ - 'attachment; filename=bundle-%d-%s.mbox' % (bundle.id, bundle.name) - - response.write(mbox) - return response - -@login_required -def bundle_redir(request, bundle_id): - bundle = get_object_or_404(Bundle, id = bundle_id, owner = request.user) - return HttpResponseRedirect(bundle.get_absolute_url()) - -@login_required -def mbox_redir(request, bundle_id): - bundle = get_object_or_404(Bundle, id = bundle_id, owner = request.user) - return HttpResponseRedirect(django.core.urlresolvers.reverse( - 'patchwork.views.bundle.mbox', kwargs = { - 'username': request.user.username, - 'bundlename': bundle.name, - })) - - - diff --git a/apps/patchwork/views/mail.py b/apps/patchwork/views/mail.py deleted file mode 100644 index aebba34..0000000 --- a/apps/patchwork/views/mail.py +++ /dev/null @@ -1,119 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2010 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -from patchwork.requestcontext import PatchworkRequestContext -from patchwork.models import EmailOptout, EmailConfirmation -from patchwork.forms import OptinoutRequestForm, EmailForm -from django.shortcuts import render_to_response -from django.template.loader import render_to_string -from django.conf import settings as conf_settings -from django.core.mail import send_mail -from django.core.urlresolvers import reverse -from django.http import HttpResponseRedirect - -def settings(request): - context = PatchworkRequestContext(request) - if request.method == 'POST': - form = EmailForm(data = request.POST) - if form.is_valid(): - email = form.cleaned_data['email'] - is_optout = EmailOptout.objects.filter(email = email).count() > 0 - context.update({ - 'email': email, - 'is_optout': is_optout, - }) - return render_to_response('patchwork/mail-settings.html', context) - - else: - form = EmailForm() - context['form'] = form - return render_to_response('patchwork/mail-form.html', context) - -def optout_confirm(request, conf): - context = PatchworkRequestContext(request) - - email = conf.email.strip().lower() - # silently ignore duplicated optouts - if EmailOptout.objects.filter(email = email).count() == 0: - optout = EmailOptout(email = email) - optout.save() - - conf.deactivate() - context['email'] = conf.email - - return render_to_response('patchwork/optout.html', context) - -def optin_confirm(request, conf): - context = PatchworkRequestContext(request) - - email = conf.email.strip().lower() - EmailOptout.objects.filter(email = email).delete() - - conf.deactivate() - context['email'] = conf.email - - return render_to_response('patchwork/optin.html', context) - -def optinout(request, action, description): - context = PatchworkRequestContext(request) - - mail_template = 'patchwork/%s-request.mail' % action - html_template = 'patchwork/%s-request.html' % action - - if request.method != 'POST': - return HttpResponseRedirect(reverse(settings)) - - form = OptinoutRequestForm(data = request.POST) - if not form.is_valid(): - context['error'] = ('There was an error in the %s form. ' + - 'Please review the form and re-submit.') % \ - description - context['form'] = form - return render_to_response(html_template, context) - - email = form.cleaned_data['email'] - if action == 'optin' and \ - EmailOptout.objects.filter(email = email).count() == 0: - context['error'] = ('The email address %s is not on the ' + - 'patchwork opt-out list, so you don\'t ' + - 'need to opt back in') % email - context['form'] = form - return render_to_response(html_template, context) - - conf = EmailConfirmation(type = action, email = email) - conf.save() - context['confirmation'] = conf - mail = render_to_string(mail_template, context) - try: - send_mail('Patchwork %s confirmation' % description, mail, - conf_settings.DEFAULT_FROM_EMAIL, [email]) - context['email'] = mail - context['email_sent'] = True - except Exception, ex: - context['error'] = 'An error occurred during confirmation . ' + \ - 'Please try again later.' - context['admins'] = conf_settings.ADMINS - - return render_to_response(html_template, context) - -def optout(request): - return optinout(request, 'optout', 'opt-out') - -def optin(request): - return optinout(request, 'optin', 'opt-in') diff --git a/apps/patchwork/views/patch.py b/apps/patchwork/views/patch.py deleted file mode 100644 index 62ff853..0000000 --- a/apps/patchwork/views/patch.py +++ /dev/null @@ -1,107 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -from patchwork.models import Patch, Project, Bundle -from patchwork.forms import PatchForm, CreateBundleForm -from patchwork.requestcontext import PatchworkRequestContext -from django.shortcuts import render_to_response, get_object_or_404 -from django.http import HttpResponse, HttpResponseForbidden -from patchwork.views import generic_list, patch_to_mbox - -def patch(request, patch_id): - context = PatchworkRequestContext(request) - patch = get_object_or_404(Patch, id=patch_id) - context.project = patch.project - editable = patch.is_editable(request.user) - - form = None - createbundleform = None - - if editable: - form = PatchForm(instance = patch) - if request.user.is_authenticated(): - createbundleform = CreateBundleForm() - - if request.method == 'POST': - action = request.POST.get('action', None) - if action: - action = action.lower() - - if action == 'createbundle': - bundle = Bundle(owner = request.user, project = patch.project) - createbundleform = CreateBundleForm(instance = bundle, - data = request.POST) - if createbundleform.is_valid(): - createbundleform.save() - bundle.append_patch(patch) - bundle.save() - createbundleform = CreateBundleForm() - context.add_message('Bundle %s created' % bundle.name) - - elif action == 'addtobundle': - bundle = get_object_or_404(Bundle, id = \ - request.POST.get('bundle_id')) - try: - bundle.append_patch(patch) - bundle.save() - context.add_message('Patch added to bundle "%s"' % bundle.name) - except Exception, ex: - context.add_message("Couldn't add patch '%s' to bundle %s: %s" \ - % (patch.name, bundle.name, ex.message)) - - # all other actions require edit privs - elif not editable: - return HttpResponseForbidden() - - elif action is None: - form = PatchForm(data = request.POST, instance = patch) - if form.is_valid(): - form.save() - context.add_message('Patch updated') - - context['patch'] = patch - context['patchform'] = form - context['createbundleform'] = createbundleform - context['project'] = patch.project - - return render_to_response('patchwork/patch.html', context) - -def content(request, patch_id): - patch = get_object_or_404(Patch, id=patch_id) - response = HttpResponse(content_type="text/x-patch") - response.write(patch.content) - response['Content-Disposition'] = 'attachment; filename=' + \ - patch.filename().replace(';', '').replace('\n', '') - return response - -def mbox(request, patch_id): - patch = get_object_or_404(Patch, id=patch_id) - response = HttpResponse(content_type="text/plain") - response.write(patch_to_mbox(patch).as_string(True)) - response['Content-Disposition'] = 'attachment; filename=' + \ - patch.filename().replace(';', '').replace('\n', '') - return response - - -def list(request, project_id): - project = get_object_or_404(Project, linkname=project_id) - context = generic_list(request, project, 'patchwork.views.patch.list', - view_args = {'project_id': project.linkname}) - return render_to_response('patchwork/list.html', context) diff --git a/apps/patchwork/views/project.py b/apps/patchwork/views/project.py deleted file mode 100644 index 114dbe0..0000000 --- a/apps/patchwork/views/project.py +++ /dev/null @@ -1,38 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2009 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -from patchwork.models import Patch, Project -from django.shortcuts import render_to_response, get_object_or_404 -from django.contrib.auth.models import User -from patchwork.requestcontext import PatchworkRequestContext - -def project(request, project_id): - context = PatchworkRequestContext(request) - project = get_object_or_404(Project, linkname = project_id) - context.project = project - - context['maintainers'] = User.objects.filter( \ - profile__maintainer_projects = project) - context['n_patches'] = Patch.objects.filter(project = project, - archived = False).count() - context['n_archived_patches'] = Patch.objects.filter(project = project, - archived = True).count() - - return render_to_response('patchwork/project.html', context) diff --git a/apps/patchwork/views/user.py b/apps/patchwork/views/user.py deleted file mode 100644 index 126ecc9..0000000 --- a/apps/patchwork/views/user.py +++ /dev/null @@ -1,216 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -from django.contrib.auth.decorators import login_required -from patchwork.requestcontext import PatchworkRequestContext -from django.shortcuts import render_to_response, get_object_or_404 -from django.contrib import auth -from django.contrib.sites.models import Site -from django.http import HttpResponseRedirect -from patchwork.models import Project, Bundle, Person, EmailConfirmation, \ - State, EmailOptout -from patchwork.forms import UserProfileForm, UserPersonLinkForm, \ - RegistrationForm -from patchwork.filters import DelegateFilter -from patchwork.views import generic_list -from django.template.loader import render_to_string -from django.conf import settings -from django.core.mail import send_mail -import django.core.urlresolvers - -def register(request): - context = PatchworkRequestContext(request) - if request.method == 'POST': - form = RegistrationForm(request.POST) - if form.is_valid(): - data = form.cleaned_data - # create inactive user - user = auth.models.User.objects.create_user(data['username'], - data['email'], - data['password']) - user.is_active = False; - user.first_name = data.get('first_name', '') - user.last_name = data.get('last_name', '') - user.save() - - # create confirmation - conf = EmailConfirmation(type = 'registration', user = user, - email = user.email) - conf.save() - - # send email - mail_ctx = {'site': Site.objects.get_current(), - 'confirmation': conf} - - subject = render_to_string('patchwork/activation_email_subject.txt', - mail_ctx).replace('\n', ' ').strip() - - message = render_to_string('patchwork/activation_email.txt', - mail_ctx) - - send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, - [conf.email]) - - # setting 'confirmation' in the template indicates success - context['confirmation'] = conf - - else: - form = RegistrationForm() - - return render_to_response('patchwork/registration_form.html', - { 'form': form }, - context_instance=context) - -def register_confirm(request, conf): - conf.user.is_active = True - conf.user.save() - conf.deactivate() - try: - person = Person.objects.get(email__iexact = conf.user.email) - except Person.DoesNotExist: - person = Person(email = conf.user.email, - name = conf.user.profile.name()) - person.user = conf.user - person.save() - - return render_to_response('patchwork/registration-confirm.html') - -@login_required -def profile(request): - context = PatchworkRequestContext(request) - - if request.method == 'POST': - form = UserProfileForm(instance = request.user.profile, - data = request.POST) - if form.is_valid(): - form.save() - else: - form = UserProfileForm(instance = request.user.profile) - - context.project = request.user.profile.primary_project - context['bundles'] = Bundle.objects.filter(owner = request.user) - context['profileform'] = form - - optout_query = '%s.%s IN (SELECT %s FROM %s)' % ( - Person._meta.db_table, - Person._meta.get_field('email').column, - EmailOptout._meta.get_field('email').column, - EmailOptout._meta.db_table) - people = Person.objects.filter(user = request.user) \ - .extra(select = {'is_optout': optout_query}) - context['linked_emails'] = people - context['linkform'] = UserPersonLinkForm() - - return render_to_response('patchwork/profile.html', context) - -@login_required -def link(request): - context = PatchworkRequestContext(request) - - if request.method == 'POST': - form = UserPersonLinkForm(request.POST) - if form.is_valid(): - conf = EmailConfirmation(type = 'userperson', - user = request.user, - email = form.cleaned_data['email']) - conf.save() - context['confirmation'] = conf - - try: - send_mail('Patchwork email address confirmation', - render_to_string('patchwork/user-link.mail', - context), - settings.DEFAULT_FROM_EMAIL, - [form.cleaned_data['email']]) - except Exception: - context['confirmation'] = None - context['error'] = 'An error occurred during confirmation. ' + \ - 'Please try again later' - else: - form = UserPersonLinkForm() - context['linkform'] = form - - return render_to_response('patchwork/user-link.html', context) - -@login_required -def link_confirm(request, conf): - context = PatchworkRequestContext(request) - - try: - person = Person.objects.get(email__iexact = conf.email) - except Person.DoesNotExist: - person = Person(email = conf.email) - - person.link_to_user(conf.user) - person.save() - conf.deactivate() - - context['person'] = person - - return render_to_response('patchwork/user-link-confirm.html', context) - -@login_required -def unlink(request, person_id): - person = get_object_or_404(Person, id = person_id) - - if request.method == 'POST': - if person.email != request.user.email: - person.user = None - person.save() - - url = django.core.urlresolvers.reverse('patchwork.views.user.profile') - return HttpResponseRedirect(url) - - -@login_required -def todo_lists(request): - todo_lists = [] - - for project in Project.objects.all(): - patches = request.user.profile.todo_patches(project = project) - if not patches.count(): - continue - - todo_lists.append({'project': project, 'n_patches': patches.count()}) - - if len(todo_lists) == 1: - return todo_list(request, todo_lists[0]['project'].linkname) - - context = PatchworkRequestContext(request) - context['todo_lists'] = todo_lists - context.project = request.user.profile.primary_project - return render_to_response('patchwork/todo-lists.html', context) - -@login_required -def todo_list(request, project_id): - project = get_object_or_404(Project, linkname = project_id) - patches = request.user.profile.todo_patches(project = project) - filter_settings = [(DelegateFilter, - {'delegate': request.user})] - - context = generic_list(request, project, - 'patchwork.views.user.todo_list', - view_args = {'project_id': project.linkname}, - filter_settings = filter_settings, - patches = patches) - - context['action_required_states'] = \ - State.objects.filter(action_required = True).all() - return render_to_response('patchwork/todo-list.html', context) diff --git a/apps/patchwork/views/xmlrpc.py b/apps/patchwork/views/xmlrpc.py deleted file mode 100644 index 84ed408..0000000 --- a/apps/patchwork/views/xmlrpc.py +++ /dev/null @@ -1,450 +0,0 @@ -# Patchwork - automated patch tracking system -# Copyright (C) 2008 Jeremy Kerr -# -# This file is part of the Patchwork package. -# -# Patchwork is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Patchwork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Patchwork; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -# Patchwork XMLRPC interface -# - -from SimpleXMLRPCServer import SimpleXMLRPCDispatcher -from django.http import HttpResponse, HttpResponseRedirect, \ - HttpResponseServerError -from django.core import urlresolvers -from django.contrib.auth import authenticate -from patchwork.models import Patch, Project, Person, State -from patchwork.views import patch_to_mbox -from django.views.decorators.csrf import csrf_exempt - -import sys -import base64 -import xmlrpclib - -class PatchworkXMLRPCDispatcher(SimpleXMLRPCDispatcher): - def __init__(self): - if sys.version_info[:3] >= (2,5,): - SimpleXMLRPCDispatcher.__init__(self, allow_none=False, - encoding=None) - def _dumps(obj, *args, **kwargs): - kwargs['allow_none'] = self.allow_none - kwargs['encoding'] = self.encoding - return xmlrpclib.dumps(obj, *args, **kwargs) - else: - def _dumps(obj, *args, **kwargs): - return xmlrpclib.dumps(obj, *args, **kwargs) - SimpleXMLRPCDispatcher.__init__(self) - - self.dumps = _dumps - - # map of name => (auth, func) - self.func_map = {} - - def register_function(self, fn, auth_required): - self.func_map[fn.__name__] = (auth_required, fn) - - - def _user_for_request(self, request): - auth_header = None - - if 'HTTP_AUTHORIZATION' in request.META: - auth_header = request.META.get('HTTP_AUTHORIZATION') - elif 'Authorization' in request.META: - auth_header = request.META.get('Authorization') - - if auth_header is None or auth_header == '': - raise Exception("No authentication credentials given") - - str = auth_header.strip() - - if not str.startswith('Basic '): - raise Exception("Authentication scheme not supported") - - str = str[len('Basic '):].strip() - - try: - decoded = base64.decodestring(str) - username, password = decoded.split(':', 1) - except: - raise Exception("Invalid authentication credentials") - - return authenticate(username = username, password = password) - - - def _dispatch(self, request, method, params): - if method not in self.func_map.keys(): - raise Exception('method "%s" is not supported' % method) - - auth_required, fn = self.func_map[method] - - if auth_required: - user = self._user_for_request(request) - if not user: - raise Exception("Invalid username/password") - - params = (user,) + params - - return fn(*params) - - def _marshaled_dispatch(self, request): - try: - params, method = xmlrpclib.loads(request.body) - - response = self._dispatch(request, method, params) - # wrap response in a singleton tuple - response = (response,) - response = self.dumps(response, methodresponse=1) - except xmlrpclib.Fault, fault: - response = self.dumps(fault) - except: - # report exception back to server - response = self.dumps( - xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)), - ) - - return response - -dispatcher = PatchworkXMLRPCDispatcher() - -# XMLRPC view function -@csrf_exempt -def xmlrpc(request): - if request.method != 'POST': - return HttpResponseRedirect( - urlresolvers.reverse('patchwork.views.help', - kwargs = {'path': 'pwclient/'})) - - response = HttpResponse() - try: - ret = dispatcher._marshaled_dispatch(request) - response.write(ret) - except Exception: - return HttpResponseServerError() - - return response - -# decorator for XMLRPC methods. Setting login_required to true will call -# the decorated function with a non-optional user as the first argument. -def xmlrpc_method(login_required = False): - def wrap(f): - dispatcher.register_function(f, login_required) - return f - - return wrap - - - -# We allow most of the Django field lookup types for remote queries -LOOKUP_TYPES = ["iexact", "contains", "icontains", "gt", "gte", "lt", - "in", "startswith", "istartswith", "endswith", - "iendswith", "range", "year", "month", "day", "isnull" ] - -####################################################################### -# Helper functions -####################################################################### - -def project_to_dict(obj): - """Return a trimmed down dictionary representation of a Project - object which is OK to send to the client.""" - return \ - { - 'id' : obj.id, - 'linkname' : obj.linkname, - 'name' : obj.name, - } - -def person_to_dict(obj): - """Return a trimmed down dictionary representation of a Person - object which is OK to send to the client.""" - - # Make sure we don't return None even if the user submitted a patch - # with no real name. XMLRPC can't marshall None. - if obj.name is not None: - name = obj.name - else: - name = obj.email - - return \ - { - 'id' : obj.id, - 'email' : obj.email, - 'name' : name, - 'user' : unicode(obj.user).encode("utf-8"), - } - -def patch_to_dict(obj): - """Return a trimmed down dictionary representation of a Patch - object which is OK to send to the client.""" - return \ - { - 'id' : obj.id, - 'date' : unicode(obj.date).encode("utf-8"), - 'filename' : obj.filename(), - 'msgid' : obj.msgid, - 'name' : obj.name, - 'project' : unicode(obj.project).encode("utf-8"), - 'project_id' : obj.project_id, - 'state' : unicode(obj.state).encode("utf-8"), - 'state_id' : obj.state_id, - 'archived' : obj.archived, - 'submitter' : unicode(obj.submitter).encode("utf-8"), - 'submitter_id' : obj.submitter_id, - 'delegate' : unicode(obj.delegate).encode("utf-8"), - 'delegate_id' : max(obj.delegate_id, 0), - 'commit_ref' : max(obj.commit_ref, ''), - } - -def bundle_to_dict(obj): - """Return a trimmed down dictionary representation of a Bundle - object which is OK to send to the client.""" - return \ - { - 'id' : obj.id, - 'name' : obj.name, - 'n_patches' : obj.n_patches(), - 'public_url' : obj.public_url(), - } - -def state_to_dict(obj): - """Return a trimmed down dictionary representation of a State - object which is OK to send to the client.""" - return \ - { - 'id' : obj.id, - 'name' : obj.name, - } - -####################################################################### -# Public XML-RPC methods -####################################################################### - -@xmlrpc_method(False) -def pw_rpc_version(): - """Return Patchwork XML-RPC interface version.""" - return 1 - -@xmlrpc_method(False) -def project_list(search_str="", max_count=0): - """Get a list of projects matching the given filters.""" - try: - if len(search_str) > 0: - projects = Project.objects.filter(linkname__icontains = search_str) - else: - projects = Project.objects.all() - - if max_count > 0: - return map(project_to_dict, projects)[:max_count] - else: - return map(project_to_dict, projects) - except: - return [] - -@xmlrpc_method(False) -def project_get(project_id): - """Return structure for the given project ID.""" - try: - project = Project.objects.filter(id = project_id)[0] - return project_to_dict(project) - except: - return {} - -@xmlrpc_method(False) -def person_list(search_str="", max_count=0): - """Get a list of Person objects matching the given filters.""" - try: - if len(search_str) > 0: - people = (Person.objects.filter(name__icontains = search_str) | - Person.objects.filter(email__icontains = search_str)) - else: - people = Person.objects.all() - - if max_count > 0: - return map(person_to_dict, people)[:max_count] - else: - return map(person_to_dict, people) - - except: - return [] - -@xmlrpc_method(False) -def person_get(person_id): - """Return structure for the given person ID.""" - try: - person = Person.objects.filter(id = person_id)[0] - return person_to_dict(person) - except: - return {} - -@xmlrpc_method(False) -def patch_list(filter={}): - """Get a list of patches matching the given filters.""" - try: - # We allow access to many of the fields. But, some fields are - # filtered by raw object so we must lookup by ID instead over - # XML-RPC. - ok_fields = [ - "id", - "name", - "project_id", - "submitter_id", - "delegate_id", - "archived", - "state_id", - "date", - "commit_ref", - "hash", - "msgid", - "max_count", - ] - - dfilter = {} - max_count = 0 - - for key in filter: - parts = key.split("__") - if parts[0] not in ok_fields: - # Invalid field given - return [] - if len(parts) > 1: - if LOOKUP_TYPES.count(parts[1]) == 0: - # Invalid lookup type given - return [] - - if parts[0] == 'project_id': - dfilter['project'] = Project.objects.filter(id = - filter[key])[0] - elif parts[0] == 'submitter_id': - dfilter['submitter'] = Person.objects.filter(id = - filter[key])[0] - elif parts[0] == 'delegate_id': - dfilter['delegate'] = Person.objects.filter(id = - filter[key])[0] - elif parts[0] == 'state_id': - dfilter['state'] = State.objects.filter(id = - filter[key])[0] - elif parts[0] == 'max_count': - max_count = filter[key] - else: - dfilter[key] = filter[key] - - patches = Patch.objects.filter(**dfilter) - - if max_count > 0: - return map(patch_to_dict, patches[:max_count]) - else: - return map(patch_to_dict, patches) - - except: - return [] - -@xmlrpc_method(False) -def patch_get(patch_id): - """Return structure for the given patch ID.""" - try: - patch = Patch.objects.filter(id = patch_id)[0] - return patch_to_dict(patch) - except: - return {} - -@xmlrpc_method(False) -def patch_get_by_hash(hash): - """Return structure for the given patch hash.""" - try: - patch = Patch.objects.filter(hash = hash)[0] - return patch_to_dict(patch) - except: - return {} - -@xmlrpc_method(False) -def patch_get_by_project_hash(project, hash): - """Return structure for the given patch hash.""" - try: - patch = Patch.objects.filter(project__linkname = project, - hash = hash)[0] - return patch_to_dict(patch) - except: - return {} - -@xmlrpc_method(False) -def patch_get_mbox(patch_id): - """Return mbox string for the given patch ID.""" - try: - patch = Patch.objects.filter(id = patch_id)[0] - return patch_to_mbox(patch).as_string(True) - except: - return "" - -@xmlrpc_method(False) -def patch_get_diff(patch_id): - """Return diff for the given patch ID.""" - try: - patch = Patch.objects.filter(id = patch_id)[0] - return patch.content - except: - return "" - -@xmlrpc_method(True) -def patch_set(user, patch_id, params): - """Update a patch with the key,value pairs in params. Only some parameters - can be set""" - try: - ok_params = ['state', 'commit_ref', 'archived'] - - patch = Patch.objects.get(id = patch_id) - - if not patch.is_editable(user): - raise Exception('No permissions to edit this patch') - - for (k, v) in params.iteritems(): - if k not in ok_params: - continue - - if k == 'state': - patch.state = State.objects.get(id = v) - - else: - setattr(patch, k, v) - - patch.save() - - return True - - except: - raise - -@xmlrpc_method(False) -def state_list(search_str="", max_count=0): - """Get a list of state structures matching the given search string.""" - try: - if len(search_str) > 0: - states = State.objects.filter(name__icontains = search_str) - else: - states = State.objects.all() - - if max_count > 0: - return map(state_to_dict, states)[:max_count] - else: - return map(state_to_dict, states) - except: - return [] - -@xmlrpc_method(False) -def state_get(state_id): - """Return structure for the given state ID.""" - try: - state = State.objects.filter(id = state_id)[0] - return state_to_dict(state) - except: - return {} diff --git a/docs/HACKING b/docs/HACKING index cab59eb..c1b478e 100644 --- a/docs/HACKING +++ b/docs/HACKING @@ -43,9 +43,9 @@ It's always a good idea to use virtualenv to develop python software. 5. Now one can run patchwork within that environment - (django-1.7)$ ./apps/manage.py --version + (django-1.7)$ ./manage.py --version 1.7 - (django-1.7)$ ./apps/manage.py runserver + (django-1.7)$ ./manage.py runserver 6. To exit the virtual environment diff --git a/docs/INSTALL b/docs/INSTALL index 16ab2b5..b006178 100644 --- a/docs/INSTALL +++ b/docs/INSTALL @@ -135,8 +135,7 @@ in brackets): Then, get patchwork to create its tables in your configured database: - cd apps/ - PYTHONPATH=../lib/python ./manage.py syncdb + PYTHONPATH=lib/python ./manage.py syncdb And add privileges for your mail and web users. This is only needed if you use the ident-based approach. If you use password-based database @@ -190,7 +189,7 @@ in brackets): Once you have apache set up, you can start the fastcgi server with: - cd /srv/patchwork/apps + cd /srv/patchwork/ ./manage.py runfcgi method=prefork \ socket=/srv/patchwork/var/fcgi.sock \ pidfile=/srv/patchwork/var/fcgi.pid @@ -222,14 +221,14 @@ in brackets): directory. (Note, do not use the parsemail.py script directly). Something like this in /etc/aliases is suitable for postfix: - patchwork: "|/srv/patchwork/apps/patchwork/bin/parsemail.sh" + patchwork: "|/srv/patchwork/patchwork/bin/parsemail.sh" You may need to customise the parsemail.sh script if you haven't installed patchwork in /srv/patchwork. Test that you can deliver a patch to this script: - sudo -u nobody /srv/patchwork/apps/patchwork/bin/parsemail.sh < mail + sudo -u nobody /srv/patchwork/patchwork/bin/parsemail.sh < mail 7. Set up the patchwork cron script @@ -240,9 +239,9 @@ in brackets): Something like this in your crontab should work: # m h dom mon dow command - PYTHONPATH=apps:. + PYTHONPATH=. DJANGO_SETTINGS_MODULE=settings - */10 * * * * cd patchwork; python apps/patchwork/bin/patchwork-cron.py + */10 * * * * cd patchwork; python patchwork/bin/patchwork-cron.py - the frequency should be the same as the NOTIFICATION_DELAY_MINUTES diff --git a/lib/apache2/patchwork.mod_python.conf b/lib/apache2/patchwork.mod_python.conf index 6395738..c46f86c 100644 --- a/lib/apache2/patchwork.mod_python.conf +++ b/lib/apache2/patchwork.mod_python.conf @@ -7,7 +7,7 @@ NameVirtualHost patchwork.example.com:80 SetHandler python-program PythonHandler django.core.handlers.modpython - PythonPath "['/srv/patchwork/apps', '/srv/patchwork/lib/python'] + sys.path" + PythonPath "['/srv/patchwork', '/srv/patchwork/lib/python'] + sys.path" SetEnv DJANGO_SETTINGS_MODULE settings diff --git a/lib/apache2/patchwork.wsgi b/lib/apache2/patchwork.wsgi index 869bb9d..52feb58 100644 --- a/lib/apache2/patchwork.wsgi +++ b/lib/apache2/patchwork.wsgi @@ -12,8 +12,7 @@ import sys basedir = os.path.join( os.path.dirname(__file__), os.path.pardir, os.path.pardir) sys.path.append(basedir) -sys.path.append(os.path.join(basedir, 'apps')) -os.environ['DJANGO_SETTINGS_MODULE'] = 'apps.settings' +os.environ['DJANGO_SETTINGS_MODULE'] = 'patchwork.settings.prod' import django.core.handlers.wsgi application = django.core.handlers.wsgi.WSGIHandler() diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..04eac77 --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "patchwork.settings.prod") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/patchwork/__init__.py b/patchwork/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/patchwork/admin.py b/patchwork/admin.py new file mode 100644 index 0000000..5297903 --- /dev/null +++ b/patchwork/admin.py @@ -0,0 +1,50 @@ +from django.contrib import admin +from patchwork.models import Project, Person, UserProfile, State, Patch, \ + Comment, Bundle + +class ProjectAdmin(admin.ModelAdmin): + list_display = ('name', 'linkname','listid', 'listemail') +admin.site.register(Project, ProjectAdmin) + +class PersonAdmin(admin.ModelAdmin): + list_display = ('__unicode__', 'has_account') + search_fields = ('name', 'email') + def has_account(self, person): + return bool(person.user) + has_account.boolean = True + has_account.admin_order_field = 'user' + has_account.short_description = 'Account' +admin.site.register(Person, PersonAdmin) + +class UserProfileAdmin(admin.ModelAdmin): + search_fields = ('user__username', 'user__first_name', 'user__last_name') +admin.site.register(UserProfile, UserProfileAdmin) + +class StateAdmin(admin.ModelAdmin): + list_display = ('name', 'action_required') +admin.site.register(State, StateAdmin) + +class PatchAdmin(admin.ModelAdmin): + list_display = ('name', 'submitter', 'project', 'state', 'date', + 'archived', 'is_pull_request') + list_filter = ('project', 'state', 'archived') + search_fields = ('name', 'submitter__name', 'submitter__email') + date_hierarchy = 'date' + def is_pull_request(self, patch): + return bool(patch.pull_url) + is_pull_request.boolean = True + is_pull_request.admin_order_field = 'pull_url' + is_pull_request.short_description = 'Pull' +admin.site.register(Patch, PatchAdmin) + +class CommentAdmin(admin.ModelAdmin): + list_display = ('patch', 'submitter', 'date') + search_fields = ('patch__name', 'submitter__name', 'submitter__email') + date_hierarchy = 'date' +admin.site.register(Comment, CommentAdmin) + +class BundleAdmin(admin.ModelAdmin): + list_display = ('name', 'owner', 'project', 'public') + list_filter = ('public', 'project') + search_fields = ('name', 'owner') +admin.site.register(Bundle, BundleAdmin) diff --git a/patchwork/bin/__init__.py b/patchwork/bin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/patchwork/bin/bash_completion b/patchwork/bin/bash_completion new file mode 100644 index 0000000..a120a76 --- /dev/null +++ b/patchwork/bin/bash_completion @@ -0,0 +1,29 @@ +# Autocompletion for bash. + +_pwclient() { + local cur prev words cword split + + if declare -f _init_completion >/dev/null; then + _init_completion -s || return + else + cur=$(_get_cword) + prev=${COMP_WORDS[COMP_CWORD-1]} + fi + + case "${COMP_CWORD}" in + 0|1) return 0;; + esac + + projects="$(sed -r -e '/\[options\]/d;' \ + -e '/^\[(.+)\]$/!d;' \ + -e 's//\1/;' ~/.pwclientrc 2>/dev/null)" + + case "${prev}" in + -p) COMPREPLY=( $(compgen -W "${projects}" -- "${cur}" ) );; + esac + + return 0 +} +complete -F _pwclient pwclient + +# vim: ft=sh diff --git a/patchwork/bin/parsemail-batch.sh b/patchwork/bin/parsemail-batch.sh new file mode 100755 index 0000000..31ef4f0 --- /dev/null +++ b/patchwork/bin/parsemail-batch.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +PATCHWORK_BINDIR=`dirname $0` + +if [ $# -ne 1 ] +then + echo "usage: $0 " >&2 + exit 1 +fi + +mail_dir="$1" + +echo "dir: $mail_dir" + +if [ ! -d "$mail_dir" ] +then + echo "$mail_dir should be a directory"? >&2 + exit 1 +fi + +ls -1rt "$mail_dir" | +while read line; +do + echo $line + $PATCHWORK_BINDIR/parsemail.sh < "$mail_dir/$line" +done diff --git a/patchwork/bin/parsemail.py b/patchwork/bin/parsemail.py new file mode 100755 index 0000000..19e6e57 --- /dev/null +++ b/patchwork/bin/parsemail.py @@ -0,0 +1,455 @@ +#!/usr/bin/env python +# +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import sys +import re +import datetime +import time +import operator +import codecs +from email import message_from_file +try: + from email.header import Header, decode_header + from email.utils import parsedate_tz, mktime_tz +except ImportError: + # Python 2.4 compatibility + from email.Header import Header, decode_header + from email.Utils import parsedate_tz, mktime_tz + +from patchwork.parser import parse_patch +from patchwork.models import Patch, Project, Person, Comment, State, \ + get_default_initial_patch_state +from django.contrib.auth.models import User + +list_id_headers = ['List-ID', 'X-Mailing-List', 'X-list'] + +whitespace_re = re.compile('\s+') +def normalise_space(str): + return whitespace_re.sub(' ', str).strip() + +def clean_header(header): + """ Decode (possibly non-ascii) headers """ + + def decode(fragment): + (frag_str, frag_encoding) = fragment + if frag_encoding: + return frag_str.decode(frag_encoding) + return frag_str.decode() + + fragments = map(decode, decode_header(header)) + + return normalise_space(u' '.join(fragments)) + +def find_project(mail): + project = None + listid_res = [re.compile('.*<([^>]+)>.*', re.S), + re.compile('^([\S]+)$', re.S)] + + for header in list_id_headers: + if header in mail: + + for listid_re in listid_res: + match = listid_re.match(mail.get(header)) + if match: + break + + if not match: + continue + + listid = match.group(1) + + try: + project = Project.objects.get(listid = listid) + break + except: + pass + + return project + +def find_author(mail): + + from_header = clean_header(mail.get('From')) + (name, email) = (None, None) + + # tuple of (regex, fn) + # - where fn returns a (name, email) tuple from the match groups resulting + # from re.match().groups() + from_res = [ + # for "Firstname Lastname" style addresses + (re.compile('"?(.*?)"?\s*<([^>]+)>'), (lambda g: (g[0], g[1]))), + + # for example@example.com (Firstname Lastname) style addresses + (re.compile('"?(.*?)"?\s*\(([^\)]+)\)'), (lambda g: (g[1], g[0]))), + + # everything else + (re.compile('(.*)'), (lambda g: (None, g[0]))), + ] + + for regex, fn in from_res: + match = regex.match(from_header) + if match: + (name, email) = fn(match.groups()) + break + + if email is None: + raise Exception("Could not parse From: header") + + email = email.strip() + if name is not None: + name = name.strip() + + new_person = False + + try: + person = Person.objects.get(email__iexact = email) + except Person.DoesNotExist: + person = Person(name = name, email = email) + new_person = True + + return (person, new_person) + +def mail_date(mail): + t = parsedate_tz(mail.get('Date', '')) + if not t: + return datetime.datetime.utcnow() + return datetime.datetime.utcfromtimestamp(mktime_tz(t)) + +def mail_headers(mail): + return reduce(operator.__concat__, + ['%s: %s\n' % (k, Header(v, header_name = k, \ + continuation_ws = '\t').encode()) \ + for (k, v) in mail.items()]) + +def find_pull_request(content): + git_re = re.compile('^The following changes since commit.*' + + '^are available in the git repository at:\n' + '^\s*([\S]+://[^\n]+)$', + re.DOTALL | re.MULTILINE) + match = git_re.search(content) + if match: + return match.group(1) + return None + +def try_decode(payload, charset): + try: + payload = unicode(payload, charset) + except UnicodeDecodeError: + return None + return payload + +def find_content(project, mail): + patchbuf = None + commentbuf = '' + pullurl = None + + for part in mail.walk(): + if part.get_content_maintype() != 'text': + continue + + payload = part.get_payload(decode=True) + subtype = part.get_content_subtype() + + if not isinstance(payload, unicode): + charset = part.get_content_charset() + + # Check that we have a charset that we understand. Otherwise, + # ignore it and fallback to our standard set. + if charset is not None: + try: + codec = codecs.lookup(charset) + except LookupError: + charset = None + + # If there is no charset or if it is unknown, then try some common + # charsets before we fail. + if charset is None: + try_charsets = ['utf-8', 'windows-1252', 'iso-8859-1'] + else: + try_charsets = [charset] + + for cset in try_charsets: + decoded_payload = try_decode(payload, cset) + if decoded_payload is not None: + break + payload = decoded_payload + + # Could not find a valid decoded payload. Fail. + if payload is None: + return (None, None) + + if subtype in ['x-patch', 'x-diff']: + patchbuf = payload + + elif subtype == 'plain': + c = payload + + if not patchbuf: + (patchbuf, c) = parse_patch(payload) + + if not pullurl: + pullurl = find_pull_request(payload) + + if c is not None: + commentbuf += c.strip() + '\n' + + patch = None + comment = None + + if pullurl or patchbuf: + name = clean_subject(mail.get('Subject'), [project.linkname]) + patch = Patch(name = name, pull_url = pullurl, content = patchbuf, + date = mail_date(mail), headers = mail_headers(mail)) + + if commentbuf: + if patch: + cpatch = patch + else: + cpatch = find_patch_for_comment(project, mail) + if not cpatch: + return (None, None) + comment = Comment(patch = cpatch, date = mail_date(mail), + content = clean_content(commentbuf), + headers = mail_headers(mail)) + + return (patch, comment) + +def find_patch_for_comment(project, mail): + # construct a list of possible reply message ids + refs = [] + if 'In-Reply-To' in mail: + refs.append(mail.get('In-Reply-To')) + + if 'References' in mail: + rs = mail.get('References').split() + rs.reverse() + for r in rs: + if r not in refs: + refs.append(r) + + for ref in refs: + patch = None + + # first, check for a direct reply + try: + patch = Patch.objects.get(project = project, msgid = ref) + return patch + except Patch.DoesNotExist: + pass + + # see if we have comments that refer to a patch + try: + comment = Comment.objects.get(patch__project = project, msgid = ref) + return comment.patch + except Comment.DoesNotExist: + pass + + + return None + +split_re = re.compile('[,\s]+') + +def split_prefixes(prefix): + """ Turn a prefix string into a list of prefix tokens + + >>> split_prefixes('PATCH') + ['PATCH'] + >>> split_prefixes('PATCH,RFC') + ['PATCH', 'RFC'] + >>> split_prefixes('') + [] + >>> split_prefixes('PATCH,') + ['PATCH'] + >>> split_prefixes('PATCH ') + ['PATCH'] + >>> split_prefixes('PATCH,RFC') + ['PATCH', 'RFC'] + >>> split_prefixes('PATCH 1/2') + ['PATCH', '1/2'] + """ + matches = split_re.split(prefix) + return [ s for s in matches if s != '' ] + +re_re = re.compile('^(re|fwd?)[:\s]\s*', re.I) +prefix_re = re.compile('^\[([^\]]*)\]\s*(.*)$') + +def clean_subject(subject, drop_prefixes = None): + """ Clean a Subject: header from an incoming patch. + + Removes Re: and Fwd: strings, as well as [PATCH]-style prefixes. By + default, only [PATCH] is removed, and we keep any other bracketed data + in the subject. If drop_prefixes is provided, remove those too, + comparing case-insensitively. + + >>> clean_subject('meep') + 'meep' + >>> clean_subject('Re: meep') + 'meep' + >>> clean_subject('[PATCH] meep') + 'meep' + >>> clean_subject('[PATCH] meep \\n meep') + 'meep meep' + >>> clean_subject('[PATCH RFC] meep') + '[RFC] meep' + >>> clean_subject('[PATCH,RFC] meep') + '[RFC] meep' + >>> clean_subject('[PATCH,1/2] meep') + '[1/2] meep' + >>> clean_subject('[PATCH RFC 1/2] meep') + '[RFC,1/2] meep' + >>> clean_subject('[PATCH] [RFC] meep') + '[RFC] meep' + >>> clean_subject('[PATCH] [RFC,1/2] meep') + '[RFC,1/2] meep' + >>> clean_subject('[PATCH] [RFC] [1/2] meep') + '[RFC,1/2] meep' + >>> clean_subject('[PATCH] rewrite [a-z] regexes') + 'rewrite [a-z] regexes' + >>> clean_subject('[PATCH] [RFC] rewrite [a-z] regexes') + '[RFC] rewrite [a-z] regexes' + >>> clean_subject('[foo] [bar] meep', ['foo']) + '[bar] meep' + >>> clean_subject('[FOO] [bar] meep', ['foo']) + '[bar] meep' + """ + + subject = clean_header(subject) + + if drop_prefixes is None: + drop_prefixes = [] + else: + drop_prefixes = [ s.lower() for s in drop_prefixes ] + + drop_prefixes.append('patch') + + # remove Re:, Fwd:, etc + subject = re_re.sub(' ', subject) + + subject = normalise_space(subject) + + prefixes = [] + + match = prefix_re.match(subject) + + while match: + prefix_str = match.group(1) + prefixes += [ p for p in split_prefixes(prefix_str) \ + if p.lower() not in drop_prefixes] + + subject = match.group(2) + match = prefix_re.match(subject) + + subject = normalise_space(subject) + + subject = subject.strip() + if prefixes: + subject = '[%s] %s' % (','.join(prefixes), subject) + + return subject + +sig_re = re.compile('^(-- |_+)\n.*', re.S | re.M) +def clean_content(str): + """ Try to remove signature (-- ) and list footer (_____) cruft """ + str = sig_re.sub('', str) + return str.strip() + +def get_state(state_name): + """ Return the state with the given name or the default State """ + if state_name: + try: + return State.objects.get(name__iexact=state_name) + except State.DoesNotExist: + pass + return get_default_initial_patch_state() + +def get_delegate(delegate_email): + """ Return the delegate with the given email or None """ + if delegate_email: + try: + return User.objects.get(email__iexact=delegate_email) + except User.DoesNotExist: + pass + return None + +def parse_mail(mail): + + # some basic sanity checks + if 'From' not in mail: + return 0 + + if 'Subject' not in mail: + return 0 + + if 'Message-Id' not in mail: + return 0 + + hint = mail.get('X-Patchwork-Hint', '').lower() + if hint == 'ignore': + return 0; + + project = find_project(mail) + if project is None: + print "no project found" + return 0 + + msgid = mail.get('Message-Id').strip() + + (author, save_required) = find_author(mail) + + (patch, comment) = find_content(project, mail) + + if patch: + # we delay the saving until we know we have a patch. + if save_required: + author.save() + save_required = False + patch.submitter = author + patch.msgid = msgid + patch.project = project + patch.state = get_state(mail.get('X-Patchwork-State', '').strip()) + patch.delegate = get_delegate( + mail.get('X-Patchwork-Delegate', '').strip()) + try: + patch.save() + except Exception, ex: + print str(ex) + + if comment: + if save_required: + author.save() + # looks like the original constructor for Comment takes the pk + # when the Comment is created. reset it here. + if patch: + comment.patch = patch + comment.submitter = author + comment.msgid = msgid + try: + comment.save() + except Exception, ex: + print str(ex) + + return 0 + +def main(args): + mail = message_from_file(sys.stdin) + return parse_mail(mail) + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/patchwork/bin/parsemail.sh b/patchwork/bin/parsemail.sh new file mode 100755 index 0000000..d9ad005 --- /dev/null +++ b/patchwork/bin/parsemail.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +BIN_DIR=`dirname $0` +PATCHWORK_BASE=`readlink -e $BIN_DIR/../..` + +PYTHONPATH="$PATCHWORK_BASE":"$PATCHWORK_BASE/lib/python:$PYTHONPATH" \ + DJANGO_SETTINGS_MODULE=settings \ + "$PATCHWORK_BASE/patchwork/bin/parsemail.py" + +exit 0 diff --git a/patchwork/bin/patchwork-cron.py b/patchwork/bin/patchwork-cron.py new file mode 100755 index 0000000..148e97c --- /dev/null +++ b/patchwork/bin/patchwork-cron.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +import sys +from patchwork.utils import send_notifications, do_expiry + +def main(args): + errors = send_notifications() + for (recipient, error) in errors: + print "Failed sending to %s: %s" % (recipient.email, ex) + + do_expiry() + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + diff --git a/patchwork/bin/pwclient b/patchwork/bin/pwclient new file mode 100755 index 0000000..8d1f476 --- /dev/null +++ b/patchwork/bin/pwclient @@ -0,0 +1,744 @@ +#!/usr/bin/env python +# +# Patchwork command line client +# Copyright (C) 2008 Nate Case +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os +import sys +import xmlrpclib +import argparse +import string +import tempfile +import subprocess +import base64 +import ConfigParser +import shutil +import re + +# Default Patchwork remote XML-RPC server URL +# This script will check the PW_XMLRPC_URL environment variable +# for the URL to access. If that is unspecified, it will fallback to +# the hardcoded default value specified here. +DEFAULT_URL = "http://patchwork/xmlrpc/" +CONFIG_FILE = os.path.expanduser('~/.pwclientrc') + +class Filter: + """Filter for selecting patches.""" + def __init__(self): + # These fields refer to specific objects, so they are special + # because we have to resolve them to IDs before passing the + # filter to the server + self.state = "" + self.project = "" + + # The dictionary that gets passed to via XML-RPC + self.d = {} + + def add(self, field, value): + if field == 'state': + self.state = value + elif field == 'project': + self.project = value + else: + # OK to add directly + self.d[field] = value + + def resolve_ids(self, rpc): + """Resolve State, Project, and Person IDs based on filter strings.""" + if self.state != "": + id = state_id_by_name(rpc, self.state) + if id == 0: + sys.stderr.write("Note: No State found matching %s*, " \ + "ignoring filter\n" % self.state) + else: + self.d['state_id'] = id + + if self.project != None: + id = project_id_by_name(rpc, self.project) + if id == 0: + sys.stderr.write("Note: No Project found matching %s, " \ + "ignoring filter\n" % self.project) + else: + self.d['project_id'] = id + + def __str__(self): + """Return human-readable description of the filter.""" + return str(self.d) + +class BasicHTTPAuthTransport(xmlrpclib.SafeTransport): + + def __init__(self, username = None, password = None, use_https = False): + self.username = username + self.password = password + self.use_https = use_https + xmlrpclib.SafeTransport.__init__(self) + + def authenticated(self): + return self.username != None and self.password != None + + def send_host(self, connection, host): + xmlrpclib.Transport.send_host(self, connection, host) + if not self.authenticated(): + return + credentials = '%s:%s' % (self.username, self.password) + auth = 'Basic ' + base64.encodestring(credentials).strip() + connection.putheader('Authorization', auth) + + def make_connection(self, host): + if self.use_https: + fn = xmlrpclib.SafeTransport.make_connection + else: + fn = xmlrpclib.Transport.make_connection + return fn(self, host) + +def project_id_by_name(rpc, linkname): + """Given a project short name, look up the Project ID.""" + if len(linkname) == 0: + return 0 + projects = rpc.project_list(linkname, 0) + for project in projects: + if project['linkname'] == linkname: + return project['id'] + return 0 + +def state_id_by_name(rpc, name): + """Given a partial state name, look up the state ID.""" + if len(name) == 0: + return 0 + states = rpc.state_list(name, 0) + for state in states: + if state['name'].lower().startswith(name.lower()): + return state['id'] + return 0 + +def person_ids_by_name(rpc, name): + """Given a partial name or email address, return a list of the + person IDs that match.""" + if len(name) == 0: + return [] + people = rpc.person_list(name, 0) + return map(lambda x: x['id'], people) + +def list_patches(patches, format_str=None): + """Dump a list of patches to stdout.""" + if format_str: + format_field_re = re.compile("%{([a-z0-9_]+)}") + + def patch_field(matchobj): + fieldname = matchobj.group(1) + + if fieldname == "_msgid_": + # naive way to strip < and > from message-id + val = string.strip(str(patch["msgid"]), "<>") + else: + val = str(patch[fieldname]) + + return val + + for patch in patches: + print(format_field_re.sub(patch_field, format_str)) + else: + print("%-7s %-12s %s" % ("ID", "State", "Name")) + print("%-7s %-12s %s" % ("--", "-----", "----")) + for patch in patches: + print("%-7d %-12s %s" % (patch['id'], patch['state'], patch['name'])) + +def action_list(rpc, filter, submitter_str, delegate_str, format_str=None): + filter.resolve_ids(rpc) + + if submitter_str != None: + ids = person_ids_by_name(rpc, submitter_str) + if len(ids) == 0: + sys.stderr.write("Note: Nobody found matching *%s*\n" % \ + submitter_str) + else: + for id in ids: + person = rpc.person_get(id) + print "Patches submitted by %s <%s>:" % \ + (unicode(person['name']).encode("utf-8"), \ + unicode(person['email']).encode("utf-8")) + f = filter + f.add("submitter_id", id) + patches = rpc.patch_list(f.d) + list_patches(patches, format_str) + return + + if delegate_str != None: + ids = person_ids_by_name(rpc, delegate_str) + if len(ids) == 0: + sys.stderr.write("Note: Nobody found matching *%s*\n" % \ + delegate_str) + else: + for id in ids: + person = rpc.person_get(id) + print "Patches delegated to %s <%s>:" % \ + (person['name'], person['email']) + f = filter + f.add("delegate_id", id) + patches = rpc.patch_list(f.d) + list_patches(patches, format_str) + return + + patches = rpc.patch_list(filter.d) + list_patches(patches, format_str) + +def action_projects(rpc): + projects = rpc.project_list("", 0) + print("%-5s %-24s %s" % ("ID", "Name", "Description")) + print("%-5s %-24s %s" % ("--", "----", "-----------")) + for project in projects: + print("%-5d %-24s %s" % (project['id'], \ + project['linkname'], \ + project['name'])) + +def action_states(rpc): + states = rpc.state_list("", 0) + print("%-5s %s" % ("ID", "Name")) + print("%-5s %s" % ("--", "----")) + for state in states: + print("%-5d %s" % (state['id'], state['name'])) + +def action_info(rpc, patch_id): + patch = rpc.patch_get(patch_id) + s = "Information for patch id %d" % (patch_id) + print(s) + print('-' * len(s)) + for key, value in sorted(patch.iteritems()): + print("- %- 14s: %s" % (key, unicode(value).encode("utf-8"))) + +def action_get(rpc, patch_id): + patch = rpc.patch_get(patch_id) + s = rpc.patch_get_mbox(patch_id) + + if patch == {} or len(s) == 0: + sys.stderr.write("Unable to get patch %d\n" % patch_id) + sys.exit(1) + + base_fname = fname = os.path.basename(patch['filename']) + i = 0 + while os.path.exists(fname): + fname = "%s.%d" % (base_fname, i) + i += 1 + + try: + f = open(fname, "w") + except: + sys.stderr.write("Unable to open %s for writing\n" % fname) + sys.exit(1) + + try: + f.write(unicode(s).encode("utf-8")) + f.close() + print "Saved patch to %s" % fname + except: + sys.stderr.write("Failed to write to %s\n" % fname) + sys.exit(1) + +def action_apply(rpc, patch_id, apply_cmd=None): + patch = rpc.patch_get(patch_id) + if patch == {}: + sys.stderr.write("Error getting information on patch ID %d\n" % \ + patch_id) + sys.exit(1) + + if apply_cmd is None: + print "Applying patch #%d to current directory" % patch_id + apply_cmd = ['patch', '-p1'] + else: + print "Applying patch #%d using %s" % ( + patch_id, repr(' '.join(apply_cmd))) + + print "Description: %s" % patch['name'] + s = rpc.patch_get_mbox(patch_id) + if len(s) > 0: + proc = subprocess.Popen(apply_cmd, stdin = subprocess.PIPE) + proc.communicate(unicode(s).encode('utf-8')) + return proc.returncode + else: + sys.stderr.write("Error: No patch content found\n") + sys.exit(1) + +def action_update_patch(rpc, patch_id, state = None, archived = None, commit = None): + patch = rpc.patch_get(patch_id) + if patch == {}: + sys.stderr.write("Error getting information on patch ID %d\n" % \ + patch_id) + sys.exit(1) + + params = {} + + if state: + state_id = state_id_by_name(rpc, state) + if state_id == 0: + sys.stderr.write("Error: No State found matching %s*\n" % state) + sys.exit(1) + params['state'] = state_id + + if commit: + params['commit_ref'] = commit + + if archived: + params['archived'] = archived == 'yes' + + success = False + try: + success = rpc.patch_set(patch_id, params) + except xmlrpclib.Fault, f: + sys.stderr.write("Error updating patch: %s\n" % f.faultString) + + if not success: + sys.stderr.write("Patch not updated\n") + +def patch_id_from_hash(rpc, project, hash): + try: + patch = rpc.patch_get_by_project_hash(project, hash) + except xmlrpclib.Fault: + # the server may not have the newer patch_get_by_project_hash function, + # so fall back to hash-only. + patch = rpc.patch_get_by_hash(hash) + + if patch == {}: + sys.stderr.write("No patch has the hash provided\n") + sys.exit(1) + + patch_id = patch['id'] + # be super paranoid + try: + patch_id = int(patch_id) + except: + sys.stderr.write("Invalid patch ID obtained from server\n") + sys.exit(1) + return patch_id + +auth_actions = ['update'] + +# unfortunately we currently have to revert to this ugly hack.. +class _RecursiveHelpAction(argparse._HelpAction): + + def __call__(self, parser, namespace, values, option_string=None): + parser.print_help() + print + + subparsers_actions = [ + action for action in parser._actions + if isinstance(action, argparse._SubParsersAction) + ] + hash_n_id_actions = set(['hash', 'id', 'help']) + for subparsers_action in subparsers_actions: + for choice, subparser in subparsers_action.choices.items(): + # gross but the whole thing is.. + if (len(subparser._actions) == 3 \ + and set([a.dest for a in subparser._actions]) \ + == hash_n_id_actions) \ + or len(subparser._actions) == 0: + continue + print("command '{}'".format(choice)) + print(subparser.format_help()) + + parser.exit() + +def main(): + hash_parser = argparse.ArgumentParser(add_help=False, version=False) + hash_parser.add_argument( + '-h', metavar='HASH', dest='hash', action='store', + help='''Lookup by patch hash''' + ) + hash_parser.add_argument( + 'id', metavar='ID', nargs='*', action='store', type=int, + help='Patch ID', + ) + hash_parser.add_argument( + '-p', metavar='PROJECT', + help='''Lookup patch in project''' + ) + + filter_parser = argparse.ArgumentParser(add_help=False, version=False) + filter_parser.add_argument( + '-s', metavar='STATE', + help='''Filter by patch state (e.g., 'New', 'Accepted', etc.)''' + ) + filter_parser.add_argument( + '-a', choices=['yes','no'], + help='''Filter by patch archived state''' + ) + filter_parser.add_argument( + '-p', metavar='PROJECT', + help='''Filter by project name (see 'projects' for list)''' + ) + filter_parser.add_argument( + '-w', metavar='WHO', + help='''Filter by submitter (name, e-mail substring search)''' + ) + filter_parser.add_argument( + '-d', metavar='WHO', + help='''Filter by delegate (name, e-mail substring search)''' + ) + filter_parser.add_argument( + '-n', metavar='MAX#', + type=int, + help='''Restrict number of results''' + ) + filter_parser.add_argument( + '-m', metavar='MESSAGEID', + help='''Filter by Message-Id''' + ) + filter_parser.add_argument( + '-f', metavar='FORMAT', + help='''Print output in the given format. You can use tags matching ''' + '''fields, e.g. %%{id}, %%{state}, or %%{msgid}.''' + ) + filter_parser.add_argument( + 'patch_name', metavar='STR', nargs='?', + help='substring to search for patches by name', + ) + help_parser = argparse.ArgumentParser(add_help=False, version=False) + help_parser.add_argument( + '--help', action='help', help=argparse.SUPPRESS, + #help='''show this help message and exit''' + ) + + action_parser = argparse.ArgumentParser( + prog='pwclient', + add_help=False, + version=False, + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog='''(apply | get | info | view | update) (-h HASH | ID [ID ...])''', + ) + action_parser.add_argument( + '--help', + #action='help', + action=_RecursiveHelpAction, + help='''Print this help text''' + ) + + subparsers = action_parser.add_subparsers( + title='Commands', + metavar='' + ) + apply_parser = subparsers.add_parser( + 'apply', parents=[hash_parser, help_parser], + add_help=False, + help='''Apply a patch (in the current dir, using -p1)''' + ) + apply_parser.set_defaults(subcmd='apply') + git_am_parser = subparsers.add_parser( + 'git-am', parents=[hash_parser, help_parser], + add_help=False, + help='''Apply a patch to current git branch using "git am".''' + ) + git_am_parser.set_defaults(subcmd='git_am') + git_am_parser.add_argument( + '-s', '--signoff', + action='store_true', + help='''pass --signoff to git-am''' + ) + get_parser = subparsers.add_parser( + 'get', parents=[hash_parser, help_parser], + add_help=False, + help='''Download a patch and save it locally''' + ) + get_parser.set_defaults(subcmd='get') + info_parser = subparsers.add_parser( + 'info', parents=[hash_parser, help_parser], + add_help=False, + help='''Display patchwork info about a given patch ID''' + ) + info_parser.set_defaults(subcmd='info') + projects_parser = subparsers.add_parser( + 'projects', + add_help=False, + help='''List all projects''' + ) + projects_parser.set_defaults(subcmd='projects') + states_parser = subparsers.add_parser( + 'states', + add_help=False, + help='''Show list of potential patch states''' + ) + states_parser.set_defaults(subcmd='states') + view_parser = subparsers.add_parser( + 'view', parents=[hash_parser, help_parser], + add_help=False, + help='''View a patch''' + ) + view_parser.set_defaults(subcmd='view') + update_parser = subparsers.add_parser( + 'update', parents=[hash_parser, help_parser], + add_help=False, + help='''Update patch''', + epilog='''Using a COMMIT-REF allows for only one ID to be specified''', + ) + update_parser.add_argument( + '-c', metavar='COMMIT-REF', + help='''commit reference hash''' + ) + update_parser.add_argument( + '-s', metavar='STATE', + required=True, + help='''Set patch state (e.g., 'Accepted', 'Superseded' etc.)''' + ) + update_parser.add_argument( + '-a', choices=['yes', 'no'], + help='''Set patch archived state''' + ) + update_parser.set_defaults(subcmd='update') + list_parser = subparsers.add_parser("list", + add_help=False, + #aliases=['search'], + parents=[filter_parser, help_parser], + help='''List patches, using the optional filters specified + below and an optional substring to search for patches + by name''' + ) + list_parser.set_defaults(subcmd='list') + search_parser = subparsers.add_parser("search", + add_help=False, + parents=[filter_parser, help_parser], + help='''Alias for "list"''' + ) + # Poor man's argparse aliases: + # We register the "search" parser but effectively use "list" for the + # help-text. + search_parser.set_defaults(subcmd='list') + if len(sys.argv) < 2: + action_parser.print_help() + sys.exit(0) + + args = action_parser.parse_args() + args = dict(vars(args)) + action = args.get('subcmd') + + if args.get('hash') and len(args.get('id')): + # mimic mutual exclusive group + sys.stderr.write("Error: [-h HASH] and [ID [ID ...]] " + + "are mutually exlusive\n") + locals()[action + '_parser'].print_help() + sys.exit(1) + + # set defaults + filt = Filter() + commit_str = None + url = DEFAULT_URL + + archived_str = args.get('a') + state_str = args.get('s') + project_str = args.get('p') + submitter_str = args.get('w') + delegate_str = args.get('d') + format_str = args.get('f') + hash_str = args.get('hash') + patch_ids = args.get('id') + msgid_str = args.get('m') + if args.get('c'): + # update multiple IDs with a single commit-hash does not make sense + if action == 'update' and patch_ids and len(patch_ids) > 1: + sys.stderr.write( + "Declining update with COMMIT-REF on multiple IDs\n" + ) + update_parser.print_help() + sys.exit(1) + commit_str = args.get('c') + + if args.get('n') != None: + try: + filt.add("max_count", args.get('n')) + except: + sys.stderr.write("Invalid maximum count '%s'\n" % args.get('n')) + action_parser.print_help() + sys.exit(1) + + do_signoff = args.get('signoff') + + # grab settings from config files + config = ConfigParser.ConfigParser() + config.read([CONFIG_FILE]) + + if not config.has_section('options'): + sys.stderr.write('~/.pwclientrc is in the old format. Migrating it...') + + old_project = config.get('base','project') + + new_config = ConfigParser.ConfigParser() + new_config.add_section('options') + + new_config.set('options','default',old_project) + new_config.add_section(old_project) + + new_config.set(old_project,'url',config.get('base','url')) + if config.has_option('auth', 'username'): + new_config.set(old_project,'username',config.get('auth','username')) + if config.has_option('auth', 'password'): + new_config.set(old_project,'password',config.get('auth','password')) + + old_config_file = CONFIG_FILE + '.orig' + shutil.copy2(CONFIG_FILE,old_config_file) + + with open(CONFIG_FILE, 'wb') as fd: + new_config.write(fd) + + sys.stderr.write(' Done.\n') + sys.stderr.write('Your old ~/.pwclientrc was saved to %s\n' % old_config_file) + sys.stderr.write('and was converted to the new format. You may want to\n') + sys.stderr.write('inspect it before continuing.\n') + sys.exit(1) + + if not project_str: + try: + project_str = config.get('options', 'default') + except: + sys.stderr.write("No default project configured in ~/.pwclientrc\n") + action_parser.print_help() + sys.exit(1) + + if not config.has_section(project_str): + sys.stderr.write("No section for project %s\n" % project_str) + sys.exit(1) + if not config.has_option(project_str, 'url'): + sys.stderr.write("No URL for project %s\n" % project_str) + sys.exit(1) + if not do_signoff and config.has_option('options', 'signoff'): + do_signoff = config.getboolean('options', 'signoff') + if not do_signoff and config.has_option(project_str, 'signoff'): + do_signoff = config.getboolean(project_str, 'signoff') + + url = config.get(project_str, 'url') + + transport = None + if action in auth_actions: + if config.has_option(project_str, 'username') and \ + config.has_option(project_str, 'password'): + + use_https = url.startswith('https') + + transport = BasicHTTPAuthTransport( \ + config.get(project_str, 'username'), + config.get(project_str, 'password'), + use_https) + + else: + sys.stderr.write(("The %s action requires authentication, " + "but no username or password\nis configured\n") % action) + sys.exit(1) + + if project_str: + filt.add("project", project_str) + + if state_str: + filt.add("state", state_str) + + if archived_str: + filt.add("archived", archived_str == 'yes') + + if msgid_str: + filt.add("msgid", msgid_str) + + try: + rpc = xmlrpclib.Server(url, transport = transport) + except: + sys.stderr.write("Unable to connect to %s\n" % url) + sys.exit(1) + + # It should be safe to assume hash_str is not zero, but who knows.. + if hash_str != None: + patch_ids = [patch_id_from_hash(rpc, project_str, hash_str)] + + # helper for non_empty() to print correct helptext + h = locals()[action + '_parser'] + + # Require either hash_str or IDs for + def non_empty(h, patch_ids): + """Error out if no patch IDs were specified""" + if patch_ids == None or len(patch_ids) < 1: + sys.stderr.write("Error: Missing Argument! " + + "Either [-h HASH] or [ID [ID ...]] are required\n") + if h: + h.print_help() + sys.exit(1) + return patch_ids + + if action == 'list' or action == 'search': + if args.get('patch_name') != None: + filt.add("name__icontains", args.get('patch_name')) + action_list(rpc, filt, submitter_str, delegate_str, format_str) + + elif action.startswith('project'): + action_projects(rpc) + + elif action.startswith('state'): + action_states(rpc) + + elif action == 'view': + pager = os.environ.get('PAGER') + if pager: + pager = subprocess.Popen( + pager.split(), stdin=subprocess.PIPE + ) + if pager: + i = list() + for patch_id in non_empty(h, patch_ids): + s = rpc.patch_get_mbox(patch_id) + if len(s) > 0: + i.append(unicode(s).encode("utf-8")) + if len(i) > 0: + pager.communicate(input="\n".join(i)) + pager.stdin.close() + else: + for patch_id in non_empty(h, patch_ids): + s = rpc.patch_get_mbox(patch_id) + if len(s) > 0: + print unicode(s).encode("utf-8") + + elif action == 'info': + for patch_id in non_empty(h, patch_ids): + action_info(rpc, patch_id) + + elif action == 'get': + for patch_id in non_empty(h, patch_ids): + action_get(rpc, patch_id) + + elif action == 'apply': + for patch_id in non_empty(h, patch_ids): + ret = action_apply(rpc, patch_id) + if ret: + sys.stderr.write("Apply failed with exit status %d\n" % ret) + sys.exit(1) + + elif action == 'git_am': + cmd = ['git', 'am'] + if do_signoff: + cmd.append('-s') + for patch_id in non_empty(h, patch_ids): + ret = action_apply(rpc, patch_id, cmd) + if ret: + sys.stderr.write("'git am' failed with exit status %d\n" % ret) + sys.exit(1) + + elif action == 'update': + for patch_id in non_empty(h, patch_ids): + action_update_patch(rpc, patch_id, state = state_str, + archived = archived_str, commit = commit_str + ) + + else: + sys.stderr.write("Unknown action '%s'\n" % action) + action_parser.print_help() + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/patchwork/bin/rehash.py b/patchwork/bin/rehash.py new file mode 100755 index 0000000..c44e49b --- /dev/null +++ b/patchwork/bin/rehash.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from patchwork.models import Patch +import sys + +if __name__ == '__main__': + if len(sys.argv) > 1: + patches = Patch.objects.filter(id__in = sys.argv[1:]) + else: + patches = Patch.objects.all() + + for patch in patches: + print patch.id, patch.name + patch.hash = None + patch.save() diff --git a/patchwork/bin/update-patchwork-status.py b/patchwork/bin/update-patchwork-status.py new file mode 100755 index 0000000..2da5d23 --- /dev/null +++ b/patchwork/bin/update-patchwork-status.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +import sys +import subprocess +from optparse import OptionParser + +def commits(options, revlist): + cmd = ['git', 'rev-list', revlist] + proc = subprocess.Popen(cmd, stdout = subprocess.PIPE, cwd = options.repodir) + + revs = [] + + for line in proc.stdout.readlines(): + revs.append(line.strip()) + + return revs + +def commit(options, rev): + cmd = ['git', 'diff', '%(rev)s^..%(rev)s' % {'rev': rev}] + proc = subprocess.Popen(cmd, stdout = subprocess.PIPE, cwd = options.repodir) + + buf = proc.communicate()[0] + + return buf + + +def main(args): + parser = OptionParser(usage = '%prog [options] revspec') + parser.add_option("-p", "--project", dest = "project", action = 'store', + help="use project PROJECT", metavar="PROJECT") + parser.add_option("-d", "--dir", dest = "repodir", action = 'store', + help="use git repo in DIR", metavar="DIR") + + (options, args) = parser.parse_args(args[1:]) + + if len(args) != 1: + parser.error("incorrect number of arguments") + + revspec = args[0] + revs = commits(options, revspec) + + for rev in revs: + print rev + print commit(options, rev) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + + diff --git a/patchwork/context_processors.py b/patchwork/context_processors.py new file mode 100644 index 0000000..f4ab5a9 --- /dev/null +++ b/patchwork/context_processors.py @@ -0,0 +1,32 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +from patchwork.models import Bundle +from patchwork.utils import order_map, get_order + +def bundle(request): + user = request.user + if not user.is_authenticated(): + return {} + return {'bundles': Bundle.objects.filter(owner = user)} + + +def patchlists(request): + diff --git a/patchwork/filters.py b/patchwork/filters.py new file mode 100644 index 0000000..8c9690e --- /dev/null +++ b/patchwork/filters.py @@ -0,0 +1,471 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +from patchwork.models import Person, State +from django.utils.safestring import mark_safe +from django.utils.html import escape +from django.contrib.auth.models import User +from urllib import quote + +class Filter(object): + def __init__(self, filters): + self.filters = filters + self.applied = False + self.forced = False + + def name(self): + """The 'name' of the filter, to be displayed in the filter UI""" + return self.name + + def condition(self): + """The current condition of the filter, to be displayed in the + filter UI""" + return self.key + + def key(self): + """The key for this filter, to appear in the querystring. A key of + None will remove the param=ley pair from the querystring.""" + return None + + def set_status(self, *kwargs): + """Views can call this to force a specific filter status. For example, + a user's todo page needs to setup the delegate filter to show + that user's delegated patches""" + pass + + def parse(self, dict): + if self.param not in dict.keys(): + return + self._set_key(dict[self.param]) + + def url_without_me(self): + return self.filters.querystring_without_filter(self) + + def form_function(self): + return 'function(form) { return "unimplemented" }' + + def form(self): + if self.forced: + return mark_safe('%s' % (self.param, + self.condition())) + return self.condition() + return self._form() + + def kwargs(self): + return {} + + def __str__(self): + return '%s: %s' % (self.name, self.kwargs()) + + +class SubmitterFilter(Filter): + param = 'submitter' + def __init__(self, filters): + super(SubmitterFilter, self).__init__(filters) + self.name = 'Submitter' + self.person = None + self.person_match = None + + def _set_key(self, str): + self.person = None + self.person_match = None + submitter_id = None + try: + submitter_id = int(str) + except ValueError: + pass + except: + return + + if submitter_id: + self.person = Person.objects.get(id = int(str)) + self.applied = True + return + + + people = Person.objects.filter(name__icontains = str) + + if not people: + return + + self.person_match = str + self.applied = True + + def kwargs(self): + if self.person: + user = self.person.user + if user: + return {'submitter__in': + Person.objects.filter(user = user).values('pk').query} + return {'submitter': self.person} + + if self.person_match: + return {'submitter__name__icontains': self.person_match} + return {} + + def condition(self): + if self.person: + return self.person.name + elif self.person_match: + return self.person_match + return '' + + def _form(self): + name = '' + if self.person: + name = self.person.name + return mark_safe((' ' % escape(name)) + + '') + + def key(self): + if self.person: + return self.person.id + return self.person_match + +class StateFilter(Filter): + param = 'state' + any_key = '*' + action_req_str = 'Action Required' + + def __init__(self, filters): + super(StateFilter, self).__init__(filters) + self.name = 'State' + self.state = None + self.applied = True + + def _set_key(self, str): + self.state = None + + if str == self.any_key: + self.applied = False + return + + try: + self.state = State.objects.get(id=int(str)) + except: + return + + self.applied = True + + def kwargs(self): + if self.state is not None: + return {'state': self.state} + else: + return {'state__in': \ + State.objects.filter(action_required = True) \ + .values('pk').query} + + def condition(self): + if self.state: + return self.state.name + return self.action_req_str + + def key(self): + if self.state is not None: + return self.state.id + if not self.applied: + return '*' + return None + + def _form(self): + str = '' + return mark_safe(str); + + def form_function(self): + return 'function(form) { return form.x.value }' + + def url_without_me(self): + qs = self.filters.querystring_without_filter(self) + if qs != '?': + qs += '&' + return qs + '%s=%s' % (self.param, self.any_key) + +class SearchFilter(Filter): + param = 'q' + def __init__(self, filters): + super(SearchFilter, self).__init__(filters) + self.name = 'Search' + self.param = 'q' + self.search = None + + def _set_key(self, str): + str = str.strip() + if str == '': + return + self.search = str + self.applied = True + + def kwargs(self): + return {'name__icontains': self.search} + + def condition(self): + return self.search + + def key(self): + return self.search + + def _form(self): + value = '' + if self.search: + value = escape(self.search) + return mark_safe('' %\ + (self.param, value)) + + def form_function(self): + return mark_safe('function(form) { return form.x.value }') + +class ArchiveFilter(Filter): + param = 'archive' + def __init__(self, filters): + super(ArchiveFilter, self).__init__(filters) + self.name = 'Archived' + self.archive_state = False + self.applied = True + self.param_map = { + True: 'true', + False: '', + None: 'both' + } + self.description_map = { + True: 'Yes', + False: 'No', + None: 'Both' + } + + def _set_key(self, str): + self.archive_state = False + self.applied = True + for (k, v) in self.param_map.iteritems(): + if str == v: + self.archive_state = k + if self.archive_state == None: + self.applied = False + + def kwargs(self): + if self.archive_state == None: + return {} + return {'archived': self.archive_state} + + def condition(self): + return self.description_map[self.archive_state] + + def key(self): + if self.archive_state == False: + return None + return self.param_map[self.archive_state] + + def _form(self): + s = '' + for b in [False, True, None]: + label = self.description_map[b] + selected = '' + if self.archive_state == b: + selected = 'checked="true"' + s += ('%(label)s' + \ + '    ') % \ + {'label': label, + 'param': self.param, + 'selected': selected, + 'value': self.param_map[b] + } + return mark_safe(s) + + def url_without_me(self): + qs = self.filters.querystring_without_filter(self) + if qs != '?': + qs += '&' + return qs + 'archive=both' + + +class DelegateFilter(Filter): + param = 'delegate' + no_delegate_key = '-' + no_delegate_str = 'Nobody' + AnyDelegate = 1 + + def __init__(self, filters): + super(DelegateFilter, self).__init__(filters) + self.name = 'Delegate' + self.param = 'delegate' + self.delegate = None + + def _set_key(self, str): + if str == self.no_delegate_key: + self.applied = True + self.delegate = None + return + + applied = False + try: + self.delegate = User.objects.get(id = str) + self.applied = True + except: + pass + + def kwargs(self): + if not self.applied: + return {} + return {'delegate': self.delegate} + + def condition(self): + if self.delegate: + return self.delegate.profile.name() + return self.no_delegate_str + + def _form(self): + delegates = User.objects.filter(profile__maintainer_projects = + self.filters.project) + + str = '' + + return mark_safe(str) + + def key(self): + if self.delegate: + return self.delegate.id + if self.applied: + return self.no_delegate_key + return None + + def set_status(self, *args, **kwargs): + if 'delegate' in kwargs: + self.applied = self.forced = True + self.delegate = kwargs['delegate'] + if self.AnyDelegate in args: + self.applied = False + self.forced = True + +filterclasses = [SubmitterFilter, \ + StateFilter, + SearchFilter, + ArchiveFilter, + DelegateFilter] + +class Filters: + + def __init__(self, request): + self._filters = map(lambda c: c(self), filterclasses) + self.dict = request.GET + self.project = None + + for f in self._filters: + f.parse(self.dict) + + def set_project(self, project): + self.project = project + + def filter_conditions(self): + kwargs = {} + for f in self._filters: + if f.applied: + kwargs.update(f.kwargs()) + return kwargs + + def apply(self, queryset): + kwargs = self.filter_conditions() + if not kwargs: + return queryset + return queryset.filter(**kwargs) + + def params(self): + return [ (f.param, f.key()) for f in self._filters \ + if f.key() is not None ] + + def querystring(self, remove = None): + params = dict(self.params()) + + for (k, v) in self.dict.iteritems(): + if k not in params: + params[k] = v + + if remove is not None: + if remove.param in params.keys(): + del params[remove.param] + + pairs = params.iteritems() + + def sanitise(s): + if not isinstance(s, basestring): + s = unicode(s) + return quote(s.encode('utf-8')) + + return '?' + '&'.join(['%s=%s' % (sanitise(k), sanitise(v)) + for (k, v) in pairs]) + + def querystring_without_filter(self, filter): + return self.querystring(filter) + + def applied_filters(self): + return filter(lambda x: x.applied, self._filters) + + def available_filters(self): + return self._filters + + def set_status(self, filterclass, *args, **kwargs): + for f in self._filters: + if isinstance(f, filterclass): + f.set_status(*args, **kwargs) + return diff --git a/patchwork/fixtures/default_projects.xml b/patchwork/fixtures/default_projects.xml new file mode 100644 index 0000000..c67fa56 --- /dev/null +++ b/patchwork/fixtures/default_projects.xml @@ -0,0 +1,18 @@ + + + + + + cbe-oss-dev + Cell Broadband Engine development + cbe-oss-dev.ozlabs.org + cbe-oss-dev@ozlabs.org + + + linuxppc-dev + Linux PPC development + linuxppc-dev.ozlabs.org + linuxppc-dev@ozlabs.org + + + diff --git a/patchwork/fixtures/initial_data.xml b/patchwork/fixtures/initial_data.xml new file mode 100644 index 0000000..86e1105 --- /dev/null +++ b/patchwork/fixtures/initial_data.xml @@ -0,0 +1,55 @@ + + + + + + New + 0 + True + + + Under Review + 1 + True + + + Accepted + 2 + False + + + Rejected + 3 + False + + + RFC + 4 + False + + + Not Applicable + 5 + False + + + Changes Requested + 6 + False + + + Awaiting Upstream + 7 + False + + + Superseded + 8 + False + + + Deferred + 9 + False + + diff --git a/patchwork/forms.py b/patchwork/forms.py new file mode 100644 index 0000000..0327958 --- /dev/null +++ b/patchwork/forms.py @@ -0,0 +1,237 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +from django.contrib.auth.models import User +from django import forms + +from patchwork.models import Patch, State, Bundle, UserProfile + +class RegistrationForm(forms.Form): + first_name = forms.CharField(max_length = 30, required = False) + last_name = forms.CharField(max_length = 30, required = False) + username = forms.RegexField(regex = r'^\w+$', max_length=30, + label=u'Username') + email = forms.EmailField(max_length=100, label=u'Email address') + password = forms.CharField(widget=forms.PasswordInput(), + label='Password') + + def clean_username(self): + value = self.cleaned_data['username'] + try: + user = User.objects.get(username__iexact = value) + except User.DoesNotExist: + return self.cleaned_data['username'] + raise forms.ValidationError('This username is already taken. ' + \ + 'Please choose another.') + + def clean_email(self): + value = self.cleaned_data['email'] + try: + user = User.objects.get(email__iexact = value) + except User.DoesNotExist: + return self.cleaned_data['email'] + raise forms.ValidationError('This email address is already in use ' + \ + 'for the account "%s".\n' % user.username) + + def clean(self): + return self.cleaned_data + +class LoginForm(forms.Form): + username = forms.CharField(max_length = 30) + password = forms.CharField(widget = forms.PasswordInput) + +class BundleForm(forms.ModelForm): + name = forms.RegexField(regex = r'^[^/]+$', max_length=50, label=u'Name', + error_messages = {'invalid': 'Bundle names can\'t contain slashes'}) + + class Meta: + model = Bundle + fields = ['name', 'public'] + +class CreateBundleForm(BundleForm): + def __init__(self, *args, **kwargs): + super(CreateBundleForm, self).__init__(*args, **kwargs) + + class Meta: + model = Bundle + fields = ['name'] + + def clean_name(self): + name = self.cleaned_data['name'] + count = Bundle.objects.filter(owner = self.instance.owner, \ + name = name).count() + if count > 0: + raise forms.ValidationError('A bundle called %s already exists' \ + % name) + return name + +class DeleteBundleForm(forms.Form): + name = 'deletebundleform' + form_name = forms.CharField(initial = name, widget = forms.HiddenInput) + bundle_id = forms.IntegerField(widget = forms.HiddenInput) + +class DelegateField(forms.ModelChoiceField): + def __init__(self, project, *args, **kwargs): + queryset = User.objects.filter(profile__in = \ + UserProfile.objects \ + .filter(maintainer_projects = project) \ + .values('pk').query) + super(DelegateField, self).__init__(queryset, *args, **kwargs) + + +class PatchForm(forms.ModelForm): + def __init__(self, instance = None, project = None, *args, **kwargs): + if (not project) and instance: + project = instance.project + if not project: + raise Exception("meep") + super(PatchForm, self).__init__(instance = instance, *args, **kwargs) + self.fields['delegate'] = DelegateField(project, required = False) + + class Meta: + model = Patch + fields = ['state', 'archived', 'delegate'] + +class UserProfileForm(forms.ModelForm): + class Meta: + model = UserProfile + fields = ['primary_project', 'patches_per_page'] + +class OptionalDelegateField(DelegateField): + no_change_choice = ('*', 'no change') + to_field_name = None + + def __init__(self, no_change_choice = None, *args, **kwargs): + self.filter = None + if (no_change_choice): + self.no_change_choice = no_change_choice + super(OptionalDelegateField, self). \ + __init__(initial = self.no_change_choice[0], *args, **kwargs) + + def _get_choices(self): + choices = list( + super(OptionalDelegateField, self)._get_choices()) + choices.append(self.no_change_choice) + return choices + + choices = property(_get_choices, forms.ChoiceField._set_choices) + + def is_no_change(self, value): + return value == self.no_change_choice[0] + + def clean(self, value): + if value == self.no_change_choice[0]: + return value + return super(OptionalDelegateField, self).clean(value) + +class OptionalModelChoiceField(forms.ModelChoiceField): + no_change_choice = ('*', 'no change') + to_field_name = None + + def __init__(self, no_change_choice = None, *args, **kwargs): + self.filter = None + if (no_change_choice): + self.no_change_choice = no_change_choice + super(OptionalModelChoiceField, self). \ + __init__(initial = self.no_change_choice[0], *args, **kwargs) + + def _get_choices(self): + choices = list( + super(OptionalModelChoiceField, self)._get_choices()) + choices.append(self.no_change_choice) + return choices + + choices = property(_get_choices, forms.ChoiceField._set_choices) + + def is_no_change(self, value): + return value == self.no_change_choice[0] + + def clean(self, value): + if value == self.no_change_choice[0]: + return value + return super(OptionalModelChoiceField, self).clean(value) + +class MultipleBooleanField(forms.ChoiceField): + no_change_choice = ('*', 'no change') + def __init__(self, *args, **kwargs): + super(MultipleBooleanField, self).__init__(*args, **kwargs) + self.choices = [self.no_change_choice] + \ + [(True, 'Archived'), (False, 'Unarchived')] + + def is_no_change(self, value): + return value == self.no_change_choice[0] + + # TODO: Check whether it'd be worth to use a TypedChoiceField here; I + # think that'd allow us to get rid of the custom valid_value() and + # to_python() methods. + def valid_value(self, value): + if value in [v1 for (v1, v2) in self.choices]: + return True + return False + + def to_python(self, value): + if value is None or self.is_no_change(value): + return self.no_change_choice[0] + elif value == 'True': + return True + elif value == 'False': + return False + else: + raise ValueError('Unknown value: %s' % value) + +class MultiplePatchForm(forms.Form): + action = 'update' + state = OptionalModelChoiceField(queryset = State.objects.all()) + archived = MultipleBooleanField() + + def __init__(self, project, *args, **kwargs): + super(MultiplePatchForm, self).__init__(*args, **kwargs) + self.fields['delegate'] = OptionalDelegateField(project = project, + required = False) + + def save(self, instance, commit = True): + opts = instance.__class__._meta + if self.errors: + raise ValueError("The %s could not be changed because the data " + "didn't validate." % opts.object_name) + data = self.cleaned_data + # Update the instance + for f in opts.fields: + if not f.name in data: + continue + + field = self.fields.get(f.name, None) + if not field: + continue + + if field.is_no_change(data[f.name]): + continue + + setattr(instance, f.name, data[f.name]) + + if commit: + instance.save() + return instance + +class EmailForm(forms.Form): + email = forms.EmailField(max_length = 200) + +UserPersonLinkForm = EmailForm +OptinoutRequestForm = EmailForm diff --git a/patchwork/models.py b/patchwork/models.py new file mode 100644 index 0000000..54b8656 --- /dev/null +++ b/patchwork/models.py @@ -0,0 +1,386 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from django.db import models +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from django.contrib.sites.models import Site +from django.conf import settings +from patchwork.parser import hash_patch + +import re +import datetime, time +import random + +class Person(models.Model): + email = models.CharField(max_length=255, unique = True) + name = models.CharField(max_length=255, null = True, blank = True) + user = models.ForeignKey(User, null = True, blank = True, + on_delete = models.SET_NULL) + + def __unicode__(self): + if self.name: + return u'%s <%s>' % (self.name, self.email) + else: + return self.email + + def link_to_user(self, user): + self.name = user.profile.name() + self.user = user + + class Meta: + verbose_name_plural = 'People' + +class Project(models.Model): + linkname = models.CharField(max_length=255, unique=True) + name = models.CharField(max_length=255, unique=True) + listid = models.CharField(max_length=255, unique=True) + listemail = models.CharField(max_length=200) + web_url = models.CharField(max_length=2000, blank=True) + scm_url = models.CharField(max_length=2000, blank=True) + webscm_url = models.CharField(max_length=2000, blank=True) + send_notifications = models.BooleanField(default=False) + + def __unicode__(self): + return self.name + + def is_editable(self, user): + if not user.is_authenticated(): + return False + return self in user.profile.maintainer_projects.all() + + class Meta: + ordering = ['linkname'] + + +class UserProfile(models.Model): + user = models.OneToOneField(User, unique = True, related_name='profile') + primary_project = models.ForeignKey(Project, null = True, blank = True) + maintainer_projects = models.ManyToManyField(Project, + related_name = 'maintainer_project') + send_email = models.BooleanField(default = False, + help_text = 'Selecting this option allows patchwork to send ' + + 'email on your behalf') + patches_per_page = models.PositiveIntegerField(default = 100, + null = False, blank = False, + help_text = 'Number of patches to display per page') + + def name(self): + if self.user.first_name or self.user.last_name: + names = filter(bool, [self.user.first_name, self.user.last_name]) + return u' '.join(names) + return self.user.username + + def contributor_projects(self): + submitters = Person.objects.filter(user = self.user) + return Project.objects.filter(id__in = + Patch.objects.filter( + submitter__in = submitters) + .values('project_id').query) + + def sync_person(self): + pass + + def n_todo_patches(self): + return self.todo_patches().count() + + def todo_patches(self, project = None): + + # filter on project, if necessary + if project: + qs = Patch.objects.filter(project = project) + else: + qs = Patch.objects + + qs = qs.filter(archived = False) \ + .filter(delegate = self.user) \ + .filter(state__in = + State.objects.filter(action_required = True) + .values('pk').query) + return qs + + def __unicode__(self): + return self.name() + +def _user_saved_callback(sender, created, instance, **kwargs): + try: + profile = instance.profile + except UserProfile.DoesNotExist: + profile = UserProfile(user = instance) + profile.save() + +models.signals.post_save.connect(_user_saved_callback, sender = User) + +class State(models.Model): + name = models.CharField(max_length = 100) + ordering = models.IntegerField(unique = True) + action_required = models.BooleanField(default = True) + + def __unicode__(self): + return self.name + + class Meta: + ordering = ['ordering'] + +class HashField(models.CharField): + __metaclass__ = models.SubfieldBase + + def __init__(self, algorithm = 'sha1', *args, **kwargs): + self.algorithm = algorithm + try: + import hashlib + def _construct(string = ''): + return hashlib.new(self.algorithm, string) + self.construct = _construct + self.n_bytes = len(hashlib.new(self.algorithm).hexdigest()) + except ImportError: + modules = { 'sha1': 'sha', 'md5': 'md5'} + + if algorithm not in modules.keys(): + raise NameError("Unknown algorithm '%s'" % algorithm) + + self.construct = __import__(modules[algorithm]).new + + self.n_bytes = len(self.construct().hexdigest()) + + kwargs['max_length'] = self.n_bytes + super(HashField, self).__init__(*args, **kwargs) + + def db_type(self, connection=None): + return 'char(%d)' % self.n_bytes + +def get_default_initial_patch_state(): + return State.objects.get(ordering=0) + +class Patch(models.Model): + project = models.ForeignKey(Project) + msgid = models.CharField(max_length=255) + name = models.CharField(max_length=255) + date = models.DateTimeField(default=datetime.datetime.now) + submitter = models.ForeignKey(Person) + delegate = models.ForeignKey(User, blank = True, null = True) + state = models.ForeignKey(State, default=get_default_initial_patch_state) + archived = models.BooleanField(default = False) + headers = models.TextField(blank = True) + content = models.TextField(null = True, blank = True) + pull_url = models.CharField(max_length=255, null = True, blank = True) + commit_ref = models.CharField(max_length=255, null = True, blank = True) + hash = HashField(null = True, blank = True) + + def __unicode__(self): + return self.name + + def comments(self): + return Comment.objects.filter(patch = self) + + def save(self): + try: + s = self.state + except: + self.state = State.objects.get(ordering = 0) + + if self.hash is None and self.content is not None: + self.hash = hash_patch(self.content).hexdigest() + + super(Patch, self).save() + + def is_editable(self, user): + if not user.is_authenticated(): + return False + + if self.submitter.user == user or self.delegate == user: + return True + + return self.project.is_editable(user) + + def filename(self): + fname_re = re.compile('[^-_A-Za-z0-9\.]+') + str = fname_re.sub('-', self.name) + return str.strip('-') + '.patch' + + @models.permalink + def get_absolute_url(self): + return ('patchwork.views.patch.patch', (), {'patch_id': self.id}) + + class Meta: + verbose_name_plural = 'Patches' + ordering = ['date'] + unique_together = [('msgid', 'project')] + +class Comment(models.Model): + patch = models.ForeignKey(Patch) + msgid = models.CharField(max_length=255) + submitter = models.ForeignKey(Person) + date = models.DateTimeField(default = datetime.datetime.now) + headers = models.TextField(blank = True) + content = models.TextField() + + response_re = re.compile( \ + '^(Tested|Reviewed|Acked|Signed-off|Nacked|Reported)-by: .*$', + re.M | re.I) + + def patch_responses(self): + return ''.join([ match.group(0) + '\n' for match in + self.response_re.finditer(self.content)]) + + class Meta: + ordering = ['date'] + unique_together = [('msgid', 'patch')] + +class Bundle(models.Model): + owner = models.ForeignKey(User) + project = models.ForeignKey(Project) + name = models.CharField(max_length = 50, null = False, blank = False) + patches = models.ManyToManyField(Patch, through = 'BundlePatch') + public = models.BooleanField(default = False) + + def n_patches(self): + return self.patches.all().count() + + def ordered_patches(self): + return self.patches.order_by('bundlepatch__order') + + def append_patch(self, patch): + # todo: use the aggregate queries in django 1.1 + orders = BundlePatch.objects.filter(bundle = self).order_by('-order') \ + .values('order') + + if len(orders) > 0: + max_order = orders[0]['order'] + else: + max_order = 0 + + # see if the patch is already in this bundle + if BundlePatch.objects.filter(bundle = self, patch = patch).count(): + raise Exception("patch is already in bundle") + + bp = BundlePatch.objects.create(bundle = self, patch = patch, + order = max_order + 1) + bp.save() + + class Meta: + unique_together = [('owner', 'name')] + + def public_url(self): + if not self.public: + return None + site = Site.objects.get_current() + return 'http://%s%s' % (site.domain, + reverse('patchwork.views.bundle.bundle', + kwargs = { + 'username': self.owner.username, + 'bundlename': self.name + })) + + @models.permalink + def get_absolute_url(self): + return ('patchwork.views.bundle.bundle', (), { + 'username': self.owner.username, + 'bundlename': self.name, + }) + +class BundlePatch(models.Model): + patch = models.ForeignKey(Patch) + bundle = models.ForeignKey(Bundle) + order = models.IntegerField() + + class Meta: + unique_together = [('bundle', 'patch')] + ordering = ['order'] + +class EmailConfirmation(models.Model): + validity = datetime.timedelta(days = settings.CONFIRMATION_VALIDITY_DAYS) + type = models.CharField(max_length = 20, choices = [ + ('userperson', 'User-Person association'), + ('registration', 'Registration'), + ('optout', 'Email opt-out'), + ]) + email = models.CharField(max_length = 200) + user = models.ForeignKey(User, null = True) + key = HashField() + date = models.DateTimeField(default = datetime.datetime.now) + active = models.BooleanField(default = True) + + def deactivate(self): + self.active = False + self.save() + + def is_valid(self): + return self.date + self.validity > datetime.datetime.now() + + def save(self): + max = 1 << 32 + if self.key == '': + str = '%s%s%d' % (self.user, self.email, random.randint(0, max)) + self.key = self._meta.get_field('key').construct(str).hexdigest() + super(EmailConfirmation, self).save() + +class EmailOptout(models.Model): + email = models.CharField(max_length = 200, primary_key = True) + + def __unicode__(self): + return self.email + + @classmethod + def is_optout(cls, email): + email = email.lower().strip() + return cls.objects.filter(email = email).count() > 0 + +class PatchChangeNotification(models.Model): + patch = models.ForeignKey(Patch, primary_key = True) + last_modified = models.DateTimeField(default = datetime.datetime.now) + orig_state = models.ForeignKey(State) + +def _patch_change_callback(sender, instance, **kwargs): + # we only want notification of modified patches + if instance.pk is None: + return + + if instance.project is None or not instance.project.send_notifications: + return + + try: + orig_patch = Patch.objects.get(pk = instance.pk) + except Patch.DoesNotExist: + return + + # If there's no interesting changes, abort without creating the + # notification + if orig_patch.state == instance.state: + return + + notification = None + try: + notification = PatchChangeNotification.objects.get(patch = instance) + except PatchChangeNotification.DoesNotExist: + pass + + if notification is None: + notification = PatchChangeNotification(patch = instance, + orig_state = orig_patch.state) + + elif notification.orig_state == instance.state: + # If we're back at the original state, there is no need to notify + notification.delete() + return + + notification.last_modified = datetime.datetime.now() + notification.save() + +models.signals.pre_save.connect(_patch_change_callback, sender = Patch) diff --git a/patchwork/paginator.py b/patchwork/paginator.py new file mode 100644 index 0000000..31c0190 --- /dev/null +++ b/patchwork/paginator.py @@ -0,0 +1,88 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +from django.core import paginator +from django.conf import settings + +DEFAULT_PATCHES_PER_PAGE = 100 +LONG_PAGE_THRESHOLD = 30 +LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 10 +LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 8 +NUM_PAGES_OUTSIDE_RANGE = 2 +ADJACENT_PAGES = 4 + +# parts from: +# http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/ + +class Paginator(paginator.Paginator): + def __init__(self, request, objects): + + patches_per_page = settings.DEFAULT_PATCHES_PER_PAGE + + if request.user.is_authenticated(): + patches_per_page = request.user.profile.patches_per_page + + n = request.META.get('ppp') + if n: + try: + patches_per_page = int(n) + except ValueError: + pass + + super(Paginator, self).__init__(objects, patches_per_page) + + try: + page_no = int(request.GET.get('page')) + self.current_page = self.page(int(page_no)) + except Exception: + page_no = 1 + self.current_page = self.page(page_no) + + self.leading_set = self.trailing_set = [] + + pages = self.num_pages + + if pages <= LEADING_PAGE_RANGE_DISPLAYED: + self.adjacent_set = [n for n in range(1, pages + 1) \ + if n > 0 and n <= pages] + elif page_no <= LEADING_PAGE_RANGE: + self.adjacent_set = [n for n in \ + range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) \ + if n > 0 and n <= pages] + self.leading_set = [n + pages for n in \ + range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)] + elif page_no > pages - TRAILING_PAGE_RANGE: + self.adjacent_set = [n for n in \ + range(pages - TRAILING_PAGE_RANGE_DISPLAYED + 1, \ + pages + 1) if n > 0 and n <= pages] + self.trailing_set = [n + 1 for n in range(0, \ + NUM_PAGES_OUTSIDE_RANGE)] + else: + self.adjacent_set = [n for n in range(page_no - ADJACENT_PAGES, \ + page_no + ADJACENT_PAGES + 1) if n > 0 and n <= pages] + self.leading_set = [n + pages for n in \ + range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)] + self.trailing_set = [n + 1 for n in \ + range(0, NUM_PAGES_OUTSIDE_RANGE)] + + + self.leading_set.reverse() + self.long_page = \ + len(self.current_page.object_list) >= LONG_PAGE_THRESHOLD diff --git a/patchwork/parser.py b/patchwork/parser.py new file mode 100644 index 0000000..a51a7b6 --- /dev/null +++ b/patchwork/parser.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python +# +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +import re + +try: + import hashlib + sha1_hash = hashlib.sha1 +except ImportError: + import sha + sha1_hash = sha.sha + +_hunk_re = re.compile('^\@\@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? \@\@') +_filename_re = re.compile('^(---|\+\+\+) (\S+)') + +def parse_patch(text): + patchbuf = '' + commentbuf = '' + buf = '' + + # state specified the line we just saw, and what to expect next + state = 0 + # 0: text + # 1: suspected patch header (diff, ====, Index:) + # 2: patch header line 1 (---) + # 3: patch header line 2 (+++) + # 4: patch hunk header line (@@ line) + # 5: patch hunk content + # 6: patch meta header (rename from/rename to) + # + # valid transitions: + # 0 -> 1 (diff, ===, Index:) + # 0 -> 2 (---) + # 1 -> 2 (---) + # 2 -> 3 (+++) + # 3 -> 4 (@@ line) + # 4 -> 5 (patch content) + # 5 -> 1 (run out of lines from @@-specifed count) + # 1 -> 6 (rename from / rename to) + # 6 -> 2 (---) + # 6 -> 1 (other text) + # + # Suspected patch header is stored into buf, and appended to + # patchbuf if we find a following hunk. Otherwise, append to + # comment after parsing. + + # line counts while parsing a patch hunk + lc = (0, 0) + hunk = 0 + + + for line in text.split('\n'): + line += '\n' + + if state == 0: + if line.startswith('diff ') or line.startswith('===') \ + or line.startswith('Index: '): + state = 1 + buf += line + + elif line.startswith('--- '): + state = 2 + buf += line + + else: + commentbuf += line + + elif state == 1: + buf += line + if line.startswith('--- '): + state = 2 + + if line.startswith('rename from ') or line.startswith('rename to '): + state = 6 + + elif state == 2: + if line.startswith('+++ '): + state = 3 + buf += line + + elif hunk: + state = 1 + buf += line + + else: + state = 0 + commentbuf += buf + line + buf = '' + + elif state == 3: + match = _hunk_re.match(line) + if match: + + def fn(x): + if not x: + return 1 + return int(x) + + lc = map(fn, match.groups()) + + state = 4 + patchbuf += buf + line + buf = '' + + elif line.startswith('--- '): + patchbuf += buf + line + buf = '' + state = 2 + + elif hunk and line.startswith('\ No newline at end of file'): + # If we had a hunk and now we see this, it's part of the patch, + # and we're still expecting another @@ line. + patchbuf += line + + elif hunk: + state = 1 + buf += line + + else: + state = 0 + commentbuf += buf + line + buf = '' + + elif state == 4 or state == 5: + if line.startswith('-'): + lc[0] -= 1 + elif line.startswith('+'): + lc[1] -= 1 + elif line.startswith('\ No newline at end of file'): + # Special case: Not included as part of the hunk's line count + pass + else: + lc[0] -= 1 + lc[1] -= 1 + + patchbuf += line + + if lc[0] <= 0 and lc[1] <= 0: + state = 3 + hunk += 1 + else: + state = 5 + + elif state == 6: + if line.startswith('rename to ') or line.startswith('rename from '): + patchbuf += buf + line + buf = '' + + elif line.startswith('--- '): + patchbuf += buf + line + buf = '' + state = 2 + + else: + buf += line + state = 1 + + else: + raise Exception("Unknown state %d! (line '%s')" % (state, line)) + + commentbuf += buf + + if patchbuf == '': + patchbuf = None + + if commentbuf == '': + commentbuf = None + + return (patchbuf, commentbuf) + +def hash_patch(str): + # normalise spaces + str = str.replace('\r', '') + str = str.strip() + '\n' + + prefixes = ['-', '+', ' '] + hash = sha1_hash() + + for line in str.split('\n'): + + if len(line) <= 0: + continue + + hunk_match = _hunk_re.match(line) + filename_match = _filename_re.match(line) + + if filename_match: + # normalise -p1 top-directories + if filename_match.group(1) == '---': + filename = 'a/' + else: + filename = 'b/' + filename += '/'.join(filename_match.group(2).split('/')[1:]) + + line = filename_match.group(1) + ' ' + filename + + elif hunk_match: + # remove line numbers, but leave line counts + def fn(x): + if not x: + return 1 + return int(x) + line_nos = map(fn, hunk_match.groups()) + line = '@@ -%d +%d @@' % tuple(line_nos) + + elif line[0] in prefixes: + # if we have a +, - or context line, leave as-is + pass + + else: + # other lines are ignored + continue + + hash.update(line.encode('utf-8') + '\n') + + return hash + + +def main(args): + from optparse import OptionParser + + parser = OptionParser() + parser.add_option('-p', '--patch', action = 'store_true', + dest = 'print_patch', help = 'print parsed patch') + parser.add_option('-c', '--comment', action = 'store_true', + dest = 'print_comment', help = 'print parsed comment') + parser.add_option('-#', '--hash', action = 'store_true', + dest = 'print_hash', help = 'print patch hash') + + (options, args) = parser.parse_args() + + # decode from (assumed) UTF-8 + content = sys.stdin.read().decode('utf-8') + + (patch, comment) = parse_patch(content) + + if options.print_hash and patch: + print hash_patch(patch).hexdigest() + + if options.print_patch and patch: + print "Patch: ------\n" + patch + + if options.print_comment and comment: + print "Comment: ----\n" + comment + +if __name__ == '__main__': + import sys + sys.exit(main(sys.argv)) diff --git a/patchwork/requestcontext.py b/patchwork/requestcontext.py new file mode 100644 index 0000000..3b1afaf --- /dev/null +++ b/patchwork/requestcontext.py @@ -0,0 +1,89 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from django.template import RequestContext +from django.utils.html import escape +from django.contrib.sites.models import Site +from django.conf import settings +from patchwork.filters import Filters +from patchwork.models import Bundle, Project + +def bundle(request): + user = request.user + if not user.is_authenticated(): + return {} + return {'bundles': Bundle.objects.filter(owner = user)} + +def _params_as_qs(params): + return '&'.join([ '%s=%s' % (escape(k), escape(v)) for k, v in params ]) + +def _params_as_hidden_fields(params): + return '\n'.join([ '' % \ + (escape(k), escape(v)) for k, v in params ]) + +class PatchworkRequestContext(RequestContext): + def __init__(self, request, project = None, + dict = None, processors = None, + list_view = None, list_view_params = {}): + self._project = project + self.filters = Filters(request) + if processors is None: + processors = [] + processors.append(bundle) + super(PatchworkRequestContext, self). \ + __init__(request, dict, processors); + + self.update({ + 'filters': self.filters, + 'messages': [], + }) + if list_view: + params = self.filters.params() + for param in ['order', 'page']: + value = request.REQUEST.get(param, None) + if value: + params.append((param, value)) + self.update({ + 'list_view': { + 'view': list_view, + 'view_params': list_view_params, + 'params': params + }}) + + self.projects = Project.objects.all() + + self.update({ + 'project': self.project, + 'site': Site.objects.get_current(), + 'settings': settings, + 'other_projects': len(self.projects) > 1 + }) + + def _set_project(self, project): + self._project = project + self.filters.set_project(project) + self.update({'project': self._project}) + + def _get_project(self): + return self._project + + project = property(_get_project, _set_project) + + def add_message(self, message): + self['messages'].append(message) diff --git a/patchwork/settings/__init__.py b/patchwork/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/patchwork/settings/base.py b/patchwork/settings/base.py new file mode 100644 index 0000000..9b52989 --- /dev/null +++ b/patchwork/settings/base.py @@ -0,0 +1,115 @@ +""" +Base settings for patchwork project. +""" + +import os + +import django + +ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), + os.pardir, os.pardir) + +# +# Core settings +# https://docs.djangoproject.com/en/1.6/ref/settings/#core-settings +# + +# Models + +INSTALLED_APPS = [ + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.admin', + 'django.contrib.staticfiles', + 'patchwork', +] + +# HTTP + +MIDDLEWARE_CLASSES = [ + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', +] + +if django.VERSION < (1, 7): + MIDDLEWARE_CLASSES.append('django.middleware.doc.XViewMiddleware') +else: + MIDDLEWARE_CLASSES.append( + 'django.contrib.admindocs.middleware.XViewMiddleware') + +# Globalization + +TIME_ZONE = 'Australia/Canberra' + +LANGUAGE_CODE = 'en-au' + +USE_I18N = True + +# URLs + +ROOT_URLCONF = 'patchwork.urls' + +# Templates + +TEMPLATE_DIRS = ( + os.path.join(ROOT_DIR, 'templates'), +) + + +# +# Auth settings +# https://docs.djangoproject.com/en/1.6/ref/settings/#auth +# + +LOGIN_URL = '/user/login/' +LOGIN_REDIRECT_URL = '/user/' + + +# +# Sites settings +# https://docs.djangoproject.com/en/1.6/ref/settings/#sites +# + +SITE_ID = 1 + + +# +# Static files settings +# https://docs.djangoproject.com/en/1.6/ref/settings/#static-files +# + +STATIC_URL = '/static/' + +STATICFILES_DIRS = [ + os.path.join(ROOT_DIR, 'htdocs'), +] + + +# +# Patchwork settings +# + +DEFAULT_PATCHES_PER_PAGE = 100 +DEFAULT_FROM_EMAIL = 'Patchwork ' + +CONFIRMATION_VALIDITY_DAYS = 7 + +NOTIFICATION_DELAY_MINUTES = 10 +NOTIFICATION_FROM_EMAIL = DEFAULT_FROM_EMAIL + +# Set to True to enable the Patchwork XML-RPC interface +ENABLE_XMLRPC = False + +# Set to True to enable redirections or URLs from previous versions +# of patchwork +COMPAT_REDIR = True + +# Set to True to always generate https:// links instead of guessing +# the scheme based on current access. This is useful if SSL protocol +# is terminated upstream of the server (e.g. at the load balancer) +FORCE_HTTPS_LINKS = False diff --git a/patchwork/settings/dev.py b/patchwork/settings/dev.py new file mode 100644 index 0000000..6e373cc --- /dev/null +++ b/patchwork/settings/dev.py @@ -0,0 +1,52 @@ +""" +Development settings for patchwork project. + +These are also used in unit tests. + +Design based on: + http://www.revsys.com/blog/2014/nov/21/recommended-django-project-layout/ +""" + +import django + +from base import * + +# +# Core settings +# https://docs.djangoproject.com/en/1.6/ref/settings/#core-settings +# + +# Security + +SECRET_KEY = '00000000000000000000000000000000000000000000000000' + +# Debugging + +DEBUG = True + +# Templates + +TEMPLATE_DEBUG = True + +# Database + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'HOST': 'localhost', + 'PORT': '', + 'USER': os.environ['PW_TEST_DB_USER'], + 'PASSWORD': os.environ['PW_TEST_DB_PASS'], + 'NAME': 'patchwork', + 'TEST_CHARSET': 'utf8', + }, +} + +if django.VERSION >= (1, 7): + TEST_RUNNER = 'django.test.runner.DiscoverRunner' + +# +# Patchwork settings +# + +ENABLE_XMLRPC = True diff --git a/patchwork/settings/prod.py b/patchwork/settings/prod.py new file mode 100644 index 0000000..d71f3df --- /dev/null +++ b/patchwork/settings/prod.py @@ -0,0 +1,62 @@ +""" +Sample production-ready settings for patchwork project. + +Most of these are commented out as they will be installation dependent. + +Design based on: + http://www.revsys.com/blog/2014/nov/21/recommended-django-project-layout/ +""" + +from base import * + +# +# Core settings +# https://docs.djangoproject.com/en/1.6/ref/settings/#core-settings +# + +# Security + +# SECRET_KEY = '00000000000000000000000000000000000000000000000000' + +# Email + +# ADMINS = ( +# ('Jeremy Kerr', 'jk@ozlabs.org'), +# ) + +# Database + +# DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.postgresql_psycopg2', +# 'NAME': 'patchwork', +# }, +# } + +# File Uploads + +# MEDIA_ROOT = os.path.join( +# ROOT_DIR, 'lib', 'python', 'django', 'contrib', 'admin', 'media') + + +# +# Static files settings +# https://docs.djangoproject.com/en/1.6/ref/settings/#static-files +# + +# STATIC_ROOT = '/srv/patchwork/htdocs' + + +# +# Custom user overrides (for legacy) +# + +try: + from local_settings import * +except ImportError, ex: + import sys + sys.stderr.write(\ + ("settings.py: error importing local settings file:\n" + \ + "\t%s\n" + \ + "Do you have a local_settings.py module?\n") % str(ex)) + raise diff --git a/patchwork/templates/patchwork/activation_email.txt b/patchwork/templates/patchwork/activation_email.txt new file mode 100644 index 0000000..caf514a --- /dev/null +++ b/patchwork/templates/patchwork/activation_email.txt @@ -0,0 +1,11 @@ +Hi, + +This email is to confirm your account on the patchwork patch-tracking +system. You can activate your account by visiting the url: + + http://{{site.domain}}{% url 'patchwork.views.confirm' key=confirmation.key %} + +If you didn't request a user account on patchwork, then you can ignore +this mail. + +Happy patchworking. diff --git a/patchwork/templates/patchwork/activation_email_subject.txt b/patchwork/templates/patchwork/activation_email_subject.txt new file mode 100644 index 0000000..c409f38 --- /dev/null +++ b/patchwork/templates/patchwork/activation_email_subject.txt @@ -0,0 +1 @@ +Patchwork account confirmation diff --git a/patchwork/templates/patchwork/bundle.html b/patchwork/templates/patchwork/bundle.html new file mode 100644 index 0000000..4a96b6b --- /dev/null +++ b/patchwork/templates/patchwork/bundle.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} + +{% load person %} +{% load static %} + +{% block headers %} + + + +{% endblock %} +{% block title %}{{project.name}}{% endblock %} +{% block heading %}bundle: {{bundle.owner.username}} / +{{bundle.name}}{% endblock %} + +{% block body %} + +

This bundle contains patches for the {{ bundle.project.linkname }} +project.

+ +

Download bundle as mbox

+ +{% if bundleform %} +
+ {% csrf_token %} + + + + + + + +{{ bundleform }} + + + +
Bundle settings
+ + +
+
+ +
+{% endif %} + +{% include "patchwork/patch-list.html" %} + +{% endblock %} diff --git a/patchwork/templates/patchwork/bundles.html b/patchwork/templates/patchwork/bundles.html new file mode 100644 index 0000000..11fb89d --- /dev/null +++ b/patchwork/templates/patchwork/bundles.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} + +{% load static %} + +{% block title %}Bundles{% endblock %} +{% block heading %}Bundles{% endblock %} + +{% block body %} + +{% if bundles %} + + + + + + + + +{% for bundle in bundles %} + + + + + + + + + +{% endfor %} +
NameProjectPublic LinkPatches + DownloadDelete
{{ bundle.name }}{{ bundle.project.linkname }} + {% if bundle.public %} + {{ bundle.public_url }} + {% endif %} + {{ bundle.n_patches }}download +
+ {% csrf_token %} + {{ bundle.delete_form.as_p }} + +
+
+{% endif %} + +

Bundles are groups of related patches. You can create bundles by +selecting patches from a project, then using the 'create bundle' form +to give your bundle a name. Each bundle can be public or private; public +bundles are given a persistent URL, based you your username and the name +of the bundle. Private bundles are only visible to you.

+ +{% if not bundles %} +

You have no bundles.

+{% endif %} +{% endblock %} diff --git a/patchwork/templates/patchwork/confirm-error.html b/patchwork/templates/patchwork/confirm-error.html new file mode 100644 index 0000000..81292e2 --- /dev/null +++ b/patchwork/templates/patchwork/confirm-error.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block title %}Confirmation{% endblock %} +{% block heading %}Confirmation{% endblock %} + + +{% block body %} + +{% if error == 'inactive' %} +

This confirmation has already been processed; you've probably visited this +page before.

+{% endif %} + +{% if error == 'expired' %} +

The confirmation has expired. If you'd still like to perform the +{{conf.get_type_display}} process, you'll need to resubmit the request.

+{% endif %} + +{% endblock %} diff --git a/patchwork/templates/patchwork/filters.html b/patchwork/templates/patchwork/filters.html new file mode 100644 index 0000000..10ca587 --- /dev/null +++ b/patchwork/templates/patchwork/filters.html @@ -0,0 +1,183 @@ +{% load static %} + + + +
+
+ Filters: + {% if filters.applied_filters %} + {% for filter in filters.applied_filters %} + {{ filter.name }} = {{ filter.condition }} + {% if not filter.forced %} + remove filter + {% endif %} + {% if not forloop.last %}   |   {% endif %} + {% endfor %} + {% else %} + none + add filter + {% endif %} +
+ +
+ + diff --git a/patchwork/templates/patchwork/help/about.html b/patchwork/templates/patchwork/help/about.html new file mode 100644 index 0000000..7befa6b --- /dev/null +++ b/patchwork/templates/patchwork/help/about.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block title %}About{% endblock %} +{% block heading %} - About Patchwork{% endblock %} + +{% block body %} + +

Patchwork is free software, and is available from the +patchwork website.

+ +

Patchwork is built on the django +web framework.

+ +

Icons from the Sweetie icon set. + +{% endblock %} + diff --git a/patchwork/templates/patchwork/help/index.html b/patchwork/templates/patchwork/help/index.html new file mode 100644 index 0000000..5cb6467 --- /dev/null +++ b/patchwork/templates/patchwork/help/index.html @@ -0,0 +1,2 @@ +{% extends "base.html" %} + diff --git a/patchwork/templates/patchwork/help/pwclient.html b/patchwork/templates/patchwork/help/pwclient.html new file mode 100644 index 0000000..7101ec1 --- /dev/null +++ b/patchwork/templates/patchwork/help/pwclient.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block title %}Command-line client{% endblock %} +{% block heading %} - Command-line client{% endblock %} + +{% block body %} + +

pwclient is the command-line client for patchwork. Currently, +it provides access to some read-only features of patchwork, such as downloading +and applying patches.

+ +

To use pwclient, you will need:

+
    +
  • The pwclient + program (11kB, python script)
  • +
  • (optional) a .pwclientrc file in your home directory.
  • +
+ +

You can create your own .pwclientrc file. Each +patchwork project +provides a sample linked from the 'project info' page.

+ +{% endblock %} diff --git a/patchwork/templates/patchwork/list.html b/patchwork/templates/patchwork/list.html new file mode 100644 index 0000000..654fe8c --- /dev/null +++ b/patchwork/templates/patchwork/list.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% load person %} +{% load static %} + +{% block title %}{{project.name}}{% endblock %} +{% block heading %}{{project.name}}{% endblock %} + +{% block body %} + +

Incoming patches

+ +{% if errors %} +

The following error{{ errors|length|pluralize:" was,s were" }} encountered +while updating patches:

+
    +{% for error in errors %} +
  • {{ error }}
  • +{% endfor %} +
+{% endif %} + +{% include "patchwork/patch-list.html" %} + +{% endblock %} diff --git a/patchwork/templates/patchwork/login.html b/patchwork/templates/patchwork/login.html new file mode 100644 index 0000000..2dfc2a7 --- /dev/null +++ b/patchwork/templates/patchwork/login.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block title %}Login{% endblock %} +{% block heading %}Login{% endblock %} + + +{% block body %} +
+{% csrf_token %} + + + + + {% if error %} + + + + {% endif %} + {{ form }} + + + +
login
{{ error }}
+ +
+
+{% endblock %} diff --git a/patchwork/templates/patchwork/logout.html b/patchwork/templates/patchwork/logout.html new file mode 100644 index 0000000..f030aee --- /dev/null +++ b/patchwork/templates/patchwork/logout.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block title %}Logout{% endblock %} +{% block heading %}Logout{% endblock %} + +{% block body %} +

Logged out

+{% endblock %} diff --git a/patchwork/templates/patchwork/mail-form.html b/patchwork/templates/patchwork/mail-form.html new file mode 100644 index 0000000..d71b2fb --- /dev/null +++ b/patchwork/templates/patchwork/mail-form.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} + +{% block title %}mail settings{% endblock %} +{% block heading %}mail settings{% endblock %} + +{% block body %} + +

You can configure patchwork to send you mail on certain events, +or block automated mail altogether. Enter your email address to +view or change your email settings.

+ +
+{% csrf_token %} + +{% if form.errors %} + + + +{% endif %} + + + + + + + +
+ There was an error accessing your mail settings: +
{{ form.email.label_tag }} + {{form.email}} + {{form.email.errors}} +
+ +
+
+ + +{% endblock %} diff --git a/patchwork/templates/patchwork/mail-settings.html b/patchwork/templates/patchwork/mail-settings.html new file mode 100644 index 0000000..440af08 --- /dev/null +++ b/patchwork/templates/patchwork/mail-settings.html @@ -0,0 +1,37 @@ +{% extends "base.html" %} + +{% block title %}mail settings{% endblock %} +{% block heading %}mail settings{% endblock %} + +{% block body %} +

Settings for {{email}}:

+ + + + +{% if is_optout %} + + + +{% else %} + + +{% endif %} + +
Opt-out listPatchwork may not send automated notifications to + this address. +
+ {% csrf_token %} + + +
+
Patchwork may send automated notifications to + this address. +
+ {% csrf_token %} + + +
+
+ +{% endblock %} diff --git a/patchwork/templates/patchwork/optin-request.html b/patchwork/templates/patchwork/optin-request.html new file mode 100644 index 0000000..3dfb1bd --- /dev/null +++ b/patchwork/templates/patchwork/optin-request.html @@ -0,0 +1,50 @@ +{% extends "base.html" %} + +{% block title %}opt-in{% endblock %} +{% block heading %}opt-in{% endblock %} + +{% block body %} +{% if email_sent %} +

Opt-in confirmation email sent

+

An opt-in confirmation mail has been sent to +{{confirmation.email}}, containing a link. Please click on +that link to confirm your opt-in.

+{% else %} +{% if error %} +

{{error}}

+{% endif %} + +{% if form %} +

This form allows you to opt-in to automated email from patchwork. Use +this if you have previously opted-out of patchwork mail, but now want to +received notifications from patchwork.

+When you submit it, an email will be sent to your address with a link to click +to finalise the opt-in. Patchwork does this to prevent someone opting you in +without your consent.

+
+{% csrf_token %} +{{form.email.errors}} +
+{{form.email.label_tag}}: {{form.email}} +
+ +
+{% endif %} + +{% if error and admins %} +

If you are having trouble opting in, please email +{% for admin in admins %} +{% if admins|length > 1 and forloop.last %} or {% endif %} +{{admin.0}} <{{admin.1}}>{% if admins|length > 2 and not forloop.last %}, {% endif %} +{% endfor %} +{% endif %} + +{% endif %} + +{% if user.is_authenticated %} +

Return to your user +profile.

+{% endif %} + +{% endblock %} diff --git a/patchwork/templates/patchwork/optin-request.mail b/patchwork/templates/patchwork/optin-request.mail new file mode 100644 index 0000000..d97c78b --- /dev/null +++ b/patchwork/templates/patchwork/optin-request.mail @@ -0,0 +1,12 @@ +Hi, + +This email is to confirm that you would like to opt-in to automated +email from the patchwork system at {{site.domain}}. + +To complete the opt-in process, visit: + + http://{{site.domain}}{% url 'patchwork.views.confirm' key=confirmation.key %} + +If you didn't request this opt-in, you don't need to do anything. + +Happy patchworking. diff --git a/patchwork/templates/patchwork/optin.html b/patchwork/templates/patchwork/optin.html new file mode 100644 index 0000000..01aaa0e --- /dev/null +++ b/patchwork/templates/patchwork/optin.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block title %}opt-in{% endblock %} +{% block heading %}opt-in{% endblock %} + +{% block body %} + +

Opt-in complete. You have sucessfully opted back in to +automated email from this patchwork system, using the address +{{email}}.

+

If you later decide that you no longer want to receive automated mail from +patchwork, just visit http://{{site.domain}}{% url 'patchwork.views.mail.settings' %}, or +visit the main patchwork page and navigate from there.

+{% if user.is_authenticated %} +

Return to your user +profile.

+{% endif %} +{% endblock %} diff --git a/patchwork/templates/patchwork/optout-request.html b/patchwork/templates/patchwork/optout-request.html new file mode 100644 index 0000000..092dbbb --- /dev/null +++ b/patchwork/templates/patchwork/optout-request.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} + +{% block title %}opt-out{% endblock %} +{% block heading %}opt-out{% endblock %} + +{% block body %} +{% if email_sent %} +

Opt-out confirmation email sent

+

An opt-out confirmation mail has been sent to +{{confirmation.email}}, containing a link. Please click on +that link to confirm your opt-out.

+{% else %} +{% if error %} +

{{error}}

+{% endif %} + +{% if form %} +

This form allows you to opt-out of automated email from patchwork.

+

If you opt-out of email, Patchwork may still email you if you do certain +actions yourself (such as create a new patchwork account), but will not send +you unsolicited email.

+When you submit it, one email will be sent to your address with a link to click +to finalise the opt-out. Patchwork does this to prevent someone opting you out +without your consent.

+
+{% csrf_token %} +{{form.email.errors}} +
+{{form.email.label_tag}}: {{form.email}} +
+ +
+{% endif %} + +{% if error and admins %} +

If you are having trouble opting out, please email +{% for admin in admins %} +{% if admins|length > 1 and forloop.last %} or {% endif %} +{{admin.0}} <{{admin.1}}>{% if admins|length > 2 and not forloop.last %}, {% endif %} +{% endfor %} +{% endif %} + +{% endif %} + +{% if user.is_authenticated %} +

Return to your user +profile.

+{% endif %} + +{% endblock %} diff --git a/patchwork/templates/patchwork/optout-request.mail b/patchwork/templates/patchwork/optout-request.mail new file mode 100644 index 0000000..67203ca --- /dev/null +++ b/patchwork/templates/patchwork/optout-request.mail @@ -0,0 +1,12 @@ +Hi, + +This email is to confirm that you would like to opt-out from all email +from the patchwork system at {{site.domain}}. + +To complete the opt-out process, visit: + + http://{{site.domain}}{% url 'patchwork.views.confirm' key=confirmation.key %} + +If you didn't request this opt-out, you don't need to do anything. + +Happy patchworking. diff --git a/patchwork/templates/patchwork/optout.html b/patchwork/templates/patchwork/optout.html new file mode 100644 index 0000000..b140bf4 --- /dev/null +++ b/patchwork/templates/patchwork/optout.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block title %}opt-out{% endblock %} +{% block heading %}opt-out{% endblock %} + +{% block body %} + +

Opt-out complete. You have successfully opted-out of +automated notifications from this patchwork system, from the address +{{email}}

+

Please note that you may still receive email from other patchwork setups at +different sites, as they are run independently. You may need to opt-out of +those separately.

+

If you later decide to receive mail from patchwork, just visit +http://{{site.domain}}{% url 'patchwork.views.mail.settings' %}, or +visit the main patchwork page and navigate from there.

+{% if user.is_authenticated %} +

Return to your user +profile.

+{% endif %} +{% endblock %} diff --git a/patchwork/templates/patchwork/pagination.html b/patchwork/templates/patchwork/pagination.html new file mode 100644 index 0000000..3e95126 --- /dev/null +++ b/patchwork/templates/patchwork/pagination.html @@ -0,0 +1,45 @@ +{% load listurl %} + +{% ifnotequal page.paginator.num_pages 1 %} +
+{% if page.has_previous %} + + « Previous +{% else %} + « Previous +{% endif %} + +{% if page.paginator.trailing_set %} + {% for p in page.paginator.trailing_set %} + {{ p }} + {% endfor %} + ... +{% endif %} + +{% for p in page.paginator.adjacent_set %} + {% ifequal p page.number %} + {{ p }} + {% else %} + {{ p }} + {% endifequal %} +{% endfor %} + +{% if page.paginator.leading_set %} + ... + {% for p in page.paginator.leading_set %} + {{ p }} + {% endfor %} +{% endif %} + +{% if page.has_next %} + + Next » + +{% else %} + Next » +{% endif %} +
+{% endifnotequal %} diff --git a/patchwork/templates/patchwork/patch-change-notification-subject.text b/patchwork/templates/patchwork/patch-change-notification-subject.text new file mode 100644 index 0000000..c9d96d4 --- /dev/null +++ b/patchwork/templates/patchwork/patch-change-notification-subject.text @@ -0,0 +1 @@ +[{{ projects|join:"," }}] Patch notification: {{notifications|length}} patch{{notifications|length|pluralize:"es"}} updated diff --git a/patchwork/templates/patchwork/patch-change-notification.mail b/patchwork/templates/patchwork/patch-change-notification.mail new file mode 100644 index 0000000..4246704 --- /dev/null +++ b/patchwork/templates/patchwork/patch-change-notification.mail @@ -0,0 +1,20 @@ +Hello, + +The following patch{{notifications|length|pluralize:"es"}} (submitted by you) {{notifications|length|pluralize:"has,have"}} been updated in patchwork: +{% for notification in notifications %} + * {{notification.patch.project.linkname}}: {{notification.patch.name|safe}} + - http://{{site.domain}}{{notification.patch.get_absolute_url}} + - for: {{notification.patch.project.name}} + was: {{notification.orig_state}} + now: {{notification.patch.state}} +{% endfor %} +This email is a notification only - you do not need to respond. + +Happy patchworking. + +-- + +This is an automated mail sent by the patchwork system at +{{site.domain}}. To stop receiving these notifications, edit +your mail settings at: + http://{{site.domain}}{% url 'patchwork.views.mail.settings' %} diff --git a/patchwork/templates/patchwork/patch-list.html b/patchwork/templates/patchwork/patch-list.html new file mode 100644 index 0000000..675f67f --- /dev/null +++ b/patchwork/templates/patchwork/patch-list.html @@ -0,0 +1,268 @@ +{% load person %} +{% load listurl %} +{% load static %} + +{% include "patchwork/pagination.html" %} + + + + + + {% if order.editable %} + + {% endif %} + +
+ {% include "patchwork/filters.html" %} + +
+ {% csrf_token %} + + + + + +
+
+ +{% if page.paginator.long_page and user.is_authenticated %} +
+ +
+{% endif %} + +
+{% csrf_token %} + + + + + + {% if user.is_authenticated %} + + {% endif %} + + + + + + + + + + + + + + +{% if page.paginator.count %} + + {% for patch in page.object_list %} + + {% if user.is_authenticated %} + + {% endif %} + + + + + + + {% endfor %} + +
+ + + {% ifequal order.name "name" %} + Patch + {% else %} + {% if not order.editable %} + Patch + {% else %} + Patch + {% endif %} + {% endifequal %} + + {% ifequal order.name "date" %} + Date + {% else %} + {% if not order.editable %} + Date + {% else %} + Date + {% endif %} + {% endifequal %} + + {% ifequal order.name "submitter" %} + Submitter + {% else %} + {% if not order.editable %} + Submitter + {% else %} + Submitter + {% endif %} + {% endifequal %} + + {% ifequal order.name "delegate" %} + Delegate + {% else %} + {% if not order.editable %} + Delegate + {% else %} + Delegate + {% endif %} + {% endifequal %} + + {% ifequal order.name "state" %} + State + {% else %} + {% if not order.editable %} + State + {% else %} + State + {% endif %} + {% endifequal %} +
+ + {{ patch.name|default:"[no subject]" }}{{ patch.date|date:"Y-m-d" }}{{ patch.submitter|personify:project }}{{ patch.delegate.username }}{{ patch.state }}
+ +{% include "patchwork/pagination.html" %} + +
+ +{% if patchform %} +
+

Properties

+ + + + + + + + + + + + + + + +
Change state: + {{ patchform.state }} + {{ patchform.state.errors }} +
Delegate to: + + {{ patchform.delegate }} + {{ patchform.delegate.errors }} +
Archive: + + {{ patchform.archived }} + {{ patchform.archived.errors }} +
+ +
+
+ +{% endif %} + +{% if user.is_authenticated %} +
+

Bundling

+ + + + + + {% if bundles %} + + + + + {% endif %} + {% if bundle %} + + + + + {% endif %} +
Create bundle: + + +
Add to bundle: + + +
Remove from bundle: + + +
+
+{% endif %} + + +
+
+
+ +{% else %} + + No patches to display + +{% endif %} + + +
+ diff --git a/patchwork/templates/patchwork/patch.html b/patchwork/templates/patchwork/patch.html new file mode 100644 index 0000000..f18ee3b --- /dev/null +++ b/patchwork/templates/patchwork/patch.html @@ -0,0 +1,199 @@ +{% extends "base.html" %} + +{% load syntax %} +{% load person %} +{% load patch %} + +{% block title %}{{patch.name}}{% endblock %} +{% block heading %}{{patch.name}}{%endblock%} + +{% block body %} + + + + + + + + + + + + + + + + + + + + + + + + + + +{% if patch.commit_ref %} + + + + +{% endif %} +{% if patch.delegate %} + + + + +{% endif %} + + + + +
Submitter{{ patch.submitter|personify:project }}
Date{{ patch.date }}
Message ID{{ patch.msgid }}
Download + mbox +{% if patch.content %}| + patch +{% endif %} +
Permalink{{ patch.get_absolute_url }} +
State{{ patch.state.name }}{% if patch.archived %}, archived{% endif %}
Commit{{ patch.commit_ref }}
Delegated to:{{ patch.delegate.profile.name }}
Headersshow + +
+ +
+ +{% if patchform %} +
+

Patch Properties

+
+ {% csrf_token %} + + + + + + + + + + + + + + + + + +
Change state: + {{ patchform.state }} + {{ patchform.state.errors }} +
Delegate to: + {{ patchform.delegate }} + {{ patchform.delegate.errors }} +
Archived: + {{ patchform.archived }} + {{ patchform.archived.errors }} +
+ +
+
+
+{% endif %} + +{% if createbundleform %} +
+

Bundling

+ + + + + +{% if bundles %} + + + + +{% endif %} +
Create bundle: + {% if createbundleform.non_field_errors %} +
{{createbundleform.non_field_errors}}
+ {% endif %} +
+ {% csrf_token %} + + {% if createbundleform.name.errors %} +
{{createbundleform.name.errors}}
+ {% endif %} + {{ createbundleform.name }} + +
+
Add to bundle: +
+ {% csrf_token %} + + + +
+
+ +
+{% endif %} + +
+
+
+ +{% if patch.pull_url %} +

Pull-request

+{{ patch.pull_url }} +{% endif %} + +

Comments

+{% for comment in patch.comments %} +
+
{{ comment.submitter|personify:project }} - {{comment.date}}
+
+{{ comment|commentsyntax }}
+
+
+{% endfor %} + +{% if patch.content %} +

Patch

+
+
+{{ patch|patchsyntax }}
+
+
+{% endif %} + + +{% endblock %} diff --git a/patchwork/templates/patchwork/profile.html b/patchwork/templates/patchwork/profile.html new file mode 100644 index 0000000..116d6d6 --- /dev/null +++ b/patchwork/templates/patchwork/profile.html @@ -0,0 +1,144 @@ +{% extends "base.html" %} + +{% block title %}User Profile: {{ user.username }}{% endblock %} +{% block heading %}User Profile: {{ user.username }}{% endblock %} + + +{% block body %} + +

+{% if user.profile.maintainer_projects.count %} +Maintainer of +{% for project in user.profile.maintainer_projects.all %} +{{ project.linkname }}{% if not forloop.last %},{% endif %}{% endfor %}. +{% endif %} + +{% if user.profile.contributor_projects.count %} +Contributor to +{% for project in user.profile.contributor_projects.all %} +{{ project.linkname }}{% if not forloop.last %},{% endif %}{% endfor %}. +{% endif %} +

+ +
+
+

Todo

+{% if user.profile.n_todo_patches %} +

Your todo + list contains {{ user.profile.n_todo_patches }} + patch{{ user.profile.n_todo_patches|pluralize:"es" }}.

+{% else %} +

Your todo list contains patches that have been delegated to you. You + have no items in your todo list at present.

+{% endif %} +
+ +
+

Linked email addresses

+

The following email addresses are associated with this patchwork account. +Adding alternative addresses allows patchwork to group contributions that +you have made under different addresses.

+

The "notify?" column allows you to opt-in or -out of automated +patchwork notification emails. Setting it to "no" will disable automated +notifications for that address.

+

Adding a new email address will send a confirmation email to that +address.

+ + + + + + +{% for email in linked_emails %} + + + + + +{% endfor %} + + + +
emailactionnotify?
{{ email.email }} + {% ifnotequal user.email email.email %} +
+ {% csrf_token %} + +
+ {% endifnotequal %} +
+ {% if email.is_optout %} +
+ No, + {% csrf_token %} + + +
+ {% else %} +
+ Yes, + {% csrf_token %} + + +
+ {% endif %} +
+
+ {% csrf_token %} + {{ linkform.email }} + +
+
+
+
+ +
+ +
+

Bundles

+ +{% if bundles %} +

You have the following bundle{{ bundle|length|pluralize }}:

+ +

Visit the bundles + page to manage your bundles.

+{% else %} +

You have no bundles.

+{% endif %} +
+ + +
+

Settings

+ +
+ {% csrf_token %} + +{{ profileform }} + + + +
+ + +
+
+
+ +
+

Authentication

+Change password +
+ +
+ +

+ +{% endblock %} diff --git a/patchwork/templates/patchwork/project.html b/patchwork/templates/patchwork/project.html new file mode 100644 index 0000000..be8cadc --- /dev/null +++ b/patchwork/templates/patchwork/project.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} + +{% block title %}{{ project.name }}{% endblock %} +{% block heading %}{{ project.name }}{% endblock %} + +{% block body %} + + + + + + + + + + + + + + + + + +{% if project.web_url %} + + + + +{% endif %} +{% if project.webscm_url %} + + + + +{% endif %} +{% if project.scm_url %} + + + + +{% endif %} +
Name{{project.name}} +
List address{{project.listemail}}
Maintainer{{maintainers|length|pluralize}} + {% for maintainer in maintainers %} + {{ maintainer.profile.name }} + <{{maintainer.email}}> +
+ {% endfor %} +
Patch count{{n_patches}} (+ {{n_archived_patches}} archived)
Website{{project.web_url}}
Source Code Web Interface{{project.webscm_url}}
Source Code Manager URL{{project.scm_url}}
+ +{% if settings.ENABLE_XMLRPC %} +

Sample patchwork +client configuration for this project: .pwclientrc.

+{% endif %} + +{% endblock %} diff --git a/patchwork/templates/patchwork/projects.html b/patchwork/templates/patchwork/projects.html new file mode 100644 index 0000000..8c727ad --- /dev/null +++ b/patchwork/templates/patchwork/projects.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block title %}Project List{% endblock %} +{% block heading %}Project List{% endblock %} + +{% block body %} + +{% if projects %} +
+ {% for p in projects %} +
+

+ {{p.linkname}} +

+
{{p.name}}
+{% if p.web_url %} + +{% endif %} +
+ {% endfor %} +
+{% else %} +

Patchwork doesn't have any projects to display!

+{% endif %} + +{% endblock %} diff --git a/patchwork/templates/patchwork/pwclient b/patchwork/templates/patchwork/pwclient new file mode 120000 index 0000000..5ce255f --- /dev/null +++ b/patchwork/templates/patchwork/pwclient @@ -0,0 +1 @@ +../../bin/pwclient \ No newline at end of file diff --git a/patchwork/templates/patchwork/pwclientrc b/patchwork/templates/patchwork/pwclientrc new file mode 100644 index 0000000..d331003 --- /dev/null +++ b/patchwork/templates/patchwork/pwclientrc @@ -0,0 +1,15 @@ +# Sample .pwclientrc file for the {{ project.linkname }} project, +# running on {{ site.domain }}. +# +# Just append this file to your existing ~/.pwclientrc +# If you do not already have a ~/.pwclientrc, then copy this file to +# ~/.pwclientrc, and uncomment the following two lines: +# [options] +# default={{ project.linkname }} + +[{{ project.linkname }}] +url= {{scheme}}://{{site.domain}}{% url 'patchwork.views.xmlrpc.xmlrpc' %} +{% if user.is_authenticated %} +username: {{ user.username }} +password: +{% endif %} diff --git a/patchwork/templates/patchwork/register.mail b/patchwork/templates/patchwork/register.mail new file mode 100644 index 0000000..9079203 --- /dev/null +++ b/patchwork/templates/patchwork/register.mail @@ -0,0 +1,11 @@ +Hi, + +This email is to confirm your account on the patchwork patch-tracking +system. You can activate your account by visiting the url: + + http://{{site.domain}}{% url 'registration_activateactivation_key'=request.key %} + +If you didn't request a user account on patchwork, then you can ignore +this mail. + +Happy patchworking. diff --git a/patchwork/templates/patchwork/registration-confirm.html b/patchwork/templates/patchwork/registration-confirm.html new file mode 100644 index 0000000..6111401 --- /dev/null +++ b/patchwork/templates/patchwork/registration-confirm.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block title %}Registration{% endblock %} +{% block heading %}Registration{% endblock %} + +{% block body %} +

Registraton confirmed!

+ +

Your patchwork registration is complete. Head over to your profile to start using +patchwork's extra features.

+ +{% endblock %} diff --git a/patchwork/templates/patchwork/registration_form.html b/patchwork/templates/patchwork/registration_form.html new file mode 100644 index 0000000..3a314b8 --- /dev/null +++ b/patchwork/templates/patchwork/registration_form.html @@ -0,0 +1,121 @@ +{% extends "base.html" %} + +{% block title %}Registration{% endblock %} +{% block heading %}Registration{% endblock %} + + +{% block body %} + +{% if confirmation and not error %} +

Registration successful!

+

A confirmation email has been sent to {{ confirmation.email }}. You'll + need to visit the link provided in that email to confirm your + registration.

+

+{% else %} +

By creating a patchwork account, you can:

+

    +
  • create "bundles" of patches
  • +
  • update the state of your own patches
  • +
+
+{% csrf_token %} + + + + + {% if error %} + + + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
register
{{ error }}
{{ form.first_name.label_tag }} +{% if form.first_name.errors %} + {{ form.first_name.errors }} +{% endif %} + {{ form.first_name }} +{% if form.first_name.help_text %} +
{{ form.first_name.help_text }}
+{% endif %} +
{{ form.last_name.label_tag }} +{% if form.last_name.errors %} + {{ form.last_name.errors }} +{% endif %} + {{ form.last_name }} +{% if form.last_name.help_text %} +
{{ form.last_name.help_text }}
+{% endif %} +
+ Your name is used to identify you on the site +
{{ form.email.label_tag }} +{% if form.email.errors %} + {{ form.email.errors }} +{% endif %} + {{ form.email }} +{% if form.email.help_text %} +
{{ form.email.help_text }}
+{% endif %} +
+ Patchwork will send a confirmation email to this address +
{{ form.username.label_tag }} +{% if form.username.errors %} + {{ form.username.errors }} +{% endif %} + {{ form.username }} +{% if form.username.help_text %} +
{{ form.username.help_text }}
+{% endif %} +
{{ form.password.label_tag }} +{% if form.password.errors %} + {{ form.password.errors }} +{% endif %} + {{ form.password }} +{% if form.password.help_text %} +
{{ form.password.help_text }}
+{% endif %} +
+ +
+
+{% endif %} + +{% endblock %} diff --git a/patchwork/templates/patchwork/todo-list.html b/patchwork/templates/patchwork/todo-list.html new file mode 100644 index 0000000..b301901 --- /dev/null +++ b/patchwork/templates/patchwork/todo-list.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% load person %} + +{% block title %}{{ user }}'s todo list{% endblock %} +{% block heading %}{{user}}'s todo list for {{ project.linkname }}{% endblock %} + +{% block body %} + +

A Patchwork Todo-list contains patches that are assigned to you, and +are in an "action required" state +({% for state in action_required_states %}{% if forloop.last and not forloop.first %} or {% endif %}{{ state }}{% if not forloop.last and not forloop.first %}, {%endif %}{% endfor %}), and are not archived. +

+ +{% include "patchwork/patch-list.html" %} + +{% endblock %} diff --git a/patchwork/templates/patchwork/todo-lists.html b/patchwork/templates/patchwork/todo-lists.html new file mode 100644 index 0000000..e268160 --- /dev/null +++ b/patchwork/templates/patchwork/todo-lists.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} + +{% block title %}{{ user }}'s todo lists{% endblock %} +{% block heading %}{{ user }}'s todo lists{% endblock %} + +{% block body %} + +{% if todo_lists %} +

You have multiple todo lists. Each todo list contains patches for a single + project.

+ + + + + +{% for todo_list in todo_lists %} + + + + +{% endfor %} +
projectpatches
{{ todo_list.project.name }}{{ todo_list.n_patches }}
+ +{% else %} + No todo lists +{% endif %} +{% endblock %} diff --git a/patchwork/templates/patchwork/user-link-confirm.html b/patchwork/templates/patchwork/user-link-confirm.html new file mode 100644 index 0000000..449bfeb --- /dev/null +++ b/patchwork/templates/patchwork/user-link-confirm.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block title %}{{ user.username }}{% endblock %} +{% block heading %}link accounts for {{ user.username }}{% endblock %} + + +{% block body %} + +{% if errors %} +

{{ errors }}

+{% else %} +

You have sucessfully linked the email address {{ person.email }} to + your patchwork account

+ +{% endif %} +

Back to your + profile.

+ +{% endblock %} diff --git a/patchwork/templates/patchwork/user-link.html b/patchwork/templates/patchwork/user-link.html new file mode 100644 index 0000000..e436c3a --- /dev/null +++ b/patchwork/templates/patchwork/user-link.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} + +{% block title %}{{ user.username }}{% endblock %} +{% block heading %}link accounts for {{ user.username }}{% endblock %} + + +{% block body %} + +{% if confirmation and not error %} +

A confirmation email has been sent to {{ confirmation.email }}. Click +on the link provided in the email to confirm that this address belongs to +you.

+ +{% else %} + + {% if form.errors %} +

There was an error submitting your link request.

+ {{ form.non_field_errors }} + {% endif %} + {% if error %} +
  • {{error}}
+ {% endif %} + +
+ {% csrf_token %} + {{linkform.email.errors}} + Link an email address: {{ linkform.email }} +
+ +{% endif %} + +{% endblock %} diff --git a/patchwork/templates/patchwork/user-link.mail b/patchwork/templates/patchwork/user-link.mail new file mode 100644 index 0000000..8db6726 --- /dev/null +++ b/patchwork/templates/patchwork/user-link.mail @@ -0,0 +1,12 @@ +Hi, + +This email is to confirm that you own the email address: + + {{ confirmation.email }} + +So that you can add it to your patchwork profile. You can confirm this +email address by visiting the url: + + http://{{site.domain}}{% url 'patchwork.views.confirm' key=confirmation.key %} + +Happy patchworking. diff --git a/patchwork/templatetags/__init__.py b/patchwork/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/patchwork/templatetags/filter.py b/patchwork/templatetags/filter.py new file mode 100644 index 0000000..7a5d9df --- /dev/null +++ b/patchwork/templatetags/filter.py @@ -0,0 +1,36 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from django import template +from django.utils.html import escape + +import re + + +register = template.Library() + +@register.filter +def personify(person): + if person.name: + linktext = escape(person.name) + else: + linktext = escape(person.email) + + return '%s' % (escape(person.email), linktext) + diff --git a/patchwork/templatetags/listurl.py b/patchwork/templatetags/listurl.py new file mode 100644 index 0000000..5fe03e4 --- /dev/null +++ b/patchwork/templatetags/listurl.py @@ -0,0 +1,136 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from django import template +from django.utils.html import escape +from django.utils.safestring import mark_safe +from django.utils.encoding import smart_str +from patchwork.filters import filterclasses +from django.conf import settings +from django.core.urlresolvers import reverse, NoReverseMatch +import re + +register = template.Library() + +# params to preserve across views +list_params = [ c.param for c in filterclasses ] + ['order', 'page'] + +class ListURLNode(template.defaulttags.URLNode): + def __init__(self, kwargs): + super(ListURLNode, self).__init__(None, [], {}, False) + self.params = {} + for (k, v) in kwargs.iteritems(): + if k in list_params: + self.params[k] = v + + def render(self, context): + view_name = template.Variable('list_view.view').resolve(context) + kwargs = template.Variable('list_view.view_params') \ + .resolve(context) + + str = None + try: + str = reverse(view_name, args=[], kwargs=kwargs) + except NoReverseMatch: + try: + project_name = settings.SETTINGS_MODULE.split('.')[0] + str = reverse(project_name + '.' + view_name, + args=[], kwargs=kwargs) + except NoReverseMatch: + raise + + if str is None: + return '' + + params = [] + try: + qs_var = template.Variable('list_view.params') + params = dict(qs_var.resolve(context)) + except Exception: + pass + + for (k, v) in self.params.iteritems(): + params[smart_str(k,'ascii')] = v.resolve(context) + + if not params: + return str + + return str + '?' + '&'.join(['%s=%s' % (k, escape(v)) \ + for (k, v) in params.iteritems()]) + +@register.tag +def listurl(parser, token): + bits = token.contents.split(' ', 1) + if len(bits) < 1: + raise TemplateSyntaxError("'%s' takes at least one argument" + " (path to a view)" % bits[0]) + kwargs = {} + if len(bits) > 1: + for arg in bits[1].split(','): + if '=' in arg: + k, v = arg.split('=', 1) + k = k.strip() + kwargs[k] = parser.compile_filter(v) + else: + raise TemplateSyntaxError("'%s' requires name=value params" \ + % bits[0]) + return ListURLNode(kwargs) + +class ListFieldsNode(template.Node): + def __init__(self, params): + self.params = params + + def render(self, context): + self.view_name = template.Variable('list_view.view').resolve(context) + try: + qs_var = template.Variable('list_view.params') + params = dict(qs_var.resolve(context)) + except Exception: + pass + + params.update(self.params) + + if not params: + return '' + + str = '' + for (k, v) in params.iteritems(): + str += '' % \ + (k, escape(v)) + + return mark_safe(str) + +@register.tag +def listfields(parser, token): + bits = token.contents.split(' ', 1) + if len(bits) < 1: + raise TemplateSyntaxError("'%s' takes at least one argument" + " (path to a view)" % bits[0]) + params = {} + if len(bits) > 2: + for arg in bits[2].split(','): + if '=' in arg: + k, v = arg.split('=', 1) + k = k.strip() + params[k] = parser.compile_filter(v) + else: + raise TemplateSyntaxError("'%s' requires name=value params" \ + % bits[0]) + return ListFieldsNode(bits[1], params) + diff --git a/patchwork/templatetags/order.py b/patchwork/templatetags/order.py new file mode 100644 index 0000000..e392f03 --- /dev/null +++ b/patchwork/templatetags/order.py @@ -0,0 +1,66 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +from django import template +import re + +register = template.Library() + +@register.tag(name = 'ifpatcheditable') +def do_patch_is_editable(parser, token): + try: + tag_name, name, cur_order = token.split_contents() + except ValueError: + raise template.TemplateSyntaxError("%r tag requires two arguments" \ + % token.contents.split()[0]) + + end_tag = 'endifpatcheditable' + nodelist_true = parser.parse([end_tag, 'else']) + + token = parser.next_token() + if token.contents == 'else': + nodelist_false = parser.parse([end_tag]) + parser.delete_first_token() + else: + nodelist_false = template.NodeList() + + return EditablePatchNode(patch_var, nodelist_true, nodelist_false) + +class EditablePatchNode(template.Node): + def __init__(self, patch_var, nodelist_true, nodelist_false): + self.nodelist_true = nodelist_true + self.nodelist_false = nodelist_false + self.patch_var = template.Variable(patch_var) + self.user_var = template.Variable('user') + + def render(self, context): + try: + patch = self.patch_var.resolve(context) + user = self.user_var.resolve(context) + except template.VariableDoesNotExist: + return '' + + if not user.is_authenticated(): + return self.nodelist_false.render(context) + + if not patch.is_editable(user): + return self.nodelist_false.render(context) + + return self.nodelist_true.render(context) diff --git a/patchwork/templatetags/patch.py b/patchwork/templatetags/patch.py new file mode 100644 index 0000000..bec0cab --- /dev/null +++ b/patchwork/templatetags/patch.py @@ -0,0 +1,65 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from django import template +import re + +register = template.Library() + +@register.tag(name = 'ifpatcheditable') +def do_patch_is_editable(parser, token): + try: + tag_name, patch_var = token.split_contents() + except ValueError: + raise template.TemplateSyntaxError("%r tag requires one argument" \ + % token.contents.split()[0]) + + end_tag = 'endifpatcheditable' + nodelist_true = parser.parse([end_tag, 'else']) + + token = parser.next_token() + if token.contents == 'else': + nodelist_false = parser.parse([end_tag]) + parser.delete_first_token() + else: + nodelist_false = template.NodeList() + + return EditablePatchNode(patch_var, nodelist_true, nodelist_false) + +class EditablePatchNode(template.Node): + def __init__(self, patch_var, nodelist_true, nodelist_false): + self.nodelist_true = nodelist_true + self.nodelist_false = nodelist_false + self.patch_var = template.Variable(patch_var) + self.user_var = template.Variable('user') + + def render(self, context): + try: + patch = self.patch_var.resolve(context) + user = self.user_var.resolve(context) + except template.VariableDoesNotExist: + return '' + + if not user.is_authenticated(): + return self.nodelist_false.render(context) + + if not patch.is_editable(user): + return self.nodelist_false.render(context) + + return self.nodelist_true.render(context) diff --git a/patchwork/templatetags/person.py b/patchwork/templatetags/person.py new file mode 100644 index 0000000..c337c74 --- /dev/null +++ b/patchwork/templatetags/person.py @@ -0,0 +1,43 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from django import template +from django.utils.html import escape +from django.utils.safestring import mark_safe +from django.core.urlresolvers import reverse +from patchwork.filters import SubmitterFilter +import re + +register = template.Library() + +@register.filter +def personify(person, project): + + if person.name: + linktext = escape(person.name) + else: + linktext = escape(person.email) + + url = reverse('patchwork.views.patch.list', kwargs = {'project_id' : project.linkname}) + str = '%s' % \ + (url, SubmitterFilter.param, escape(person.id), linktext) + + return mark_safe(str) + + diff --git a/patchwork/templatetags/pwurl.py b/patchwork/templatetags/pwurl.py new file mode 100644 index 0000000..98bc1ca --- /dev/null +++ b/patchwork/templatetags/pwurl.py @@ -0,0 +1,76 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from django import template +from django.utils.html import escape +from django.utils.safestring import mark_safe +from patchwork.filters import filterclasses +import re + +register = template.Library() + +# params to preserve across views +list_params = [ c.param for c in filterclasses ] + ['order', 'page'] + +class ListURLNode(template.defaulttags.URLNode): + def __init__(self, *args, **kwargs): + super(ListURLNode, self).__init__(*args, **kwargs) + self.params = {} + for (k, v) in kwargs: + if k in list_params: + self.params[k] = v + + def render(self, context): + self.view_name = template.Variable('list_view.view') + str = super(ListURLNode, self).render(context) + if str == '': + return str + params = [] + try: + qs_var = template.Variable('list_view.params') + params = dict(qs_var.resolve(context)) + except Exception: + pass + + params.update(self.params) + + if not params: + return str + + return str + '?' + '&'.join(['%s=%s' % (k, escape(v)) \ + for (k, v) in params.iteritems()]) + +@register.tag +def listurl(parser, token): + bits = token.contents.split(' ', 1) + if len(bits) < 1: + raise TemplateSyntaxError("'%s' takes at least one argument" + " (path to a view)" % bits[0]) + args = [''] + kwargs = {} + if len(bits) > 1: + for arg in bits[2].split(','): + if '=' in arg: + k, v = arg.split('=', 1) + k = k.strip() + kwargs[k] = parser.compile_filter(v) + else: + args.append(parser.compile_filter(arg)) + return PatchworkURLNode(bits[1], args, kwargs) + diff --git a/patchwork/templatetags/syntax.py b/patchwork/templatetags/syntax.py new file mode 100644 index 0000000..abdbb4d --- /dev/null +++ b/patchwork/templatetags/syntax.py @@ -0,0 +1,75 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from django import template +from django.utils.html import escape +from django.utils.safestring import mark_safe +import re + +register = template.Library() + +def _compile(t): + (r, str) = t + return (re.compile(r, re.M | re.I), str) + +_patch_span_res = map(_compile, [ + ('^(Index:?|diff|\-\-\-|\+\+\+|\*\*\*) .*$', 'p_header'), + ('^\+.*$', 'p_add'), + ('^-.*$', 'p_del'), + ('^!.*$', 'p_mod'), + ]) + +_patch_chunk_re = \ + re.compile('^(@@ \-\d+(?:,\d+)? \+\d+(?:,\d+)? @@)(.*)$', re.M | re.I) + +_comment_span_res = map(_compile, [ + ('^\s*Signed-off-by: .*$', 'signed-off-by'), + ('^\s*Acked-by: .*$', 'acked-by'), + ('^\s*Nacked-by: .*$', 'nacked-by'), + ('^\s*Tested-by: .*$', 'tested-by'), + ('^\s*Reviewed-by: .*$', 'reviewed-by'), + ('^\s*From: .*$', 'from'), + ('^\s*>.*$', 'quote'), + ]) + +_span = '%s' + +@register.filter +def patchsyntax(patch): + content = escape(patch.content) + + for (r,cls) in _patch_span_res: + content = r.sub(lambda x: _span % (cls, x.group(0)), content) + + content = _patch_chunk_re.sub( \ + lambda x: \ + _span % ('p_chunk', x.group(1)) + ' ' + \ + _span % ('p_context', x.group(2)), \ + content) + + return mark_safe(content) + +@register.filter +def commentsyntax(comment): + content = escape(comment.content) + + for (r,cls) in _comment_span_res: + content = r.sub(lambda x: _span % (cls, x.group(0)), content) + + return mark_safe(content) diff --git a/patchwork/tests/__init__.py b/patchwork/tests/__init__.py new file mode 100644 index 0000000..85200bd --- /dev/null +++ b/patchwork/tests/__init__.py @@ -0,0 +1,34 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from patchwork.tests.test_patchparser import * +from patchwork.tests.test_encodings import * +from patchwork.tests.test_bundles import * +from patchwork.tests.test_mboxviews import * +from patchwork.tests.test_updates import * +from patchwork.tests.test_filters import * +from patchwork.tests.test_confirm import * +from patchwork.tests.test_registration import * +from patchwork.tests.test_user import * +from patchwork.tests.test_mail_settings import * +from patchwork.tests.test_notifications import * +from patchwork.tests.test_list import * +from patchwork.tests.test_person import * +from patchwork.tests.test_expiry import * +from patchwork.tests.test_xmlrpc import * diff --git a/patchwork/tests/mail/0001-git-pull-request.mbox b/patchwork/tests/mail/0001-git-pull-request.mbox new file mode 100644 index 0000000..0dbedbe --- /dev/null +++ b/patchwork/tests/mail/0001-git-pull-request.mbox @@ -0,0 +1,348 @@ +From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010 +Return-Path: +X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on bilbo.ozlabs.org +X-Spam-Level: +X-Spam-Status: No, score=0.0 required=3.0 tests=none autolearn=disabled + version=3.3.1 +X-Original-To: jk@ozlabs.org +Delivered-To: jk@ozlabs.org +Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) + by ozlabs.org (Postfix) with ESMTP id ED4B3100937 + for ; Fri, 22 Oct 2010 14:51:54 +1100 (EST) +Received: by ozlabs.org (Postfix) + id BF799B70CB; Fri, 22 Oct 2010 14:51:50 +1100 (EST) +Delivered-To: linuxppc-dev@ozlabs.org +Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) + (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) + (Client did not present a certificate) + by ozlabs.org (Postfix) with ESMTPS id 94629B7043 + for ; Fri, 22 Oct 2010 14:51:49 +1100 (EST) +Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1]) + by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o9M3p3SP018234; + Thu, 21 Oct 2010 22:51:04 -0500 +Subject: [git pull] Please pull powerpc.git next branch +From: Benjamin Herrenschmidt +To: Linus Torvalds +Date: Fri, 22 Oct 2010 14:51:02 +1100 +Message-ID: <1287719462.2198.37.camel@pasglop> +Mime-Version: 1.0 +X-Mailer: Evolution 2.30.3 +Cc: linuxppc-dev list , + Andrew Morton , + Linux Kernel list +X-BeenThere: linuxppc-dev@lists.ozlabs.org +X-Mailman-Version: 2.1.13 +Precedence: list +List-Id: Linux on PowerPC Developers Mail List +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Content-Type: text/plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Sender: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org +Errors-To: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org +X-UID: 11446 +X-Length: 16781 +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Hi Linus ! + +Here's powerpc's batch for this merge window. Mostly bits and pieces, +such as Anton doing some performance tuning left and right, and the +usual churn. One hilight is the support for the new Freescale e5500 core +(64-bit BookE). Another one is that we now wire up the whole lot of +socket calls as direct syscalls in addition to the old style indirect +method. + +Cheers, +Ben. + +The following changes since commit e10117d36ef758da0690c95ecffc09d5dd7da479: + Linus Torvalds (1): + Merge branch 'upstream-linus' of git://git.kernel.org/.../jgarzik/libata-dev + +are available in the git repository at: + + git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git next + +Andreas Schwab (1): + powerpc: Remove fpscr use from [kvm_]cvt_{fd,df} + +Anton Blanchard (5): + powerpc: Optimise 64bit csum_partial + powerpc: Optimise 64bit csum_partial_copy_generic and add csum_and_copy_from_user + powerpc: Add 64bit csum_and_copy_to_user + powerpc: Feature nop out reservation clear when stcx checks address + powerpc: Check end of stack canary at oops time + +Arnd Bergmann (1): + powerpc/spufs: Use llseek in all file operations + +Benjamin Herrenschmidt (4): + powerpc/dma: Add optional platform override of dma_set_mask() + powerpc/dart_iommu: Support for 64-bit iommu bypass window on PCIe + Merge remote branch 'kumar/merge' into next + Merge remote branch 'jwb/next' into next + +Denis Kirjanov (1): + powerpc: Use is_32bit_task() helper to test 32-bit binary + +Harninder Rai (1): + powerpc/85xx: add cache-sram support + +Ian Munsie (1): + powerpc: Wire up direct socket system calls + +Ilya Yanok (1): + powerpc/mpc83xx: Support for MPC8308 P1M board + +Joe Perches (2): + powerpc: Use static const char arrays + powerpc: Remove pr_ uses of KERN_ + +Josh Boyer (1): + powerpc/44x: Update ppc44x_defconfig + +Julia Lawall (7): + powerpc/via-pmu-led.c: Add of_node_put to avoid memory leak + powerpc/maple: Add of_node_put to avoid memory leak + powerpc/powermac/pfunc_core.c: Add of_node_put to avoid memory leak + powerpc/cell: Add of_node_put to avoid memory leak + powerpc/chrp/nvram.c: Add of_node_put to avoid memory leak + powerpc/irq.c: Add of_node_put to avoid memory leak + i2c/i2c-pasemi.c: Fix unsigned return type + +Kumar Gala (11): + powerpc/ppc64e: Fix link problem when building ppc64e_defconfig + powerpc/fsl-pci: Fix MSI support on 83xx platforms + powerpc/mpc8xxx_gpio: Add support for 'qoriq-gpio' controllers + powerpc/fsl-booke: Add PCI device ids for P2040/P3041/P5010/P5020 QoirQ chips + powerpc/fsl-booke: Add p3041 DS board support + powerpc: Fix compile error with paca code on ppc64e + powerpc/fsl-booke: Add support for FSL 64-bit e5500 core + powerpc/fsl-booke: Add support for FSL Arch v1.0 MMU in setup_page_sizes + powerpc/fsl-booke64: Use TLB CAMs to cover linear mapping on FSL 64-bit chips + powerpc/fsl-booke: Add p5020 DS board support + powerpc/fsl-booke: Add e55xx (64-bit) smp defconfig + +Matthew McClintock (7): + powerpc/mm: Assume first cpu is boot_cpuid not 0 + powerpc/kexec: make masking/disabling interrupts generic + powerpc/85xx: Remove call to mpic_teardown_this_cpu in kexec + powerpc/85xx: Minor fixups for kexec on 85xx + powerpc/85xx: flush dcache before resetting cores + powerpc/fsl_soc: Search all global-utilities nodes for rstccr + powerpc/fsl_booke: Add support to boot from core other than 0 + +Michael Neuling (1): + powerpc: Move arch_sd_sibling_asym_packing() to smp.c + +Nathan Fontenot (3): + powerpc/pseries: Export device tree updating routines + powerpc/pseries: Export rtas_ibm_suspend_me() + powerpc/pseries: Partition migration in the kernel + +Nishanth Aravamudan (8): + powerpc/pci: Fix return type of BUID_{HI,LO} macros + powerpc/dma: Fix dma_iommu_dma_supported compare + powerpc/dma: Fix check for direct DMA support + powerpc/vio: Use put_device() on device_register failure + powerpc/viobus: Free TCE table on device release + powerpc/pseries: Use kmemdup + powerpc/pci: Cleanup device dma setup code + powerpc/pseries/xics: Use cpu_possible_mask rather than cpu_all_mask + +Paul Gortmaker (1): + powerpc: Fix invalid page flags in create TLB CAM path for PTE_64BIT + +Paul Mackerras (5): + powerpc: Abstract indexing of lppaca structs + powerpc: Dynamically allocate most lppaca structs + powerpc: Account time using timebase rather than PURR + powerpc/pseries: Re-enable dispatch trace log userspace interface + powerpc/perf: Fix sampling enable for PPC970 + +Scott Wood (1): + oprofile/fsl emb: Don't set MSR[PMM] until after clearing the interrupt. + +Sean MacLennan (2): + powerpc: Fix incorrect .stabs entry for copy_32.S + powerpc: mtmsrd not defined + +Shaohui Xie (1): + fsl_rio: Add comments for sRIO registers. + +Stephen Rothwell (1): + powerpc: define a compat_sys_recv cond_syscall + +Timur Tabi (5): + powerpc: export ppc_proc_freq and ppc_tb_freq as GPL symbols + powerpc/watchdog: Allow the Book-E driver to be compiled as a module + powerpc/p1022: Add probing for individual DMA channels + powerpc/85xx: add ngPIXIS FPGA device tree node to the P1022DS board + powerpc/watchdog: Make default timeout for Book-E watchdog a Kconfig option + +Tirumala Marri (1): + powerpc/44x: Add support for the AMCC APM821xx SoC + +matt mooney (1): + powerpc/Makefiles: Change to new flag variables + + arch/powerpc/boot/addnote.c | 4 +- + arch/powerpc/boot/dts/bluestone.dts | 254 +++++++++++++ + arch/powerpc/boot/dts/mpc8308_p1m.dts | 332 ++++++++++++++++ + arch/powerpc/boot/dts/p1022ds.dts | 11 + + arch/powerpc/configs/44x/bluestone_defconfig | 68 ++++ + arch/powerpc/configs/e55xx_smp_defconfig | 84 ++++ + arch/powerpc/configs/ppc44x_defconfig | 9 +- + arch/powerpc/configs/ppc64e_defconfig | 4 +- + arch/powerpc/include/asm/checksum.h | 10 + + arch/powerpc/include/asm/compat.h | 4 +- + arch/powerpc/include/asm/cputable.h | 14 +- + arch/powerpc/include/asm/dma-mapping.h | 14 +- + arch/powerpc/include/asm/elf.h | 2 +- + arch/powerpc/include/asm/exception-64s.h | 3 +- + arch/powerpc/include/asm/fsl_85xx_cache_sram.h | 48 +++ + arch/powerpc/include/asm/kexec.h | 1 + + arch/powerpc/include/asm/kvm_fpu.h | 4 +- + arch/powerpc/include/asm/lppaca.h | 29 ++ + arch/powerpc/include/asm/machdep.h | 3 + + arch/powerpc/include/asm/mmu-book3e.h | 15 + + arch/powerpc/include/asm/paca.h | 10 +- + arch/powerpc/include/asm/page_64.h | 4 +- + arch/powerpc/include/asm/ppc-pci.h | 4 +- + arch/powerpc/include/asm/ppc_asm.h | 50 ++- + arch/powerpc/include/asm/processor.h | 4 +- + arch/powerpc/include/asm/pte-common.h | 7 + + arch/powerpc/include/asm/rtas.h | 1 + + arch/powerpc/include/asm/systbl.h | 19 + + arch/powerpc/include/asm/system.h | 4 +- + arch/powerpc/include/asm/time.h | 5 - + arch/powerpc/include/asm/unistd.h | 21 +- + arch/powerpc/kernel/Makefile | 4 +- + arch/powerpc/kernel/align.c | 4 +- + arch/powerpc/kernel/asm-offsets.c | 12 +- + arch/powerpc/kernel/cpu_setup_44x.S | 1 + + arch/powerpc/kernel/cpu_setup_fsl_booke.S | 15 + + arch/powerpc/kernel/cputable.c | 43 ++- + arch/powerpc/kernel/crash.c | 13 +- + arch/powerpc/kernel/dma-iommu.c | 21 +- + arch/powerpc/kernel/dma.c | 20 +- + arch/powerpc/kernel/entry_64.S | 40 ++ + arch/powerpc/kernel/fpu.S | 10 - + arch/powerpc/kernel/head_fsl_booke.S | 10 +- + arch/powerpc/kernel/irq.c | 6 +- + arch/powerpc/kernel/lparcfg.c | 14 +- + arch/powerpc/kernel/machine_kexec.c | 24 ++ + arch/powerpc/kernel/machine_kexec_32.c | 4 + + arch/powerpc/kernel/paca.c | 70 ++++- + arch/powerpc/kernel/pci-common.c | 4 +- + arch/powerpc/kernel/ppc970-pmu.c | 2 + + arch/powerpc/kernel/process.c | 12 - + arch/powerpc/kernel/ptrace.c | 2 +- + arch/powerpc/kernel/rtas.c | 4 +- + arch/powerpc/kernel/setup_32.c | 2 +- + arch/powerpc/kernel/smp.c | 14 +- + arch/powerpc/kernel/time.c | 275 +++++++------- + arch/powerpc/kernel/traps.c | 5 + + arch/powerpc/kernel/vdso.c | 6 +- + arch/powerpc/kernel/vdso32/Makefile | 6 +- + arch/powerpc/kernel/vdso64/Makefile | 6 +- + arch/powerpc/kernel/vio.c | 10 +- + arch/powerpc/kvm/Makefile | 2 +- + arch/powerpc/kvm/book3s_paired_singles.c | 44 +-- + arch/powerpc/kvm/emulate.c | 4 +- + arch/powerpc/kvm/fpu.S | 8 - + arch/powerpc/lib/Makefile | 7 +- + arch/powerpc/lib/checksum_64.S | 482 +++++++++++++++++------- + arch/powerpc/lib/checksum_wrappers_64.c | 102 +++++ + arch/powerpc/lib/copy_32.S | 2 +- + arch/powerpc/lib/ldstfp.S | 36 +- + arch/powerpc/lib/locks.c | 4 +- + arch/powerpc/lib/sstep.c | 8 + + arch/powerpc/math-emu/Makefile | 2 +- + arch/powerpc/mm/Makefile | 6 +- + arch/powerpc/mm/fault.c | 6 + + arch/powerpc/mm/fsl_booke_mmu.c | 15 +- + arch/powerpc/mm/mmu_context_nohash.c | 6 +- + arch/powerpc/mm/mmu_decl.h | 5 +- + arch/powerpc/mm/tlb_nohash.c | 56 +++- + arch/powerpc/mm/tlb_nohash_low.S | 2 +- + arch/powerpc/oprofile/Makefile | 4 +- + arch/powerpc/oprofile/backtrace.c | 2 +- + arch/powerpc/oprofile/op_model_fsl_emb.c | 15 +- + arch/powerpc/platforms/44x/Kconfig | 16 + + arch/powerpc/platforms/44x/ppc44x_simple.c | 1 + + arch/powerpc/platforms/83xx/Kconfig | 4 +- + arch/powerpc/platforms/83xx/mpc830x_rdb.c | 3 +- + arch/powerpc/platforms/85xx/Kconfig | 28 ++- + arch/powerpc/platforms/85xx/Makefile | 2 + + arch/powerpc/platforms/85xx/p1022_ds.c | 2 + + arch/powerpc/platforms/85xx/p3041_ds.c | 64 ++++ + arch/powerpc/platforms/85xx/p5020_ds.c | 69 ++++ + arch/powerpc/platforms/85xx/smp.c | 83 ++++- + arch/powerpc/platforms/Kconfig.cputype | 8 +- + arch/powerpc/platforms/cell/ras.c | 4 +- + arch/powerpc/platforms/cell/spider-pic.c | 4 +- + arch/powerpc/platforms/cell/spufs/file.c | 18 + + arch/powerpc/platforms/chrp/nvram.c | 4 +- + arch/powerpc/platforms/iseries/Makefile | 2 +- + arch/powerpc/platforms/iseries/dt.c | 4 +- + arch/powerpc/platforms/iseries/smp.c | 2 +- + arch/powerpc/platforms/maple/setup.c | 1 + + arch/powerpc/platforms/powermac/pfunc_core.c | 9 +- + arch/powerpc/platforms/pseries/Makefile | 13 +- + arch/powerpc/platforms/pseries/dlpar.c | 7 +- + arch/powerpc/platforms/pseries/dtl.c | 224 +++++++++--- + arch/powerpc/platforms/pseries/lpar.c | 25 ++- + arch/powerpc/platforms/pseries/mobility.c | 362 ++++++++++++++++++ + arch/powerpc/platforms/pseries/pseries.h | 9 + + arch/powerpc/platforms/pseries/setup.c | 52 +++ + arch/powerpc/platforms/pseries/xics.c | 2 +- + arch/powerpc/sysdev/Makefile | 5 +- + arch/powerpc/sysdev/dart_iommu.c | 74 ++++- + arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h | 101 +++++ + arch/powerpc/sysdev/fsl_85xx_cache_sram.c | 159 ++++++++ + arch/powerpc/sysdev/fsl_85xx_l2ctlr.c | 231 +++++++++++ + arch/powerpc/sysdev/fsl_msi.c | 9 +- + arch/powerpc/sysdev/fsl_pci.c | 60 +++- + arch/powerpc/sysdev/fsl_pci.h | 1 + + arch/powerpc/sysdev/fsl_rio.c | 65 ++-- + arch/powerpc/sysdev/fsl_soc.c | 20 +- + arch/powerpc/sysdev/mpc8xxx_gpio.c | 3 + + arch/powerpc/sysdev/pmi.c | 2 +- + arch/powerpc/xmon/Makefile | 4 +- + drivers/i2c/busses/i2c-pasemi.c | 2 +- + drivers/macintosh/via-pmu-led.c | 4 +- + drivers/watchdog/Kconfig | 22 +- + drivers/watchdog/booke_wdt.c | 47 ++- + include/linux/pci_ids.h | 8 + + kernel/sys_ni.c | 1 + + 130 files changed, 3676 insertions(+), 683 deletions(-) + create mode 100644 arch/powerpc/boot/dts/bluestone.dts + create mode 100644 arch/powerpc/boot/dts/mpc8308_p1m.dts + create mode 100644 arch/powerpc/configs/44x/bluestone_defconfig + create mode 100644 arch/powerpc/configs/e55xx_smp_defconfig + create mode 100644 arch/powerpc/include/asm/fsl_85xx_cache_sram.h + create mode 100644 arch/powerpc/lib/checksum_wrappers_64.c + create mode 100644 arch/powerpc/platforms/85xx/p3041_ds.c + create mode 100644 arch/powerpc/platforms/85xx/p5020_ds.c + create mode 100644 arch/powerpc/platforms/pseries/mobility.c + create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h + create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_sram.c + create mode 100644 arch/powerpc/sysdev/fsl_85xx_l2ctlr.c + + +_______________________________________________ +Linuxppc-dev mailing list +Linuxppc-dev@lists.ozlabs.org +https://lists.ozlabs.org/listinfo/linuxppc-dev diff --git a/patchwork/tests/mail/0002-git-pull-request-wrapped.mbox b/patchwork/tests/mail/0002-git-pull-request-wrapped.mbox new file mode 100644 index 0000000..d3ccee1 --- /dev/null +++ b/patchwork/tests/mail/0002-git-pull-request-wrapped.mbox @@ -0,0 +1,349 @@ +From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010 +Return-Path: +X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on bilbo.ozlabs.org +X-Spam-Level: +X-Spam-Status: No, score=0.0 required=3.0 tests=none autolearn=disabled + version=3.3.1 +X-Original-To: jk@ozlabs.org +Delivered-To: jk@ozlabs.org +Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) + by ozlabs.org (Postfix) with ESMTP id ED4B3100937 + for ; Fri, 22 Oct 2010 14:51:54 +1100 (EST) +Received: by ozlabs.org (Postfix) + id BF799B70CB; Fri, 22 Oct 2010 14:51:50 +1100 (EST) +Delivered-To: linuxppc-dev@ozlabs.org +Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) + (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) + (Client did not present a certificate) + by ozlabs.org (Postfix) with ESMTPS id 94629B7043 + for ; Fri, 22 Oct 2010 14:51:49 +1100 (EST) +Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1]) + by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o9M3p3SP018234; + Thu, 21 Oct 2010 22:51:04 -0500 +Subject: [git pull] Please pull powerpc.git next branch +From: Benjamin Herrenschmidt +To: Linus Torvalds +Date: Fri, 22 Oct 2010 14:51:02 +1100 +Message-ID: <1287719462.2198.37.camel@pasglop> +Mime-Version: 1.0 +X-Mailer: Evolution 2.30.3 +Cc: linuxppc-dev list , + Andrew Morton , + Linux Kernel list +X-BeenThere: linuxppc-dev@lists.ozlabs.org +X-Mailman-Version: 2.1.13 +Precedence: list +List-Id: Linux on PowerPC Developers Mail List +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Content-Type: text/plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Sender: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org +Errors-To: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org +X-UID: 11446 +X-Length: 16781 +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Hi Linus ! + +Here's powerpc's batch for this merge window. Mostly bits and pieces, +such as Anton doing some performance tuning left and right, and the +usual churn. One hilight is the support for the new Freescale e5500 core +(64-bit BookE). Another one is that we now wire up the whole lot of +socket calls as direct syscalls in addition to the old style indirect +method. + +Cheers, +Ben. + +The following changes since commit +e10117d36ef758da0690c95ecffc09d5dd7da479: + Linus Torvalds (1): + Merge branch 'upstream-linus' of git://git.kernel.org/.../jgarzik/libata-dev + +are available in the git repository at: + + git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git next + +Andreas Schwab (1): + powerpc: Remove fpscr use from [kvm_]cvt_{fd,df} + +Anton Blanchard (5): + powerpc: Optimise 64bit csum_partial + powerpc: Optimise 64bit csum_partial_copy_generic and add csum_and_copy_from_user + powerpc: Add 64bit csum_and_copy_to_user + powerpc: Feature nop out reservation clear when stcx checks address + powerpc: Check end of stack canary at oops time + +Arnd Bergmann (1): + powerpc/spufs: Use llseek in all file operations + +Benjamin Herrenschmidt (4): + powerpc/dma: Add optional platform override of dma_set_mask() + powerpc/dart_iommu: Support for 64-bit iommu bypass window on PCIe + Merge remote branch 'kumar/merge' into next + Merge remote branch 'jwb/next' into next + +Denis Kirjanov (1): + powerpc: Use is_32bit_task() helper to test 32-bit binary + +Harninder Rai (1): + powerpc/85xx: add cache-sram support + +Ian Munsie (1): + powerpc: Wire up direct socket system calls + +Ilya Yanok (1): + powerpc/mpc83xx: Support for MPC8308 P1M board + +Joe Perches (2): + powerpc: Use static const char arrays + powerpc: Remove pr_ uses of KERN_ + +Josh Boyer (1): + powerpc/44x: Update ppc44x_defconfig + +Julia Lawall (7): + powerpc/via-pmu-led.c: Add of_node_put to avoid memory leak + powerpc/maple: Add of_node_put to avoid memory leak + powerpc/powermac/pfunc_core.c: Add of_node_put to avoid memory leak + powerpc/cell: Add of_node_put to avoid memory leak + powerpc/chrp/nvram.c: Add of_node_put to avoid memory leak + powerpc/irq.c: Add of_node_put to avoid memory leak + i2c/i2c-pasemi.c: Fix unsigned return type + +Kumar Gala (11): + powerpc/ppc64e: Fix link problem when building ppc64e_defconfig + powerpc/fsl-pci: Fix MSI support on 83xx platforms + powerpc/mpc8xxx_gpio: Add support for 'qoriq-gpio' controllers + powerpc/fsl-booke: Add PCI device ids for P2040/P3041/P5010/P5020 QoirQ chips + powerpc/fsl-booke: Add p3041 DS board support + powerpc: Fix compile error with paca code on ppc64e + powerpc/fsl-booke: Add support for FSL 64-bit e5500 core + powerpc/fsl-booke: Add support for FSL Arch v1.0 MMU in setup_page_sizes + powerpc/fsl-booke64: Use TLB CAMs to cover linear mapping on FSL 64-bit chips + powerpc/fsl-booke: Add p5020 DS board support + powerpc/fsl-booke: Add e55xx (64-bit) smp defconfig + +Matthew McClintock (7): + powerpc/mm: Assume first cpu is boot_cpuid not 0 + powerpc/kexec: make masking/disabling interrupts generic + powerpc/85xx: Remove call to mpic_teardown_this_cpu in kexec + powerpc/85xx: Minor fixups for kexec on 85xx + powerpc/85xx: flush dcache before resetting cores + powerpc/fsl_soc: Search all global-utilities nodes for rstccr + powerpc/fsl_booke: Add support to boot from core other than 0 + +Michael Neuling (1): + powerpc: Move arch_sd_sibling_asym_packing() to smp.c + +Nathan Fontenot (3): + powerpc/pseries: Export device tree updating routines + powerpc/pseries: Export rtas_ibm_suspend_me() + powerpc/pseries: Partition migration in the kernel + +Nishanth Aravamudan (8): + powerpc/pci: Fix return type of BUID_{HI,LO} macros + powerpc/dma: Fix dma_iommu_dma_supported compare + powerpc/dma: Fix check for direct DMA support + powerpc/vio: Use put_device() on device_register failure + powerpc/viobus: Free TCE table on device release + powerpc/pseries: Use kmemdup + powerpc/pci: Cleanup device dma setup code + powerpc/pseries/xics: Use cpu_possible_mask rather than cpu_all_mask + +Paul Gortmaker (1): + powerpc: Fix invalid page flags in create TLB CAM path for PTE_64BIT + +Paul Mackerras (5): + powerpc: Abstract indexing of lppaca structs + powerpc: Dynamically allocate most lppaca structs + powerpc: Account time using timebase rather than PURR + powerpc/pseries: Re-enable dispatch trace log userspace interface + powerpc/perf: Fix sampling enable for PPC970 + +Scott Wood (1): + oprofile/fsl emb: Don't set MSR[PMM] until after clearing the interrupt. + +Sean MacLennan (2): + powerpc: Fix incorrect .stabs entry for copy_32.S + powerpc: mtmsrd not defined + +Shaohui Xie (1): + fsl_rio: Add comments for sRIO registers. + +Stephen Rothwell (1): + powerpc: define a compat_sys_recv cond_syscall + +Timur Tabi (5): + powerpc: export ppc_proc_freq and ppc_tb_freq as GPL symbols + powerpc/watchdog: Allow the Book-E driver to be compiled as a module + powerpc/p1022: Add probing for individual DMA channels + powerpc/85xx: add ngPIXIS FPGA device tree node to the P1022DS board + powerpc/watchdog: Make default timeout for Book-E watchdog a Kconfig option + +Tirumala Marri (1): + powerpc/44x: Add support for the AMCC APM821xx SoC + +matt mooney (1): + powerpc/Makefiles: Change to new flag variables + + arch/powerpc/boot/addnote.c | 4 +- + arch/powerpc/boot/dts/bluestone.dts | 254 +++++++++++++ + arch/powerpc/boot/dts/mpc8308_p1m.dts | 332 ++++++++++++++++ + arch/powerpc/boot/dts/p1022ds.dts | 11 + + arch/powerpc/configs/44x/bluestone_defconfig | 68 ++++ + arch/powerpc/configs/e55xx_smp_defconfig | 84 ++++ + arch/powerpc/configs/ppc44x_defconfig | 9 +- + arch/powerpc/configs/ppc64e_defconfig | 4 +- + arch/powerpc/include/asm/checksum.h | 10 + + arch/powerpc/include/asm/compat.h | 4 +- + arch/powerpc/include/asm/cputable.h | 14 +- + arch/powerpc/include/asm/dma-mapping.h | 14 +- + arch/powerpc/include/asm/elf.h | 2 +- + arch/powerpc/include/asm/exception-64s.h | 3 +- + arch/powerpc/include/asm/fsl_85xx_cache_sram.h | 48 +++ + arch/powerpc/include/asm/kexec.h | 1 + + arch/powerpc/include/asm/kvm_fpu.h | 4 +- + arch/powerpc/include/asm/lppaca.h | 29 ++ + arch/powerpc/include/asm/machdep.h | 3 + + arch/powerpc/include/asm/mmu-book3e.h | 15 + + arch/powerpc/include/asm/paca.h | 10 +- + arch/powerpc/include/asm/page_64.h | 4 +- + arch/powerpc/include/asm/ppc-pci.h | 4 +- + arch/powerpc/include/asm/ppc_asm.h | 50 ++- + arch/powerpc/include/asm/processor.h | 4 +- + arch/powerpc/include/asm/pte-common.h | 7 + + arch/powerpc/include/asm/rtas.h | 1 + + arch/powerpc/include/asm/systbl.h | 19 + + arch/powerpc/include/asm/system.h | 4 +- + arch/powerpc/include/asm/time.h | 5 - + arch/powerpc/include/asm/unistd.h | 21 +- + arch/powerpc/kernel/Makefile | 4 +- + arch/powerpc/kernel/align.c | 4 +- + arch/powerpc/kernel/asm-offsets.c | 12 +- + arch/powerpc/kernel/cpu_setup_44x.S | 1 + + arch/powerpc/kernel/cpu_setup_fsl_booke.S | 15 + + arch/powerpc/kernel/cputable.c | 43 ++- + arch/powerpc/kernel/crash.c | 13 +- + arch/powerpc/kernel/dma-iommu.c | 21 +- + arch/powerpc/kernel/dma.c | 20 +- + arch/powerpc/kernel/entry_64.S | 40 ++ + arch/powerpc/kernel/fpu.S | 10 - + arch/powerpc/kernel/head_fsl_booke.S | 10 +- + arch/powerpc/kernel/irq.c | 6 +- + arch/powerpc/kernel/lparcfg.c | 14 +- + arch/powerpc/kernel/machine_kexec.c | 24 ++ + arch/powerpc/kernel/machine_kexec_32.c | 4 + + arch/powerpc/kernel/paca.c | 70 ++++- + arch/powerpc/kernel/pci-common.c | 4 +- + arch/powerpc/kernel/ppc970-pmu.c | 2 + + arch/powerpc/kernel/process.c | 12 - + arch/powerpc/kernel/ptrace.c | 2 +- + arch/powerpc/kernel/rtas.c | 4 +- + arch/powerpc/kernel/setup_32.c | 2 +- + arch/powerpc/kernel/smp.c | 14 +- + arch/powerpc/kernel/time.c | 275 +++++++------- + arch/powerpc/kernel/traps.c | 5 + + arch/powerpc/kernel/vdso.c | 6 +- + arch/powerpc/kernel/vdso32/Makefile | 6 +- + arch/powerpc/kernel/vdso64/Makefile | 6 +- + arch/powerpc/kernel/vio.c | 10 +- + arch/powerpc/kvm/Makefile | 2 +- + arch/powerpc/kvm/book3s_paired_singles.c | 44 +-- + arch/powerpc/kvm/emulate.c | 4 +- + arch/powerpc/kvm/fpu.S | 8 - + arch/powerpc/lib/Makefile | 7 +- + arch/powerpc/lib/checksum_64.S | 482 +++++++++++++++++------- + arch/powerpc/lib/checksum_wrappers_64.c | 102 +++++ + arch/powerpc/lib/copy_32.S | 2 +- + arch/powerpc/lib/ldstfp.S | 36 +- + arch/powerpc/lib/locks.c | 4 +- + arch/powerpc/lib/sstep.c | 8 + + arch/powerpc/math-emu/Makefile | 2 +- + arch/powerpc/mm/Makefile | 6 +- + arch/powerpc/mm/fault.c | 6 + + arch/powerpc/mm/fsl_booke_mmu.c | 15 +- + arch/powerpc/mm/mmu_context_nohash.c | 6 +- + arch/powerpc/mm/mmu_decl.h | 5 +- + arch/powerpc/mm/tlb_nohash.c | 56 +++- + arch/powerpc/mm/tlb_nohash_low.S | 2 +- + arch/powerpc/oprofile/Makefile | 4 +- + arch/powerpc/oprofile/backtrace.c | 2 +- + arch/powerpc/oprofile/op_model_fsl_emb.c | 15 +- + arch/powerpc/platforms/44x/Kconfig | 16 + + arch/powerpc/platforms/44x/ppc44x_simple.c | 1 + + arch/powerpc/platforms/83xx/Kconfig | 4 +- + arch/powerpc/platforms/83xx/mpc830x_rdb.c | 3 +- + arch/powerpc/platforms/85xx/Kconfig | 28 ++- + arch/powerpc/platforms/85xx/Makefile | 2 + + arch/powerpc/platforms/85xx/p1022_ds.c | 2 + + arch/powerpc/platforms/85xx/p3041_ds.c | 64 ++++ + arch/powerpc/platforms/85xx/p5020_ds.c | 69 ++++ + arch/powerpc/platforms/85xx/smp.c | 83 ++++- + arch/powerpc/platforms/Kconfig.cputype | 8 +- + arch/powerpc/platforms/cell/ras.c | 4 +- + arch/powerpc/platforms/cell/spider-pic.c | 4 +- + arch/powerpc/platforms/cell/spufs/file.c | 18 + + arch/powerpc/platforms/chrp/nvram.c | 4 +- + arch/powerpc/platforms/iseries/Makefile | 2 +- + arch/powerpc/platforms/iseries/dt.c | 4 +- + arch/powerpc/platforms/iseries/smp.c | 2 +- + arch/powerpc/platforms/maple/setup.c | 1 + + arch/powerpc/platforms/powermac/pfunc_core.c | 9 +- + arch/powerpc/platforms/pseries/Makefile | 13 +- + arch/powerpc/platforms/pseries/dlpar.c | 7 +- + arch/powerpc/platforms/pseries/dtl.c | 224 +++++++++--- + arch/powerpc/platforms/pseries/lpar.c | 25 ++- + arch/powerpc/platforms/pseries/mobility.c | 362 ++++++++++++++++++ + arch/powerpc/platforms/pseries/pseries.h | 9 + + arch/powerpc/platforms/pseries/setup.c | 52 +++ + arch/powerpc/platforms/pseries/xics.c | 2 +- + arch/powerpc/sysdev/Makefile | 5 +- + arch/powerpc/sysdev/dart_iommu.c | 74 ++++- + arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h | 101 +++++ + arch/powerpc/sysdev/fsl_85xx_cache_sram.c | 159 ++++++++ + arch/powerpc/sysdev/fsl_85xx_l2ctlr.c | 231 +++++++++++ + arch/powerpc/sysdev/fsl_msi.c | 9 +- + arch/powerpc/sysdev/fsl_pci.c | 60 +++- + arch/powerpc/sysdev/fsl_pci.h | 1 + + arch/powerpc/sysdev/fsl_rio.c | 65 ++-- + arch/powerpc/sysdev/fsl_soc.c | 20 +- + arch/powerpc/sysdev/mpc8xxx_gpio.c | 3 + + arch/powerpc/sysdev/pmi.c | 2 +- + arch/powerpc/xmon/Makefile | 4 +- + drivers/i2c/busses/i2c-pasemi.c | 2 +- + drivers/macintosh/via-pmu-led.c | 4 +- + drivers/watchdog/Kconfig | 22 +- + drivers/watchdog/booke_wdt.c | 47 ++- + include/linux/pci_ids.h | 8 + + kernel/sys_ni.c | 1 + + 130 files changed, 3676 insertions(+), 683 deletions(-) + create mode 100644 arch/powerpc/boot/dts/bluestone.dts + create mode 100644 arch/powerpc/boot/dts/mpc8308_p1m.dts + create mode 100644 arch/powerpc/configs/44x/bluestone_defconfig + create mode 100644 arch/powerpc/configs/e55xx_smp_defconfig + create mode 100644 arch/powerpc/include/asm/fsl_85xx_cache_sram.h + create mode 100644 arch/powerpc/lib/checksum_wrappers_64.c + create mode 100644 arch/powerpc/platforms/85xx/p3041_ds.c + create mode 100644 arch/powerpc/platforms/85xx/p5020_ds.c + create mode 100644 arch/powerpc/platforms/pseries/mobility.c + create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h + create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_sram.c + create mode 100644 arch/powerpc/sysdev/fsl_85xx_l2ctlr.c + + +_______________________________________________ +Linuxppc-dev mailing list +Linuxppc-dev@lists.ozlabs.org +https://lists.ozlabs.org/listinfo/linuxppc-dev diff --git a/patchwork/tests/mail/0003-git-pull-request-with-diff.mbox b/patchwork/tests/mail/0003-git-pull-request-with-diff.mbox new file mode 100644 index 0000000..b4d578c --- /dev/null +++ b/patchwork/tests/mail/0003-git-pull-request-with-diff.mbox @@ -0,0 +1,141 @@ +From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010 +Return-Path: +X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on bilbo.ozlabs.org +X-Spam-Level: +X-Spam-Status: No, score=0.0 required=3.0 tests=none autolearn=disabled + version=3.3.1 +X-Original-To: jk@ozlabs.org +Delivered-To: jk@ozlabs.org +Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) + by ozlabs.org (Postfix) with ESMTP id ED4B3100937 + for ; Fri, 22 Oct 2010 14:51:54 +1100 (EST) +Received: by ozlabs.org (Postfix) + id BF799B70CB; Fri, 22 Oct 2010 14:51:50 +1100 (EST) +Delivered-To: linuxppc-dev@ozlabs.org +Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) + (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) + (Client did not present a certificate) + by ozlabs.org (Postfix) with ESMTPS id 94629B7043 + for ; Fri, 22 Oct 2010 14:51:49 +1100 (EST) +Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1]) + by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o9M3p3SP018234; + Thu, 21 Oct 2010 22:51:04 -0500 +Subject: [git pull] Please pull powerpc.git next branch +From: Benjamin Herrenschmidt +To: Linus Torvalds +Date: Fri, 22 Oct 2010 14:51:02 +1100 +Message-ID: <1287719462.2198.37.camel@pasglop> +Mime-Version: 1.0 +X-Mailer: Evolution 2.30.3 +Cc: linuxppc-dev list , + Andrew Morton , + Linux Kernel list +X-BeenThere: linuxppc-dev@lists.ozlabs.org +X-Mailman-Version: 2.1.13 +Precedence: list +List-Id: Linux on PowerPC Developers Mail List +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Content-Type: text/plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Sender: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org +Errors-To: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org +X-UID: 11446 +X-Length: 16781 +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +The following changes since commit e10117d36ef758da0690c95ecffc09d5dd7da479: + Linus Torvalds (1): + Merge branch 'upstream-linus' of git://git.kernel.org/.../jgarzik/libata-dev + +are available in the git repository at: + + git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip.git x86-fixes-for-linus + +------------------> +H. Peter Anvin (1): + x86-32: Make sure the stack is set up before we use it +Matthieu CASTET (1): + x86, nx: Don't force pages RW when setting NX bits + +Suresh Siddha (1): + x86, mtrr: Avoid MTRR reprogramming on BP during boot on UP platforms + + + arch/x86/include/asm/smp.h | 5 +---- + arch/x86/kernel/acpi/sleep.c | 2 +- + arch/x86/kernel/cpu/mtrr/main.c | 10 +++++++++- + arch/x86/kernel/head_32.S | 30 +++++++++++++----------------- + arch/x86/kernel/smpboot.c | 4 ++-- + arch/x86/mm/pageattr.c | 8 -------- + 6 files changed, 26 insertions(+), 33 deletions(-) +diff --git a/arch/x86/include/asm/smp.h b/arch/x86/include/asm/smp.h +index 4c2f63c..1f46951 100644 +--- a/arch/x86/include/asm/smp.h ++++ b/arch/x86/include/asm/smp.h +@@ -40,10 +40,7 @@ DECLARE_EARLY_PER_CPU(u16, x86_cpu_to_apicid); + DECLARE_EARLY_PER_CPU(u16, x86_bios_cpu_apicid); + + /* Static state in head.S used to set up a CPU */ +-extern struct { +- void *sp; +- unsigned short ss; +-} stack_start; ++extern unsigned long stack_start; /* Initial stack pointer address */ + + struct smp_ops { + void (*smp_prepare_boot_cpu)(void); +diff --git a/arch/x86/kernel/acpi/sleep.c b/arch/x86/kernel/acpi/sleep.c +index 69fd72a..4d9ebba 100644 +--- a/arch/x86/kernel/acpi/sleep.c ++++ b/arch/x86/kernel/acpi/sleep.c +@@ -100,7 +100,7 @@ int acpi_save_state_mem(void) + #else /* CONFIG_64BIT */ + header->trampoline_segment = setup_trampoline() >> 4; + #ifdef CONFIG_SMP +- stack_start.sp = temp_stack + sizeof(temp_stack); ++ stack_start = (unsigned long)temp_stack + sizeof(temp_stack); + early_gdt_descr.address = + (unsigned long)get_cpu_gdt_table(smp_processor_id()); + initial_gs = per_cpu_offset(smp_processor_id()); +diff --git a/arch/x86/kernel/cpu/mtrr/main.c b/arch/x86/kernel/cpu/mtrr/main.c +index 01c0f3e..bebabec 100644 +--- a/arch/x86/kernel/cpu/mtrr/main.c ++++ b/arch/x86/kernel/cpu/mtrr/main.c +@@ -793,13 +793,21 @@ void set_mtrr_aps_delayed_init(void) + } + + /* +- * MTRR initialization for all AP's ++ * Delayed MTRR initialization for all AP's + */ + void mtrr_aps_init(void) + { + if (!use_intel()) + return; + ++ /* ++ * Check if someone has requested the delay of AP MTRR initialization, ++ * by doing set_mtrr_aps_delayed_init(), prior to this point. If not, ++ * then we are done. ++ */ ++ if (!mtrr_aps_delayed_init) ++ return; ++ + set_mtrr(~0U, 0, 0, 0); + mtrr_aps_delayed_init = false; + } +_______________________________________________ +Linuxppc-dev mailing list +Linuxppc-dev@lists.ozlabs.org +https://lists.ozlabs.org/listinfo/linuxppc-dev diff --git a/patchwork/tests/mail/0004-git-pull-request-git+ssh.mbox b/patchwork/tests/mail/0004-git-pull-request-git+ssh.mbox new file mode 100644 index 0000000..da96465 --- /dev/null +++ b/patchwork/tests/mail/0004-git-pull-request-git+ssh.mbox @@ -0,0 +1,348 @@ +From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010 +Return-Path: +X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on bilbo.ozlabs.org +X-Spam-Level: +X-Spam-Status: No, score=0.0 required=3.0 tests=none autolearn=disabled + version=3.3.1 +X-Original-To: jk@ozlabs.org +Delivered-To: jk@ozlabs.org +Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) + by ozlabs.org (Postfix) with ESMTP id ED4B3100937 + for ; Fri, 22 Oct 2010 14:51:54 +1100 (EST) +Received: by ozlabs.org (Postfix) + id BF799B70CB; Fri, 22 Oct 2010 14:51:50 +1100 (EST) +Delivered-To: linuxppc-dev@ozlabs.org +Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) + (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) + (Client did not present a certificate) + by ozlabs.org (Postfix) with ESMTPS id 94629B7043 + for ; Fri, 22 Oct 2010 14:51:49 +1100 (EST) +Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1]) + by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o9M3p3SP018234; + Thu, 21 Oct 2010 22:51:04 -0500 +Subject: [git pull] Please pull powerpc.git next branch +From: Benjamin Herrenschmidt +To: Linus Torvalds +Date: Fri, 22 Oct 2010 14:51:02 +1100 +Message-ID: <1287719462.2198.37.camel@pasglop> +Mime-Version: 1.0 +X-Mailer: Evolution 2.30.3 +Cc: linuxppc-dev list , + Andrew Morton , + Linux Kernel list +X-BeenThere: linuxppc-dev@lists.ozlabs.org +X-Mailman-Version: 2.1.13 +Precedence: list +List-Id: Linux on PowerPC Developers Mail List +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Content-Type: text/plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Sender: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org +Errors-To: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org +X-UID: 11446 +X-Length: 16781 +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Hi Linus ! + +Here's powerpc's batch for this merge window. Mostly bits and pieces, +such as Anton doing some performance tuning left and right, and the +usual churn. One hilight is the support for the new Freescale e5500 core +(64-bit BookE). Another one is that we now wire up the whole lot of +socket calls as direct syscalls in addition to the old style indirect +method. + +Cheers, +Ben. + +The following changes since commit e10117d36ef758da0690c95ecffc09d5dd7da479: + Linus Torvalds (1): + Merge branch 'upstream-linus' of git://git.kernel.org/.../jgarzik/libata-dev + +are available in the git repository at: + + git+ssh://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git next + +Andreas Schwab (1): + powerpc: Remove fpscr use from [kvm_]cvt_{fd,df} + +Anton Blanchard (5): + powerpc: Optimise 64bit csum_partial + powerpc: Optimise 64bit csum_partial_copy_generic and add csum_and_copy_from_user + powerpc: Add 64bit csum_and_copy_to_user + powerpc: Feature nop out reservation clear when stcx checks address + powerpc: Check end of stack canary at oops time + +Arnd Bergmann (1): + powerpc/spufs: Use llseek in all file operations + +Benjamin Herrenschmidt (4): + powerpc/dma: Add optional platform override of dma_set_mask() + powerpc/dart_iommu: Support for 64-bit iommu bypass window on PCIe + Merge remote branch 'kumar/merge' into next + Merge remote branch 'jwb/next' into next + +Denis Kirjanov (1): + powerpc: Use is_32bit_task() helper to test 32-bit binary + +Harninder Rai (1): + powerpc/85xx: add cache-sram support + +Ian Munsie (1): + powerpc: Wire up direct socket system calls + +Ilya Yanok (1): + powerpc/mpc83xx: Support for MPC8308 P1M board + +Joe Perches (2): + powerpc: Use static const char arrays + powerpc: Remove pr_ uses of KERN_ + +Josh Boyer (1): + powerpc/44x: Update ppc44x_defconfig + +Julia Lawall (7): + powerpc/via-pmu-led.c: Add of_node_put to avoid memory leak + powerpc/maple: Add of_node_put to avoid memory leak + powerpc/powermac/pfunc_core.c: Add of_node_put to avoid memory leak + powerpc/cell: Add of_node_put to avoid memory leak + powerpc/chrp/nvram.c: Add of_node_put to avoid memory leak + powerpc/irq.c: Add of_node_put to avoid memory leak + i2c/i2c-pasemi.c: Fix unsigned return type + +Kumar Gala (11): + powerpc/ppc64e: Fix link problem when building ppc64e_defconfig + powerpc/fsl-pci: Fix MSI support on 83xx platforms + powerpc/mpc8xxx_gpio: Add support for 'qoriq-gpio' controllers + powerpc/fsl-booke: Add PCI device ids for P2040/P3041/P5010/P5020 QoirQ chips + powerpc/fsl-booke: Add p3041 DS board support + powerpc: Fix compile error with paca code on ppc64e + powerpc/fsl-booke: Add support for FSL 64-bit e5500 core + powerpc/fsl-booke: Add support for FSL Arch v1.0 MMU in setup_page_sizes + powerpc/fsl-booke64: Use TLB CAMs to cover linear mapping on FSL 64-bit chips + powerpc/fsl-booke: Add p5020 DS board support + powerpc/fsl-booke: Add e55xx (64-bit) smp defconfig + +Matthew McClintock (7): + powerpc/mm: Assume first cpu is boot_cpuid not 0 + powerpc/kexec: make masking/disabling interrupts generic + powerpc/85xx: Remove call to mpic_teardown_this_cpu in kexec + powerpc/85xx: Minor fixups for kexec on 85xx + powerpc/85xx: flush dcache before resetting cores + powerpc/fsl_soc: Search all global-utilities nodes for rstccr + powerpc/fsl_booke: Add support to boot from core other than 0 + +Michael Neuling (1): + powerpc: Move arch_sd_sibling_asym_packing() to smp.c + +Nathan Fontenot (3): + powerpc/pseries: Export device tree updating routines + powerpc/pseries: Export rtas_ibm_suspend_me() + powerpc/pseries: Partition migration in the kernel + +Nishanth Aravamudan (8): + powerpc/pci: Fix return type of BUID_{HI,LO} macros + powerpc/dma: Fix dma_iommu_dma_supported compare + powerpc/dma: Fix check for direct DMA support + powerpc/vio: Use put_device() on device_register failure + powerpc/viobus: Free TCE table on device release + powerpc/pseries: Use kmemdup + powerpc/pci: Cleanup device dma setup code + powerpc/pseries/xics: Use cpu_possible_mask rather than cpu_all_mask + +Paul Gortmaker (1): + powerpc: Fix invalid page flags in create TLB CAM path for PTE_64BIT + +Paul Mackerras (5): + powerpc: Abstract indexing of lppaca structs + powerpc: Dynamically allocate most lppaca structs + powerpc: Account time using timebase rather than PURR + powerpc/pseries: Re-enable dispatch trace log userspace interface + powerpc/perf: Fix sampling enable for PPC970 + +Scott Wood (1): + oprofile/fsl emb: Don't set MSR[PMM] until after clearing the interrupt. + +Sean MacLennan (2): + powerpc: Fix incorrect .stabs entry for copy_32.S + powerpc: mtmsrd not defined + +Shaohui Xie (1): + fsl_rio: Add comments for sRIO registers. + +Stephen Rothwell (1): + powerpc: define a compat_sys_recv cond_syscall + +Timur Tabi (5): + powerpc: export ppc_proc_freq and ppc_tb_freq as GPL symbols + powerpc/watchdog: Allow the Book-E driver to be compiled as a module + powerpc/p1022: Add probing for individual DMA channels + powerpc/85xx: add ngPIXIS FPGA device tree node to the P1022DS board + powerpc/watchdog: Make default timeout for Book-E watchdog a Kconfig option + +Tirumala Marri (1): + powerpc/44x: Add support for the AMCC APM821xx SoC + +matt mooney (1): + powerpc/Makefiles: Change to new flag variables + + arch/powerpc/boot/addnote.c | 4 +- + arch/powerpc/boot/dts/bluestone.dts | 254 +++++++++++++ + arch/powerpc/boot/dts/mpc8308_p1m.dts | 332 ++++++++++++++++ + arch/powerpc/boot/dts/p1022ds.dts | 11 + + arch/powerpc/configs/44x/bluestone_defconfig | 68 ++++ + arch/powerpc/configs/e55xx_smp_defconfig | 84 ++++ + arch/powerpc/configs/ppc44x_defconfig | 9 +- + arch/powerpc/configs/ppc64e_defconfig | 4 +- + arch/powerpc/include/asm/checksum.h | 10 + + arch/powerpc/include/asm/compat.h | 4 +- + arch/powerpc/include/asm/cputable.h | 14 +- + arch/powerpc/include/asm/dma-mapping.h | 14 +- + arch/powerpc/include/asm/elf.h | 2 +- + arch/powerpc/include/asm/exception-64s.h | 3 +- + arch/powerpc/include/asm/fsl_85xx_cache_sram.h | 48 +++ + arch/powerpc/include/asm/kexec.h | 1 + + arch/powerpc/include/asm/kvm_fpu.h | 4 +- + arch/powerpc/include/asm/lppaca.h | 29 ++ + arch/powerpc/include/asm/machdep.h | 3 + + arch/powerpc/include/asm/mmu-book3e.h | 15 + + arch/powerpc/include/asm/paca.h | 10 +- + arch/powerpc/include/asm/page_64.h | 4 +- + arch/powerpc/include/asm/ppc-pci.h | 4 +- + arch/powerpc/include/asm/ppc_asm.h | 50 ++- + arch/powerpc/include/asm/processor.h | 4 +- + arch/powerpc/include/asm/pte-common.h | 7 + + arch/powerpc/include/asm/rtas.h | 1 + + arch/powerpc/include/asm/systbl.h | 19 + + arch/powerpc/include/asm/system.h | 4 +- + arch/powerpc/include/asm/time.h | 5 - + arch/powerpc/include/asm/unistd.h | 21 +- + arch/powerpc/kernel/Makefile | 4 +- + arch/powerpc/kernel/align.c | 4 +- + arch/powerpc/kernel/asm-offsets.c | 12 +- + arch/powerpc/kernel/cpu_setup_44x.S | 1 + + arch/powerpc/kernel/cpu_setup_fsl_booke.S | 15 + + arch/powerpc/kernel/cputable.c | 43 ++- + arch/powerpc/kernel/crash.c | 13 +- + arch/powerpc/kernel/dma-iommu.c | 21 +- + arch/powerpc/kernel/dma.c | 20 +- + arch/powerpc/kernel/entry_64.S | 40 ++ + arch/powerpc/kernel/fpu.S | 10 - + arch/powerpc/kernel/head_fsl_booke.S | 10 +- + arch/powerpc/kernel/irq.c | 6 +- + arch/powerpc/kernel/lparcfg.c | 14 +- + arch/powerpc/kernel/machine_kexec.c | 24 ++ + arch/powerpc/kernel/machine_kexec_32.c | 4 + + arch/powerpc/kernel/paca.c | 70 ++++- + arch/powerpc/kernel/pci-common.c | 4 +- + arch/powerpc/kernel/ppc970-pmu.c | 2 + + arch/powerpc/kernel/process.c | 12 - + arch/powerpc/kernel/ptrace.c | 2 +- + arch/powerpc/kernel/rtas.c | 4 +- + arch/powerpc/kernel/setup_32.c | 2 +- + arch/powerpc/kernel/smp.c | 14 +- + arch/powerpc/kernel/time.c | 275 +++++++------- + arch/powerpc/kernel/traps.c | 5 + + arch/powerpc/kernel/vdso.c | 6 +- + arch/powerpc/kernel/vdso32/Makefile | 6 +- + arch/powerpc/kernel/vdso64/Makefile | 6 +- + arch/powerpc/kernel/vio.c | 10 +- + arch/powerpc/kvm/Makefile | 2 +- + arch/powerpc/kvm/book3s_paired_singles.c | 44 +-- + arch/powerpc/kvm/emulate.c | 4 +- + arch/powerpc/kvm/fpu.S | 8 - + arch/powerpc/lib/Makefile | 7 +- + arch/powerpc/lib/checksum_64.S | 482 +++++++++++++++++------- + arch/powerpc/lib/checksum_wrappers_64.c | 102 +++++ + arch/powerpc/lib/copy_32.S | 2 +- + arch/powerpc/lib/ldstfp.S | 36 +- + arch/powerpc/lib/locks.c | 4 +- + arch/powerpc/lib/sstep.c | 8 + + arch/powerpc/math-emu/Makefile | 2 +- + arch/powerpc/mm/Makefile | 6 +- + arch/powerpc/mm/fault.c | 6 + + arch/powerpc/mm/fsl_booke_mmu.c | 15 +- + arch/powerpc/mm/mmu_context_nohash.c | 6 +- + arch/powerpc/mm/mmu_decl.h | 5 +- + arch/powerpc/mm/tlb_nohash.c | 56 +++- + arch/powerpc/mm/tlb_nohash_low.S | 2 +- + arch/powerpc/oprofile/Makefile | 4 +- + arch/powerpc/oprofile/backtrace.c | 2 +- + arch/powerpc/oprofile/op_model_fsl_emb.c | 15 +- + arch/powerpc/platforms/44x/Kconfig | 16 + + arch/powerpc/platforms/44x/ppc44x_simple.c | 1 + + arch/powerpc/platforms/83xx/Kconfig | 4 +- + arch/powerpc/platforms/83xx/mpc830x_rdb.c | 3 +- + arch/powerpc/platforms/85xx/Kconfig | 28 ++- + arch/powerpc/platforms/85xx/Makefile | 2 + + arch/powerpc/platforms/85xx/p1022_ds.c | 2 + + arch/powerpc/platforms/85xx/p3041_ds.c | 64 ++++ + arch/powerpc/platforms/85xx/p5020_ds.c | 69 ++++ + arch/powerpc/platforms/85xx/smp.c | 83 ++++- + arch/powerpc/platforms/Kconfig.cputype | 8 +- + arch/powerpc/platforms/cell/ras.c | 4 +- + arch/powerpc/platforms/cell/spider-pic.c | 4 +- + arch/powerpc/platforms/cell/spufs/file.c | 18 + + arch/powerpc/platforms/chrp/nvram.c | 4 +- + arch/powerpc/platforms/iseries/Makefile | 2 +- + arch/powerpc/platforms/iseries/dt.c | 4 +- + arch/powerpc/platforms/iseries/smp.c | 2 +- + arch/powerpc/platforms/maple/setup.c | 1 + + arch/powerpc/platforms/powermac/pfunc_core.c | 9 +- + arch/powerpc/platforms/pseries/Makefile | 13 +- + arch/powerpc/platforms/pseries/dlpar.c | 7 +- + arch/powerpc/platforms/pseries/dtl.c | 224 +++++++++--- + arch/powerpc/platforms/pseries/lpar.c | 25 ++- + arch/powerpc/platforms/pseries/mobility.c | 362 ++++++++++++++++++ + arch/powerpc/platforms/pseries/pseries.h | 9 + + arch/powerpc/platforms/pseries/setup.c | 52 +++ + arch/powerpc/platforms/pseries/xics.c | 2 +- + arch/powerpc/sysdev/Makefile | 5 +- + arch/powerpc/sysdev/dart_iommu.c | 74 ++++- + arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h | 101 +++++ + arch/powerpc/sysdev/fsl_85xx_cache_sram.c | 159 ++++++++ + arch/powerpc/sysdev/fsl_85xx_l2ctlr.c | 231 +++++++++++ + arch/powerpc/sysdev/fsl_msi.c | 9 +- + arch/powerpc/sysdev/fsl_pci.c | 60 +++- + arch/powerpc/sysdev/fsl_pci.h | 1 + + arch/powerpc/sysdev/fsl_rio.c | 65 ++-- + arch/powerpc/sysdev/fsl_soc.c | 20 +- + arch/powerpc/sysdev/mpc8xxx_gpio.c | 3 + + arch/powerpc/sysdev/pmi.c | 2 +- + arch/powerpc/xmon/Makefile | 4 +- + drivers/i2c/busses/i2c-pasemi.c | 2 +- + drivers/macintosh/via-pmu-led.c | 4 +- + drivers/watchdog/Kconfig | 22 +- + drivers/watchdog/booke_wdt.c | 47 ++- + include/linux/pci_ids.h | 8 + + kernel/sys_ni.c | 1 + + 130 files changed, 3676 insertions(+), 683 deletions(-) + create mode 100644 arch/powerpc/boot/dts/bluestone.dts + create mode 100644 arch/powerpc/boot/dts/mpc8308_p1m.dts + create mode 100644 arch/powerpc/configs/44x/bluestone_defconfig + create mode 100644 arch/powerpc/configs/e55xx_smp_defconfig + create mode 100644 arch/powerpc/include/asm/fsl_85xx_cache_sram.h + create mode 100644 arch/powerpc/lib/checksum_wrappers_64.c + create mode 100644 arch/powerpc/platforms/85xx/p3041_ds.c + create mode 100644 arch/powerpc/platforms/85xx/p5020_ds.c + create mode 100644 arch/powerpc/platforms/pseries/mobility.c + create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h + create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_sram.c + create mode 100644 arch/powerpc/sysdev/fsl_85xx_l2ctlr.c + + +_______________________________________________ +Linuxppc-dev mailing list +Linuxppc-dev@lists.ozlabs.org +https://lists.ozlabs.org/listinfo/linuxppc-dev diff --git a/patchwork/tests/mail/0005-git-pull-request-ssh.mbox b/patchwork/tests/mail/0005-git-pull-request-ssh.mbox new file mode 100644 index 0000000..7f4c93e --- /dev/null +++ b/patchwork/tests/mail/0005-git-pull-request-ssh.mbox @@ -0,0 +1,348 @@ +From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010 +Return-Path: +X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on bilbo.ozlabs.org +X-Spam-Level: +X-Spam-Status: No, score=0.0 required=3.0 tests=none autolearn=disabled + version=3.3.1 +X-Original-To: jk@ozlabs.org +Delivered-To: jk@ozlabs.org +Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) + by ozlabs.org (Postfix) with ESMTP id ED4B3100937 + for ; Fri, 22 Oct 2010 14:51:54 +1100 (EST) +Received: by ozlabs.org (Postfix) + id BF799B70CB; Fri, 22 Oct 2010 14:51:50 +1100 (EST) +Delivered-To: linuxppc-dev@ozlabs.org +Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) + (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) + (Client did not present a certificate) + by ozlabs.org (Postfix) with ESMTPS id 94629B7043 + for ; Fri, 22 Oct 2010 14:51:49 +1100 (EST) +Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1]) + by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o9M3p3SP018234; + Thu, 21 Oct 2010 22:51:04 -0500 +Subject: [git pull] Please pull powerpc.git next branch +From: Benjamin Herrenschmidt +To: Linus Torvalds +Date: Fri, 22 Oct 2010 14:51:02 +1100 +Message-ID: <1287719462.2198.37.camel@pasglop> +Mime-Version: 1.0 +X-Mailer: Evolution 2.30.3 +Cc: linuxppc-dev list , + Andrew Morton , + Linux Kernel list +X-BeenThere: linuxppc-dev@lists.ozlabs.org +X-Mailman-Version: 2.1.13 +Precedence: list +List-Id: Linux on PowerPC Developers Mail List +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Content-Type: text/plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Sender: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org +Errors-To: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org +X-UID: 11446 +X-Length: 16781 +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Hi Linus ! + +Here's powerpc's batch for this merge window. Mostly bits and pieces, +such as Anton doing some performance tuning left and right, and the +usual churn. One hilight is the support for the new Freescale e5500 core +(64-bit BookE). Another one is that we now wire up the whole lot of +socket calls as direct syscalls in addition to the old style indirect +method. + +Cheers, +Ben. + +The following changes since commit e10117d36ef758da0690c95ecffc09d5dd7da479: + Linus Torvalds (1): + Merge branch 'upstream-linus' of git://git.kernel.org/.../jgarzik/libata-dev + +are available in the git repository at: + + ssh://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git next + +Andreas Schwab (1): + powerpc: Remove fpscr use from [kvm_]cvt_{fd,df} + +Anton Blanchard (5): + powerpc: Optimise 64bit csum_partial + powerpc: Optimise 64bit csum_partial_copy_generic and add csum_and_copy_from_user + powerpc: Add 64bit csum_and_copy_to_user + powerpc: Feature nop out reservation clear when stcx checks address + powerpc: Check end of stack canary at oops time + +Arnd Bergmann (1): + powerpc/spufs: Use llseek in all file operations + +Benjamin Herrenschmidt (4): + powerpc/dma: Add optional platform override of dma_set_mask() + powerpc/dart_iommu: Support for 64-bit iommu bypass window on PCIe + Merge remote branch 'kumar/merge' into next + Merge remote branch 'jwb/next' into next + +Denis Kirjanov (1): + powerpc: Use is_32bit_task() helper to test 32-bit binary + +Harninder Rai (1): + powerpc/85xx: add cache-sram support + +Ian Munsie (1): + powerpc: Wire up direct socket system calls + +Ilya Yanok (1): + powerpc/mpc83xx: Support for MPC8308 P1M board + +Joe Perches (2): + powerpc: Use static const char arrays + powerpc: Remove pr_ uses of KERN_ + +Josh Boyer (1): + powerpc/44x: Update ppc44x_defconfig + +Julia Lawall (7): + powerpc/via-pmu-led.c: Add of_node_put to avoid memory leak + powerpc/maple: Add of_node_put to avoid memory leak + powerpc/powermac/pfunc_core.c: Add of_node_put to avoid memory leak + powerpc/cell: Add of_node_put to avoid memory leak + powerpc/chrp/nvram.c: Add of_node_put to avoid memory leak + powerpc/irq.c: Add of_node_put to avoid memory leak + i2c/i2c-pasemi.c: Fix unsigned return type + +Kumar Gala (11): + powerpc/ppc64e: Fix link problem when building ppc64e_defconfig + powerpc/fsl-pci: Fix MSI support on 83xx platforms + powerpc/mpc8xxx_gpio: Add support for 'qoriq-gpio' controllers + powerpc/fsl-booke: Add PCI device ids for P2040/P3041/P5010/P5020 QoirQ chips + powerpc/fsl-booke: Add p3041 DS board support + powerpc: Fix compile error with paca code on ppc64e + powerpc/fsl-booke: Add support for FSL 64-bit e5500 core + powerpc/fsl-booke: Add support for FSL Arch v1.0 MMU in setup_page_sizes + powerpc/fsl-booke64: Use TLB CAMs to cover linear mapping on FSL 64-bit chips + powerpc/fsl-booke: Add p5020 DS board support + powerpc/fsl-booke: Add e55xx (64-bit) smp defconfig + +Matthew McClintock (7): + powerpc/mm: Assume first cpu is boot_cpuid not 0 + powerpc/kexec: make masking/disabling interrupts generic + powerpc/85xx: Remove call to mpic_teardown_this_cpu in kexec + powerpc/85xx: Minor fixups for kexec on 85xx + powerpc/85xx: flush dcache before resetting cores + powerpc/fsl_soc: Search all global-utilities nodes for rstccr + powerpc/fsl_booke: Add support to boot from core other than 0 + +Michael Neuling (1): + powerpc: Move arch_sd_sibling_asym_packing() to smp.c + +Nathan Fontenot (3): + powerpc/pseries: Export device tree updating routines + powerpc/pseries: Export rtas_ibm_suspend_me() + powerpc/pseries: Partition migration in the kernel + +Nishanth Aravamudan (8): + powerpc/pci: Fix return type of BUID_{HI,LO} macros + powerpc/dma: Fix dma_iommu_dma_supported compare + powerpc/dma: Fix check for direct DMA support + powerpc/vio: Use put_device() on device_register failure + powerpc/viobus: Free TCE table on device release + powerpc/pseries: Use kmemdup + powerpc/pci: Cleanup device dma setup code + powerpc/pseries/xics: Use cpu_possible_mask rather than cpu_all_mask + +Paul Gortmaker (1): + powerpc: Fix invalid page flags in create TLB CAM path for PTE_64BIT + +Paul Mackerras (5): + powerpc: Abstract indexing of lppaca structs + powerpc: Dynamically allocate most lppaca structs + powerpc: Account time using timebase rather than PURR + powerpc/pseries: Re-enable dispatch trace log userspace interface + powerpc/perf: Fix sampling enable for PPC970 + +Scott Wood (1): + oprofile/fsl emb: Don't set MSR[PMM] until after clearing the interrupt. + +Sean MacLennan (2): + powerpc: Fix incorrect .stabs entry for copy_32.S + powerpc: mtmsrd not defined + +Shaohui Xie (1): + fsl_rio: Add comments for sRIO registers. + +Stephen Rothwell (1): + powerpc: define a compat_sys_recv cond_syscall + +Timur Tabi (5): + powerpc: export ppc_proc_freq and ppc_tb_freq as GPL symbols + powerpc/watchdog: Allow the Book-E driver to be compiled as a module + powerpc/p1022: Add probing for individual DMA channels + powerpc/85xx: add ngPIXIS FPGA device tree node to the P1022DS board + powerpc/watchdog: Make default timeout for Book-E watchdog a Kconfig option + +Tirumala Marri (1): + powerpc/44x: Add support for the AMCC APM821xx SoC + +matt mooney (1): + powerpc/Makefiles: Change to new flag variables + + arch/powerpc/boot/addnote.c | 4 +- + arch/powerpc/boot/dts/bluestone.dts | 254 +++++++++++++ + arch/powerpc/boot/dts/mpc8308_p1m.dts | 332 ++++++++++++++++ + arch/powerpc/boot/dts/p1022ds.dts | 11 + + arch/powerpc/configs/44x/bluestone_defconfig | 68 ++++ + arch/powerpc/configs/e55xx_smp_defconfig | 84 ++++ + arch/powerpc/configs/ppc44x_defconfig | 9 +- + arch/powerpc/configs/ppc64e_defconfig | 4 +- + arch/powerpc/include/asm/checksum.h | 10 + + arch/powerpc/include/asm/compat.h | 4 +- + arch/powerpc/include/asm/cputable.h | 14 +- + arch/powerpc/include/asm/dma-mapping.h | 14 +- + arch/powerpc/include/asm/elf.h | 2 +- + arch/powerpc/include/asm/exception-64s.h | 3 +- + arch/powerpc/include/asm/fsl_85xx_cache_sram.h | 48 +++ + arch/powerpc/include/asm/kexec.h | 1 + + arch/powerpc/include/asm/kvm_fpu.h | 4 +- + arch/powerpc/include/asm/lppaca.h | 29 ++ + arch/powerpc/include/asm/machdep.h | 3 + + arch/powerpc/include/asm/mmu-book3e.h | 15 + + arch/powerpc/include/asm/paca.h | 10 +- + arch/powerpc/include/asm/page_64.h | 4 +- + arch/powerpc/include/asm/ppc-pci.h | 4 +- + arch/powerpc/include/asm/ppc_asm.h | 50 ++- + arch/powerpc/include/asm/processor.h | 4 +- + arch/powerpc/include/asm/pte-common.h | 7 + + arch/powerpc/include/asm/rtas.h | 1 + + arch/powerpc/include/asm/systbl.h | 19 + + arch/powerpc/include/asm/system.h | 4 +- + arch/powerpc/include/asm/time.h | 5 - + arch/powerpc/include/asm/unistd.h | 21 +- + arch/powerpc/kernel/Makefile | 4 +- + arch/powerpc/kernel/align.c | 4 +- + arch/powerpc/kernel/asm-offsets.c | 12 +- + arch/powerpc/kernel/cpu_setup_44x.S | 1 + + arch/powerpc/kernel/cpu_setup_fsl_booke.S | 15 + + arch/powerpc/kernel/cputable.c | 43 ++- + arch/powerpc/kernel/crash.c | 13 +- + arch/powerpc/kernel/dma-iommu.c | 21 +- + arch/powerpc/kernel/dma.c | 20 +- + arch/powerpc/kernel/entry_64.S | 40 ++ + arch/powerpc/kernel/fpu.S | 10 - + arch/powerpc/kernel/head_fsl_booke.S | 10 +- + arch/powerpc/kernel/irq.c | 6 +- + arch/powerpc/kernel/lparcfg.c | 14 +- + arch/powerpc/kernel/machine_kexec.c | 24 ++ + arch/powerpc/kernel/machine_kexec_32.c | 4 + + arch/powerpc/kernel/paca.c | 70 ++++- + arch/powerpc/kernel/pci-common.c | 4 +- + arch/powerpc/kernel/ppc970-pmu.c | 2 + + arch/powerpc/kernel/process.c | 12 - + arch/powerpc/kernel/ptrace.c | 2 +- + arch/powerpc/kernel/rtas.c | 4 +- + arch/powerpc/kernel/setup_32.c | 2 +- + arch/powerpc/kernel/smp.c | 14 +- + arch/powerpc/kernel/time.c | 275 +++++++------- + arch/powerpc/kernel/traps.c | 5 + + arch/powerpc/kernel/vdso.c | 6 +- + arch/powerpc/kernel/vdso32/Makefile | 6 +- + arch/powerpc/kernel/vdso64/Makefile | 6 +- + arch/powerpc/kernel/vio.c | 10 +- + arch/powerpc/kvm/Makefile | 2 +- + arch/powerpc/kvm/book3s_paired_singles.c | 44 +-- + arch/powerpc/kvm/emulate.c | 4 +- + arch/powerpc/kvm/fpu.S | 8 - + arch/powerpc/lib/Makefile | 7 +- + arch/powerpc/lib/checksum_64.S | 482 +++++++++++++++++------- + arch/powerpc/lib/checksum_wrappers_64.c | 102 +++++ + arch/powerpc/lib/copy_32.S | 2 +- + arch/powerpc/lib/ldstfp.S | 36 +- + arch/powerpc/lib/locks.c | 4 +- + arch/powerpc/lib/sstep.c | 8 + + arch/powerpc/math-emu/Makefile | 2 +- + arch/powerpc/mm/Makefile | 6 +- + arch/powerpc/mm/fault.c | 6 + + arch/powerpc/mm/fsl_booke_mmu.c | 15 +- + arch/powerpc/mm/mmu_context_nohash.c | 6 +- + arch/powerpc/mm/mmu_decl.h | 5 +- + arch/powerpc/mm/tlb_nohash.c | 56 +++- + arch/powerpc/mm/tlb_nohash_low.S | 2 +- + arch/powerpc/oprofile/Makefile | 4 +- + arch/powerpc/oprofile/backtrace.c | 2 +- + arch/powerpc/oprofile/op_model_fsl_emb.c | 15 +- + arch/powerpc/platforms/44x/Kconfig | 16 + + arch/powerpc/platforms/44x/ppc44x_simple.c | 1 + + arch/powerpc/platforms/83xx/Kconfig | 4 +- + arch/powerpc/platforms/83xx/mpc830x_rdb.c | 3 +- + arch/powerpc/platforms/85xx/Kconfig | 28 ++- + arch/powerpc/platforms/85xx/Makefile | 2 + + arch/powerpc/platforms/85xx/p1022_ds.c | 2 + + arch/powerpc/platforms/85xx/p3041_ds.c | 64 ++++ + arch/powerpc/platforms/85xx/p5020_ds.c | 69 ++++ + arch/powerpc/platforms/85xx/smp.c | 83 ++++- + arch/powerpc/platforms/Kconfig.cputype | 8 +- + arch/powerpc/platforms/cell/ras.c | 4 +- + arch/powerpc/platforms/cell/spider-pic.c | 4 +- + arch/powerpc/platforms/cell/spufs/file.c | 18 + + arch/powerpc/platforms/chrp/nvram.c | 4 +- + arch/powerpc/platforms/iseries/Makefile | 2 +- + arch/powerpc/platforms/iseries/dt.c | 4 +- + arch/powerpc/platforms/iseries/smp.c | 2 +- + arch/powerpc/platforms/maple/setup.c | 1 + + arch/powerpc/platforms/powermac/pfunc_core.c | 9 +- + arch/powerpc/platforms/pseries/Makefile | 13 +- + arch/powerpc/platforms/pseries/dlpar.c | 7 +- + arch/powerpc/platforms/pseries/dtl.c | 224 +++++++++--- + arch/powerpc/platforms/pseries/lpar.c | 25 ++- + arch/powerpc/platforms/pseries/mobility.c | 362 ++++++++++++++++++ + arch/powerpc/platforms/pseries/pseries.h | 9 + + arch/powerpc/platforms/pseries/setup.c | 52 +++ + arch/powerpc/platforms/pseries/xics.c | 2 +- + arch/powerpc/sysdev/Makefile | 5 +- + arch/powerpc/sysdev/dart_iommu.c | 74 ++++- + arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h | 101 +++++ + arch/powerpc/sysdev/fsl_85xx_cache_sram.c | 159 ++++++++ + arch/powerpc/sysdev/fsl_85xx_l2ctlr.c | 231 +++++++++++ + arch/powerpc/sysdev/fsl_msi.c | 9 +- + arch/powerpc/sysdev/fsl_pci.c | 60 +++- + arch/powerpc/sysdev/fsl_pci.h | 1 + + arch/powerpc/sysdev/fsl_rio.c | 65 ++-- + arch/powerpc/sysdev/fsl_soc.c | 20 +- + arch/powerpc/sysdev/mpc8xxx_gpio.c | 3 + + arch/powerpc/sysdev/pmi.c | 2 +- + arch/powerpc/xmon/Makefile | 4 +- + drivers/i2c/busses/i2c-pasemi.c | 2 +- + drivers/macintosh/via-pmu-led.c | 4 +- + drivers/watchdog/Kconfig | 22 +- + drivers/watchdog/booke_wdt.c | 47 ++- + include/linux/pci_ids.h | 8 + + kernel/sys_ni.c | 1 + + 130 files changed, 3676 insertions(+), 683 deletions(-) + create mode 100644 arch/powerpc/boot/dts/bluestone.dts + create mode 100644 arch/powerpc/boot/dts/mpc8308_p1m.dts + create mode 100644 arch/powerpc/configs/44x/bluestone_defconfig + create mode 100644 arch/powerpc/configs/e55xx_smp_defconfig + create mode 100644 arch/powerpc/include/asm/fsl_85xx_cache_sram.h + create mode 100644 arch/powerpc/lib/checksum_wrappers_64.c + create mode 100644 arch/powerpc/platforms/85xx/p3041_ds.c + create mode 100644 arch/powerpc/platforms/85xx/p5020_ds.c + create mode 100644 arch/powerpc/platforms/pseries/mobility.c + create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h + create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_sram.c + create mode 100644 arch/powerpc/sysdev/fsl_85xx_l2ctlr.c + + +_______________________________________________ +Linuxppc-dev mailing list +Linuxppc-dev@lists.ozlabs.org +https://lists.ozlabs.org/listinfo/linuxppc-dev diff --git a/patchwork/tests/mail/0006-git-pull-request-http.mbox b/patchwork/tests/mail/0006-git-pull-request-http.mbox new file mode 100644 index 0000000..e4f9007 --- /dev/null +++ b/patchwork/tests/mail/0006-git-pull-request-http.mbox @@ -0,0 +1,348 @@ +From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010 +Return-Path: +X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on bilbo.ozlabs.org +X-Spam-Level: +X-Spam-Status: No, score=0.0 required=3.0 tests=none autolearn=disabled + version=3.3.1 +X-Original-To: jk@ozlabs.org +Delivered-To: jk@ozlabs.org +Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) + by ozlabs.org (Postfix) with ESMTP id ED4B3100937 + for ; Fri, 22 Oct 2010 14:51:54 +1100 (EST) +Received: by ozlabs.org (Postfix) + id BF799B70CB; Fri, 22 Oct 2010 14:51:50 +1100 (EST) +Delivered-To: linuxppc-dev@ozlabs.org +Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) + (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) + (Client did not present a certificate) + by ozlabs.org (Postfix) with ESMTPS id 94629B7043 + for ; Fri, 22 Oct 2010 14:51:49 +1100 (EST) +Received: from [IPv6:::1] (localhost.localdomain [127.0.0.1]) + by gate.crashing.org (8.14.1/8.13.8) with ESMTP id o9M3p3SP018234; + Thu, 21 Oct 2010 22:51:04 -0500 +Subject: [git pull] Please pull powerpc.git next branch +From: Benjamin Herrenschmidt +To: Linus Torvalds +Date: Fri, 22 Oct 2010 14:51:02 +1100 +Message-ID: <1287719462.2198.37.camel@pasglop> +Mime-Version: 1.0 +X-Mailer: Evolution 2.30.3 +Cc: linuxppc-dev list , + Andrew Morton , + Linux Kernel list +X-BeenThere: linuxppc-dev@lists.ozlabs.org +X-Mailman-Version: 2.1.13 +Precedence: list +List-Id: Linux on PowerPC Developers Mail List +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +Content-Type: text/plain; + charset="us-ascii" +Content-Transfer-Encoding: 7bit +Sender: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org +Errors-To: linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org +X-UID: 11446 +X-Length: 16781 +Status: R +X-Status: N +X-KMail-EncryptionState: +X-KMail-SignatureState: +X-KMail-MDN-Sent: + +Hi Linus ! + +Here's powerpc's batch for this merge window. Mostly bits and pieces, +such as Anton doing some performance tuning left and right, and the +usual churn. One hilight is the support for the new Freescale e5500 core +(64-bit BookE). Another one is that we now wire up the whole lot of +socket calls as direct syscalls in addition to the old style indirect +method. + +Cheers, +Ben. + +The following changes since commit e10117d36ef758da0690c95ecffc09d5dd7da479: + Linus Torvalds (1): + Merge branch 'upstream-linus' of git://git.kernel.org/.../jgarzik/libata-dev + +are available in the git repository at: + + http://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git next + +Andreas Schwab (1): + powerpc: Remove fpscr use from [kvm_]cvt_{fd,df} + +Anton Blanchard (5): + powerpc: Optimise 64bit csum_partial + powerpc: Optimise 64bit csum_partial_copy_generic and add csum_and_copy_from_user + powerpc: Add 64bit csum_and_copy_to_user + powerpc: Feature nop out reservation clear when stcx checks address + powerpc: Check end of stack canary at oops time + +Arnd Bergmann (1): + powerpc/spufs: Use llseek in all file operations + +Benjamin Herrenschmidt (4): + powerpc/dma: Add optional platform override of dma_set_mask() + powerpc/dart_iommu: Support for 64-bit iommu bypass window on PCIe + Merge remote branch 'kumar/merge' into next + Merge remote branch 'jwb/next' into next + +Denis Kirjanov (1): + powerpc: Use is_32bit_task() helper to test 32-bit binary + +Harninder Rai (1): + powerpc/85xx: add cache-sram support + +Ian Munsie (1): + powerpc: Wire up direct socket system calls + +Ilya Yanok (1): + powerpc/mpc83xx: Support for MPC8308 P1M board + +Joe Perches (2): + powerpc: Use static const char arrays + powerpc: Remove pr_ uses of KERN_ + +Josh Boyer (1): + powerpc/44x: Update ppc44x_defconfig + +Julia Lawall (7): + powerpc/via-pmu-led.c: Add of_node_put to avoid memory leak + powerpc/maple: Add of_node_put to avoid memory leak + powerpc/powermac/pfunc_core.c: Add of_node_put to avoid memory leak + powerpc/cell: Add of_node_put to avoid memory leak + powerpc/chrp/nvram.c: Add of_node_put to avoid memory leak + powerpc/irq.c: Add of_node_put to avoid memory leak + i2c/i2c-pasemi.c: Fix unsigned return type + +Kumar Gala (11): + powerpc/ppc64e: Fix link problem when building ppc64e_defconfig + powerpc/fsl-pci: Fix MSI support on 83xx platforms + powerpc/mpc8xxx_gpio: Add support for 'qoriq-gpio' controllers + powerpc/fsl-booke: Add PCI device ids for P2040/P3041/P5010/P5020 QoirQ chips + powerpc/fsl-booke: Add p3041 DS board support + powerpc: Fix compile error with paca code on ppc64e + powerpc/fsl-booke: Add support for FSL 64-bit e5500 core + powerpc/fsl-booke: Add support for FSL Arch v1.0 MMU in setup_page_sizes + powerpc/fsl-booke64: Use TLB CAMs to cover linear mapping on FSL 64-bit chips + powerpc/fsl-booke: Add p5020 DS board support + powerpc/fsl-booke: Add e55xx (64-bit) smp defconfig + +Matthew McClintock (7): + powerpc/mm: Assume first cpu is boot_cpuid not 0 + powerpc/kexec: make masking/disabling interrupts generic + powerpc/85xx: Remove call to mpic_teardown_this_cpu in kexec + powerpc/85xx: Minor fixups for kexec on 85xx + powerpc/85xx: flush dcache before resetting cores + powerpc/fsl_soc: Search all global-utilities nodes for rstccr + powerpc/fsl_booke: Add support to boot from core other than 0 + +Michael Neuling (1): + powerpc: Move arch_sd_sibling_asym_packing() to smp.c + +Nathan Fontenot (3): + powerpc/pseries: Export device tree updating routines + powerpc/pseries: Export rtas_ibm_suspend_me() + powerpc/pseries: Partition migration in the kernel + +Nishanth Aravamudan (8): + powerpc/pci: Fix return type of BUID_{HI,LO} macros + powerpc/dma: Fix dma_iommu_dma_supported compare + powerpc/dma: Fix check for direct DMA support + powerpc/vio: Use put_device() on device_register failure + powerpc/viobus: Free TCE table on device release + powerpc/pseries: Use kmemdup + powerpc/pci: Cleanup device dma setup code + powerpc/pseries/xics: Use cpu_possible_mask rather than cpu_all_mask + +Paul Gortmaker (1): + powerpc: Fix invalid page flags in create TLB CAM path for PTE_64BIT + +Paul Mackerras (5): + powerpc: Abstract indexing of lppaca structs + powerpc: Dynamically allocate most lppaca structs + powerpc: Account time using timebase rather than PURR + powerpc/pseries: Re-enable dispatch trace log userspace interface + powerpc/perf: Fix sampling enable for PPC970 + +Scott Wood (1): + oprofile/fsl emb: Don't set MSR[PMM] until after clearing the interrupt. + +Sean MacLennan (2): + powerpc: Fix incorrect .stabs entry for copy_32.S + powerpc: mtmsrd not defined + +Shaohui Xie (1): + fsl_rio: Add comments for sRIO registers. + +Stephen Rothwell (1): + powerpc: define a compat_sys_recv cond_syscall + +Timur Tabi (5): + powerpc: export ppc_proc_freq and ppc_tb_freq as GPL symbols + powerpc/watchdog: Allow the Book-E driver to be compiled as a module + powerpc/p1022: Add probing for individual DMA channels + powerpc/85xx: add ngPIXIS FPGA device tree node to the P1022DS board + powerpc/watchdog: Make default timeout for Book-E watchdog a Kconfig option + +Tirumala Marri (1): + powerpc/44x: Add support for the AMCC APM821xx SoC + +matt mooney (1): + powerpc/Makefiles: Change to new flag variables + + arch/powerpc/boot/addnote.c | 4 +- + arch/powerpc/boot/dts/bluestone.dts | 254 +++++++++++++ + arch/powerpc/boot/dts/mpc8308_p1m.dts | 332 ++++++++++++++++ + arch/powerpc/boot/dts/p1022ds.dts | 11 + + arch/powerpc/configs/44x/bluestone_defconfig | 68 ++++ + arch/powerpc/configs/e55xx_smp_defconfig | 84 ++++ + arch/powerpc/configs/ppc44x_defconfig | 9 +- + arch/powerpc/configs/ppc64e_defconfig | 4 +- + arch/powerpc/include/asm/checksum.h | 10 + + arch/powerpc/include/asm/compat.h | 4 +- + arch/powerpc/include/asm/cputable.h | 14 +- + arch/powerpc/include/asm/dma-mapping.h | 14 +- + arch/powerpc/include/asm/elf.h | 2 +- + arch/powerpc/include/asm/exception-64s.h | 3 +- + arch/powerpc/include/asm/fsl_85xx_cache_sram.h | 48 +++ + arch/powerpc/include/asm/kexec.h | 1 + + arch/powerpc/include/asm/kvm_fpu.h | 4 +- + arch/powerpc/include/asm/lppaca.h | 29 ++ + arch/powerpc/include/asm/machdep.h | 3 + + arch/powerpc/include/asm/mmu-book3e.h | 15 + + arch/powerpc/include/asm/paca.h | 10 +- + arch/powerpc/include/asm/page_64.h | 4 +- + arch/powerpc/include/asm/ppc-pci.h | 4 +- + arch/powerpc/include/asm/ppc_asm.h | 50 ++- + arch/powerpc/include/asm/processor.h | 4 +- + arch/powerpc/include/asm/pte-common.h | 7 + + arch/powerpc/include/asm/rtas.h | 1 + + arch/powerpc/include/asm/systbl.h | 19 + + arch/powerpc/include/asm/system.h | 4 +- + arch/powerpc/include/asm/time.h | 5 - + arch/powerpc/include/asm/unistd.h | 21 +- + arch/powerpc/kernel/Makefile | 4 +- + arch/powerpc/kernel/align.c | 4 +- + arch/powerpc/kernel/asm-offsets.c | 12 +- + arch/powerpc/kernel/cpu_setup_44x.S | 1 + + arch/powerpc/kernel/cpu_setup_fsl_booke.S | 15 + + arch/powerpc/kernel/cputable.c | 43 ++- + arch/powerpc/kernel/crash.c | 13 +- + arch/powerpc/kernel/dma-iommu.c | 21 +- + arch/powerpc/kernel/dma.c | 20 +- + arch/powerpc/kernel/entry_64.S | 40 ++ + arch/powerpc/kernel/fpu.S | 10 - + arch/powerpc/kernel/head_fsl_booke.S | 10 +- + arch/powerpc/kernel/irq.c | 6 +- + arch/powerpc/kernel/lparcfg.c | 14 +- + arch/powerpc/kernel/machine_kexec.c | 24 ++ + arch/powerpc/kernel/machine_kexec_32.c | 4 + + arch/powerpc/kernel/paca.c | 70 ++++- + arch/powerpc/kernel/pci-common.c | 4 +- + arch/powerpc/kernel/ppc970-pmu.c | 2 + + arch/powerpc/kernel/process.c | 12 - + arch/powerpc/kernel/ptrace.c | 2 +- + arch/powerpc/kernel/rtas.c | 4 +- + arch/powerpc/kernel/setup_32.c | 2 +- + arch/powerpc/kernel/smp.c | 14 +- + arch/powerpc/kernel/time.c | 275 +++++++------- + arch/powerpc/kernel/traps.c | 5 + + arch/powerpc/kernel/vdso.c | 6 +- + arch/powerpc/kernel/vdso32/Makefile | 6 +- + arch/powerpc/kernel/vdso64/Makefile | 6 +- + arch/powerpc/kernel/vio.c | 10 +- + arch/powerpc/kvm/Makefile | 2 +- + arch/powerpc/kvm/book3s_paired_singles.c | 44 +-- + arch/powerpc/kvm/emulate.c | 4 +- + arch/powerpc/kvm/fpu.S | 8 - + arch/powerpc/lib/Makefile | 7 +- + arch/powerpc/lib/checksum_64.S | 482 +++++++++++++++++------- + arch/powerpc/lib/checksum_wrappers_64.c | 102 +++++ + arch/powerpc/lib/copy_32.S | 2 +- + arch/powerpc/lib/ldstfp.S | 36 +- + arch/powerpc/lib/locks.c | 4 +- + arch/powerpc/lib/sstep.c | 8 + + arch/powerpc/math-emu/Makefile | 2 +- + arch/powerpc/mm/Makefile | 6 +- + arch/powerpc/mm/fault.c | 6 + + arch/powerpc/mm/fsl_booke_mmu.c | 15 +- + arch/powerpc/mm/mmu_context_nohash.c | 6 +- + arch/powerpc/mm/mmu_decl.h | 5 +- + arch/powerpc/mm/tlb_nohash.c | 56 +++- + arch/powerpc/mm/tlb_nohash_low.S | 2 +- + arch/powerpc/oprofile/Makefile | 4 +- + arch/powerpc/oprofile/backtrace.c | 2 +- + arch/powerpc/oprofile/op_model_fsl_emb.c | 15 +- + arch/powerpc/platforms/44x/Kconfig | 16 + + arch/powerpc/platforms/44x/ppc44x_simple.c | 1 + + arch/powerpc/platforms/83xx/Kconfig | 4 +- + arch/powerpc/platforms/83xx/mpc830x_rdb.c | 3 +- + arch/powerpc/platforms/85xx/Kconfig | 28 ++- + arch/powerpc/platforms/85xx/Makefile | 2 + + arch/powerpc/platforms/85xx/p1022_ds.c | 2 + + arch/powerpc/platforms/85xx/p3041_ds.c | 64 ++++ + arch/powerpc/platforms/85xx/p5020_ds.c | 69 ++++ + arch/powerpc/platforms/85xx/smp.c | 83 ++++- + arch/powerpc/platforms/Kconfig.cputype | 8 +- + arch/powerpc/platforms/cell/ras.c | 4 +- + arch/powerpc/platforms/cell/spider-pic.c | 4 +- + arch/powerpc/platforms/cell/spufs/file.c | 18 + + arch/powerpc/platforms/chrp/nvram.c | 4 +- + arch/powerpc/platforms/iseries/Makefile | 2 +- + arch/powerpc/platforms/iseries/dt.c | 4 +- + arch/powerpc/platforms/iseries/smp.c | 2 +- + arch/powerpc/platforms/maple/setup.c | 1 + + arch/powerpc/platforms/powermac/pfunc_core.c | 9 +- + arch/powerpc/platforms/pseries/Makefile | 13 +- + arch/powerpc/platforms/pseries/dlpar.c | 7 +- + arch/powerpc/platforms/pseries/dtl.c | 224 +++++++++--- + arch/powerpc/platforms/pseries/lpar.c | 25 ++- + arch/powerpc/platforms/pseries/mobility.c | 362 ++++++++++++++++++ + arch/powerpc/platforms/pseries/pseries.h | 9 + + arch/powerpc/platforms/pseries/setup.c | 52 +++ + arch/powerpc/platforms/pseries/xics.c | 2 +- + arch/powerpc/sysdev/Makefile | 5 +- + arch/powerpc/sysdev/dart_iommu.c | 74 ++++- + arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h | 101 +++++ + arch/powerpc/sysdev/fsl_85xx_cache_sram.c | 159 ++++++++ + arch/powerpc/sysdev/fsl_85xx_l2ctlr.c | 231 +++++++++++ + arch/powerpc/sysdev/fsl_msi.c | 9 +- + arch/powerpc/sysdev/fsl_pci.c | 60 +++- + arch/powerpc/sysdev/fsl_pci.h | 1 + + arch/powerpc/sysdev/fsl_rio.c | 65 ++-- + arch/powerpc/sysdev/fsl_soc.c | 20 +- + arch/powerpc/sysdev/mpc8xxx_gpio.c | 3 + + arch/powerpc/sysdev/pmi.c | 2 +- + arch/powerpc/xmon/Makefile | 4 +- + drivers/i2c/busses/i2c-pasemi.c | 2 +- + drivers/macintosh/via-pmu-led.c | 4 +- + drivers/watchdog/Kconfig | 22 +- + drivers/watchdog/booke_wdt.c | 47 ++- + include/linux/pci_ids.h | 8 + + kernel/sys_ni.c | 1 + + 130 files changed, 3676 insertions(+), 683 deletions(-) + create mode 100644 arch/powerpc/boot/dts/bluestone.dts + create mode 100644 arch/powerpc/boot/dts/mpc8308_p1m.dts + create mode 100644 arch/powerpc/configs/44x/bluestone_defconfig + create mode 100644 arch/powerpc/configs/e55xx_smp_defconfig + create mode 100644 arch/powerpc/include/asm/fsl_85xx_cache_sram.h + create mode 100644 arch/powerpc/lib/checksum_wrappers_64.c + create mode 100644 arch/powerpc/platforms/85xx/p3041_ds.c + create mode 100644 arch/powerpc/platforms/85xx/p5020_ds.c + create mode 100644 arch/powerpc/platforms/pseries/mobility.c + create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h + create mode 100644 arch/powerpc/sysdev/fsl_85xx_cache_sram.c + create mode 100644 arch/powerpc/sysdev/fsl_85xx_l2ctlr.c + + +_______________________________________________ +Linuxppc-dev mailing list +Linuxppc-dev@lists.ozlabs.org +https://lists.ozlabs.org/listinfo/linuxppc-dev diff --git a/patchwork/tests/mail/0007-cvs-format-diff.mbox b/patchwork/tests/mail/0007-cvs-format-diff.mbox new file mode 100644 index 0000000..99735fa --- /dev/null +++ b/patchwork/tests/mail/0007-cvs-format-diff.mbox @@ -0,0 +1,134 @@ +Received: with ECARTIS (v1.0.0; list linux-mips); Tue, 06 Dec 2011 01:49:42 +0100 (CET) +Received: from mail3.caviumnetworks.com ([12.108.191.235]:14337 "EHLO + mail3.caviumnetworks.com" rhost-flags-OK-OK-OK-OK) + by eddie.linux-mips.org with ESMTP id S1903632Ab1LFAth (ORCPT + ); Tue, 6 Dec 2011 01:49:37 +0100 +Received: from caexch01.caveonetworks.com (Not Verified[192.168.16.9]) by mail3.caviumnetworks.com with MailMarshal (v6,7,2,8378) + id ; Mon, 05 Dec 2011 16:51:04 -0800 +Received: from caexch01.caveonetworks.com ([192.168.16.9]) by caexch01.caveonetworks.com with Microsoft SMTPSVC(6.0.3790.4675); + Mon, 5 Dec 2011 16:49:36 -0800 +Received: from dd1.caveonetworks.com ([64.2.3.195]) by caexch01.caveonetworks.com over TLS secured channel with Microsoft SMTPSVC(6.0.3790.4675); + Mon, 5 Dec 2011 16:49:35 -0800 +Message-ID: <4EDD669F.30207@cavium.com> +Date: Mon, 05 Dec 2011 16:49:35 -0800 +From: David Daney +User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.15) Gecko/20101027 Fedora/3.0.10-1.fc12 Thunderbird/3.0.10 +MIME-Version: 1.0 +To: binutils +CC: linux-mips , + Manuel Lauss , + Debian MIPS +Subject: [Patch]: Fix ld pr11138 FAILures on mips*. +Content-Type: multipart/mixed; + boundary="------------080709040708040308010506" +X-OriginalArrivalTime: 06 Dec 2011 00:49:35.0825 (UTC) FILETIME=[ECF8DC10:01CCB3B0] +Return-Path: +X-Envelope-To: <"|/home/ecartis/ecartis -s linux-mips"> (uid 0) +X-Orcpt: rfc822;linux-mips@linux-mips.org +Original-Recipient: rfc822;linux-mips@linux-mips.org +X-archive-position: 32041 +X-ecartis-version: Ecartis v1.0.0 +Sender: linux-mips-bounce@linux-mips.org +Errors-to: linux-mips-bounce@linux-mips.org +X-original-sender: david.daney@cavium.com +Precedence: bulk +X-list: linux-mips + +This is a multi-part message in MIME format. +--------------080709040708040308010506 +Content-Type: text/plain; charset=ISO-8859-1; format=flowed +Content-Transfer-Encoding: 7bit + +The pr11138 testcase links an executable with a version script. On +mips64-linux the presence of a version script was causing the +MIPS_RLD_MAP dynamic tag to be populated with a NULL value. When such +an executable was run ld.so would try to dereference this and receive +SIGSEGV, thus killing the process. + +The root cause of this is that the mips linker synthesizes a special +symbol "__RLD_MAP", and then sets MIPS_RLD_MAP to point to it. When a +version script is present, this symbol gets versioned along with all the +rest, and when it is time to take its address, the symbol can no longer +be found as it has had version information appended to its name. + +Since "__RLD_MAP" is really part of the ABI, we want to exclude it from +symbol versioning. To this end, I introduced a new symbol flag +'no_sym_version' to tag this type of symbol. When the "__RLD_MAP" +symbol is created, we set this flag. + +In _bfd_elf_link_assign_sym_version, we then skip all symbols that have +'no_sym_version' set, and everything now works. + +This problem has also been reported in the wild when linking the firefox +executable. + +Tested on mips64-linux-gnu and x86_64-linux-gnu + +Ok to commit? + +2011-12-05 David Daney + + * elf-bfd.h (elf_link_hash_entry): Add no_sym_version field. + * elflink.c (_bfd_elf_link_assign_sym_version): Don't assign a + version if no_sym_version is set. + * elfxx-mips.c (_bfd_mips_elf_create_dynamic_sections): Set + no_sym_version for "__RLD_MAP". + +--------------080709040708040308010506 +Content-Type: text/plain; + name="dd-2.patch" +Content-Transfer-Encoding: 7bit +Content-Disposition: attachment; + filename="dd-2.patch" + +Index: bfd/elf-bfd.h +=================================================================== +RCS file: /cvs/src/src/bfd/elf-bfd.h,v +retrieving revision 1.329 +diff -u -p -r1.329 elf-bfd.h +--- bfd/elf-bfd.h 17 Aug 2011 00:39:38 -0000 1.329 ++++ bfd/elf-bfd.h 5 Dec 2011 20:15:49 -0000 +@@ -198,6 +198,8 @@ struct elf_link_hash_entry + unsigned int pointer_equality_needed : 1; + /* Symbol is a unique global symbol. */ + unsigned int unique_global : 1; ++ /* Symbol should not be versioned. It is part of the ABI */ ++ unsigned int no_sym_version : 1; + + /* String table index in .dynstr if this is a dynamic symbol. */ + unsigned long dynstr_index; +Index: bfd/elflink.c +=================================================================== +RCS file: /cvs/src/src/bfd/elflink.c,v +retrieving revision 1.430 +diff -u -p -r1.430 elflink.c +--- bfd/elflink.c 15 Nov 2011 11:33:57 -0000 1.430 ++++ bfd/elflink.c 5 Dec 2011 20:15:50 -0000 +@@ -1946,6 +1946,9 @@ _bfd_elf_link_assign_sym_version (struct + if (!h->def_regular) + return TRUE; + ++ if (h->no_sym_version) ++ return TRUE; ++ + bed = get_elf_backend_data (info->output_bfd); + p = strchr (h->root.root.string, ELF_VER_CHR); + if (p != NULL && h->verinfo.vertree == NULL) +Index: bfd/elfxx-mips.c +=================================================================== +RCS file: /cvs/src/src/bfd/elfxx-mips.c,v +retrieving revision 1.296 +diff -u -p -r1.296 elfxx-mips.c +--- bfd/elfxx-mips.c 29 Nov 2011 20:28:54 -0000 1.296 ++++ bfd/elfxx-mips.c 5 Dec 2011 20:15:50 -0000 +@@ -7260,6 +7260,7 @@ _bfd_mips_elf_create_dynamic_sections (b + h = (struct elf_link_hash_entry *) bh; + h->non_elf = 0; + h->def_regular = 1; ++ h->no_sym_version = 1; + h->type = STT_OBJECT; + + if (! bfd_elf_link_record_dynamic_symbol (info, h)) + +--------------080709040708040308010506-- + diff --git a/patchwork/tests/mail/0008-git-rename.mbox b/patchwork/tests/mail/0008-git-rename.mbox new file mode 100644 index 0000000..8277049 --- /dev/null +++ b/patchwork/tests/mail/0008-git-rename.mbox @@ -0,0 +1,24 @@ +From: "Yann E. MORIN" +Subject: [Buildroot] [PATCH 01/11] package/rpi-userland: rename patches +Date: Tue, 8 Oct 2013 22:09:47 +0000 + +Rename patches to follow standard naming scheme. + +Signed-off-by: "Yann E. MORIN" +--- + ...d-pkgconfig-files.patch => rpi-userland-000-add-pkgconfig-files.patch} | 0 + ...erland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch} | 0 + 2 files changed, 0 insertions(+), 0 deletions(-) + rename package/rpi-userland/{rpi-userland-add-pkgconfig-files.patch => rpi-userland-000-add-pkgconfig-files.patch} (100%) + rename package/rpi-userland/{rpi-userland-makefiles-0001-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch => rpi-userland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch} (100%) + +diff --git a/package/rpi-userland/rpi-userland-add-pkgconfig-files.patch b/package/rpi-userland/rpi-userland-000-add-pkgconfig-files.patch +similarity index 100% +rename from package/rpi-userland/rpi-userland-add-pkgconfig-files.patch +rename to package/rpi-userland/rpi-userland-000-add-pkgconfig-files.patch +diff --git a/package/rpi-userland/rpi-userland-makefiles-0001-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch b/package/rpi-userland/rpi-userland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch +similarity index 100% +rename from package/rpi-userland/rpi-userland-makefiles-0001-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch +rename to package/rpi-userland/rpi-userland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch +-- +1.8.1.2 diff --git a/patchwork/tests/mail/0009-git-rename-with-diff.mbox b/patchwork/tests/mail/0009-git-rename-with-diff.mbox new file mode 100644 index 0000000..761cfc1 --- /dev/null +++ b/patchwork/tests/mail/0009-git-rename-with-diff.mbox @@ -0,0 +1,32 @@ +From: "Yann E. MORIN" +Subject: [Buildroot] [PATCH 01/11] package/rpi-userland: rename patches +Date: Tue, 8 Oct 2013 22:09:47 +0000 + +Rename patches to follow standard naming scheme. + +Signed-off-by: "Yann E. MORIN" +--- + ...d-pkgconfig-files.patch => rpi-userland-000-add-pkgconfig-files.patch} | 0 + ...erland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch} | 0 + 2 files changed, 0 insertions(+), 0 deletions(-) + rename package/rpi-userland/{rpi-userland-add-pkgconfig-files.patch => rpi-userland-000-add-pkgconfig-files.patch} (100%) + rename package/rpi-userland/{rpi-userland-makefiles-0001-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch => rpi-userland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch} (100%) + +diff --git a/package/rpi-userland/rpi-userland-add-pkgconfig-files.patch b/package/rpi-userland/rpi-userland-000-add-pkgconfig-files.patch +similarity index 100% +rename from package/rpi-userland/rpi-userland-add-pkgconfig-files.patch +rename to package/rpi-userland/rpi-userland-000-add-pkgconfig-files.patch +@@ -100,7 +100,7 @@ + a + a +-a ++b + c + c + c +diff --git a/package/rpi-userland/rpi-userland-makefiles-0001-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch b/package/rpi-userland/rpi-userland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch +similarity index 100% +rename from package/rpi-userland/rpi-userland-makefiles-0001-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch +rename to package/rpi-userland/rpi-userland-001-makefiles-cmake-vmcs.cmake-allow-to-override-VMCS_IN.patch +-- +1.8.1.2 diff --git a/patchwork/tests/mail/0010-invalid-charset.mbox b/patchwork/tests/mail/0010-invalid-charset.mbox new file mode 100644 index 0000000..10b369d --- /dev/null +++ b/patchwork/tests/mail/0010-invalid-charset.mbox @@ -0,0 +1,90 @@ +From libc-alpha-return-50517-siddhesh=redhat.com@sourceware.org Thu Jun 5 10:36:33 2014 +Received: (qmail 11948 invoked by alias); 4 Jun 2014 17:51:01 -0000 +Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm +List-Id: +Sender: libc-alpha-owner@sourceware.org +Date: Wed, 4 Jun 2014 17:50:46 +0000 +From: "Joseph S. Myers" +To: +Subject: Fix pow overflow in non-default rounding modes (bug 16315) +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="-1152306461-1522705971-1401904246=:3719" +Content-Length: 24171 + +---1152306461-1522705971-1401904246=:3719 +Content-Type: text/plain; charset="none" +Content-Transfer-Encoding: QUOTED-PRINTABLE + +This patch, relative to a tree with + applied, +fixes bug 16315, bad pow handling of overflow/underflow in non-default +rounding modes. Tests of pow are duly converted to ALL_RM_TEST to run +all tests in all rounding modes. + +There are two main issues here. First, various implementations +compute a negative result by negating a positive result, but this +yields inappropriate overflow / underflow values for directed +rounding, so either overflow / underflow results need recomputing in +the correct sign, or the relevant overflowing / underflowing operation +needs to be made to have a result of the correct sign. Second, the +dbl-64 implementation sets FE_TONEAREST internally; in the overflow / +underflow case, the result needs recomputing in the original rounding +mode. + +Tested x86_64 and x86 and ulps updated accordingly. + +(auto-libm-test-out diffs omitted below.) + +2014-06-04 Joseph Myers + +=09[BZ #16315] +=09* sysdeps/i386/fpu/e_pow.S (__ieee754_pow): Ensure possibly +=09overflowing or underflowing operations take place with sign of +=09result. +=09* sysdeps/i386/fpu/e_powf.S (__ieee754_powf): Likewise. +=09* sysdeps/i386/fpu/e_powl.S (__ieee754_powl): Likewise. +=09* sysdeps/ieee754/dbl-64/e_pow.c: Include . +=09(__ieee754_pow): Recompute overflowing and underflowing results in +=09original rounding mode. +=09* sysdeps/x86/fpu/powl_helper.c: Include . +=09(__powl_helper): Allow negative argument X and scale negated value +=09as needed. Avoid passing value outside [-1, 1] to f2xm1. +=09* sysdeps/x86_64/fpu/e_powl.S (__ieee754_powl): Ensure possibly +=09overflowing or underflowing operations take place with sign of +=09result. +=09* sysdeps/x86_64/fpu/multiarch/e_pow.c [HAVE_FMA4_SUPPORT]: +=09Include . +=09* math/auto-libm-test-in: Add more tests of pow. +=09* math/auto-libm-test-out: Regenerated. +=09* math/libm-test.inc (pow_test): Use ALL_RM_TEST. +=09(pow_tonearest_test_data): Remove. +=09(pow_test_tonearest): Likewise. +=09(pow_towardzero_test_data): Likewise. +=09(pow_test_towardzero): Likewise. +=09(pow_downward_test_data): Likewise. +=09(pow_test_downward): Likewise. +=09(pow_upward_test_data): Likewise. +=09(pow_test_upward): Likewise. +=09(main): Don't call removed functions. +=09* sysdeps/i386/fpu/libm-test-ulps: Update. +=09* sysdeps/x86_64/fpu/libm-test-ulps: Likewise. + +diff --git a/sysdeps/x86_64/fpu/multiarch/e_pow.c b/sysdeps/x86_64/fpu/mult= +iarch/e_pow.c +index a740b6c..433cce0 100644 +--- a/sysdeps/x86_64/fpu/multiarch/e_pow.c ++++ b/sysdeps/x86_64/fpu/multiarch/e_pow.c +@@ -1,5 +1,6 @@ + #ifdef HAVE_FMA4_SUPPORT + # include ++# include + # include +=20 + extern double __ieee754_pow_sse2 (double, double); + +--=20 +Joseph S. Myers +joseph@codesourcery.com +---1152306461-1522705971-1401904246=:3719-- diff --git a/patchwork/tests/mail/0011-no-newline-at-end-of-file.mbox b/patchwork/tests/mail/0011-no-newline-at-end-of-file.mbox new file mode 100644 index 0000000..3ed0597 --- /dev/null +++ b/patchwork/tests/mail/0011-no-newline-at-end-of-file.mbox @@ -0,0 +1,45 @@ +Subject: [PATCH v3 5/5] selftests, powerpc: Add test for VPHN +From: Greg Kurz +To: Michael Ellerman +Cc: Benjamin Herrenschmidt , + linuxppc-dev@lists.ozlabs.org +Date: Mon, 23 Feb 2015 16:14:44 +0100 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit + +The goal is to verify vphn_unpack_associativity() parses VPHN numbers +correctly. We feed it with a variety of input values and compare with +expected results. + +diff --git a/tools/testing/selftests/powerpc/Makefile b/tools/testing/selftests/powerpc/Makefile +index 1d5e7ad..476b8dd 100644 +--- a/tools/testing/selftests/powerpc/Makefile ++++ b/tools/testing/selftests/powerpc/Makefile +@@ -13,7 +13,7 @@ CFLAGS := -Wall -O2 -flto -Wall -Werror -DGIT_VERSION='"$(GIT_VERSION)"' -I$(CUR + + export CC CFLAGS + +-TARGETS = pmu copyloops mm tm primitives stringloops ++TARGETS = pmu copyloops mm tm primitives stringloops vphn + + endif + +diff --git a/tools/testing/selftests/powerpc/vphn/vphn.c b/tools/testing/selftests/powerpc/vphn/vphn.c +new file mode 120000 +index 0000000..186b906 +--- /dev/null ++++ b/tools/testing/selftests/powerpc/vphn/vphn.c +@@ -0,0 +1 @@ ++../../../../../arch/powerpc/mm/vphn.c +\ No newline at end of file +diff --git a/tools/testing/selftests/powerpc/vphn/vphn.h b/tools/testing/selftests/powerpc/vphn/vphn.h +new file mode 120000 +index 0000000..7131efe +--- /dev/null ++++ b/tools/testing/selftests/powerpc/vphn/vphn.h +@@ -0,0 +1 @@ ++../../../../../arch/powerpc/mm/vphn.h +\ No newline at end of file + + diff --git a/patchwork/tests/patches/0001-add-line.patch b/patchwork/tests/patches/0001-add-line.patch new file mode 100644 index 0000000..c6cb9f1 --- /dev/null +++ b/patchwork/tests/patches/0001-add-line.patch @@ -0,0 +1,7 @@ +diff --git a/meep.text b/meep.text +index 3d75d48..a57f4dd 100644 +--- a/meep.text ++++ b/meep.text +@@ -1,1 +1,2 @@ + meep ++meep diff --git a/patchwork/tests/patches/0002-utf-8.patch b/patchwork/tests/patches/0002-utf-8.patch new file mode 100644 index 0000000..71a2f24 --- /dev/null +++ b/patchwork/tests/patches/0002-utf-8.patch @@ -0,0 +1,7 @@ +diff --git a/meep.text b/meep.text +index 3d75d48..a57f4dd 100644 +--- a/meep.text ++++ b/meep.text +@@ -1,1 +1,2 @@ + meep ++meëp diff --git a/patchwork/tests/test_bundles.py b/patchwork/tests/test_bundles.py new file mode 100644 index 0000000..38f3a2c --- /dev/null +++ b/patchwork/tests/test_bundles.py @@ -0,0 +1,646 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2009 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import unittest +import datetime +from django.test import TestCase +from django.test.client import Client +from django.utils.http import urlencode +from django.conf import settings +from patchwork.models import Patch, Bundle, BundlePatch, Person +from patchwork.tests.utils import defaults, create_user, find_in_context + +def bundle_url(bundle): + return '/bundle/%s/%s/' % (bundle.owner.username, bundle.name) + +class BundleListTest(TestCase): + def setUp(self): + self.user = create_user() + self.client.login(username = self.user.username, + password = self.user.username) + + def testNoBundles(self): + response = self.client.get('/user/bundles/') + self.failUnlessEqual(response.status_code, 200) + self.failUnlessEqual( + len(find_in_context(response.context, 'bundles')), 0) + + def testSingleBundle(self): + defaults.project.save() + bundle = Bundle(owner = self.user, project = defaults.project) + bundle.save() + response = self.client.get('/user/bundles/') + self.failUnlessEqual(response.status_code, 200) + self.failUnlessEqual( + len(find_in_context(response.context, 'bundles')), 1) + + def tearDown(self): + self.user.delete() + +class BundleTestBase(TestCase): + def setUp(self, patch_count=3): + patch_names = ['testpatch%d' % (i) for i in range(1, patch_count+1)] + self.user = create_user() + self.client.login(username = self.user.username, + password = self.user.username) + defaults.project.save() + self.bundle = Bundle(owner = self.user, project = defaults.project, + name = 'testbundle') + self.bundle.save() + self.patches = [] + + for patch_name in patch_names: + patch = Patch(project = defaults.project, + msgid = patch_name, name = patch_name, + submitter = Person.objects.get(user = self.user), + content = '') + patch.save() + self.patches.append(patch) + + def tearDown(self): + for patch in self.patches: + patch.delete() + self.bundle.delete() + self.user.delete() + +class BundleViewTest(BundleTestBase): + + def testEmptyBundle(self): + response = self.client.get(bundle_url(self.bundle)) + self.failUnlessEqual(response.status_code, 200) + page = find_in_context(response.context, 'page') + self.failUnlessEqual(len(page.object_list), 0) + + def testNonEmptyBundle(self): + self.bundle.append_patch(self.patches[0]) + + response = self.client.get(bundle_url(self.bundle)) + self.failUnlessEqual(response.status_code, 200) + page = find_in_context(response.context, 'page') + self.failUnlessEqual(len(page.object_list), 1) + + def testBundleOrder(self): + for patch in self.patches: + self.bundle.append_patch(patch) + + response = self.client.get(bundle_url(self.bundle)) + + pos = 0 + for patch in self.patches: + next_pos = response.content.find(patch.name) + # ensure that this patch is after the previous + self.failUnless(next_pos > pos) + pos = next_pos + + # reorder and recheck + i = 0 + for patch in self.patches.__reversed__(): + bundlepatch = BundlePatch.objects.get(bundle = self.bundle, + patch = patch) + bundlepatch.order = i + bundlepatch.save() + i += 1 + + response = self.client.get(bundle_url(self.bundle)) + pos = len(response.content) + for patch in self.patches: + next_pos = response.content.find(patch.name) + # ensure that this patch is now *before* the previous + self.failUnless(next_pos < pos) + pos = next_pos + +class BundleUpdateTest(BundleTestBase): + + def setUp(self): + super(BundleUpdateTest, self).setUp() + self.newname = 'newbundlename' + + def checkPatchformErrors(self, response): + formname = 'patchform' + if not formname in response.context: + return + form = response.context[formname] + if not form: + return + self.assertEquals(form.errors, {}) + + def publicString(self, public): + if public: + return 'on' + return '' + + def testNoAction(self): + data = { + 'form': 'bundle', + 'name': self.newname, + 'public': self.publicString(not self.bundle.public) + } + response = self.client.post(bundle_url(self.bundle), data) + self.assertEqual(response.status_code, 200) + + bundle = Bundle.objects.get(pk = self.bundle.pk) + self.assertEqual(bundle.name, self.bundle.name) + self.assertEqual(bundle.public, self.bundle.public) + + def testUpdateName(self): + newname = 'newbundlename' + data = { + 'form': 'bundle', + 'action': 'update', + 'name': newname, + 'public': self.publicString(self.bundle.public) + } + response = self.client.post(bundle_url(self.bundle), data) + bundle = Bundle.objects.get(pk = self.bundle.pk) + self.assertRedirects(response, bundle_url(bundle)) + self.assertEqual(bundle.name, newname) + self.assertEqual(bundle.public, self.bundle.public) + + def testUpdatePublic(self): + newname = 'newbundlename' + data = { + 'form': 'bundle', + 'action': 'update', + 'name': self.bundle.name, + 'public': self.publicString(not self.bundle.public) + } + response = self.client.post(bundle_url(self.bundle), data) + self.assertEqual(response.status_code, 200) + bundle = Bundle.objects.get(pk = self.bundle.pk) + self.assertEqual(bundle.name, self.bundle.name) + self.assertEqual(bundle.public, not self.bundle.public) + + # check other forms for errors + self.checkPatchformErrors(response) + +class BundleMaintainerUpdateTest(BundleUpdateTest): + + def setUp(self): + super(BundleMaintainerUpdateTest, self).setUp() + profile = self.user.profile + profile.maintainer_projects.add(defaults.project) + profile.save() + +class BundlePublicViewTest(BundleTestBase): + + def setUp(self): + super(BundlePublicViewTest, self).setUp() + self.client.logout() + self.bundle.append_patch(self.patches[0]) + self.url = bundle_url(self.bundle) + + def testPublicBundle(self): + self.bundle.public = True + self.bundle.save() + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.patches[0].name) + + def testPrivateBundle(self): + self.bundle.public = False + self.bundle.save() + response = self.client.get(self.url) + self.assertEqual(response.status_code, 404) + +class BundlePublicViewMboxTest(BundlePublicViewTest): + def setUp(self): + super(BundlePublicViewMboxTest, self).setUp() + self.url = bundle_url(self.bundle) + "mbox/" + +class BundlePublicModifyTest(BundleTestBase): + """Ensure that non-owners can't modify bundles""" + + def setUp(self): + super(BundlePublicModifyTest, self).setUp() + self.bundle.public = True + self.bundle.save() + self.other_user = create_user() + + def testBundleFormPresence(self): + """Check for presence of the modify form on the bundle""" + self.client.login(username = self.other_user.username, + password = self.other_user.username) + response = self.client.get(bundle_url(self.bundle)) + self.assertNotContains(response, 'name="form" value="bundle"') + self.assertNotContains(response, 'Change order') + + def testBundleFormSubmission(self): + oldname = 'oldbundlename' + newname = 'newbundlename' + data = { + 'form': 'bundle', + 'action': 'update', + 'name': newname, + } + self.bundle.name = oldname + self.bundle.save() + + # first, check that we can modify with the owner + self.client.login(username = self.user.username, + password = self.user.username) + response = self.client.post(bundle_url(self.bundle), data) + self.bundle = Bundle.objects.get(pk = self.bundle.pk) + self.assertEqual(self.bundle.name, newname) + + # reset bundle name + self.bundle.name = oldname + self.bundle.save() + + # log in with a different user, and check that we can no longer modify + self.client.login(username = self.other_user.username, + password = self.other_user.username) + response = self.client.post(bundle_url(self.bundle), data) + self.bundle = Bundle.objects.get(pk = self.bundle.pk) + self.assertNotEqual(self.bundle.name, newname) + +class BundleCreateFromListTest(BundleTestBase): + def testCreateEmptyBundle(self): + newbundlename = 'testbundle-new' + params = {'form': 'patchlistform', + 'bundle_name': newbundlename, + 'action': 'Create', + 'project': defaults.project.id} + + response = self.client.post( + '/project/%s/list/' % defaults.project.linkname, + params) + + self.assertContains(response, 'Bundle %s created' % newbundlename) + + def testCreateNonEmptyBundle(self): + newbundlename = 'testbundle-new' + patch = self.patches[0] + + params = {'form': 'patchlistform', + 'bundle_name': newbundlename, + 'action': 'Create', + 'project': defaults.project.id, + 'patch_id:%d' % patch.id: 'checked'} + + response = self.client.post( + '/project/%s/list/' % defaults.project.linkname, + params) + + self.assertContains(response, 'Bundle %s created' % newbundlename) + self.assertContains(response, 'added to bundle %s' % newbundlename, + count = 1) + + bundle = Bundle.objects.get(name = newbundlename) + self.failUnlessEqual(bundle.patches.count(), 1) + self.failUnlessEqual(bundle.patches.all()[0], patch) + + def testCreateNonEmptyBundleEmptyName(self): + newbundlename = 'testbundle-new' + patch = self.patches[0] + + n_bundles = Bundle.objects.count() + + params = {'form': 'patchlistform', + 'bundle_name': '', + 'action': 'Create', + 'project': defaults.project.id, + 'patch_id:%d' % patch.id: 'checked'} + + response = self.client.post( + '/project/%s/list/' % defaults.project.linkname, + params) + + self.assertContains(response, 'No bundle name was specified', + status_code = 200) + + # test that no new bundles are present + self.failUnlessEqual(n_bundles, Bundle.objects.count()) + + def testCreateDuplicateName(self): + newbundlename = 'testbundle-dup' + patch = self.patches[0] + + params = {'form': 'patchlistform', + 'bundle_name': newbundlename, + 'action': 'Create', + 'project': defaults.project.id, + 'patch_id:%d' % patch.id: 'checked'} + + response = self.client.post( + '/project/%s/list/' % defaults.project.linkname, + params) + + n_bundles = Bundle.objects.count() + self.assertContains(response, 'Bundle %s created' % newbundlename) + self.assertContains(response, 'added to bundle %s' % newbundlename, + count = 1) + + bundle = Bundle.objects.get(name = newbundlename) + self.failUnlessEqual(bundle.patches.count(), 1) + self.failUnlessEqual(bundle.patches.all()[0], patch) + + response = self.client.post( + '/project/%s/list/' % defaults.project.linkname, + params) + + self.assertNotContains(response, 'Bundle %s created' % newbundlename) + self.assertContains(response, 'You already have a bundle called') + self.assertEqual(Bundle.objects.count(), n_bundles) + self.assertEqual(bundle.patches.count(), 1) + +class BundleCreateFromPatchTest(BundleTestBase): + def testCreateNonEmptyBundle(self): + newbundlename = 'testbundle-new' + patch = self.patches[0] + + params = {'name': newbundlename, + 'action': 'createbundle'} + + response = self.client.post('/patch/%d/' % patch.id, params) + + self.assertContains(response, + 'Bundle %s created' % newbundlename) + + bundle = Bundle.objects.get(name = newbundlename) + self.failUnlessEqual(bundle.patches.count(), 1) + self.failUnlessEqual(bundle.patches.all()[0], patch) + + def testCreateWithExistingName(self): + newbundlename = self.bundle.name + patch = self.patches[0] + + params = {'name': newbundlename, + 'action': 'createbundle'} + + response = self.client.post('/patch/%d/' % patch.id, params) + + self.assertContains(response, + 'A bundle called %s already exists' % newbundlename) + + count = Bundle.objects.count() + self.failUnlessEqual(Bundle.objects.count(), 1) + +class BundleAddFromListTest(BundleTestBase): + def testAddToEmptyBundle(self): + patch = self.patches[0] + params = {'form': 'patchlistform', + 'action': 'Add', + 'project': defaults.project.id, + 'bundle_id': self.bundle.id, + 'patch_id:%d' % patch.id: 'checked'} + + response = self.client.post( + '/project/%s/list/' % defaults.project.linkname, + params) + + self.assertContains(response, 'added to bundle %s' % self.bundle.name, + count = 1) + + self.failUnlessEqual(self.bundle.patches.count(), 1) + self.failUnlessEqual(self.bundle.patches.all()[0], patch) + + def testAddToNonEmptyBundle(self): + self.bundle.append_patch(self.patches[0]) + patch = self.patches[1] + params = {'form': 'patchlistform', + 'action': 'Add', + 'project': defaults.project.id, + 'bundle_id': self.bundle.id, + 'patch_id:%d' % patch.id: 'checked'} + + response = self.client.post( + '/project/%s/list/' % defaults.project.linkname, + params) + + self.assertContains(response, 'added to bundle %s' % self.bundle.name, + count = 1) + + self.failUnlessEqual(self.bundle.patches.count(), 2) + self.failUnless(self.patches[0] in self.bundle.patches.all()) + self.failUnless(self.patches[1] in self.bundle.patches.all()) + + # check order + bps = [ BundlePatch.objects.get(bundle = self.bundle, + patch = self.patches[i]) \ + for i in [0, 1] ] + self.failUnless(bps[0].order < bps[1].order) + + def testAddDuplicate(self): + self.bundle.append_patch(self.patches[0]) + count = self.bundle.patches.count() + patch = self.patches[0] + + params = {'form': 'patchlistform', + 'action': 'Add', + 'project': defaults.project.id, + 'bundle_id': self.bundle.id, + 'patch_id:%d' % patch.id: 'checked'} + + response = self.client.post( + '/project/%s/list/' % defaults.project.linkname, + params) + + self.assertContains(response, 'Patch '%s' already in bundle' \ + % patch.name, count = 1, status_code = 200) + + self.assertEquals(count, self.bundle.patches.count()) + + def testAddNewAndDuplicate(self): + self.bundle.append_patch(self.patches[0]) + count = self.bundle.patches.count() + patch = self.patches[0] + + params = {'form': 'patchlistform', + 'action': 'Add', + 'project': defaults.project.id, + 'bundle_id': self.bundle.id, + 'patch_id:%d' % patch.id: 'checked', + 'patch_id:%d' % self.patches[1].id: 'checked'} + + response = self.client.post( + '/project/%s/list/' % defaults.project.linkname, + params) + + self.assertContains(response, 'Patch '%s' already in bundle' \ + % patch.name, count = 1, status_code = 200) + self.assertContains(response, 'Patch '%s' added to bundle' \ + % self.patches[1].name, count = 1, + status_code = 200) + self.assertEquals(count + 1, self.bundle.patches.count()) + +class BundleAddFromPatchTest(BundleTestBase): + def testAddToEmptyBundle(self): + patch = self.patches[0] + params = {'action': 'addtobundle', + 'bundle_id': self.bundle.id} + + response = self.client.post('/patch/%d/' % patch.id, params) + + self.assertContains(response, + 'added to bundle "%s"' % self.bundle.name, + count = 1) + + self.failUnlessEqual(self.bundle.patches.count(), 1) + self.failUnlessEqual(self.bundle.patches.all()[0], patch) + + def testAddToNonEmptyBundle(self): + self.bundle.append_patch(self.patches[0]) + patch = self.patches[1] + params = {'action': 'addtobundle', + 'bundle_id': self.bundle.id} + + response = self.client.post('/patch/%d/' % patch.id, params) + + self.assertContains(response, + 'added to bundle "%s"' % self.bundle.name, + count = 1) + + self.failUnlessEqual(self.bundle.patches.count(), 2) + self.failUnless(self.patches[0] in self.bundle.patches.all()) + self.failUnless(self.patches[1] in self.bundle.patches.all()) + + # check order + bps = [ BundlePatch.objects.get(bundle = self.bundle, + patch = self.patches[i]) \ + for i in [0, 1] ] + self.failUnless(bps[0].order < bps[1].order) + +class BundleInitialOrderTest(BundleTestBase): + """When creating bundles from a patch list, ensure that the patches in the + bundle are ordered by date""" + + def setUp(self): + super(BundleInitialOrderTest, self).setUp(5) + + # put patches in an arbitrary order + idxs = [2, 4, 3, 1, 0] + self.patches = [ self.patches[i] for i in idxs ] + + # set dates to be sequential + last_patch = self.patches[0] + for patch in self.patches[1:]: + patch.date = last_patch.date + datetime.timedelta(0, 1) + patch.save() + last_patch = patch + + def _testOrder(self, ids, expected_order): + newbundlename = 'testbundle-new' + + # need to define our querystring explicity to enforce ordering + params = {'form': 'patchlistform', + 'bundle_name': newbundlename, + 'action': 'Create', + 'project': defaults.project.id, + } + + data = urlencode(params) + \ + ''.join([ '&patch_id:%d=checked' % i for i in ids ]) + + response = self.client.post( + '/project/%s/list/' % defaults.project.linkname, + data = data, + content_type = 'application/x-www-form-urlencoded', + ) + + self.assertContains(response, 'Bundle %s created' % newbundlename) + self.assertContains(response, 'added to bundle %s' % newbundlename, + count = 5) + + bundle = Bundle.objects.get(name = newbundlename) + + # BundlePatches should be sorted by .order by default + bps = BundlePatch.objects.filter(bundle = bundle) + + for (bp, p) in zip(bps, expected_order): + self.assertEqual(bp.patch.pk, p.pk) + + bundle.delete() + + def testBundleForwardOrder(self): + ids = map(lambda p: p.id, self.patches) + self._testOrder(ids, self.patches) + + def testBundleReverseOrder(self): + ids = map(lambda p: p.id, self.patches) + ids.reverse() + self._testOrder(ids, self.patches) + +class BundleReorderTest(BundleTestBase): + def setUp(self): + super(BundleReorderTest, self).setUp(5) + for i in range(5): + self.bundle.append_patch(self.patches[i]) + + def checkReordering(self, neworder, start, end): + neworder_ids = [ self.patches[i].id for i in neworder ] + + firstpatch = BundlePatch.objects.get(bundle = self.bundle, + patch = self.patches[start]).patch + + slice_ids = neworder_ids[start:end] + params = {'form': 'reorderform', + 'order_start': firstpatch.id, + 'neworder': slice_ids} + + response = self.client.post(bundle_url(self.bundle), params) + + self.failUnlessEqual(response.status_code, 200) + + bps = BundlePatch.objects.filter(bundle = self.bundle) \ + .order_by('order') + + # check if patch IDs are in the expected order: + bundle_ids = [ bp.patch.id for bp in bps ] + self.failUnlessEqual(neworder_ids, bundle_ids) + + # check if order field is still sequential: + order_numbers = [ bp.order for bp in bps ] + expected_order = range(1, len(neworder)+1) # [1 ... len(neworder)] + self.failUnlessEqual(order_numbers, expected_order) + + def testBundleReorderAll(self): + # reorder all patches: + self.checkReordering([2,1,4,0,3], 0, 5) + + def testBundleReorderEnd(self): + # reorder only the last three patches + self.checkReordering([0,1,3,2,4], 2, 5) + + def testBundleReorderBegin(self): + # reorder only the first three patches + self.checkReordering([2,0,1,3,4], 0, 3) + + def testBundleReorderMiddle(self): + # reorder only 2nd, 3rd, and 4th patches + self.checkReordering([0,2,3,1,4], 1, 4) + +class BundleRedirTest(BundleTestBase): + # old URL: private bundles used to be under /user/bundle/ + + def setUp(self): + super(BundleRedirTest, self).setUp() + + @unittest.skipIf(not settings.COMPAT_REDIR, "compat redirections disabled") + def testBundleRedir(self): + url = '/user/bundle/%d/' % self.bundle.id + response = self.client.get(url) + self.assertRedirects(response, bundle_url(self.bundle)) + + @unittest.skipIf(not settings.COMPAT_REDIR, "compat redirections disabled") + def testMboxRedir(self): + url = '/user/bundle/%d/mbox/' % self.bundle.id + response = self.client.get(url) + self.assertRedirects(response,'/bundle/%s/%s/mbox/' % + (self.bundle.owner.username, + self.bundle.name)) diff --git a/patchwork/tests/test_confirm.py b/patchwork/tests/test_confirm.py new file mode 100644 index 0000000..fad5125 --- /dev/null +++ b/patchwork/tests/test_confirm.py @@ -0,0 +1,67 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2011 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import unittest +from django.test import TestCase +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from patchwork.models import EmailConfirmation, Person + +def _confirmation_url(conf): + return reverse('patchwork.views.confirm', kwargs = {'key': conf.key}) + +class TestUser(object): + username = 'testuser' + email = 'test@example.com' + secondary_email = 'test2@example.com' + password = None + + def __init__(self): + self.password = User.objects.make_random_password() + self.user = User.objects.create_user(self.username, + self.email, self.password) + +class InvalidConfirmationTest(TestCase): + def setUp(self): + EmailConfirmation.objects.all().delete() + Person.objects.all().delete() + self.user = TestUser() + self.conf = EmailConfirmation(type = 'userperson', + email = self.user.secondary_email, + user = self.user.user) + self.conf.save() + + def testInactiveConfirmation(self): + self.conf.active = False + self.conf.save() + response = self.client.get(_confirmation_url(self.conf)) + self.assertEquals(response.status_code, 200) + self.assertTemplateUsed(response, 'patchwork/confirm-error.html') + self.assertEqual(response.context['error'], 'inactive') + self.assertEqual(response.context['conf'], self.conf) + + def testExpiredConfirmation(self): + self.conf.date -= self.conf.validity + self.conf.save() + response = self.client.get(_confirmation_url(self.conf)) + self.assertEquals(response.status_code, 200) + self.assertTemplateUsed(response, 'patchwork/confirm-error.html') + self.assertEqual(response.context['error'], 'expired') + self.assertEqual(response.context['conf'], self.conf) + diff --git a/patchwork/tests/test_encodings.py b/patchwork/tests/test_encodings.py new file mode 100644 index 0000000..b9032bb --- /dev/null +++ b/patchwork/tests/test_encodings.py @@ -0,0 +1,87 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import unittest +import os +import time +from patchwork.models import Patch, Person +from patchwork.tests.utils import defaults, read_patch +from django.test import TestCase +from django.test.client import Client + +class UTF8PatchViewTest(TestCase): + patch_filename = '0002-utf-8.patch' + patch_encoding = 'utf-8' + + def setUp(self): + defaults.project.save() + defaults.patch_author_person.save() + self.patch_content = read_patch(self.patch_filename, + encoding = self.patch_encoding) + self.patch = Patch(project = defaults.project, + msgid = 'x', name = defaults.patch_name, + submitter = defaults.patch_author_person, + content = self.patch_content) + self.patch.save() + self.client = Client() + + def testPatchView(self): + response = self.client.get('/patch/%d/' % self.patch.id) + self.assertContains(response, self.patch.name) + + def testMboxView(self): + response = self.client.get('/patch/%d/mbox/' % self.patch.id) + self.assertEquals(response.status_code, 200) + self.assertTrue(self.patch.content in \ + response.content.decode(self.patch_encoding)) + + def testRawView(self): + response = self.client.get('/patch/%d/raw/' % self.patch.id) + self.assertEquals(response.status_code, 200) + self.assertEquals(response.content.decode(self.patch_encoding), + self.patch.content) + + def tearDown(self): + self.patch.delete() + defaults.patch_author_person.delete() + defaults.project.delete() + +class UTF8HeaderPatchViewTest(UTF8PatchViewTest): + patch_filename = '0002-utf-8.patch' + patch_encoding = 'utf-8' + patch_author_name = u'P\xe4tch Author' + + def setUp(self): + defaults.project.save() + self.patch_author = Person(name = self.patch_author_name, + email = defaults.patch_author_person.email) + self.patch_author.save() + self.patch_content = read_patch(self.patch_filename, + encoding = self.patch_encoding) + self.patch = Patch(project = defaults.project, + msgid = 'x', name = defaults.patch_name, + submitter = self.patch_author, + content = self.patch_content) + self.patch.save() + self.client = Client() + + def tearDown(self): + self.patch.delete() + self.patch_author.delete() + defaults.project.delete() diff --git a/patchwork/tests/test_expiry.py b/patchwork/tests/test_expiry.py new file mode 100644 index 0000000..844ed4b --- /dev/null +++ b/patchwork/tests/test_expiry.py @@ -0,0 +1,121 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2014 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import unittest +import datetime +from django.test import TestCase +from django.contrib.auth.models import User +from patchwork.models import EmailConfirmation, Person, Patch +from patchwork.tests.utils import create_user, defaults +from patchwork.utils import do_expiry + +class TestRegistrationExpiry(TestCase): + + def register(self, date): + user = create_user() + user.is_active = False + user.date_joined = user.last_login = date + user.save() + + conf = EmailConfirmation(type='registration', user=user, + email=user.email) + conf.date = date + conf.save() + + return (user, conf) + + def testOldRegistrationExpiry(self): + date = ((datetime.datetime.now() - EmailConfirmation.validity) - + datetime.timedelta(hours = 1)) + (user, conf) = self.register(date) + + do_expiry() + + self.assertFalse(User.objects.filter(pk = user.pk).exists()) + self.assertFalse(EmailConfirmation.objects.filter(pk = conf.pk) + .exists()) + + + def testRecentRegistrationExpiry(self): + date = ((datetime.datetime.now() - EmailConfirmation.validity) + + datetime.timedelta(hours = 1)) + (user, conf) = self.register(date) + + do_expiry() + + self.assertTrue(User.objects.filter(pk = user.pk).exists()) + self.assertTrue(EmailConfirmation.objects.filter(pk = conf.pk) + .exists()) + + def testInactiveRegistrationExpiry(self): + (user, conf) = self.register(datetime.datetime.now()) + + # confirm registration + conf.user.is_active = True + conf.user.save() + conf.deactivate() + + do_expiry() + + self.assertTrue(User.objects.filter(pk = user.pk).exists()) + self.assertFalse(EmailConfirmation.objects.filter(pk = conf.pk) + .exists()) + + def testPatchSubmitterExpiry(self): + defaults.project.save() + defaults.patch_author_person.save() + + # someone submits a patch... + patch = Patch(project = defaults.project, + msgid = 'test@example.com', name = 'test patch', + submitter = defaults.patch_author_person, + content = defaults.patch) + patch.save() + + # ... then starts registration... + date = ((datetime.datetime.now() - EmailConfirmation.validity) - + datetime.timedelta(hours = 1)) + userid = 'test-user' + user = User.objects.create_user(userid, + defaults.patch_author_person.email, userid) + user.is_active = False + user.date_joined = user.last_login = date + user.save() + + self.assertEqual(user.email, patch.submitter.email) + + conf = EmailConfirmation(type='registration', user=user, + email=user.email) + conf.date = date + conf.save() + + # ... which expires + do_expiry() + + # we should see no matching user + self.assertFalse(User.objects.filter(email = patch.submitter.email) + .exists()) + # but the patch and person should still be present + self.assertTrue(Person.objects.filter( + pk = defaults.patch_author_person.pk).exists()) + self.assertTrue(Patch.objects.filter(pk = patch.pk).exists()) + + # and there should be no user associated with the person + self.assertEqual(Person.objects.get(pk = + defaults.patch_author_person.pk).user, None) diff --git a/patchwork/tests/test_filters.py b/patchwork/tests/test_filters.py new file mode 100644 index 0000000..2c464e5 --- /dev/null +++ b/patchwork/tests/test_filters.py @@ -0,0 +1,45 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2011 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import unittest +from django.test import TestCase +from django.test.client import Client +from patchwork.tests.utils import defaults, create_user, find_in_context + +class FilterQueryStringTest(TestCase): + def testFilterQSEscaping(self): + """test that filter fragments in a query string are properly escaped, + and stray ampersands don't get reflected back in the filter + links""" + project = defaults.project + defaults.project.save() + url = '/project/%s/list/?submitter=a%%26b=c' % project.linkname + response = self.client.get(url) + self.failUnlessEqual(response.status_code, 200) + self.failIf('submitter=a&b=c' in response.content) + self.failIf('submitter=a&b=c' in response.content) + + def testUTF8QSHandling(self): + """test that non-ascii characters can be handled by the filter + code""" + project = defaults.project + defaults.project.save() + url = '/project/%s/list/?submitter=%%E2%%98%%83' % project.linkname + response = self.client.get(url) + self.failUnlessEqual(response.status_code, 200) diff --git a/patchwork/tests/test_list.py b/patchwork/tests/test_list.py new file mode 100644 index 0000000..a795a5f --- /dev/null +++ b/patchwork/tests/test_list.py @@ -0,0 +1,116 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2012 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import unittest +import random +import datetime +import string +import re +from django.test import TestCase +from django.test.client import Client +from patchwork.tests.utils import defaults, create_user, find_in_context +from patchwork.models import Person, Patch +from django.core.urlresolvers import reverse + +class EmptyPatchListTest(TestCase): + + def testEmptyPatchList(self): + """test that we don't output an empty table when there are no + patches present""" + project = defaults.project + defaults.project.save() + url = reverse('patchwork.views.patch.list', + kwargs={'project_id': project.linkname}) + response = self.client.get(url) + self.assertContains(response, 'No patches to display') + self.assertNotContains(response, 'tbody') + +class PatchOrderTest(TestCase): + + d = datetime.datetime + patchmeta = [ + ('AlCMyjOsx', 'AlxMyjOsx@nRbqkQV.wBw', d(2014,3,16,13, 4,50, 155643)), + ('MMZnrcDjT', 'MMmnrcDjT@qGaIfOl.tbk', d(2014,1,25,13, 4,50, 162814)), + ('WGirwRXgK', 'WGSrwRXgK@TriIETY.GhE', d(2014,2,14,13, 4,50, 169305)), + ('isjNIuiAc', 'issNIuiAc@OsEirYx.EJh', d(2014,3,15,13, 4,50, 176264)), + ('XkAQpYGws', 'XkFQpYGws@hzntTcm.JSE', d(2014,1,18,13, 4,50, 182493)), + ('uJuCPWMvi', 'uJACPWMvi@AVRBOBl.ecy', d(2014,3,12,13, 4,50, 189554)), + ('TyQmWtcbg', 'TylmWtcbg@DzrNeNH.JuB', d(2014,2, 3,13, 4,50, 195685)), + ('FpvAhWRdX', 'FpKAhWRdX@agxnCAI.wFO', d(2014,3,15,13, 4,50, 201398)), + ('bmoYvnyWa', 'bmdYvnyWa@aeoPnlX.juy', d(2014,3, 4,13, 4,50, 206800)), + ('CiReUQsAq', 'CiieUQsAq@DnOYRuf.TTI', d(2014,3,28,13, 4,50, 212169)), + ] + + def setUp(self): + defaults.project.save() + + for (name, email, date) in self.patchmeta: + patch_name = 'testpatch' + name + person = Person(name = name, email = email) + person.save() + patch = Patch(project = defaults.project, msgid = patch_name, + submitter = person, content = '', date = date) + patch.save() + + def _extract_patch_ids(self, response): + id_re = re.compile(' +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import unittest +import re +from django.test import TestCase +from django.test.client import Client +from django.core import mail +from django.core.urlresolvers import reverse +from django.contrib.auth.models import User +from patchwork.models import EmailOptout, EmailConfirmation, Person +from patchwork.tests.utils import create_user, error_strings + +class MailSettingsTest(TestCase): + view = 'patchwork.views.mail.settings' + url = reverse(view) + + def testMailSettingsGET(self): + response = self.client.get(self.url) + self.assertEquals(response.status_code, 200) + self.assertTrue(response.context['form']) + + def testMailSettingsPOST(self): + email = u'foo@example.com' + response = self.client.post(self.url, {'email': email}) + self.assertEquals(response.status_code, 200) + self.assertTemplateUsed(response, 'patchwork/mail-settings.html') + self.assertEquals(response.context['email'], email) + + def testMailSettingsPOSTEmpty(self): + response = self.client.post(self.url, {'email': ''}) + self.assertEquals(response.status_code, 200) + self.assertTemplateUsed(response, 'patchwork/mail-form.html') + self.assertFormError(response, 'form', 'email', + 'This field is required.') + + def testMailSettingsPOSTInvalid(self): + response = self.client.post(self.url, {'email': 'foo'}) + self.assertEquals(response.status_code, 200) + self.assertTemplateUsed(response, 'patchwork/mail-form.html') + self.assertFormError(response, 'form', 'email', error_strings['email']) + + def testMailSettingsPOSTOptedIn(self): + email = u'foo@example.com' + response = self.client.post(self.url, {'email': email}) + self.assertEquals(response.status_code, 200) + self.assertTemplateUsed(response, 'patchwork/mail-settings.html') + self.assertEquals(response.context['is_optout'], False) + self.assertTrue('may' in response.content) + optout_url = reverse('patchwork.views.mail.optout') + self.assertTrue(('action="%s"' % optout_url) in response.content) + + def testMailSettingsPOSTOptedOut(self): + email = u'foo@example.com' + EmailOptout(email = email).save() + response = self.client.post(self.url, {'email': email}) + self.assertEquals(response.status_code, 200) + self.assertTemplateUsed(response, 'patchwork/mail-settings.html') + self.assertEquals(response.context['is_optout'], True) + self.assertTrue('may not' in response.content) + optin_url = reverse('patchwork.views.mail.optin') + self.assertTrue(('action="%s"' % optin_url) in response.content) + +class OptoutRequestTest(TestCase): + view = 'patchwork.views.mail.optout' + url = reverse(view) + + def testOptOutRequestGET(self): + response = self.client.get(self.url) + self.assertRedirects(response, reverse('patchwork.views.mail.settings')) + + def testOptoutRequestValidPOST(self): + email = u'foo@example.com' + response = self.client.post(self.url, {'email': email}) + + # check for a confirmation object + self.assertEquals(EmailConfirmation.objects.count(), 1) + conf = EmailConfirmation.objects.get(email = email) + + # check confirmation page + self.assertEquals(response.status_code, 200) + self.assertEquals(response.context['confirmation'], conf) + self.assertTrue(email in response.content) + + # check email + url = reverse('patchwork.views.confirm', kwargs = {'key': conf.key}) + self.assertEquals(len(mail.outbox), 1) + msg = mail.outbox[0] + self.assertEquals(msg.to, [email]) + self.assertEquals(msg.subject, 'Patchwork opt-out confirmation') + self.assertTrue(url in msg.body) + + def testOptoutRequestInvalidPOSTEmpty(self): + response = self.client.post(self.url, {'email': ''}) + self.assertEquals(response.status_code, 200) + self.assertFormError(response, 'form', 'email', + 'This field is required.') + self.assertTrue(response.context['error']) + self.assertTrue('email_sent' not in response.context) + self.assertEquals(len(mail.outbox), 0) + + def testOptoutRequestInvalidPOSTNonEmail(self): + response = self.client.post(self.url, {'email': 'foo'}) + self.assertEquals(response.status_code, 200) + self.assertFormError(response, 'form', 'email', error_strings['email']) + self.assertTrue(response.context['error']) + self.assertTrue('email_sent' not in response.context) + self.assertEquals(len(mail.outbox), 0) + +class OptoutTest(TestCase): + view = 'patchwork.views.mail.optout' + url = reverse(view) + + def setUp(self): + self.email = u'foo@example.com' + self.conf = EmailConfirmation(type = 'optout', email = self.email) + self.conf.save() + + def testOptoutValidHash(self): + url = reverse('patchwork.views.confirm', + kwargs = {'key': self.conf.key}) + response = self.client.get(url) + + self.assertEquals(response.status_code, 200) + self.assertTemplateUsed(response, 'patchwork/optout.html') + self.assertTrue(self.email in response.content) + + # check that we've got an optout in the list + self.assertEquals(EmailOptout.objects.count(), 1) + self.assertEquals(EmailOptout.objects.all()[0].email, self.email) + + # check that the confirmation is now inactive + self.assertFalse(EmailConfirmation.objects.get( + pk = self.conf.pk).active) + + +class OptoutPreexistingTest(OptoutTest): + """Test that a duplicated opt-out behaves the same as the initial one""" + def setUp(self): + super(OptoutPreexistingTest, self).setUp() + EmailOptout(email = self.email).save() + +class OptinRequestTest(TestCase): + view = 'patchwork.views.mail.optin' + url = reverse(view) + + def setUp(self): + self.email = u'foo@example.com' + EmailOptout(email = self.email).save() + + def testOptInRequestGET(self): + response = self.client.get(self.url) + self.assertRedirects(response, reverse('patchwork.views.mail.settings')) + + def testOptInRequestValidPOST(self): + response = self.client.post(self.url, {'email': self.email}) + + # check for a confirmation object + self.assertEquals(EmailConfirmation.objects.count(), 1) + conf = EmailConfirmation.objects.get(email = self.email) + + # check confirmation page + self.assertEquals(response.status_code, 200) + self.assertEquals(response.context['confirmation'], conf) + self.assertTrue(self.email in response.content) + + # check email + url = reverse('patchwork.views.confirm', kwargs = {'key': conf.key}) + self.assertEquals(len(mail.outbox), 1) + msg = mail.outbox[0] + self.assertEquals(msg.to, [self.email]) + self.assertEquals(msg.subject, 'Patchwork opt-in confirmation') + self.assertTrue(url in msg.body) + + def testOptoutRequestInvalidPOSTEmpty(self): + response = self.client.post(self.url, {'email': ''}) + self.assertEquals(response.status_code, 200) + self.assertFormError(response, 'form', 'email', + 'This field is required.') + self.assertTrue(response.context['error']) + self.assertTrue('email_sent' not in response.context) + self.assertEquals(len(mail.outbox), 0) + + def testOptoutRequestInvalidPOSTNonEmail(self): + response = self.client.post(self.url, {'email': 'foo'}) + self.assertEquals(response.status_code, 200) + self.assertFormError(response, 'form', 'email', error_strings['email']) + self.assertTrue(response.context['error']) + self.assertTrue('email_sent' not in response.context) + self.assertEquals(len(mail.outbox), 0) + +class OptinTest(TestCase): + + def setUp(self): + self.email = u'foo@example.com' + self.optout = EmailOptout(email = self.email) + self.optout.save() + self.conf = EmailConfirmation(type = 'optin', email = self.email) + self.conf.save() + + def testOptinValidHash(self): + url = reverse('patchwork.views.confirm', + kwargs = {'key': self.conf.key}) + response = self.client.get(url) + + self.assertEquals(response.status_code, 200) + self.assertTemplateUsed(response, 'patchwork/optin.html') + self.assertTrue(self.email in response.content) + + # check that there's no optout remaining + self.assertEquals(EmailOptout.objects.count(), 0) + + # check that the confirmation is now inactive + self.assertFalse(EmailConfirmation.objects.get( + pk = self.conf.pk).active) + +class OptinWithoutOptoutTest(TestCase): + """Test an opt-in with no existing opt-out""" + view = 'patchwork.views.mail.optin' + url = reverse(view) + + def testOptInWithoutOptout(self): + email = u'foo@example.com' + response = self.client.post(self.url, {'email': email}) + + # check for an error message + self.assertEquals(response.status_code, 200) + self.assertTrue(bool(response.context['error'])) + self.assertTrue('not on the patchwork opt-out list' in response.content) + +class UserProfileOptoutFormTest(TestCase): + """Test that the correct optin/optout forms appear on the user profile + page, for logged-in users""" + + view = 'patchwork.views.user.profile' + url = reverse(view) + optout_url = reverse('patchwork.views.mail.optout') + optin_url = reverse('patchwork.views.mail.optin') + form_re_template = (']*action="%(url)s"[^>]*>' + '.*?]*value="%(email)s"[^>]*>.*?' + '') + secondary_email = 'test2@example.com' + + def setUp(self): + self.user = create_user() + self.client.login(username = self.user.username, + password = self.user.username) + + def _form_re(self, url, email): + return re.compile(self.form_re_template % {'url': url, 'email': email}, + re.DOTALL) + + def testMainEmailOptoutForm(self): + form_re = self._form_re(self.optout_url, self.user.email) + response = self.client.get(self.url) + self.assertEquals(response.status_code, 200) + self.assertTrue(form_re.search(response.content) is not None) + + def testMainEmailOptinForm(self): + EmailOptout(email = self.user.email).save() + form_re = self._form_re(self.optin_url, self.user.email) + response = self.client.get(self.url) + self.assertEquals(response.status_code, 200) + self.assertTrue(form_re.search(response.content) is not None) + + def testSecondaryEmailOptoutForm(self): + p = Person(email = self.secondary_email, user = self.user) + p.save() + + form_re = self._form_re(self.optout_url, p.email) + response = self.client.get(self.url) + self.assertEquals(response.status_code, 200) + self.assertTrue(form_re.search(response.content) is not None) + + def testSecondaryEmailOptinForm(self): + p = Person(email = self.secondary_email, user = self.user) + p.save() + EmailOptout(email = p.email).save() + + form_re = self._form_re(self.optin_url, p.email) + response = self.client.get(self.url) + self.assertEquals(response.status_code, 200) + self.assertTrue(form_re.search(response.content) is not None) diff --git a/patchwork/tests/test_mboxviews.py b/patchwork/tests/test_mboxviews.py new file mode 100644 index 0000000..0e57f42 --- /dev/null +++ b/patchwork/tests/test_mboxviews.py @@ -0,0 +1,209 @@ +# vim: set fileencoding=utf-8 : +# +# Patchwork - automated patch tracking system +# Copyright (C) 2009 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import unittest +import email +import datetime +import dateutil.parser, dateutil.tz +from django.test import TestCase +from django.test.client import Client +from patchwork.models import Patch, Comment, Person +from patchwork.tests.utils import defaults, create_user, find_in_context + +class MboxPatchResponseTest(TestCase): + """ Test that the mbox view appends the Acked-by from a patch comment """ + def setUp(self): + defaults.project.save() + + self.person = defaults.patch_author_person + self.person.save() + + self.patch = Patch(project = defaults.project, + msgid = 'p1', name = 'testpatch', + submitter = self.person, content = '') + self.patch.save() + comment = Comment(patch = self.patch, msgid = 'p1', + submitter = self.person, + content = 'comment 1 text\nAcked-by: 1\n') + comment.save() + + comment = Comment(patch = self.patch, msgid = 'p2', + submitter = self.person, + content = 'comment 2 text\nAcked-by: 2\n') + comment.save() + + def testPatchResponse(self): + response = self.client.get('/patch/%d/mbox/' % self.patch.id) + self.assertContains(response, + 'Acked-by: 1\nAcked-by: 2\n') + +class MboxPatchSplitResponseTest(TestCase): + """ Test that the mbox view appends the Acked-by from a patch comment, + and places it before an '---' update line. """ + def setUp(self): + defaults.project.save() + + self.person = defaults.patch_author_person + self.person.save() + + self.patch = Patch(project = defaults.project, + msgid = 'p1', name = 'testpatch', + submitter = self.person, content = '') + self.patch.save() + comment = Comment(patch = self.patch, msgid = 'p1', + submitter = self.person, + content = 'comment 1 text\nAcked-by: 1\n---\nupdate\n') + comment.save() + + comment = Comment(patch = self.patch, msgid = 'p2', + submitter = self.person, + content = 'comment 2 text\nAcked-by: 2\n') + comment.save() + + def testPatchResponse(self): + response = self.client.get('/patch/%d/mbox/' % self.patch.id) + self.assertContains(response, + 'Acked-by: 1\nAcked-by: 2\n') + +class MboxPassThroughHeaderTest(TestCase): + """ Test that we see 'Cc' and 'To' headers passed through from original + message to mbox view """ + + def setUp(self): + defaults.project.save() + self.person = defaults.patch_author_person + self.person.save() + + self.cc_header = 'Cc: CC Person ' + self.to_header = 'To: To Person ' + self.date_header = 'Date: Fri, 7 Jun 2013 15:42:54 +1000' + + self.patch = Patch(project = defaults.project, + msgid = 'p1', name = 'testpatch', + submitter = self.person, content = '') + + def testCCHeader(self): + self.patch.headers = self.cc_header + '\n' + self.patch.save() + + response = self.client.get('/patch/%d/mbox/' % self.patch.id) + self.assertContains(response, self.cc_header) + + def testToHeader(self): + self.patch.headers = self.to_header + '\n' + self.patch.save() + + response = self.client.get('/patch/%d/mbox/' % self.patch.id) + self.assertContains(response, self.to_header) + + def testDateHeader(self): + self.patch.headers = self.date_header + '\n' + self.patch.save() + + response = self.client.get('/patch/%d/mbox/' % self.patch.id) + self.assertContains(response, self.date_header) + +class MboxBrokenFromHeaderTest(TestCase): + """ Test that a person with characters outside ASCII in his name do + produce correct From header. As RFC 2822 state we must retain the + format for the mail while the name part may be coded + in some ways. """ + + def setUp(self): + defaults.project.save() + self.person = defaults.patch_author_person + self.person.name = u'©ool guŷ' + self.person.save() + + self.patch = Patch(project = defaults.project, + msgid = 'p1', name = 'testpatch', + submitter = self.person, content = '') + + def testFromHeader(self): + self.patch.save() + from_email = '<' + self.person.email + '>' + + response = self.client.get('/patch/%d/mbox/' % self.patch.id) + self.assertContains(response, from_email) + +class MboxDateHeaderTest(TestCase): + """ Test that the date provided in the patch mail view is correct """ + + def setUp(self): + defaults.project.save() + self.person = defaults.patch_author_person + self.person.save() + + self.patch = Patch(project = defaults.project, + msgid = 'p1', name = 'testpatch', + submitter = self.person, content = '') + self.patch.save() + + def testDateHeader(self): + response = self.client.get('/patch/%d/mbox/' % self.patch.id) + mail = email.message_from_string(response.content) + mail_date = dateutil.parser.parse(mail['Date']) + # patch dates are all in UTC + patch_date = self.patch.date.replace(tzinfo=dateutil.tz.tzutc(), + microsecond=0) + self.assertEqual(mail_date, patch_date) + + def testSuppliedDateHeader(self): + hour_offset = 3 + tz = dateutil.tz.tzoffset(None, hour_offset * 60 * 60) + date = datetime.datetime.utcnow() - datetime.timedelta(days = 1) + date = date.replace(tzinfo=tz, microsecond=0) + + self.patch.headers = 'Date: %s\n' % date.strftime("%a, %d %b %Y %T %z") + self.patch.save() + + response = self.client.get('/patch/%d/mbox/' % self.patch.id) + mail = email.message_from_string(response.content) + mail_date = dateutil.parser.parse(mail['Date']) + self.assertEqual(mail_date, date) + +class MboxCommentPostcriptUnchangedTest(TestCase): + """ Test that the mbox view doesn't change the postscript part of a mail. + There where always a missing blank right after the postscript + delimiter '---' and an additional newline right before. """ + def setUp(self): + defaults.project.save() + + self.person = defaults.patch_author_person + self.person.save() + + self.patch = Patch(project = defaults.project, + msgid = 'p1', name = 'testpatch', + submitter = self.person, content = '') + self.patch.save() + + self.txt = 'some comment\n---\n some/file | 1 +\n' + + comment = Comment(patch = self.patch, msgid = 'p1', + submitter = self.person, + content = self.txt) + comment.save() + + def testCommentUnchanged(self): + response = self.client.get('/patch/%d/mbox/' % self.patch.id) + self.assertContains(response, self.txt) + self.txt += "\n" + self.assertNotContains(response, self.txt) diff --git a/patchwork/tests/test_notifications.py b/patchwork/tests/test_notifications.py new file mode 100644 index 0000000..ed35140 --- /dev/null +++ b/patchwork/tests/test_notifications.py @@ -0,0 +1,255 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2011 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import datetime +from django.test import TestCase +from django.core.urlresolvers import reverse +from django.core import mail +from django.conf import settings +from django.db.utils import IntegrityError +from patchwork.models import Patch, State, PatchChangeNotification, EmailOptout +from patchwork.tests.utils import defaults, create_maintainer +from patchwork.utils import send_notifications + +class PatchNotificationModelTest(TestCase): + """Tests for the creation & update of the PatchChangeNotification model""" + + def setUp(self): + self.project = defaults.project + self.project.send_notifications = True + self.project.save() + self.submitter = defaults.patch_author_person + self.submitter.save() + self.patch = Patch(project = self.project, msgid = 'testpatch', + name = 'testpatch', content = '', + submitter = self.submitter) + + def tearDown(self): + self.patch.delete() + self.submitter.delete() + self.project.delete() + + def testPatchCreation(self): + """Ensure we don't get a notification on create""" + self.patch.save() + self.assertEqual(PatchChangeNotification.objects.count(), 0) + + def testPatchUninterestingChange(self): + """Ensure we don't get a notification for "uninteresting" changes""" + self.patch.save() + self.patch.archived = True + self.patch.save() + self.assertEqual(PatchChangeNotification.objects.count(), 0) + + def testPatchChange(self): + """Ensure we get a notification for interesting patch changes""" + self.patch.save() + oldstate = self.patch.state + state = State.objects.exclude(pk = oldstate.pk)[0] + + self.patch.state = state + self.patch.save() + self.assertEqual(PatchChangeNotification.objects.count(), 1) + notification = PatchChangeNotification.objects.all()[0] + self.assertEqual(notification.patch, self.patch) + self.assertEqual(notification.orig_state, oldstate) + + def testNotificationCancelled(self): + """Ensure we cancel notifications that are no longer valid""" + self.patch.save() + oldstate = self.patch.state + state = State.objects.exclude(pk = oldstate.pk)[0] + + self.patch.state = state + self.patch.save() + self.assertEqual(PatchChangeNotification.objects.count(), 1) + + self.patch.state = oldstate + self.patch.save() + self.assertEqual(PatchChangeNotification.objects.count(), 0) + + def testNotificationUpdated(self): + """Ensure we update notifications when the patch has a second change, + but keep the original patch details""" + self.patch.save() + oldstate = self.patch.state + newstates = State.objects.exclude(pk = oldstate.pk)[:2] + + self.patch.state = newstates[0] + self.patch.save() + self.assertEqual(PatchChangeNotification.objects.count(), 1) + notification = PatchChangeNotification.objects.all()[0] + self.assertEqual(notification.orig_state, oldstate) + orig_timestamp = notification.last_modified + + self.patch.state = newstates[1] + self.patch.save() + self.assertEqual(PatchChangeNotification.objects.count(), 1) + notification = PatchChangeNotification.objects.all()[0] + self.assertEqual(notification.orig_state, oldstate) + self.assertTrue(notification.last_modified >= orig_timestamp) + + def testProjectNotificationsDisabled(self): + """Ensure we don't see notifications created when a project is + configured not to send them""" + self.project.send_notifications = False + self.project.save() + + self.patch.save() + oldstate = self.patch.state + state = State.objects.exclude(pk = oldstate.pk)[0] + + self.patch.state = state + self.patch.save() + self.assertEqual(PatchChangeNotification.objects.count(), 0) + +class PatchNotificationEmailTest(TestCase): + + def setUp(self): + self.project = defaults.project + self.project.send_notifications = True + self.project.save() + self.submitter = defaults.patch_author_person + self.submitter.save() + self.patch = Patch(project = self.project, msgid = 'testpatch', + name = 'testpatch', content = '', + submitter = self.submitter) + self.patch.save() + + def tearDown(self): + self.patch.delete() + self.submitter.delete() + self.project.delete() + + def _expireNotifications(self, **kwargs): + timestamp = datetime.datetime.now() - \ + datetime.timedelta(minutes = + settings.NOTIFICATION_DELAY_MINUTES + 1) + + qs = PatchChangeNotification.objects.all() + if kwargs: + qs = qs.filter(**kwargs) + + qs.update(last_modified = timestamp) + + def testNoNotifications(self): + self.assertEquals(send_notifications(), []) + + def testNoReadyNotifications(self): + """ We shouldn't see immediate notifications""" + PatchChangeNotification(patch = self.patch, + orig_state = self.patch.state).save() + + errors = send_notifications() + self.assertEquals(errors, []) + self.assertEquals(len(mail.outbox), 0) + + def testNotifications(self): + PatchChangeNotification(patch = self.patch, + orig_state = self.patch.state).save() + self._expireNotifications() + + errors = send_notifications() + self.assertEquals(errors, []) + self.assertEquals(len(mail.outbox), 1) + msg = mail.outbox[0] + self.assertEquals(msg.to, [self.submitter.email]) + self.assertTrue(self.patch.get_absolute_url() in msg.body) + + def testNotificationEscaping(self): + self.patch.name = 'Patch name with " character' + self.patch.save() + PatchChangeNotification(patch = self.patch, + orig_state = self.patch.state).save() + self._expireNotifications() + + errors = send_notifications() + self.assertEquals(errors, []) + self.assertEquals(len(mail.outbox), 1) + msg = mail.outbox[0] + self.assertEquals(msg.to, [self.submitter.email]) + self.assertFalse('"' in msg.body) + + def testNotificationOptout(self): + """ensure opt-out addresses don't get notifications""" + PatchChangeNotification(patch = self.patch, + orig_state = self.patch.state).save() + self._expireNotifications() + + EmailOptout(email = self.submitter.email).save() + + errors = send_notifications() + self.assertEquals(errors, []) + self.assertEquals(len(mail.outbox), 0) + + def testNotificationMerge(self): + patches = [self.patch, + Patch(project = self.project, msgid = 'testpatch-2', + name = 'testpatch 2', content = '', + submitter = self.submitter)] + + for patch in patches: + patch.save() + PatchChangeNotification(patch = patch, + orig_state = patch.state).save() + + self.assertEquals(PatchChangeNotification.objects.count(), len(patches)) + self._expireNotifications() + errors = send_notifications() + self.assertEquals(errors, []) + self.assertEquals(len(mail.outbox), 1) + msg = mail.outbox[0] + self.assertTrue(patches[0].get_absolute_url() in msg.body) + self.assertTrue(patches[1].get_absolute_url() in msg.body) + + def testUnexpiredNotificationMerge(self): + """Test that when there are multiple pending notifications, with + at least one within the notification delay, that other notifications + are held""" + patches = [self.patch, + Patch(project = self.project, msgid = 'testpatch-2', + name = 'testpatch 2', content = '', + submitter = self.submitter)] + + for patch in patches: + patch.save() + PatchChangeNotification(patch = patch, + orig_state = patch.state).save() + + self.assertEquals(PatchChangeNotification.objects.count(), len(patches)) + self._expireNotifications() + + # update one notification, to bring it out of the notification delay + patches[0].state = State.objects.exclude(pk = patches[0].state.pk)[0] + patches[0].save() + + # the updated notification should prevent the other from being sent + errors = send_notifications() + self.assertEquals(errors, []) + self.assertEquals(len(mail.outbox), 0) + + # expire the updated notification + self._expireNotifications() + + errors = send_notifications() + self.assertEquals(errors, []) + self.assertEquals(len(mail.outbox), 1) + msg = mail.outbox[0] + self.assertTrue(patches[0].get_absolute_url() in msg.body) + self.assertTrue(patches[1].get_absolute_url() in msg.body) diff --git a/patchwork/tests/test_patchparser.py b/patchwork/tests/test_patchparser.py new file mode 100644 index 0000000..119936a --- /dev/null +++ b/patchwork/tests/test_patchparser.py @@ -0,0 +1,554 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os +from email import message_from_string +from django.test import TestCase +from patchwork.models import Project, Person, Patch, Comment, State, \ + get_default_initial_patch_state +from patchwork.tests.utils import read_patch, read_mail, create_email, \ + defaults, create_user + +try: + from email.mime.text import MIMEText +except ImportError: + # Python 2.4 compatibility + from email.MIMEText import MIMEText + +class PatchTest(TestCase): + default_sender = defaults.sender + default_subject = defaults.subject + project = defaults.project + +from patchwork.bin.parsemail import find_content, find_author, find_project, \ + parse_mail + +class InlinePatchTest(PatchTest): + patch_filename = '0001-add-line.patch' + test_comment = 'Test for attached patch' + + def setUp(self): + self.orig_patch = read_patch(self.patch_filename) + email = create_email(self.test_comment + '\n' + self.orig_patch) + (self.patch, self.comment) = find_content(self.project, email) + + def testPatchPresence(self): + self.assertTrue(self.patch is not None) + + def testPatchContent(self): + self.assertEquals(self.patch.content, self.orig_patch) + + def testCommentPresence(self): + self.assertTrue(self.comment is not None) + + def testCommentContent(self): + self.assertEquals(self.comment.content, self.test_comment) + + +class AttachmentPatchTest(InlinePatchTest): + patch_filename = '0001-add-line.patch' + test_comment = 'Test for attached patch' + content_subtype = 'x-patch' + + def setUp(self): + self.orig_patch = read_patch(self.patch_filename) + email = create_email(self.test_comment, multipart = True) + attachment = MIMEText(self.orig_patch, _subtype = self.content_subtype) + email.attach(attachment) + (self.patch, self.comment) = find_content(self.project, email) + +class AttachmentXDiffPatchTest(AttachmentPatchTest): + content_subtype = 'x-diff' + +class UTF8InlinePatchTest(InlinePatchTest): + patch_filename = '0002-utf-8.patch' + patch_encoding = 'utf-8' + + def setUp(self): + self.orig_patch = read_patch(self.patch_filename, self.patch_encoding) + email = create_email(self.test_comment + '\n' + self.orig_patch, + content_encoding = self.patch_encoding) + (self.patch, self.comment) = find_content(self.project, email) + +class NoCharsetInlinePatchTest(InlinePatchTest): + """ Test mails with no content-type or content-encoding header """ + patch_filename = '0001-add-line.patch' + + def setUp(self): + self.orig_patch = read_patch(self.patch_filename) + email = create_email(self.test_comment + '\n' + self.orig_patch) + del email['Content-Type'] + del email['Content-Transfer-Encoding'] + (self.patch, self.comment) = find_content(self.project, email) + +class SignatureCommentTest(InlinePatchTest): + patch_filename = '0001-add-line.patch' + test_comment = 'Test comment\nmore comment' + + def setUp(self): + self.orig_patch = read_patch(self.patch_filename) + email = create_email( \ + self.test_comment + '\n' + \ + '-- \nsig\n' + self.orig_patch) + (self.patch, self.comment) = find_content(self.project, email) + + +class ListFooterTest(InlinePatchTest): + patch_filename = '0001-add-line.patch' + test_comment = 'Test comment\nmore comment' + + def setUp(self): + self.orig_patch = read_patch(self.patch_filename) + email = create_email( \ + self.test_comment + '\n' + \ + '_______________________________________________\n' + \ + 'Linuxppc-dev mailing list\n' + \ + self.orig_patch) + (self.patch, self.comment) = find_content(self.project, email) + + +class DiffWordInCommentTest(InlinePatchTest): + test_comment = 'Lines can start with words beginning in "diff"\n' + \ + 'difficult\nDifferent' + + +class UpdateCommentTest(InlinePatchTest): + """ Test for '---\nUpdate: v2' style comments to patches. """ + patch_filename = '0001-add-line.patch' + test_comment = 'Test comment\nmore comment\n---\nUpdate: test update' + +class UpdateSigCommentTest(SignatureCommentTest): + """ Test for '---\nUpdate: v2' style comments to patches, with a sig """ + patch_filename = '0001-add-line.patch' + test_comment = 'Test comment\nmore comment\n---\nUpdate: test update' + +class SenderEncodingTest(TestCase): + sender_name = u'example user' + sender_email = 'user@example.com' + from_header = 'example user ' + + def setUp(self): + mail = 'From: %s\n' % self.from_header + \ + 'Subject: test\n\n' + \ + 'test' + self.email = message_from_string(mail) + (self.person, new) = find_author(self.email) + self.person.save() + + def tearDown(self): + self.person.delete() + + def testName(self): + self.assertEquals(self.person.name, self.sender_name) + + def testEmail(self): + self.assertEquals(self.person.email, self.sender_email) + + def testDBQueryName(self): + db_person = Person.objects.get(name = self.sender_name) + self.assertEquals(self.person, db_person) + + def testDBQueryEmail(self): + db_person = Person.objects.get(email = self.sender_email) + self.assertEquals(self.person, db_person) + + +class SenderUTF8QPEncodingTest(SenderEncodingTest): + sender_name = u'\xe9xample user' + from_header = '=?utf-8?q?=C3=A9xample=20user?= ' + +class SenderUTF8QPSplitEncodingTest(SenderEncodingTest): + sender_name = u'\xe9xample user' + from_header = '=?utf-8?q?=C3=A9xample?= user ' + +class SenderUTF8B64EncodingTest(SenderUTF8QPEncodingTest): + from_header = '=?utf-8?B?w6l4YW1wbGUgdXNlcg==?= ' + +class SubjectEncodingTest(PatchTest): + sender = 'example user ' + subject = 'test subject' + subject_header = 'test subject' + + def setUp(self): + mail = 'From: %s\n' % self.sender + \ + 'Subject: %s\n\n' % self.subject_header + \ + 'test\n\n' + defaults.patch + self.projects = defaults.project + self.email = message_from_string(mail) + + def testSubjectEncoding(self): + (patch, comment) = find_content(self.project, self.email) + self.assertEquals(patch.name, self.subject) + +class SubjectUTF8QPEncodingTest(SubjectEncodingTest): + subject = u'test s\xfcbject' + subject_header = '=?utf-8?q?test=20s=c3=bcbject?=' + +class SubjectUTF8QPMultipleEncodingTest(SubjectEncodingTest): + subject = u'test s\xfcbject' + subject_header = 'test =?utf-8?q?s=c3=bcbject?=' + +class SenderCorrelationTest(TestCase): + existing_sender = 'Existing Sender ' + non_existing_sender = 'Non-existing Sender ' + + def mail(self, sender): + return message_from_string('From: %s\nSubject: Test\n\ntest\n' % sender) + + def setUp(self): + self.existing_sender_mail = self.mail(self.existing_sender) + self.non_existing_sender_mail = self.mail(self.non_existing_sender) + (self.person, new) = find_author(self.existing_sender_mail) + self.person.save() + + def testExisingSender(self): + (person, new) = find_author(self.existing_sender_mail) + self.assertEqual(new, False) + self.assertEqual(person.id, self.person.id) + + def testNonExisingSender(self): + (person, new) = find_author(self.non_existing_sender_mail) + self.assertEqual(new, True) + self.assertEqual(person.id, None) + + def testExistingDifferentFormat(self): + mail = self.mail('existing@example.com') + (person, new) = find_author(mail) + self.assertEqual(new, False) + self.assertEqual(person.id, self.person.id) + + def testExistingDifferentCase(self): + mail = self.mail(self.existing_sender.upper()) + (person, new) = find_author(mail) + self.assertEqual(new, False) + self.assertEqual(person.id, self.person.id) + + def tearDown(self): + self.person.delete() + +class MultipleProjectPatchTest(TestCase): + """ Test that patches sent to multiple patchwork projects are + handled correctly """ + + test_comment = 'Test Comment' + patch_filename = '0001-add-line.patch' + msgid = '<1@example.com>' + + def setUp(self): + self.p1 = Project(linkname = 'test-project-1', name = 'Project 1', + listid = '1.example.com', listemail='1@example.com') + self.p2 = Project(linkname = 'test-project-2', name = 'Project 2', + listid = '2.example.com', listemail='2@example.com') + + self.p1.save() + self.p2.save() + + patch = read_patch(self.patch_filename) + email = create_email(self.test_comment + '\n' + patch) + email['Message-Id'] = self.msgid + + del email['List-ID'] + email['List-ID'] = '<' + self.p1.listid + '>' + parse_mail(email) + + del email['List-ID'] + email['List-ID'] = '<' + self.p2.listid + '>' + parse_mail(email) + + def testParsedProjects(self): + self.assertEquals(Patch.objects.filter(project = self.p1).count(), 1) + self.assertEquals(Patch.objects.filter(project = self.p2).count(), 1) + + def tearDown(self): + self.p1.delete() + self.p2.delete() + + +class MultipleProjectPatchCommentTest(MultipleProjectPatchTest): + """ Test that followups to multiple-project patches end up on the + correct patch """ + + comment_msgid = '<2@example.com>' + comment_content = 'test comment' + + def setUp(self): + super(MultipleProjectPatchCommentTest, self).setUp() + + for project in [self.p1, self.p2]: + email = MIMEText(self.comment_content) + email['From'] = defaults.sender + email['Subject'] = defaults.subject + email['Message-Id'] = self.comment_msgid + email['List-ID'] = '<' + project.listid + '>' + email['In-Reply-To'] = self.msgid + parse_mail(email) + + def testParsedComment(self): + for project in [self.p1, self.p2]: + patch = Patch.objects.filter(project = project)[0] + # we should see two comments now - the original mail with the patch, + # and the one we parsed in setUp() + self.assertEquals(Comment.objects.filter(patch = patch).count(), 2) + +class ListIdHeaderTest(TestCase): + """ Test that we parse List-Id headers from mails correctly """ + def setUp(self): + self.project = Project(linkname = 'test-project-1', name = 'Project 1', + listid = '1.example.com', listemail='1@example.com') + self.project.save() + + def testNoListId(self): + email = MIMEText('') + project = find_project(email) + self.assertEquals(project, None) + + def testBlankListId(self): + email = MIMEText('') + email['List-Id'] = '' + project = find_project(email) + self.assertEquals(project, None) + + def testWhitespaceListId(self): + email = MIMEText('') + email['List-Id'] = ' ' + project = find_project(email) + self.assertEquals(project, None) + + def testSubstringListId(self): + email = MIMEText('') + email['List-Id'] = 'example.com' + project = find_project(email) + self.assertEquals(project, None) + + def testShortListId(self): + """ Some mailing lists have List-Id headers in short formats, where it + is only the list ID itself (without enclosing angle-brackets). """ + email = MIMEText('') + email['List-Id'] = self.project.listid + project = find_project(email) + self.assertEquals(project, self.project) + + def testLongListId(self): + email = MIMEText('') + email['List-Id'] = 'Test text <%s>' % self.project.listid + project = find_project(email) + self.assertEquals(project, self.project) + + def tearDown(self): + self.project.delete() + +class MBoxPatchTest(PatchTest): + def setUp(self): + self.mail = read_mail(self.mail_file, project = self.project) + +class GitPullTest(MBoxPatchTest): + mail_file = '0001-git-pull-request.mbox' + + def testGitPullRequest(self): + (patch, comment) = find_content(self.project, self.mail) + self.assertTrue(patch is not None) + self.assertTrue(patch.pull_url is not None) + self.assertTrue(patch.content is None) + self.assertTrue(comment is not None) + +class GitPullWrappedTest(GitPullTest): + mail_file = '0002-git-pull-request-wrapped.mbox' + +class GitPullWithDiffTest(MBoxPatchTest): + mail_file = '0003-git-pull-request-with-diff.mbox' + + def testGitPullWithDiff(self): + (patch, comment) = find_content(self.project, self.mail) + self.assertTrue(patch is not None) + self.assertEqual('git://git.kernel.org/pub/scm/linux/kernel/git/tip/' + + 'linux-2.6-tip.git x86-fixes-for-linus', patch.pull_url) + self.assertTrue( + patch.content.startswith('diff --git a/arch/x86/include/asm/smp.h'), + patch.content) + self.assertTrue(comment is not None) + +class GitPullGitSSHUrlTest(GitPullTest): + mail_file = '0004-git-pull-request-git+ssh.mbox' + +class GitPullSSHUrlTest(GitPullTest): + mail_file = '0005-git-pull-request-ssh.mbox' + +class GitPullHTTPUrlTest(GitPullTest): + mail_file = '0006-git-pull-request-http.mbox' + +class GitRenameOnlyTest(MBoxPatchTest): + mail_file = '0008-git-rename.mbox' + + def testGitRename(self): + (patch, comment) = find_content(self.project, self.mail) + self.assertTrue(patch is not None) + self.assertTrue(comment is not None) + self.assertEqual(patch.content.count("\nrename from "), 2) + self.assertEqual(patch.content.count("\nrename to "), 2) + +class GitRenameWithDiffTest(MBoxPatchTest): + mail_file = '0009-git-rename-with-diff.mbox' + + def testGitRename(self): + (patch, comment) = find_content(self.project, self.mail) + self.assertTrue(patch is not None) + self.assertTrue(comment is not None) + self.assertEqual(patch.content.count("\nrename from "), 2) + self.assertEqual(patch.content.count("\nrename to "), 2) + self.assertEqual(patch.content.count('\n-a\n+b'), 1) + +class CVSFormatPatchTest(MBoxPatchTest): + mail_file = '0007-cvs-format-diff.mbox' + + def testPatch(self): + (patch, comment) = find_content(self.project, self.mail) + self.assertTrue(patch is not None) + self.assertTrue(comment is not None) + self.assertTrue(patch.content.startswith('Index')) + +class CharsetFallbackPatchTest(MBoxPatchTest): + """ Test mail with and invalid charset name, and check that we can parse + with one of the fallback encodings""" + + mail_file = '0010-invalid-charset.mbox' + + def testPatch(self): + (patch, comment) = find_content(self.project, self.mail) + self.assertTrue(patch is not None) + self.assertTrue(comment is not None) + +class NoNewlineAtEndOfFilePatchTest(MBoxPatchTest): + mail_file = '0011-no-newline-at-end-of-file.mbox' + + def testPatch(self): + (patch, comment) = find_content(self.project, self.mail) + self.assertTrue(patch is not None) + self.assertTrue(comment is not None) + self.assertTrue(patch.content.startswith('diff --git a/tools/testing/selftests/powerpc/Makefile')) + # Confirm the trailing no newline marker doesn't end up in the comment + self.assertFalse(comment.content.rstrip().endswith('\ No newline at end of file')) + # Confirm it's instead at the bottom of the patch + self.assertTrue(patch.content.rstrip().endswith('\ No newline at end of file')) + # Confirm we got both markers + self.assertEqual(2, patch.content.count('\ No newline at end of file')) + +class DelegateRequestTest(TestCase): + patch_filename = '0001-add-line.patch' + msgid = '<1@example.com>' + invalid_delegate_email = "nobody" + + def setUp(self): + self.patch = read_patch(self.patch_filename) + self.user = create_user() + self.p1 = Project(linkname = 'test-project-1', name = 'Project 1', + listid = '1.example.com', listemail='1@example.com') + self.p1.save() + + def get_email(self): + email = create_email(self.patch) + del email['List-ID'] + email['List-ID'] = '<' + self.p1.listid + '>' + email['Message-Id'] = self.msgid + return email + + def _assertDelegate(self, delegate): + query = Patch.objects.filter(project=self.p1) + self.assertEquals(query.count(), 1) + self.assertEquals(query[0].delegate, delegate) + + def testDelegate(self): + email = self.get_email() + email['X-Patchwork-Delegate'] = self.user.email + parse_mail(email) + self._assertDelegate(self.user) + + def testNoDelegate(self): + email = self.get_email() + parse_mail(email) + self._assertDelegate(None) + + def testInvalidDelegateFallsBackToNoDelegate(self): + email = self.get_email() + email['X-Patchwork-Delegate'] = self.invalid_delegate_email + parse_mail(email) + self._assertDelegate(None) + + def tearDown(self): + self.p1.delete() + self.user.delete() + +class InitialPatchStateTest(TestCase): + patch_filename = '0001-add-line.patch' + msgid = '<1@example.com>' + invalid_state_name = "Nonexistent Test State" + + def setUp(self): + self.patch = read_patch(self.patch_filename) + self.user = create_user() + self.p1 = Project(linkname = 'test-project-1', name = 'Project 1', + listid = '1.example.com', listemail='1@example.com') + self.p1.save() + self.default_state = get_default_initial_patch_state() + self.nondefault_state = State.objects.get(name="Accepted") + + def get_email(self): + email = create_email(self.patch) + del email['List-ID'] + email['List-ID'] = '<' + self.p1.listid + '>' + email['Message-Id'] = self.msgid + return email + + def _assertState(self, state): + query = Patch.objects.filter(project=self.p1) + self.assertEquals(query.count(), 1) + self.assertEquals(query[0].state, state) + + def testNonDefaultStateIsActuallyNotTheDefaultState(self): + self.assertNotEqual(self.default_state, self.nondefault_state) + + def testExplicitNonDefaultStateRequest(self): + email = self.get_email() + email['X-Patchwork-State'] = self.nondefault_state.name + parse_mail(email) + self._assertState(self.nondefault_state) + + def testExplicitDefaultStateRequest(self): + email = self.get_email() + email['X-Patchwork-State'] = self.default_state.name + parse_mail(email) + self._assertState(self.default_state) + + def testImplicitDefaultStateRequest(self): + email = self.get_email() + parse_mail(email) + self._assertState(self.default_state) + + def testInvalidTestStateDoesNotExist(self): + with self.assertRaises(State.DoesNotExist): + State.objects.get(name=self.invalid_state_name) + + def testInvalidStateRequestFallsBackToDefaultState(self): + email = self.get_email() + email['X-Patchwork-State'] = self.invalid_state_name + parse_mail(email) + self._assertState(self.default_state) + + def tearDown(self): + self.p1.delete() + self.user.delete() diff --git a/patchwork/tests/test_person.py b/patchwork/tests/test_person.py new file mode 100644 index 0000000..d948096 --- /dev/null +++ b/patchwork/tests/test_person.py @@ -0,0 +1,55 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2013 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import unittest +from django.test import TestCase +from django.test.client import Client +from patchwork.models import EmailConfirmation, Person, Bundle +import json + +class SubmitterCompletionTest(TestCase): + def setUp(self): + self.people = [ + Person(name = "Test Name", email = "test1@example.com"), + Person(email = "test2@example.com"), + ] + map(lambda p: p.save(), self.people) + + def testNameComplete(self): + response = self.client.get('/submitter/', {'q': 'name'}) + self.assertEquals(response.status_code, 200) + data = json.loads(response.content) + self.assertEquals(len(data), 1) + self.assertEquals(data[0]['fields']['name'], 'Test Name') + + def testEmailComplete(self): + response = self.client.get('/submitter/', {'q': 'test2'}) + self.assertEquals(response.status_code, 200) + data = json.loads(response.content) + self.assertEquals(len(data), 1) + self.assertEquals(data[0]['fields']['email'], 'test2@example.com') + + def testCompleteLimit(self): + for i in range(3,10): + person = Person(email = 'test%d@example.com' % i) + person.save() + response = self.client.get('/submitter/', {'q': 'test', 'l': 5}) + self.assertEquals(response.status_code, 200) + data = json.loads(response.content) + self.assertEquals(len(data), 5) diff --git a/patchwork/tests/test_registration.py b/patchwork/tests/test_registration.py new file mode 100644 index 0000000..845b60b --- /dev/null +++ b/patchwork/tests/test_registration.py @@ -0,0 +1,210 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2010 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import unittest +from django.test import TestCase +from django.test.client import Client +from django.core import mail +from django.core.urlresolvers import reverse +from django.contrib.auth.models import User +from patchwork.models import EmailConfirmation, Person +from patchwork.tests.utils import create_user + +def _confirmation_url(conf): + return reverse('patchwork.views.confirm', kwargs = {'key': conf.key}) + +class TestUser(object): + firstname = 'Test' + lastname = 'User' + username = 'testuser' + email = 'test@example.com' + password = 'foobar' + +class RegistrationTest(TestCase): + def setUp(self): + self.user = TestUser() + self.client = Client() + self.default_data = {'username': self.user.username, + 'first_name': self.user.firstname, + 'last_name': self.user.lastname, + 'email': self.user.email, + 'password': self.user.password} + self.required_error = 'This field is required.' + self.invalid_error = 'Enter a valid value.' + + def testRegistrationForm(self): + response = self.client.get('/register/') + self.assertEquals(response.status_code, 200) + self.assertTemplateUsed(response, 'patchwork/registration_form.html') + + def testBlankFields(self): + for field in ['username', 'email', 'password']: + data = self.default_data.copy() + del data[field] + response = self.client.post('/register/', data) + self.assertEquals(response.status_code, 200) + self.assertFormError(response, 'form', field, self.required_error) + + def testInvalidUsername(self): + data = self.default_data.copy() + data['username'] = 'invalid user' + response = self.client.post('/register/', data) + self.assertEquals(response.status_code, 200) + self.assertFormError(response, 'form', 'username', self.invalid_error) + + def testExistingUsername(self): + user = create_user() + data = self.default_data.copy() + data['username'] = user.username + response = self.client.post('/register/', data) + self.assertEquals(response.status_code, 200) + self.assertFormError(response, 'form', 'username', + 'This username is already taken. Please choose another.') + + def testExistingEmail(self): + user = create_user() + data = self.default_data.copy() + data['email'] = user.email + response = self.client.post('/register/', data) + self.assertEquals(response.status_code, 200) + self.assertFormError(response, 'form', 'email', + 'This email address is already in use ' + \ + 'for the account "%s".\n' % user.username) + + def testValidRegistration(self): + response = self.client.post('/register/', self.default_data) + self.assertEquals(response.status_code, 200) + self.assertContains(response, 'confirmation email has been sent') + + # check for presence of an inactive user object + users = User.objects.filter(username = self.user.username) + self.assertEquals(users.count(), 1) + user = users[0] + self.assertEquals(user.username, self.user.username) + self.assertEquals(user.email, self.user.email) + self.assertEquals(user.is_active, False) + + # check for confirmation object + confs = EmailConfirmation.objects.filter(user = user, + type = 'registration') + self.assertEquals(len(confs), 1) + conf = confs[0] + self.assertEquals(conf.email, self.user.email) + + # check for a sent mail + self.assertEquals(len(mail.outbox), 1) + msg = mail.outbox[0] + self.assertEquals(msg.subject, 'Patchwork account confirmation') + self.assertTrue(self.user.email in msg.to) + self.assertTrue(_confirmation_url(conf) in msg.body) + + # ...and that the URL is valid + response = self.client.get(_confirmation_url(conf)) + self.assertEquals(response.status_code, 200) + +class RegistrationConfirmationTest(TestCase): + + def setUp(self): + self.user = TestUser() + self.default_data = {'username': self.user.username, + 'first_name': self.user.firstname, + 'last_name': self.user.lastname, + 'email': self.user.email, + 'password': self.user.password} + + def testRegistrationConfirmation(self): + self.assertEqual(EmailConfirmation.objects.count(), 0) + response = self.client.post('/register/', self.default_data) + self.assertEquals(response.status_code, 200) + self.assertContains(response, 'confirmation email has been sent') + + self.assertEqual(EmailConfirmation.objects.count(), 1) + conf = EmailConfirmation.objects.filter()[0] + self.assertFalse(conf.user.is_active) + self.assertTrue(conf.active) + + response = self.client.get(_confirmation_url(conf)) + self.assertEquals(response.status_code, 200) + self.assertTemplateUsed(response, 'patchwork/registration-confirm.html') + + conf = EmailConfirmation.objects.get(pk = conf.pk) + self.assertTrue(conf.user.is_active) + self.assertFalse(conf.active) + + def testRegistrationNewPersonSetup(self): + """ Check that the person object created after registration has the + correct details """ + + # register + self.assertEqual(EmailConfirmation.objects.count(), 0) + response = self.client.post('/register/', self.default_data) + self.assertEquals(response.status_code, 200) + self.assertFalse(Person.objects.exists()) + + # confirm + conf = EmailConfirmation.objects.filter()[0] + response = self.client.get(_confirmation_url(conf)) + self.assertEquals(response.status_code, 200) + + qs = Person.objects.filter(email = self.user.email) + self.assertTrue(qs.exists()) + person = Person.objects.get(email = self.user.email) + + self.assertEquals(person.name, + self.user.firstname + ' ' + self.user.lastname) + + def testRegistrationExistingPersonSetup(self): + """ Check that the person object created after registration has the + correct details """ + + fullname = self.user.firstname + ' ' + self.user.lastname + person = Person(name = fullname, email = self.user.email) + person.save() + + # register + self.assertEqual(EmailConfirmation.objects.count(), 0) + response = self.client.post('/register/', self.default_data) + self.assertEquals(response.status_code, 200) + + # confirm + conf = EmailConfirmation.objects.filter()[0] + response = self.client.get(_confirmation_url(conf)) + self.assertEquals(response.status_code, 200) + + person = Person.objects.get(email = self.user.email) + + self.assertEquals(person.name, fullname) + + def testRegistrationExistingPersonUnmodified(self): + """ Check that an unconfirmed registration can't modify an existing + Person object""" + + fullname = self.user.firstname + ' ' + self.user.lastname + person = Person(name = fullname, email = self.user.email) + person.save() + + # register + data = self.default_data.copy() + data['first_name'] = 'invalid' + data['last_name'] = 'invalid' + self.assertEquals(data['email'], person.email) + response = self.client.post('/register/', data) + self.assertEquals(response.status_code, 200) + + self.assertEquals(Person.objects.get(pk = person.pk).name, fullname) diff --git a/patchwork/tests/test_updates.py b/patchwork/tests/test_updates.py new file mode 100644 index 0000000..177ee78 --- /dev/null +++ b/patchwork/tests/test_updates.py @@ -0,0 +1,118 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2010 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from django.test import TestCase +from django.core.urlresolvers import reverse +from patchwork.models import Patch, Person, State +from patchwork.tests.utils import defaults, create_maintainer + +class MultipleUpdateTest(TestCase): + def setUp(self): + defaults.project.save() + self.user = create_maintainer(defaults.project) + self.client.login(username = self.user.username, + password = self.user.username) + self.properties_form_id = 'patchform-properties' + self.url = reverse( + 'patchwork.views.patch.list', args = [defaults.project.linkname]) + self.base_data = { + 'action': 'Update', 'project': str(defaults.project.id), + 'form': 'patchlistform', 'archived': '*', 'delegate': '*', + 'state': '*'} + self.patches = [] + for name in ['patch one', 'patch two', 'patch three']: + patch = Patch(project = defaults.project, msgid = name, + name = name, content = '', + submitter = Person.objects.get(user = self.user)) + patch.save() + self.patches.append(patch) + + def _selectAllPatches(self, data): + for patch in self.patches: + data['patch_id:%d' % patch.id] = 'checked' + + def testArchivingPatches(self): + data = self.base_data.copy() + data.update({'archived': 'True'}) + self._selectAllPatches(data) + response = self.client.post(self.url, data) + self.assertContains(response, 'No patches to display', + status_code = 200) + for patch in [Patch.objects.get(pk = p.pk) for p in self.patches]: + self.assertTrue(patch.archived) + + def testUnArchivingPatches(self): + # Start with one patch archived and the remaining ones unarchived. + self.patches[0].archived = True + self.patches[0].save() + data = self.base_data.copy() + data.update({'archived': 'False'}) + self._selectAllPatches(data) + response = self.client.post(self.url, data) + self.assertContains(response, self.properties_form_id, + status_code = 200) + for patch in [Patch.objects.get(pk = p.pk) for p in self.patches]: + self.assertFalse(patch.archived) + + def _testStateChange(self, state): + data = self.base_data.copy() + data.update({'state': str(state)}) + self._selectAllPatches(data) + response = self.client.post(self.url, data) + self.assertContains(response, self.properties_form_id, + status_code = 200) + return response + + def testStateChangeValid(self): + states = [patch.state.pk for patch in self.patches] + state = State.objects.exclude(pk__in = states)[0] + self._testStateChange(state.pk) + for p in self.patches: + self.assertEquals(Patch.objects.get(pk = p.pk).state, state) + + def testStateChangeInvalid(self): + state = max(State.objects.all().values_list('id', flat = True)) + 1 + orig_states = [patch.state for patch in self.patches] + response = self._testStateChange(state) + self.assertEquals( \ + [Patch.objects.get(pk = p.pk).state for p in self.patches], + orig_states) + self.assertFormError(response, 'patchform', 'state', + 'Select a valid choice. That choice is not one ' + \ + 'of the available choices.') + + def _testDelegateChange(self, delegate_str): + data = self.base_data.copy() + data.update({'delegate': delegate_str}) + self._selectAllPatches(data) + response = self.client.post(self.url, data) + self.assertContains(response, self.properties_form_id, + status_code=200) + return response + + def testDelegateChangeValid(self): + delegate = create_maintainer(defaults.project) + response = self._testDelegateChange(str(delegate.pk)) + for p in self.patches: + self.assertEquals(Patch.objects.get(pk = p.pk).delegate, delegate) + + def testDelegateClear(self): + response = self._testDelegateChange('') + for p in self.patches: + self.assertEquals(Patch.objects.get(pk = p.pk).delegate, None) diff --git a/patchwork/tests/test_user.py b/patchwork/tests/test_user.py new file mode 100644 index 0000000..0faa970 --- /dev/null +++ b/patchwork/tests/test_user.py @@ -0,0 +1,195 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2010 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import unittest +from django.test import TestCase +from django.test.client import Client +from django.core import mail +from django.core.urlresolvers import reverse +from django.conf import settings +from django.contrib.auth.models import User +from patchwork.models import EmailConfirmation, Person, Bundle +from patchwork.tests.utils import defaults, error_strings + +def _confirmation_url(conf): + return reverse('patchwork.views.confirm', kwargs = {'key': conf.key}) + +class TestUser(object): + username = 'testuser' + email = 'test@example.com' + secondary_email = 'test2@example.com' + password = None + + def __init__(self): + self.password = User.objects.make_random_password() + self.user = User.objects.create_user(self.username, + self.email, self.password) + +class UserPersonRequestTest(TestCase): + def setUp(self): + self.user = TestUser() + self.client.login(username = self.user.username, + password = self.user.password) + EmailConfirmation.objects.all().delete() + + def testUserPersonRequestForm(self): + response = self.client.get('/user/link/') + self.assertEquals(response.status_code, 200) + self.assertTrue(response.context['linkform']) + + def testUserPersonRequestEmpty(self): + response = self.client.post('/user/link/', {'email': ''}) + self.assertEquals(response.status_code, 200) + self.assertTrue(response.context['linkform']) + self.assertFormError(response, 'linkform', 'email', + 'This field is required.') + + def testUserPersonRequestInvalid(self): + response = self.client.post('/user/link/', {'email': 'foo'}) + self.assertEquals(response.status_code, 200) + self.assertTrue(response.context['linkform']) + self.assertFormError(response, 'linkform', 'email', + error_strings['email']) + + def testUserPersonRequestValid(self): + response = self.client.post('/user/link/', + {'email': self.user.secondary_email}) + self.assertEquals(response.status_code, 200) + self.assertTrue(response.context['confirmation']) + + # check that we have a confirmation saved + self.assertEquals(EmailConfirmation.objects.count(), 1) + conf = EmailConfirmation.objects.all()[0] + self.assertEquals(conf.user, self.user.user) + self.assertEquals(conf.email, self.user.secondary_email) + self.assertEquals(conf.type, 'userperson') + + # check that an email has gone out... + self.assertEquals(len(mail.outbox), 1) + msg = mail.outbox[0] + self.assertEquals(msg.subject, 'Patchwork email address confirmation') + self.assertTrue(self.user.secondary_email in msg.to) + self.assertTrue(_confirmation_url(conf) in msg.body) + + # ...and that the URL is valid + response = self.client.get(_confirmation_url(conf)) + self.assertEquals(response.status_code, 200) + self.assertTemplateUsed(response, 'patchwork/user-link-confirm.html') + +class UserPersonConfirmTest(TestCase): + def setUp(self): + EmailConfirmation.objects.all().delete() + Person.objects.all().delete() + self.user = TestUser() + self.client.login(username = self.user.username, + password = self.user.password) + self.conf = EmailConfirmation(type = 'userperson', + email = self.user.secondary_email, + user = self.user.user) + self.conf.save() + + def testUserPersonConfirm(self): + self.assertEquals(Person.objects.count(), 0) + response = self.client.get(_confirmation_url(self.conf)) + self.assertEquals(response.status_code, 200) + + # check that the Person object has been created and linked + self.assertEquals(Person.objects.count(), 1) + person = Person.objects.get(email = self.user.secondary_email) + self.assertEquals(person.email, self.user.secondary_email) + self.assertEquals(person.user, self.user.user) + + # check that the confirmation has been marked as inactive. We + # need to reload the confirmation to check this. + conf = EmailConfirmation.objects.get(pk = self.conf.pk) + self.assertEquals(conf.active, False) + +class UserLoginRedirectTest(TestCase): + + def testUserLoginRedirect(self): + url = '/user/' + response = self.client.get(url) + self.assertRedirects(response, settings.LOGIN_URL + '?next=' + url) + +class UserProfileTest(TestCase): + + def setUp(self): + self.user = TestUser() + self.client.login(username = self.user.username, + password = self.user.password) + + def testUserProfile(self): + response = self.client.get('/user/') + self.assertContains(response, 'User Profile: %s' % self.user.username) + self.assertContains(response, 'User Profile: %s' % self.user.username) + + def testUserProfileNoBundles(self): + response = self.client.get('/user/') + self.assertContains(response, 'You have no bundles') + + def testUserProfileBundles(self): + project = defaults.project + project.save() + + bundle = Bundle(project = project, name = 'test-1', + owner = self.user.user) + bundle.save() + + response = self.client.get('/user/') + + self.assertContains(response, 'You have the following bundle') + self.assertContains(response, bundle.get_absolute_url()) + +class UserPasswordChangeTest(TestCase): + form_url = reverse('django.contrib.auth.views.password_change') + done_url = reverse('django.contrib.auth.views.password_change_done') + + def testPasswordChangeForm(self): + self.user = TestUser() + self.client.login(username = self.user.username, + password = self.user.password) + + response = self.client.get(self.form_url) + self.assertContains(response, 'Change my password') + + def testPasswordChange(self): + self.user = TestUser() + self.client.login(username = self.user.username, + password = self.user.password) + + old_password = self.user.password + new_password = User.objects.make_random_password() + + data = { + 'old_password': old_password, + 'new_password1': new_password, + 'new_password2': new_password, + } + + response = self.client.post(self.form_url, data) + self.assertRedirects(response, self.done_url) + + user = User.objects.get(id = self.user.user.id) + + self.assertFalse(user.check_password(old_password)) + self.assertTrue(user.check_password(new_password)) + + response = self.client.get(self.done_url) + self.assertContains(response, + "Your password has been changed sucessfully") diff --git a/patchwork/tests/test_xmlrpc.py b/patchwork/tests/test_xmlrpc.py new file mode 100644 index 0000000..2b459b2 --- /dev/null +++ b/patchwork/tests/test_xmlrpc.py @@ -0,0 +1,55 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2014 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import unittest +import xmlrpclib +from django.test import LiveServerTestCase +from django.core.urlresolvers import reverse +from django.conf import settings +from patchwork.models import Person, Patch +from patchwork.tests.utils import defaults + +@unittest.skipUnless(settings.ENABLE_XMLRPC, + "requires xmlrpc interface (use the ENABLE_XMLRPC setting)") +class XMLRPCTest(LiveServerTestCase): + + def setUp(self): + settings.STATIC_URL = '/' + self.url = (self.live_server_url + + reverse('patchwork.views.xmlrpc.xmlrpc')) + self.rpc = xmlrpclib.Server(self.url) + + def testGetRedirect(self): + response = self.client.get(self.url) + self.assertRedirects(response, + reverse('patchwork.views.help', + kwargs = {'path': 'pwclient/'})) + + def testList(self): + defaults.project.save() + defaults.patch_author_person.save() + patch = Patch(project = defaults.project, + submitter = defaults.patch_author_person, + msgid = defaults.patch_name, + content = defaults.patch) + patch.save() + + patches = self.rpc.patch_list() + self.assertEqual(len(patches), 1) + self.assertEqual(patches[0]['id'], patch.id) diff --git a/patchwork/tests/utils.py b/patchwork/tests/utils.py new file mode 100644 index 0000000..782ed36 --- /dev/null +++ b/patchwork/tests/utils.py @@ -0,0 +1,138 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os +import codecs +from patchwork.models import Project, Person +from django.contrib.auth.models import User +from django.forms.fields import EmailField + +from email import message_from_file +try: + from email.mime.text import MIMEText + from email.mime.multipart import MIMEMultipart +except ImportError: + # Python 2.4 compatibility + from email.MIMEText import MIMEText + from email.MIMEMultipart import MIMEMultipart + +# helper functions for tests +_test_mail_dir = os.path.join(os.path.dirname(__file__), 'mail') +_test_patch_dir = os.path.join(os.path.dirname(__file__), 'patches') + +class defaults(object): + project = Project(linkname = 'test-project', name = 'Test Project', + listid = 'test.example.com') + + patch_author = 'Patch Author ' + patch_author_person = Person(name = 'Patch Author', + email = 'patch-author@example.com') + + comment_author = 'Comment Author ' + + sender = 'Test Author ' + + subject = 'Test Subject' + + patch_name = 'Test Patch' + + patch = """--- /dev/null 2011-01-01 00:00:00.000000000 +0800 ++++ a 2011-01-01 00:00:00.000000000 +0800 +@@ -0,0 +1 @@ ++a +""" + +error_strings = { + 'email': 'Enter a valid email address.', +} + +_user_idx = 1 +def create_user(): + global _user_idx + userid = 'test%d' % _user_idx + email = '%s@example.com' % userid + _user_idx += 1 + + user = User.objects.create_user(userid, email, userid) + user.save() + + person = Person(email = email, name = userid, user = user) + person.save() + + return user + +def create_maintainer(project): + user = create_user() + profile = user.profile + profile.maintainer_projects.add(project) + profile.save() + return user + +def find_in_context(context, key): + if isinstance(context, list): + for c in context: + v = find_in_context(c, key) + if v is not None: + return v + else: + if key in context: + return context[key] + return None + +def read_patch(filename, encoding = None): + file_path = os.path.join(_test_patch_dir, filename) + if encoding is not None: + f = codecs.open(file_path, encoding = encoding) + else: + f = file(file_path) + + return f.read() + +def read_mail(filename, project = None): + file_path = os.path.join(_test_mail_dir, filename) + mail = message_from_file(open(file_path)) + if project is not None: + mail['List-Id'] = project.listid + return mail + +def create_email(content, subject = None, sender = None, multipart = False, + project = None, content_encoding = None): + if subject is None: + subject = defaults.subject + if sender is None: + sender = defaults.sender + if project is None: + project = defaults.project + if content_encoding is None: + content_encoding = 'us-ascii' + + if multipart: + msg = MIMEMultipart() + body = MIMEText(content, _subtype = 'plain', + _charset = content_encoding) + msg.attach(body) + else: + msg = MIMEText(content, _charset = content_encoding) + + msg['Subject'] = subject + msg['From'] = sender + msg['List-Id'] = project.listid + + + return msg diff --git a/patchwork/urls.py b/patchwork/urls.py new file mode 100644 index 0000000..b28eb90 --- /dev/null +++ b/patchwork/urls.py @@ -0,0 +1,103 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from django.conf.urls import patterns, url, include +from django.conf import settings +from django.contrib import admin +from django.contrib.auth import views as auth_views + +admin.autodiscover() + +urlpatterns = patterns('', + url(r'^admin/', include(admin.site.urls)), + + (r'^$', 'patchwork.views.projects'), + (r'^project/(?P[^/]+)/list/$', 'patchwork.views.patch.list'), + (r'^project/(?P[^/]+)/$', 'patchwork.views.project.project'), + + # patch views + (r'^patch/(?P\d+)/$', 'patchwork.views.patch.patch'), + (r'^patch/(?P\d+)/raw/$', 'patchwork.views.patch.content'), + (r'^patch/(?P\d+)/mbox/$', 'patchwork.views.patch.mbox'), + + # logged-in user stuff + (r'^user/$', 'patchwork.views.user.profile'), + (r'^user/todo/$', 'patchwork.views.user.todo_lists'), + (r'^user/todo/(?P[^/]+)/$', 'patchwork.views.user.todo_list'), + + (r'^user/bundles/$', + 'patchwork.views.bundle.bundles'), + + (r'^user/link/$', 'patchwork.views.user.link'), + (r'^user/unlink/(?P[^/]+)/$', 'patchwork.views.user.unlink'), + + # password change + url(r'^user/password-change/$', auth_views.password_change, + name='password_change'), + url(r'^user/password-change/done/$', auth_views.password_change_done, + name='password_change_done'), + + # login/logout + url(r'^user/login/$', auth_views.login, + {'template_name': 'patchwork/login.html'}, + name = 'auth_login'), + url(r'^user/logout/$', auth_views.logout, + {'template_name': 'patchwork/logout.html'}, + name = 'auth_logout'), + + # registration + (r'^register/', 'patchwork.views.user.register'), + + # public view for bundles + (r'^bundle/(?P[^/]*)/(?P[^/]*)/$', + 'patchwork.views.bundle.bundle'), + (r'^bundle/(?P[^/]*)/(?P[^/]*)/mbox/$', + 'patchwork.views.bundle.mbox'), + + (r'^confirm/(?P[0-9a-f]+)/$', 'patchwork.views.confirm'), + + # submitter autocomplete + (r'^submitter/$', 'patchwork.views.submitter_complete'), + + # email setup + (r'^mail/$', 'patchwork.views.mail.settings'), + (r'^mail/optout/$', 'patchwork.views.mail.optout'), + (r'^mail/optin/$', 'patchwork.views.mail.optin'), + + # help! + (r'^help/(?P.*)$', 'patchwork.views.help'), +) + +if settings.ENABLE_XMLRPC: + urlpatterns += patterns('', + (r'xmlrpc/$', 'patchwork.views.xmlrpc.xmlrpc'), + (r'^pwclient/$', 'patchwork.views.pwclient'), + (r'^project/(?P[^/]+)/pwclientrc/$', + 'patchwork.views.pwclientrc'), + ) + +# redirect from old urls +if settings.COMPAT_REDIR: + urlpatterns += patterns('', + (r'^user/bundle/(?P[^/]+)/$', + 'patchwork.views.bundle.bundle_redir'), + (r'^user/bundle/(?P[^/]+)/mbox/$', + 'patchwork.views.bundle.mbox_redir'), + ) + diff --git a/patchwork/utils.py b/patchwork/utils.py new file mode 100644 index 0000000..9ed9e41 --- /dev/null +++ b/patchwork/utils.py @@ -0,0 +1,248 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +import itertools +import datetime +from django.shortcuts import get_object_or_404 +from django.template.loader import render_to_string +from django.contrib.auth.models import User +from django.contrib.sites.models import Site +from django.conf import settings +from django.core.mail import EmailMessage +from django.db.models import Max, Q, F +from django.db.utils import IntegrityError +from patchwork.forms import MultiplePatchForm +from patchwork.models import Bundle, Project, BundlePatch, UserProfile, \ + PatchChangeNotification, EmailOptout, EmailConfirmation + +def get_patch_ids(d, prefix = 'patch_id'): + ids = [] + + for (k, v) in d.items(): + a = k.split(':') + if len(a) != 2: + continue + if a[0] != prefix: + continue + if not v: + continue + ids.append(a[1]) + + return ids + +class Order(object): + order_map = { + 'date': 'date', + 'name': 'name', + 'state': 'state__ordering', + 'submitter': 'submitter__name', + 'delegate': 'delegate__username', + } + default_order = ('date', True) + + def __init__(self, str = None, editable = False): + self.reversed = False + self.editable = editable + (self.order, self.reversed) = self.default_order + + if self.editable: + return + + if str is None or str == '': + return + + reversed = False + if str[0] == '-': + str = str[1:] + reversed = True + + if str not in self.order_map.keys(): + return + + self.order = str + self.reversed = reversed + + def __str__(self): + str = self.order + if self.reversed: + str = '-' + str + return str + + def name(self): + return self.order + + def reversed_name(self): + if self.reversed: + return self.order + else: + return '-' + self.order + + def apply(self, qs): + q = self.order_map[self.order] + if self.reversed: + q = '-' + q + + orders = [q] + + # if we're using a non-default order, add the default as a secondary + # ordering. We reverse the default if the primary is reversed. + (default_name, default_reverse) = self.default_order + if self.order != default_name: + q = self.order_map[default_name] + if self.reversed ^ default_reverse: + q = '-' + q + orders.append(q) + + return qs.order_by(*orders) + +bundle_actions = ['create', 'add', 'remove'] +def set_bundle(user, project, action, data, patches, context): + # set up the bundle + bundle = None + if action == 'create': + bundle_name = data['bundle_name'].strip() + if '/' in bundle_name: + return ['Bundle names can\'t contain slashes'] + + if not bundle_name: + return ['No bundle name was specified'] + + if Bundle.objects.filter(owner = user, name = bundle_name).count() > 0: + return ['You already have a bundle called "%s"' % bundle_name] + + bundle = Bundle(owner = user, project = project, + name = bundle_name) + bundle.save() + context.add_message("Bundle %s created" % bundle.name) + + elif action =='add': + bundle = get_object_or_404(Bundle, id = data['bundle_id']) + + elif action =='remove': + bundle = get_object_or_404(Bundle, id = data['removed_bundle_id']) + + if not bundle: + return ['no such bundle'] + + for patch in patches: + if action == 'create' or action == 'add': + bundlepatch_count = BundlePatch.objects.filter(bundle = bundle, + patch = patch).count() + if bundlepatch_count == 0: + bundle.append_patch(patch) + context.add_message("Patch '%s' added to bundle %s" % \ + (patch.name, bundle.name)) + else: + context.add_message("Patch '%s' already in bundle %s" % \ + (patch.name, bundle.name)) + + elif action == 'remove': + try: + bp = BundlePatch.objects.get(bundle = bundle, patch = patch) + bp.delete() + context.add_message("Patch '%s' removed from bundle %s\n" % \ + (patch.name, bundle.name)) + except Exception: + pass + + bundle.save() + + return [] + +def send_notifications(): + date_limit = datetime.datetime.now() - \ + datetime.timedelta(minutes = + settings.NOTIFICATION_DELAY_MINUTES) + + # This gets funky: we want to filter out any notifications that should + # be grouped with other notifications that aren't ready to go out yet. To + # do that, we join back onto PatchChangeNotification (PCN -> Patch -> + # Person -> Patch -> max(PCN.last_modified)), filtering out any maxima + # that are with the date_limit. + qs = PatchChangeNotification.objects \ + .annotate(m = Max('patch__submitter__patch__patchchangenotification' + '__last_modified')) \ + .filter(m__lt = date_limit) + + groups = itertools.groupby(qs.order_by('patch__submitter'), + lambda n: n.patch.submitter) + + errors = [] + + for (recipient, notifications) in groups: + notifications = list(notifications) + projects = set([ n.patch.project.linkname for n in notifications ]) + + def delete_notifications(): + pks = [ n.pk for n in notifications ] + PatchChangeNotification.objects.filter(pk__in = pks).delete() + + if EmailOptout.is_optout(recipient.email): + delete_notifications() + continue + + context = { + 'site': Site.objects.get_current(), + 'person': recipient, + 'notifications': notifications, + 'projects': projects, + } + + subject = render_to_string( + 'patchwork/patch-change-notification-subject.text', + context).strip() + content = render_to_string('patchwork/patch-change-notification.mail', + context) + + message = EmailMessage(subject = subject, body = content, + from_email = settings.NOTIFICATION_FROM_EMAIL, + to = [recipient.email], + headers = {'Precedence': 'bulk'}) + + try: + message.send() + except ex: + errors.append((recipient, ex)) + continue + + delete_notifications() + + return errors + +def do_expiry(): + # expire any pending confirmations + q = (Q(date__lt = datetime.datetime.now() - EmailConfirmation.validity) | + Q(active = False)) + EmailConfirmation.objects.filter(q).delete() + + # expire inactive users with no pending confirmation + pending_confs = EmailConfirmation.objects.values('user') + users = User.objects.filter( + is_active = False, + last_login = F('date_joined') + ).exclude( + id__in = pending_confs + ) + + # delete users + users.delete() + + + diff --git a/patchwork/views/__init__.py b/patchwork/views/__init__.py new file mode 100644 index 0000000..dfca56d --- /dev/null +++ b/patchwork/views/__init__.py @@ -0,0 +1,220 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +from base import * +from patchwork.utils import Order, get_patch_ids, bundle_actions, set_bundle +from patchwork.paginator import Paginator +from patchwork.forms import MultiplePatchForm +from patchwork.models import Comment +import re +import datetime + +try: + from email.mime.nonmultipart import MIMENonMultipart + from email.encoders import encode_7or8bit + from email.parser import HeaderParser + from email.header import Header + import email.utils +except ImportError: + # Python 2.4 compatibility + from email.MIMENonMultipart import MIMENonMultipart + from email.Encoders import encode_7or8bit + from email.Parser import HeaderParser + from email.Header import Header + import email.Utils + email.utils = email.Utils + +def generic_list(request, project, view, + view_args = {}, filter_settings = [], patches = None, + editable_order = False): + + context = PatchworkRequestContext(request, + list_view = view, + list_view_params = view_args) + + context.project = project + order = Order(request.REQUEST.get('order'), editable = editable_order) + + # Explicitly set data to None because request.POST will be an empty dict + # when the form is not submitted, but passing a non-None data argument to + # a forms.Form will make it bound and we don't want that to happen unless + # there's been a form submission. + data = None + if request.method == 'POST': + data = request.POST + user = request.user + properties_form = None + if project.is_editable(user): + + # we only pass the post data to the MultiplePatchForm if that was + # the actual form submitted + data_tmp = None + if data and data.get('form', '') == 'patchlistform': + data_tmp = data + + properties_form = MultiplePatchForm(project, data = data_tmp) + + if request.method == 'POST' and data.get('form') == 'patchlistform': + action = data.get('action', '').lower() + + # special case: the user may have hit enter in the 'create bundle' + # text field, so if non-empty, assume the create action: + if data.get('bundle_name', False): + action = 'create' + + ps = Patch.objects.filter(id__in = get_patch_ids(data)) + + if action in bundle_actions: + errors = set_bundle(user, project, action, data, ps, context) + + elif properties_form and action == properties_form.action: + errors = process_multiplepatch_form(properties_form, user, + action, ps, context) + else: + errors = [] + + if errors: + context['errors'] = errors + + for (filterclass, setting) in filter_settings: + if isinstance(setting, dict): + context.filters.set_status(filterclass, **setting) + elif isinstance(setting, list): + context.filters.set_status(filterclass, *setting) + else: + context.filters.set_status(filterclass, setting) + + if patches is None: + patches = Patch.objects.filter(project=project) + + patches = context.filters.apply(patches) + if not editable_order: + patches = order.apply(patches) + + # we don't need the content or headers for a list; they're text fields + # that can potentially contain a lot of data + patches = patches.defer('content', 'headers') + + # but we will need to follow the state and submitter relations for + # rendering the list template + patches = patches.select_related('state', 'submitter') + + paginator = Paginator(request, patches) + + context.update({ + 'page': paginator.current_page, + 'patchform': properties_form, + 'project': project, + 'order': order, + }) + + return context + + +def process_multiplepatch_form(form, user, action, patches, context): + errors = [] + if not form.is_valid() or action != form.action: + return ['The submitted form data was invalid'] + + if len(patches) == 0: + context.add_message("No patches selected; nothing updated") + return errors + + changed_patches = 0 + for patch in patches: + if not patch.is_editable(user): + errors.append("You don't have permissions to edit patch '%s'" + % patch.name) + continue + + changed_patches += 1 + form.save(patch) + + if changed_patches == 1: + context.add_message("1 patch updated") + elif changed_patches > 1: + context.add_message("%d patches updated" % changed_patches) + else: + context.add_message("No patches updated") + + return errors + +class PatchMbox(MIMENonMultipart): + patch_charset = 'utf-8' + def __init__(self, _text): + MIMENonMultipart.__init__(self, 'text', 'plain', + **{'charset': self.patch_charset}) + self.set_payload(_text.encode(self.patch_charset)) + encode_7or8bit(self) + +def patch_to_mbox(patch): + postscript_re = re.compile('\n-{2,3} ?\n') + + comment = None + try: + comment = Comment.objects.get(patch = patch, msgid = patch.msgid) + except Exception: + pass + + body = '' + if comment: + body = comment.content.strip() + "\n" + + parts = postscript_re.split(body, 1) + if len(parts) == 2: + (body, postscript) = parts + body = body.strip() + "\n" + postscript = postscript.rstrip() + else: + postscript = '' + + for comment in Comment.objects.filter(patch = patch) \ + .exclude(msgid = patch.msgid): + body += comment.patch_responses() + + if postscript: + body += '---\n' + postscript + '\n' + + if patch.content: + body += '\n' + patch.content + + delta = patch.date - datetime.datetime.utcfromtimestamp(0) + utc_timestamp = delta.seconds + delta.days*24*3600 + + mail = PatchMbox(body) + mail['Subject'] = patch.name + mail['From'] = email.utils.formataddr(( + str(Header(patch.submitter.name, mail.patch_charset)), + patch.submitter.email)) + mail['X-Patchwork-Id'] = str(patch.id) + mail['Message-Id'] = patch.msgid + mail.set_unixfrom('From patchwork ' + patch.date.ctime()) + + + copied_headers = ['To', 'Cc', 'Date'] + orig_headers = HeaderParser().parsestr(str(patch.headers)) + for header in copied_headers: + if header in orig_headers: + mail[header] = orig_headers[header] + + if 'Date' not in mail: + mail['Date'] = email.utils.formatdate(utc_timestamp) + + return mail diff --git a/patchwork/views/base.py b/patchwork/views/base.py new file mode 100644 index 0000000..6d7dd13 --- /dev/null +++ b/patchwork/views/base.py @@ -0,0 +1,122 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +from patchwork.models import Patch, Project, Person, EmailConfirmation +from django.shortcuts import render_to_response, get_object_or_404 +from django.http import HttpResponse, HttpResponseRedirect, Http404 +from patchwork.requestcontext import PatchworkRequestContext +from django.core import serializers, urlresolvers +from django.template.loader import render_to_string +from django.conf import settings +from django.db.models import Q + +def projects(request): + context = PatchworkRequestContext(request) + projects = Project.objects.all() + + if projects.count() == 1: + return HttpResponseRedirect( + urlresolvers.reverse('patchwork.views.patch.list', + kwargs = {'project_id': projects[0].linkname})) + + context['projects'] = projects + return render_to_response('patchwork/projects.html', context) + +def pwclientrc(request, project_id): + project = get_object_or_404(Project, linkname = project_id) + context = PatchworkRequestContext(request) + context.project = project + if settings.FORCE_HTTPS_LINKS or request.is_secure(): + context['scheme'] = 'https' + else: + context['scheme'] = 'http' + response = HttpResponse(content_type = "text/plain") + response['Content-Disposition'] = 'attachment; filename=.pwclientrc' + response.write(render_to_string('patchwork/pwclientrc', context)) + return response + +def pwclient(request): + context = PatchworkRequestContext(request) + response = HttpResponse(content_type = "text/x-python") + response['Content-Disposition'] = 'attachment; filename=pwclient' + response.write(render_to_string('patchwork/pwclient', context)) + return response + +def confirm(request, key): + import patchwork.views.user, patchwork.views.mail + views = { + 'userperson': patchwork.views.user.link_confirm, + 'registration': patchwork.views.user.register_confirm, + 'optout': patchwork.views.mail.optout_confirm, + 'optin': patchwork.views.mail.optin_confirm, + } + + conf = get_object_or_404(EmailConfirmation, key = key) + if conf.type not in views: + raise Http404 + + if conf.active and conf.is_valid(): + return views[conf.type](request, conf) + + context = PatchworkRequestContext(request) + context['conf'] = conf + if not conf.active: + context['error'] = 'inactive' + elif not conf.is_valid(): + context['error'] = 'expired' + + return render_to_response('patchwork/confirm-error.html', context) + +def submitter_complete(request): + search = request.GET.get('q', '') + limit = request.GET.get('l', None) + response = HttpResponse(content_type = "text/plain") + + if len(search) <= 3: + return response + + queryset = Person.objects.filter(Q(name__icontains = search) | + Q(email__icontains = search)) + if limit is not None: + try: + limit = int(limit) + except ValueError: + limit = None + + if limit is not None and limit > 0: + queryset = queryset[:limit] + + json_serializer = serializers.get_serializer("json")() + json_serializer.serialize(queryset, ensure_ascii=False, stream=response) + return response + +help_pages = {'': 'index.html', + 'about/': 'about.html', + } + +if settings.ENABLE_XMLRPC: + help_pages['pwclient/'] = 'pwclient.html' + +def help(request, path): + context = PatchworkRequestContext(request) + if path in help_pages: + return render_to_response('patchwork/help/' + help_pages[path], context) + raise Http404 + diff --git a/patchwork/views/bundle.py b/patchwork/views/bundle.py new file mode 100644 index 0000000..3fb47e2 --- /dev/null +++ b/patchwork/views/bundle.py @@ -0,0 +1,221 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import User +from django.shortcuts import render_to_response, get_object_or_404 +from patchwork.requestcontext import PatchworkRequestContext +from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound +import django.core.urlresolvers +from patchwork.models import Patch, Bundle, BundlePatch, Project +from patchwork.utils import get_patch_ids +from patchwork.forms import BundleForm, DeleteBundleForm +from patchwork.views import generic_list, patch_to_mbox +from patchwork.filters import DelegateFilter + +@login_required +def setbundle(request): + context = PatchworkRequestContext(request) + + bundle = None + + if request.method == 'POST': + action = request.POST.get('action', None) + if action is None: + pass + elif action == 'create': + project = get_object_or_404(Project, + id = request.POST.get('project')) + bundle = Bundle(owner = request.user, project = project, + name = request.POST['name']) + bundle.save() + patch_id = request.POST.get('patch_id', None) + if patch_id: + patch = get_object_or_404(Patch, id = patch_id) + try: + bundle.append_patch(patch) + except Exception: + pass + bundle.save() + elif action == 'add': + bundle = get_object_or_404(Bundle, + owner = request.user, id = request.POST['id']) + bundle.save() + + patch_id = request.get('patch_id', None) + if patch_id: + patch_ids = patch_id + else: + patch_ids = get_patch_ids(request.POST) + + for id in patch_ids: + try: + patch = Patch.objects.get(id = id) + bundle.append_patch(patch) + except: + pass + + bundle.save() + elif action == 'delete': + try: + bundle = Bundle.objects.get(owner = request.user, + id = request.POST['id']) + bundle.delete() + except Exception: + pass + + bundle = None + + else: + bundle = get_object_or_404(Bundle, owner = request.user, + id = request.POST['bundle_id']) + + if 'error' in context: + pass + + if bundle: + return HttpResponseRedirect( + django.core.urlresolvers.reverse( + 'patchwork.views.bundle.bundle', + kwargs = {'bundle_id': bundle.id} + ) + ) + else: + return HttpResponseRedirect( + django.core.urlresolvers.reverse( + 'patchwork.views.bundle.list') + ) + +@login_required +def bundles(request): + context = PatchworkRequestContext(request) + + if request.method == 'POST': + form_name = request.POST.get('form_name', '') + + if form_name == DeleteBundleForm.name: + form = DeleteBundleForm(request.POST) + if form.is_valid(): + bundle = get_object_or_404(Bundle, + id = form.cleaned_data['bundle_id']) + bundle.delete() + + bundles = Bundle.objects.filter(owner = request.user) + for bundle in bundles: + bundle.delete_form = DeleteBundleForm(auto_id = False, + initial = {'bundle_id': bundle.id}) + + context['bundles'] = bundles + + return render_to_response('patchwork/bundles.html', context) + +def bundle(request, username, bundlename): + bundle = get_object_or_404(Bundle, owner__username = username, + name = bundlename) + filter_settings = [(DelegateFilter, DelegateFilter.AnyDelegate)] + + is_owner = request.user == bundle.owner + + if not (is_owner or bundle.public): + return HttpResponseNotFound() + + if is_owner: + if request.method == 'POST' and request.POST.get('form') == 'bundle': + action = request.POST.get('action', '').lower() + if action == 'delete': + bundle.delete() + return HttpResponseRedirect( + django.core.urlresolvers.reverse( + 'patchwork.views.user.profile') + ) + elif action == 'update': + form = BundleForm(request.POST, instance = bundle) + if form.is_valid(): + form.save() + + # if we've changed the bundle name, redirect to new URL + bundle = Bundle.objects.get(pk = bundle.pk) + if bundle.name != bundlename: + return HttpResponseRedirect(bundle.get_absolute_url()) + + else: + form = BundleForm(instance = bundle) + else: + form = BundleForm(instance = bundle) + + if request.method == 'POST' and \ + request.POST.get('form') == 'reorderform': + order = get_object_or_404(BundlePatch, bundle = bundle, + patch__id = request.POST.get('order_start')).order + + for patch_id in request.POST.getlist('neworder'): + bundlepatch = get_object_or_404(BundlePatch, + bundle = bundle, patch__id = patch_id) + bundlepatch.order = order + bundlepatch.save() + order += 1 + else: + form = None + + context = generic_list(request, bundle.project, + 'patchwork.views.bundle.bundle', + view_args = {'username': bundle.owner.username, + 'bundlename': bundle.name}, + filter_settings = filter_settings, + patches = bundle.ordered_patches(), + editable_order = is_owner) + + context['bundle'] = bundle + context['bundleform'] = form + + return render_to_response('patchwork/bundle.html', context) + +def mbox(request, username, bundlename): + bundle = get_object_or_404(Bundle, owner__username = username, + name = bundlename) + + if not (request.user == bundle.owner or bundle.public): + return HttpResponseNotFound() + + mbox = '\n'.join([patch_to_mbox(p).as_string(True) + for p in bundle.ordered_patches()]) + + response = HttpResponse(content_type='text/plain') + response['Content-Disposition'] = \ + 'attachment; filename=bundle-%d-%s.mbox' % (bundle.id, bundle.name) + + response.write(mbox) + return response + +@login_required +def bundle_redir(request, bundle_id): + bundle = get_object_or_404(Bundle, id = bundle_id, owner = request.user) + return HttpResponseRedirect(bundle.get_absolute_url()) + +@login_required +def mbox_redir(request, bundle_id): + bundle = get_object_or_404(Bundle, id = bundle_id, owner = request.user) + return HttpResponseRedirect(django.core.urlresolvers.reverse( + 'patchwork.views.bundle.mbox', kwargs = { + 'username': request.user.username, + 'bundlename': bundle.name, + })) + + + diff --git a/patchwork/views/mail.py b/patchwork/views/mail.py new file mode 100644 index 0000000..aebba34 --- /dev/null +++ b/patchwork/views/mail.py @@ -0,0 +1,119 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2010 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from patchwork.requestcontext import PatchworkRequestContext +from patchwork.models import EmailOptout, EmailConfirmation +from patchwork.forms import OptinoutRequestForm, EmailForm +from django.shortcuts import render_to_response +from django.template.loader import render_to_string +from django.conf import settings as conf_settings +from django.core.mail import send_mail +from django.core.urlresolvers import reverse +from django.http import HttpResponseRedirect + +def settings(request): + context = PatchworkRequestContext(request) + if request.method == 'POST': + form = EmailForm(data = request.POST) + if form.is_valid(): + email = form.cleaned_data['email'] + is_optout = EmailOptout.objects.filter(email = email).count() > 0 + context.update({ + 'email': email, + 'is_optout': is_optout, + }) + return render_to_response('patchwork/mail-settings.html', context) + + else: + form = EmailForm() + context['form'] = form + return render_to_response('patchwork/mail-form.html', context) + +def optout_confirm(request, conf): + context = PatchworkRequestContext(request) + + email = conf.email.strip().lower() + # silently ignore duplicated optouts + if EmailOptout.objects.filter(email = email).count() == 0: + optout = EmailOptout(email = email) + optout.save() + + conf.deactivate() + context['email'] = conf.email + + return render_to_response('patchwork/optout.html', context) + +def optin_confirm(request, conf): + context = PatchworkRequestContext(request) + + email = conf.email.strip().lower() + EmailOptout.objects.filter(email = email).delete() + + conf.deactivate() + context['email'] = conf.email + + return render_to_response('patchwork/optin.html', context) + +def optinout(request, action, description): + context = PatchworkRequestContext(request) + + mail_template = 'patchwork/%s-request.mail' % action + html_template = 'patchwork/%s-request.html' % action + + if request.method != 'POST': + return HttpResponseRedirect(reverse(settings)) + + form = OptinoutRequestForm(data = request.POST) + if not form.is_valid(): + context['error'] = ('There was an error in the %s form. ' + + 'Please review the form and re-submit.') % \ + description + context['form'] = form + return render_to_response(html_template, context) + + email = form.cleaned_data['email'] + if action == 'optin' and \ + EmailOptout.objects.filter(email = email).count() == 0: + context['error'] = ('The email address %s is not on the ' + + 'patchwork opt-out list, so you don\'t ' + + 'need to opt back in') % email + context['form'] = form + return render_to_response(html_template, context) + + conf = EmailConfirmation(type = action, email = email) + conf.save() + context['confirmation'] = conf + mail = render_to_string(mail_template, context) + try: + send_mail('Patchwork %s confirmation' % description, mail, + conf_settings.DEFAULT_FROM_EMAIL, [email]) + context['email'] = mail + context['email_sent'] = True + except Exception, ex: + context['error'] = 'An error occurred during confirmation . ' + \ + 'Please try again later.' + context['admins'] = conf_settings.ADMINS + + return render_to_response(html_template, context) + +def optout(request): + return optinout(request, 'optout', 'opt-out') + +def optin(request): + return optinout(request, 'optin', 'opt-in') diff --git a/patchwork/views/patch.py b/patchwork/views/patch.py new file mode 100644 index 0000000..62ff853 --- /dev/null +++ b/patchwork/views/patch.py @@ -0,0 +1,107 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +from patchwork.models import Patch, Project, Bundle +from patchwork.forms import PatchForm, CreateBundleForm +from patchwork.requestcontext import PatchworkRequestContext +from django.shortcuts import render_to_response, get_object_or_404 +from django.http import HttpResponse, HttpResponseForbidden +from patchwork.views import generic_list, patch_to_mbox + +def patch(request, patch_id): + context = PatchworkRequestContext(request) + patch = get_object_or_404(Patch, id=patch_id) + context.project = patch.project + editable = patch.is_editable(request.user) + + form = None + createbundleform = None + + if editable: + form = PatchForm(instance = patch) + if request.user.is_authenticated(): + createbundleform = CreateBundleForm() + + if request.method == 'POST': + action = request.POST.get('action', None) + if action: + action = action.lower() + + if action == 'createbundle': + bundle = Bundle(owner = request.user, project = patch.project) + createbundleform = CreateBundleForm(instance = bundle, + data = request.POST) + if createbundleform.is_valid(): + createbundleform.save() + bundle.append_patch(patch) + bundle.save() + createbundleform = CreateBundleForm() + context.add_message('Bundle %s created' % bundle.name) + + elif action == 'addtobundle': + bundle = get_object_or_404(Bundle, id = \ + request.POST.get('bundle_id')) + try: + bundle.append_patch(patch) + bundle.save() + context.add_message('Patch added to bundle "%s"' % bundle.name) + except Exception, ex: + context.add_message("Couldn't add patch '%s' to bundle %s: %s" \ + % (patch.name, bundle.name, ex.message)) + + # all other actions require edit privs + elif not editable: + return HttpResponseForbidden() + + elif action is None: + form = PatchForm(data = request.POST, instance = patch) + if form.is_valid(): + form.save() + context.add_message('Patch updated') + + context['patch'] = patch + context['patchform'] = form + context['createbundleform'] = createbundleform + context['project'] = patch.project + + return render_to_response('patchwork/patch.html', context) + +def content(request, patch_id): + patch = get_object_or_404(Patch, id=patch_id) + response = HttpResponse(content_type="text/x-patch") + response.write(patch.content) + response['Content-Disposition'] = 'attachment; filename=' + \ + patch.filename().replace(';', '').replace('\n', '') + return response + +def mbox(request, patch_id): + patch = get_object_or_404(Patch, id=patch_id) + response = HttpResponse(content_type="text/plain") + response.write(patch_to_mbox(patch).as_string(True)) + response['Content-Disposition'] = 'attachment; filename=' + \ + patch.filename().replace(';', '').replace('\n', '') + return response + + +def list(request, project_id): + project = get_object_or_404(Project, linkname=project_id) + context = generic_list(request, project, 'patchwork.views.patch.list', + view_args = {'project_id': project.linkname}) + return render_to_response('patchwork/list.html', context) diff --git a/patchwork/views/project.py b/patchwork/views/project.py new file mode 100644 index 0000000..114dbe0 --- /dev/null +++ b/patchwork/views/project.py @@ -0,0 +1,38 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2009 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +from patchwork.models import Patch, Project +from django.shortcuts import render_to_response, get_object_or_404 +from django.contrib.auth.models import User +from patchwork.requestcontext import PatchworkRequestContext + +def project(request, project_id): + context = PatchworkRequestContext(request) + project = get_object_or_404(Project, linkname = project_id) + context.project = project + + context['maintainers'] = User.objects.filter( \ + profile__maintainer_projects = project) + context['n_patches'] = Patch.objects.filter(project = project, + archived = False).count() + context['n_archived_patches'] = Patch.objects.filter(project = project, + archived = True).count() + + return render_to_response('patchwork/project.html', context) diff --git a/patchwork/views/user.py b/patchwork/views/user.py new file mode 100644 index 0000000..126ecc9 --- /dev/null +++ b/patchwork/views/user.py @@ -0,0 +1,216 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +from django.contrib.auth.decorators import login_required +from patchwork.requestcontext import PatchworkRequestContext +from django.shortcuts import render_to_response, get_object_or_404 +from django.contrib import auth +from django.contrib.sites.models import Site +from django.http import HttpResponseRedirect +from patchwork.models import Project, Bundle, Person, EmailConfirmation, \ + State, EmailOptout +from patchwork.forms import UserProfileForm, UserPersonLinkForm, \ + RegistrationForm +from patchwork.filters import DelegateFilter +from patchwork.views import generic_list +from django.template.loader import render_to_string +from django.conf import settings +from django.core.mail import send_mail +import django.core.urlresolvers + +def register(request): + context = PatchworkRequestContext(request) + if request.method == 'POST': + form = RegistrationForm(request.POST) + if form.is_valid(): + data = form.cleaned_data + # create inactive user + user = auth.models.User.objects.create_user(data['username'], + data['email'], + data['password']) + user.is_active = False; + user.first_name = data.get('first_name', '') + user.last_name = data.get('last_name', '') + user.save() + + # create confirmation + conf = EmailConfirmation(type = 'registration', user = user, + email = user.email) + conf.save() + + # send email + mail_ctx = {'site': Site.objects.get_current(), + 'confirmation': conf} + + subject = render_to_string('patchwork/activation_email_subject.txt', + mail_ctx).replace('\n', ' ').strip() + + message = render_to_string('patchwork/activation_email.txt', + mail_ctx) + + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, + [conf.email]) + + # setting 'confirmation' in the template indicates success + context['confirmation'] = conf + + else: + form = RegistrationForm() + + return render_to_response('patchwork/registration_form.html', + { 'form': form }, + context_instance=context) + +def register_confirm(request, conf): + conf.user.is_active = True + conf.user.save() + conf.deactivate() + try: + person = Person.objects.get(email__iexact = conf.user.email) + except Person.DoesNotExist: + person = Person(email = conf.user.email, + name = conf.user.profile.name()) + person.user = conf.user + person.save() + + return render_to_response('patchwork/registration-confirm.html') + +@login_required +def profile(request): + context = PatchworkRequestContext(request) + + if request.method == 'POST': + form = UserProfileForm(instance = request.user.profile, + data = request.POST) + if form.is_valid(): + form.save() + else: + form = UserProfileForm(instance = request.user.profile) + + context.project = request.user.profile.primary_project + context['bundles'] = Bundle.objects.filter(owner = request.user) + context['profileform'] = form + + optout_query = '%s.%s IN (SELECT %s FROM %s)' % ( + Person._meta.db_table, + Person._meta.get_field('email').column, + EmailOptout._meta.get_field('email').column, + EmailOptout._meta.db_table) + people = Person.objects.filter(user = request.user) \ + .extra(select = {'is_optout': optout_query}) + context['linked_emails'] = people + context['linkform'] = UserPersonLinkForm() + + return render_to_response('patchwork/profile.html', context) + +@login_required +def link(request): + context = PatchworkRequestContext(request) + + if request.method == 'POST': + form = UserPersonLinkForm(request.POST) + if form.is_valid(): + conf = EmailConfirmation(type = 'userperson', + user = request.user, + email = form.cleaned_data['email']) + conf.save() + context['confirmation'] = conf + + try: + send_mail('Patchwork email address confirmation', + render_to_string('patchwork/user-link.mail', + context), + settings.DEFAULT_FROM_EMAIL, + [form.cleaned_data['email']]) + except Exception: + context['confirmation'] = None + context['error'] = 'An error occurred during confirmation. ' + \ + 'Please try again later' + else: + form = UserPersonLinkForm() + context['linkform'] = form + + return render_to_response('patchwork/user-link.html', context) + +@login_required +def link_confirm(request, conf): + context = PatchworkRequestContext(request) + + try: + person = Person.objects.get(email__iexact = conf.email) + except Person.DoesNotExist: + person = Person(email = conf.email) + + person.link_to_user(conf.user) + person.save() + conf.deactivate() + + context['person'] = person + + return render_to_response('patchwork/user-link-confirm.html', context) + +@login_required +def unlink(request, person_id): + person = get_object_or_404(Person, id = person_id) + + if request.method == 'POST': + if person.email != request.user.email: + person.user = None + person.save() + + url = django.core.urlresolvers.reverse('patchwork.views.user.profile') + return HttpResponseRedirect(url) + + +@login_required +def todo_lists(request): + todo_lists = [] + + for project in Project.objects.all(): + patches = request.user.profile.todo_patches(project = project) + if not patches.count(): + continue + + todo_lists.append({'project': project, 'n_patches': patches.count()}) + + if len(todo_lists) == 1: + return todo_list(request, todo_lists[0]['project'].linkname) + + context = PatchworkRequestContext(request) + context['todo_lists'] = todo_lists + context.project = request.user.profile.primary_project + return render_to_response('patchwork/todo-lists.html', context) + +@login_required +def todo_list(request, project_id): + project = get_object_or_404(Project, linkname = project_id) + patches = request.user.profile.todo_patches(project = project) + filter_settings = [(DelegateFilter, + {'delegate': request.user})] + + context = generic_list(request, project, + 'patchwork.views.user.todo_list', + view_args = {'project_id': project.linkname}, + filter_settings = filter_settings, + patches = patches) + + context['action_required_states'] = \ + State.objects.filter(action_required = True).all() + return render_to_response('patchwork/todo-list.html', context) diff --git a/patchwork/views/xmlrpc.py b/patchwork/views/xmlrpc.py new file mode 100644 index 0000000..84ed408 --- /dev/null +++ b/patchwork/views/xmlrpc.py @@ -0,0 +1,450 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2008 Jeremy Kerr +# +# This file is part of the Patchwork package. +# +# Patchwork is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Patchwork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Patchwork; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Patchwork XMLRPC interface +# + +from SimpleXMLRPCServer import SimpleXMLRPCDispatcher +from django.http import HttpResponse, HttpResponseRedirect, \ + HttpResponseServerError +from django.core import urlresolvers +from django.contrib.auth import authenticate +from patchwork.models import Patch, Project, Person, State +from patchwork.views import patch_to_mbox +from django.views.decorators.csrf import csrf_exempt + +import sys +import base64 +import xmlrpclib + +class PatchworkXMLRPCDispatcher(SimpleXMLRPCDispatcher): + def __init__(self): + if sys.version_info[:3] >= (2,5,): + SimpleXMLRPCDispatcher.__init__(self, allow_none=False, + encoding=None) + def _dumps(obj, *args, **kwargs): + kwargs['allow_none'] = self.allow_none + kwargs['encoding'] = self.encoding + return xmlrpclib.dumps(obj, *args, **kwargs) + else: + def _dumps(obj, *args, **kwargs): + return xmlrpclib.dumps(obj, *args, **kwargs) + SimpleXMLRPCDispatcher.__init__(self) + + self.dumps = _dumps + + # map of name => (auth, func) + self.func_map = {} + + def register_function(self, fn, auth_required): + self.func_map[fn.__name__] = (auth_required, fn) + + + def _user_for_request(self, request): + auth_header = None + + if 'HTTP_AUTHORIZATION' in request.META: + auth_header = request.META.get('HTTP_AUTHORIZATION') + elif 'Authorization' in request.META: + auth_header = request.META.get('Authorization') + + if auth_header is None or auth_header == '': + raise Exception("No authentication credentials given") + + str = auth_header.strip() + + if not str.startswith('Basic '): + raise Exception("Authentication scheme not supported") + + str = str[len('Basic '):].strip() + + try: + decoded = base64.decodestring(str) + username, password = decoded.split(':', 1) + except: + raise Exception("Invalid authentication credentials") + + return authenticate(username = username, password = password) + + + def _dispatch(self, request, method, params): + if method not in self.func_map.keys(): + raise Exception('method "%s" is not supported' % method) + + auth_required, fn = self.func_map[method] + + if auth_required: + user = self._user_for_request(request) + if not user: + raise Exception("Invalid username/password") + + params = (user,) + params + + return fn(*params) + + def _marshaled_dispatch(self, request): + try: + params, method = xmlrpclib.loads(request.body) + + response = self._dispatch(request, method, params) + # wrap response in a singleton tuple + response = (response,) + response = self.dumps(response, methodresponse=1) + except xmlrpclib.Fault, fault: + response = self.dumps(fault) + except: + # report exception back to server + response = self.dumps( + xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)), + ) + + return response + +dispatcher = PatchworkXMLRPCDispatcher() + +# XMLRPC view function +@csrf_exempt +def xmlrpc(request): + if request.method != 'POST': + return HttpResponseRedirect( + urlresolvers.reverse('patchwork.views.help', + kwargs = {'path': 'pwclient/'})) + + response = HttpResponse() + try: + ret = dispatcher._marshaled_dispatch(request) + response.write(ret) + except Exception: + return HttpResponseServerError() + + return response + +# decorator for XMLRPC methods. Setting login_required to true will call +# the decorated function with a non-optional user as the first argument. +def xmlrpc_method(login_required = False): + def wrap(f): + dispatcher.register_function(f, login_required) + return f + + return wrap + + + +# We allow most of the Django field lookup types for remote queries +LOOKUP_TYPES = ["iexact", "contains", "icontains", "gt", "gte", "lt", + "in", "startswith", "istartswith", "endswith", + "iendswith", "range", "year", "month", "day", "isnull" ] + +####################################################################### +# Helper functions +####################################################################### + +def project_to_dict(obj): + """Return a trimmed down dictionary representation of a Project + object which is OK to send to the client.""" + return \ + { + 'id' : obj.id, + 'linkname' : obj.linkname, + 'name' : obj.name, + } + +def person_to_dict(obj): + """Return a trimmed down dictionary representation of a Person + object which is OK to send to the client.""" + + # Make sure we don't return None even if the user submitted a patch + # with no real name. XMLRPC can't marshall None. + if obj.name is not None: + name = obj.name + else: + name = obj.email + + return \ + { + 'id' : obj.id, + 'email' : obj.email, + 'name' : name, + 'user' : unicode(obj.user).encode("utf-8"), + } + +def patch_to_dict(obj): + """Return a trimmed down dictionary representation of a Patch + object which is OK to send to the client.""" + return \ + { + 'id' : obj.id, + 'date' : unicode(obj.date).encode("utf-8"), + 'filename' : obj.filename(), + 'msgid' : obj.msgid, + 'name' : obj.name, + 'project' : unicode(obj.project).encode("utf-8"), + 'project_id' : obj.project_id, + 'state' : unicode(obj.state).encode("utf-8"), + 'state_id' : obj.state_id, + 'archived' : obj.archived, + 'submitter' : unicode(obj.submitter).encode("utf-8"), + 'submitter_id' : obj.submitter_id, + 'delegate' : unicode(obj.delegate).encode("utf-8"), + 'delegate_id' : max(obj.delegate_id, 0), + 'commit_ref' : max(obj.commit_ref, ''), + } + +def bundle_to_dict(obj): + """Return a trimmed down dictionary representation of a Bundle + object which is OK to send to the client.""" + return \ + { + 'id' : obj.id, + 'name' : obj.name, + 'n_patches' : obj.n_patches(), + 'public_url' : obj.public_url(), + } + +def state_to_dict(obj): + """Return a trimmed down dictionary representation of a State + object which is OK to send to the client.""" + return \ + { + 'id' : obj.id, + 'name' : obj.name, + } + +####################################################################### +# Public XML-RPC methods +####################################################################### + +@xmlrpc_method(False) +def pw_rpc_version(): + """Return Patchwork XML-RPC interface version.""" + return 1 + +@xmlrpc_method(False) +def project_list(search_str="", max_count=0): + """Get a list of projects matching the given filters.""" + try: + if len(search_str) > 0: + projects = Project.objects.filter(linkname__icontains = search_str) + else: + projects = Project.objects.all() + + if max_count > 0: + return map(project_to_dict, projects)[:max_count] + else: + return map(project_to_dict, projects) + except: + return [] + +@xmlrpc_method(False) +def project_get(project_id): + """Return structure for the given project ID.""" + try: + project = Project.objects.filter(id = project_id)[0] + return project_to_dict(project) + except: + return {} + +@xmlrpc_method(False) +def person_list(search_str="", max_count=0): + """Get a list of Person objects matching the given filters.""" + try: + if len(search_str) > 0: + people = (Person.objects.filter(name__icontains = search_str) | + Person.objects.filter(email__icontains = search_str)) + else: + people = Person.objects.all() + + if max_count > 0: + return map(person_to_dict, people)[:max_count] + else: + return map(person_to_dict, people) + + except: + return [] + +@xmlrpc_method(False) +def person_get(person_id): + """Return structure for the given person ID.""" + try: + person = Person.objects.filter(id = person_id)[0] + return person_to_dict(person) + except: + return {} + +@xmlrpc_method(False) +def patch_list(filter={}): + """Get a list of patches matching the given filters.""" + try: + # We allow access to many of the fields. But, some fields are + # filtered by raw object so we must lookup by ID instead over + # XML-RPC. + ok_fields = [ + "id", + "name", + "project_id", + "submitter_id", + "delegate_id", + "archived", + "state_id", + "date", + "commit_ref", + "hash", + "msgid", + "max_count", + ] + + dfilter = {} + max_count = 0 + + for key in filter: + parts = key.split("__") + if parts[0] not in ok_fields: + # Invalid field given + return [] + if len(parts) > 1: + if LOOKUP_TYPES.count(parts[1]) == 0: + # Invalid lookup type given + return [] + + if parts[0] == 'project_id': + dfilter['project'] = Project.objects.filter(id = + filter[key])[0] + elif parts[0] == 'submitter_id': + dfilter['submitter'] = Person.objects.filter(id = + filter[key])[0] + elif parts[0] == 'delegate_id': + dfilter['delegate'] = Person.objects.filter(id = + filter[key])[0] + elif parts[0] == 'state_id': + dfilter['state'] = State.objects.filter(id = + filter[key])[0] + elif parts[0] == 'max_count': + max_count = filter[key] + else: + dfilter[key] = filter[key] + + patches = Patch.objects.filter(**dfilter) + + if max_count > 0: + return map(patch_to_dict, patches[:max_count]) + else: + return map(patch_to_dict, patches) + + except: + return [] + +@xmlrpc_method(False) +def patch_get(patch_id): + """Return structure for the given patch ID.""" + try: + patch = Patch.objects.filter(id = patch_id)[0] + return patch_to_dict(patch) + except: + return {} + +@xmlrpc_method(False) +def patch_get_by_hash(hash): + """Return structure for the given patch hash.""" + try: + patch = Patch.objects.filter(hash = hash)[0] + return patch_to_dict(patch) + except: + return {} + +@xmlrpc_method(False) +def patch_get_by_project_hash(project, hash): + """Return structure for the given patch hash.""" + try: + patch = Patch.objects.filter(project__linkname = project, + hash = hash)[0] + return patch_to_dict(patch) + except: + return {} + +@xmlrpc_method(False) +def patch_get_mbox(patch_id): + """Return mbox string for the given patch ID.""" + try: + patch = Patch.objects.filter(id = patch_id)[0] + return patch_to_mbox(patch).as_string(True) + except: + return "" + +@xmlrpc_method(False) +def patch_get_diff(patch_id): + """Return diff for the given patch ID.""" + try: + patch = Patch.objects.filter(id = patch_id)[0] + return patch.content + except: + return "" + +@xmlrpc_method(True) +def patch_set(user, patch_id, params): + """Update a patch with the key,value pairs in params. Only some parameters + can be set""" + try: + ok_params = ['state', 'commit_ref', 'archived'] + + patch = Patch.objects.get(id = patch_id) + + if not patch.is_editable(user): + raise Exception('No permissions to edit this patch') + + for (k, v) in params.iteritems(): + if k not in ok_params: + continue + + if k == 'state': + patch.state = State.objects.get(id = v) + + else: + setattr(patch, k, v) + + patch.save() + + return True + + except: + raise + +@xmlrpc_method(False) +def state_list(search_str="", max_count=0): + """Get a list of state structures matching the given search string.""" + try: + if len(search_str) > 0: + states = State.objects.filter(name__icontains = search_str) + else: + states = State.objects.all() + + if max_count > 0: + return map(state_to_dict, states)[:max_count] + else: + return map(state_to_dict, states) + except: + return [] + +@xmlrpc_method(False) +def state_get(state_id): + """Return structure for the given state ID.""" + try: + state = State.objects.filter(id = state_id)[0] + return state_to_dict(state) + except: + return {} diff --git a/templates/patchwork/activation_email.txt b/templates/patchwork/activation_email.txt deleted file mode 100644 index caf514a..0000000 --- a/templates/patchwork/activation_email.txt +++ /dev/null @@ -1,11 +0,0 @@ -Hi, - -This email is to confirm your account on the patchwork patch-tracking -system. You can activate your account by visiting the url: - - http://{{site.domain}}{% url 'patchwork.views.confirm' key=confirmation.key %} - -If you didn't request a user account on patchwork, then you can ignore -this mail. - -Happy patchworking. diff --git a/templates/patchwork/activation_email_subject.txt b/templates/patchwork/activation_email_subject.txt deleted file mode 100644 index c409f38..0000000 --- a/templates/patchwork/activation_email_subject.txt +++ /dev/null @@ -1 +0,0 @@ -Patchwork account confirmation diff --git a/templates/patchwork/bundle.html b/templates/patchwork/bundle.html deleted file mode 100644 index 4a96b6b..0000000 --- a/templates/patchwork/bundle.html +++ /dev/null @@ -1,47 +0,0 @@ -{% extends "base.html" %} - -{% load person %} -{% load static %} - -{% block headers %} - - - -{% endblock %} -{% block title %}{{project.name}}{% endblock %} -{% block heading %}bundle: {{bundle.owner.username}} / -{{bundle.name}}{% endblock %} - -{% block body %} - -

This bundle contains patches for the {{ bundle.project.linkname }} -project.

- -

Download bundle as mbox

- -{% if bundleform %} -
- {% csrf_token %} - - - - - - - -{{ bundleform }} - - - -
Bundle settings
- - -
-
- -
-{% endif %} - -{% include "patchwork/patch-list.html" %} - -{% endblock %} diff --git a/templates/patchwork/bundles.html b/templates/patchwork/bundles.html deleted file mode 100644 index 11fb89d..0000000 --- a/templates/patchwork/bundles.html +++ /dev/null @@ -1,59 +0,0 @@ -{% extends "base.html" %} - -{% load static %} - -{% block title %}Bundles{% endblock %} -{% block heading %}Bundles{% endblock %} - -{% block body %} - -{% if bundles %} - - - - - - - - -{% for bundle in bundles %} - - - - - - - - - -{% endfor %} -
NameProjectPublic LinkPatches - DownloadDelete
{{ bundle.name }}{{ bundle.project.linkname }} - {% if bundle.public %} - {{ bundle.public_url }} - {% endif %} - {{ bundle.n_patches }}download -
- {% csrf_token %} - {{ bundle.delete_form.as_p }} - -
-
-{% endif %} - -

Bundles are groups of related patches. You can create bundles by -selecting patches from a project, then using the 'create bundle' form -to give your bundle a name. Each bundle can be public or private; public -bundles are given a persistent URL, based you your username and the name -of the bundle. Private bundles are only visible to you.

- -{% if not bundles %} -

You have no bundles.

-{% endif %} -{% endblock %} diff --git a/templates/patchwork/confirm-error.html b/templates/patchwork/confirm-error.html deleted file mode 100644 index 81292e2..0000000 --- a/templates/patchwork/confirm-error.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Confirmation{% endblock %} -{% block heading %}Confirmation{% endblock %} - - -{% block body %} - -{% if error == 'inactive' %} -

This confirmation has already been processed; you've probably visited this -page before.

-{% endif %} - -{% if error == 'expired' %} -

The confirmation has expired. If you'd still like to perform the -{{conf.get_type_display}} process, you'll need to resubmit the request.

-{% endif %} - -{% endblock %} diff --git a/templates/patchwork/filters.html b/templates/patchwork/filters.html deleted file mode 100644 index 10ca587..0000000 --- a/templates/patchwork/filters.html +++ /dev/null @@ -1,183 +0,0 @@ -{% load static %} - - - -
-
- Filters: - {% if filters.applied_filters %} - {% for filter in filters.applied_filters %} - {{ filter.name }} = {{ filter.condition }} - {% if not filter.forced %} - remove filter - {% endif %} - {% if not forloop.last %}   |   {% endif %} - {% endfor %} - {% else %} - none - add filter - {% endif %} -
- -
- - diff --git a/templates/patchwork/help/about.html b/templates/patchwork/help/about.html deleted file mode 100644 index 7befa6b..0000000 --- a/templates/patchwork/help/about.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "base.html" %} - -{% block title %}About{% endblock %} -{% block heading %} - About Patchwork{% endblock %} - -{% block body %} - -

Patchwork is free software, and is available from the -patchwork website.

- -

Patchwork is built on the django -web framework.

- -

Icons from the Sweetie icon set. - -{% endblock %} - diff --git a/templates/patchwork/help/index.html b/templates/patchwork/help/index.html deleted file mode 100644 index 5cb6467..0000000 --- a/templates/patchwork/help/index.html +++ /dev/null @@ -1,2 +0,0 @@ -{% extends "base.html" %} - diff --git a/templates/patchwork/help/pwclient.html b/templates/patchwork/help/pwclient.html deleted file mode 100644 index 7101ec1..0000000 --- a/templates/patchwork/help/pwclient.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Command-line client{% endblock %} -{% block heading %} - Command-line client{% endblock %} - -{% block body %} - -

pwclient is the command-line client for patchwork. Currently, -it provides access to some read-only features of patchwork, such as downloading -and applying patches.

- -

To use pwclient, you will need:

-
    -
  • The pwclient - program (11kB, python script)
  • -
  • (optional) a .pwclientrc file in your home directory.
  • -
- -

You can create your own .pwclientrc file. Each -patchwork project -provides a sample linked from the 'project info' page.

- -{% endblock %} diff --git a/templates/patchwork/list.html b/templates/patchwork/list.html deleted file mode 100644 index 654fe8c..0000000 --- a/templates/patchwork/list.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "base.html" %} - -{% load person %} -{% load static %} - -{% block title %}{{project.name}}{% endblock %} -{% block heading %}{{project.name}}{% endblock %} - -{% block body %} - -

Incoming patches

- -{% if errors %} -

The following error{{ errors|length|pluralize:" was,s were" }} encountered -while updating patches:

-
    -{% for error in errors %} -
  • {{ error }}
  • -{% endfor %} -
-{% endif %} - -{% include "patchwork/patch-list.html" %} - -{% endblock %} diff --git a/templates/patchwork/login.html b/templates/patchwork/login.html deleted file mode 100644 index 2dfc2a7..0000000 --- a/templates/patchwork/login.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Login{% endblock %} -{% block heading %}Login{% endblock %} - - -{% block body %} -
-{% csrf_token %} - - - - - {% if error %} - - - - {% endif %} - {{ form }} - - - -
login
{{ error }}
- -
-
-{% endblock %} diff --git a/templates/patchwork/logout.html b/templates/patchwork/logout.html deleted file mode 100644 index f030aee..0000000 --- a/templates/patchwork/logout.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Logout{% endblock %} -{% block heading %}Logout{% endblock %} - -{% block body %} -

Logged out

-{% endblock %} diff --git a/templates/patchwork/mail-form.html b/templates/patchwork/mail-form.html deleted file mode 100644 index d71b2fb..0000000 --- a/templates/patchwork/mail-form.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends "base.html" %} - -{% block title %}mail settings{% endblock %} -{% block heading %}mail settings{% endblock %} - -{% block body %} - -

You can configure patchwork to send you mail on certain events, -or block automated mail altogether. Enter your email address to -view or change your email settings.

- -
-{% csrf_token %} - -{% if form.errors %} - - - -{% endif %} - - - - - - - -
- There was an error accessing your mail settings: -
{{ form.email.label_tag }} - {{form.email}} - {{form.email.errors}} -
- -
-
- - -{% endblock %} diff --git a/templates/patchwork/mail-settings.html b/templates/patchwork/mail-settings.html deleted file mode 100644 index 440af08..0000000 --- a/templates/patchwork/mail-settings.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends "base.html" %} - -{% block title %}mail settings{% endblock %} -{% block heading %}mail settings{% endblock %} - -{% block body %} -

Settings for {{email}}:

- - - - -{% if is_optout %} - - - -{% else %} - - -{% endif %} - -
Opt-out listPatchwork may not send automated notifications to - this address. -
- {% csrf_token %} - - -
-
Patchwork may send automated notifications to - this address. -
- {% csrf_token %} - - -
-
- -{% endblock %} diff --git a/templates/patchwork/optin-request.html b/templates/patchwork/optin-request.html deleted file mode 100644 index 3dfb1bd..0000000 --- a/templates/patchwork/optin-request.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends "base.html" %} - -{% block title %}opt-in{% endblock %} -{% block heading %}opt-in{% endblock %} - -{% block body %} -{% if email_sent %} -

Opt-in confirmation email sent

-

An opt-in confirmation mail has been sent to -{{confirmation.email}}, containing a link. Please click on -that link to confirm your opt-in.

-{% else %} -{% if error %} -

{{error}}

-{% endif %} - -{% if form %} -

This form allows you to opt-in to automated email from patchwork. Use -this if you have previously opted-out of patchwork mail, but now want to -received notifications from patchwork.

-When you submit it, an email will be sent to your address with a link to click -to finalise the opt-in. Patchwork does this to prevent someone opting you in -without your consent.

-
-{% csrf_token %} -{{form.email.errors}} -
-{{form.email.label_tag}}: {{form.email}} -
- -
-{% endif %} - -{% if error and admins %} -

If you are having trouble opting in, please email -{% for admin in admins %} -{% if admins|length > 1 and forloop.last %} or {% endif %} -{{admin.0}} <{{admin.1}}>{% if admins|length > 2 and not forloop.last %}, {% endif %} -{% endfor %} -{% endif %} - -{% endif %} - -{% if user.is_authenticated %} -

Return to your user -profile.

-{% endif %} - -{% endblock %} diff --git a/templates/patchwork/optin-request.mail b/templates/patchwork/optin-request.mail deleted file mode 100644 index d97c78b..0000000 --- a/templates/patchwork/optin-request.mail +++ /dev/null @@ -1,12 +0,0 @@ -Hi, - -This email is to confirm that you would like to opt-in to automated -email from the patchwork system at {{site.domain}}. - -To complete the opt-in process, visit: - - http://{{site.domain}}{% url 'patchwork.views.confirm' key=confirmation.key %} - -If you didn't request this opt-in, you don't need to do anything. - -Happy patchworking. diff --git a/templates/patchwork/optin.html b/templates/patchwork/optin.html deleted file mode 100644 index 01aaa0e..0000000 --- a/templates/patchwork/optin.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "base.html" %} - -{% block title %}opt-in{% endblock %} -{% block heading %}opt-in{% endblock %} - -{% block body %} - -

Opt-in complete. You have sucessfully opted back in to -automated email from this patchwork system, using the address -{{email}}.

-

If you later decide that you no longer want to receive automated mail from -patchwork, just visit http://{{site.domain}}{% url 'patchwork.views.mail.settings' %}, or -visit the main patchwork page and navigate from there.

-{% if user.is_authenticated %} -

Return to your user -profile.

-{% endif %} -{% endblock %} diff --git a/templates/patchwork/optout-request.html b/templates/patchwork/optout-request.html deleted file mode 100644 index 092dbbb..0000000 --- a/templates/patchwork/optout-request.html +++ /dev/null @@ -1,51 +0,0 @@ -{% extends "base.html" %} - -{% block title %}opt-out{% endblock %} -{% block heading %}opt-out{% endblock %} - -{% block body %} -{% if email_sent %} -

Opt-out confirmation email sent

-

An opt-out confirmation mail has been sent to -{{confirmation.email}}, containing a link. Please click on -that link to confirm your opt-out.

-{% else %} -{% if error %} -

{{error}}

-{% endif %} - -{% if form %} -

This form allows you to opt-out of automated email from patchwork.

-

If you opt-out of email, Patchwork may still email you if you do certain -actions yourself (such as create a new patchwork account), but will not send -you unsolicited email.

-When you submit it, one email will be sent to your address with a link to click -to finalise the opt-out. Patchwork does this to prevent someone opting you out -without your consent.

-
-{% csrf_token %} -{{form.email.errors}} -
-{{form.email.label_tag}}: {{form.email}} -
- -
-{% endif %} - -{% if error and admins %} -

If you are having trouble opting out, please email -{% for admin in admins %} -{% if admins|length > 1 and forloop.last %} or {% endif %} -{{admin.0}} <{{admin.1}}>{% if admins|length > 2 and not forloop.last %}, {% endif %} -{% endfor %} -{% endif %} - -{% endif %} - -{% if user.is_authenticated %} -

Return to your user -profile.

-{% endif %} - -{% endblock %} diff --git a/templates/patchwork/optout-request.mail b/templates/patchwork/optout-request.mail deleted file mode 100644 index 67203ca..0000000 --- a/templates/patchwork/optout-request.mail +++ /dev/null @@ -1,12 +0,0 @@ -Hi, - -This email is to confirm that you would like to opt-out from all email -from the patchwork system at {{site.domain}}. - -To complete the opt-out process, visit: - - http://{{site.domain}}{% url 'patchwork.views.confirm' key=confirmation.key %} - -If you didn't request this opt-out, you don't need to do anything. - -Happy patchworking. diff --git a/templates/patchwork/optout.html b/templates/patchwork/optout.html deleted file mode 100644 index b140bf4..0000000 --- a/templates/patchwork/optout.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "base.html" %} - -{% block title %}opt-out{% endblock %} -{% block heading %}opt-out{% endblock %} - -{% block body %} - -

Opt-out complete. You have successfully opted-out of -automated notifications from this patchwork system, from the address -{{email}}

-

Please note that you may still receive email from other patchwork setups at -different sites, as they are run independently. You may need to opt-out of -those separately.

-

If you later decide to receive mail from patchwork, just visit -http://{{site.domain}}{% url 'patchwork.views.mail.settings' %}, or -visit the main patchwork page and navigate from there.

-{% if user.is_authenticated %} -

Return to your user -profile.

-{% endif %} -{% endblock %} diff --git a/templates/patchwork/pagination.html b/templates/patchwork/pagination.html deleted file mode 100644 index 3e95126..0000000 --- a/templates/patchwork/pagination.html +++ /dev/null @@ -1,45 +0,0 @@ -{% load listurl %} - -{% ifnotequal page.paginator.num_pages 1 %} -
-{% if page.has_previous %} - - « Previous -{% else %} - « Previous -{% endif %} - -{% if page.paginator.trailing_set %} - {% for p in page.paginator.trailing_set %} - {{ p }} - {% endfor %} - ... -{% endif %} - -{% for p in page.paginator.adjacent_set %} - {% ifequal p page.number %} - {{ p }} - {% else %} - {{ p }} - {% endifequal %} -{% endfor %} - -{% if page.paginator.leading_set %} - ... - {% for p in page.paginator.leading_set %} - {{ p }} - {% endfor %} -{% endif %} - -{% if page.has_next %} - - Next » - -{% else %} - Next » -{% endif %} -
-{% endifnotequal %} diff --git a/templates/patchwork/patch-change-notification-subject.text b/templates/patchwork/patch-change-notification-subject.text deleted file mode 100644 index c9d96d4..0000000 --- a/templates/patchwork/patch-change-notification-subject.text +++ /dev/null @@ -1 +0,0 @@ -[{{ projects|join:"," }}] Patch notification: {{notifications|length}} patch{{notifications|length|pluralize:"es"}} updated diff --git a/templates/patchwork/patch-change-notification.mail b/templates/patchwork/patch-change-notification.mail deleted file mode 100644 index 4246704..0000000 --- a/templates/patchwork/patch-change-notification.mail +++ /dev/null @@ -1,20 +0,0 @@ -Hello, - -The following patch{{notifications|length|pluralize:"es"}} (submitted by you) {{notifications|length|pluralize:"has,have"}} been updated in patchwork: -{% for notification in notifications %} - * {{notification.patch.project.linkname}}: {{notification.patch.name|safe}} - - http://{{site.domain}}{{notification.patch.get_absolute_url}} - - for: {{notification.patch.project.name}} - was: {{notification.orig_state}} - now: {{notification.patch.state}} -{% endfor %} -This email is a notification only - you do not need to respond. - -Happy patchworking. - --- - -This is an automated mail sent by the patchwork system at -{{site.domain}}. To stop receiving these notifications, edit -your mail settings at: - http://{{site.domain}}{% url 'patchwork.views.mail.settings' %} diff --git a/templates/patchwork/patch-list.html b/templates/patchwork/patch-list.html deleted file mode 100644 index 675f67f..0000000 --- a/templates/patchwork/patch-list.html +++ /dev/null @@ -1,268 +0,0 @@ -{% load person %} -{% load listurl %} -{% load static %} - -{% include "patchwork/pagination.html" %} - - - - - - {% if order.editable %} - - {% endif %} - -
- {% include "patchwork/filters.html" %} - -
- {% csrf_token %} - - - - - -
-
- -{% if page.paginator.long_page and user.is_authenticated %} -
- -
-{% endif %} - -
-{% csrf_token %} - - - - - - {% if user.is_authenticated %} - - {% endif %} - - - - - - - - - - - - - - -{% if page.paginator.count %} - - {% for patch in page.object_list %} - - {% if user.is_authenticated %} - - {% endif %} - - - - - - - {% endfor %} - -
- - - {% ifequal order.name "name" %} - Patch - {% else %} - {% if not order.editable %} - Patch - {% else %} - Patch - {% endif %} - {% endifequal %} - - {% ifequal order.name "date" %} - Date - {% else %} - {% if not order.editable %} - Date - {% else %} - Date - {% endif %} - {% endifequal %} - - {% ifequal order.name "submitter" %} - Submitter - {% else %} - {% if not order.editable %} - Submitter - {% else %} - Submitter - {% endif %} - {% endifequal %} - - {% ifequal order.name "delegate" %} - Delegate - {% else %} - {% if not order.editable %} - Delegate - {% else %} - Delegate - {% endif %} - {% endifequal %} - - {% ifequal order.name "state" %} - State - {% else %} - {% if not order.editable %} - State - {% else %} - State - {% endif %} - {% endifequal %} -
- - {{ patch.name|default:"[no subject]" }}{{ patch.date|date:"Y-m-d" }}{{ patch.submitter|personify:project }}{{ patch.delegate.username }}{{ patch.state }}
- -{% include "patchwork/pagination.html" %} - -
- -{% if patchform %} -
-

Properties

- - - - - - - - - - - - - - - -
Change state: - {{ patchform.state }} - {{ patchform.state.errors }} -
Delegate to: - - {{ patchform.delegate }} - {{ patchform.delegate.errors }} -
Archive: - - {{ patchform.archived }} - {{ patchform.archived.errors }} -
- -
-
- -{% endif %} - -{% if user.is_authenticated %} -
-

Bundling

- - - - - - {% if bundles %} - - - - - {% endif %} - {% if bundle %} - - - - - {% endif %} -
Create bundle: - - -
Add to bundle: - - -
Remove from bundle: - - -
-
-{% endif %} - - -
-
-
- -{% else %} - - No patches to display - -{% endif %} - - -
- diff --git a/templates/patchwork/patch.html b/templates/patchwork/patch.html deleted file mode 100644 index f18ee3b..0000000 --- a/templates/patchwork/patch.html +++ /dev/null @@ -1,199 +0,0 @@ -{% extends "base.html" %} - -{% load syntax %} -{% load person %} -{% load patch %} - -{% block title %}{{patch.name}}{% endblock %} -{% block heading %}{{patch.name}}{%endblock%} - -{% block body %} - - - - - - - - - - - - - - - - - - - - - - - - - - -{% if patch.commit_ref %} - - - - -{% endif %} -{% if patch.delegate %} - - - - -{% endif %} - - - - -
Submitter{{ patch.submitter|personify:project }}
Date{{ patch.date }}
Message ID{{ patch.msgid }}
Download - mbox -{% if patch.content %}| - patch -{% endif %} -
Permalink{{ patch.get_absolute_url }} -
State{{ patch.state.name }}{% if patch.archived %}, archived{% endif %}
Commit{{ patch.commit_ref }}
Delegated to:{{ patch.delegate.profile.name }}
Headersshow - -
- -
- -{% if patchform %} -
-

Patch Properties

-
- {% csrf_token %} - - - - - - - - - - - - - - - - - -
Change state: - {{ patchform.state }} - {{ patchform.state.errors }} -
Delegate to: - {{ patchform.delegate }} - {{ patchform.delegate.errors }} -
Archived: - {{ patchform.archived }} - {{ patchform.archived.errors }} -
- -
-
-
-{% endif %} - -{% if createbundleform %} -
-

Bundling

- - - - - -{% if bundles %} - - - - -{% endif %} -
Create bundle: - {% if createbundleform.non_field_errors %} -
{{createbundleform.non_field_errors}}
- {% endif %} -
- {% csrf_token %} - - {% if createbundleform.name.errors %} -
{{createbundleform.name.errors}}
- {% endif %} - {{ createbundleform.name }} - -
-
Add to bundle: -
- {% csrf_token %} - - - -
-
- -
-{% endif %} - -
-
-
- -{% if patch.pull_url %} -

Pull-request

-{{ patch.pull_url }} -{% endif %} - -

Comments

-{% for comment in patch.comments %} -
-
{{ comment.submitter|personify:project }} - {{comment.date}}
-
-{{ comment|commentsyntax }}
-
-
-{% endfor %} - -{% if patch.content %} -

Patch

-
-
-{{ patch|patchsyntax }}
-
-
-{% endif %} - - -{% endblock %} diff --git a/templates/patchwork/profile.html b/templates/patchwork/profile.html deleted file mode 100644 index 116d6d6..0000000 --- a/templates/patchwork/profile.html +++ /dev/null @@ -1,144 +0,0 @@ -{% extends "base.html" %} - -{% block title %}User Profile: {{ user.username }}{% endblock %} -{% block heading %}User Profile: {{ user.username }}{% endblock %} - - -{% block body %} - -

-{% if user.profile.maintainer_projects.count %} -Maintainer of -{% for project in user.profile.maintainer_projects.all %} -{{ project.linkname }}{% if not forloop.last %},{% endif %}{% endfor %}. -{% endif %} - -{% if user.profile.contributor_projects.count %} -Contributor to -{% for project in user.profile.contributor_projects.all %} -{{ project.linkname }}{% if not forloop.last %},{% endif %}{% endfor %}. -{% endif %} -

- -
-
-

Todo

-{% if user.profile.n_todo_patches %} -

Your todo - list contains {{ user.profile.n_todo_patches }} - patch{{ user.profile.n_todo_patches|pluralize:"es" }}.

-{% else %} -

Your todo list contains patches that have been delegated to you. You - have no items in your todo list at present.

-{% endif %} -
- -
-

Linked email addresses

-

The following email addresses are associated with this patchwork account. -Adding alternative addresses allows patchwork to group contributions that -you have made under different addresses.

-

The "notify?" column allows you to opt-in or -out of automated -patchwork notification emails. Setting it to "no" will disable automated -notifications for that address.

-

Adding a new email address will send a confirmation email to that -address.

- - - - - - -{% for email in linked_emails %} - - - - - -{% endfor %} - - - -
emailactionnotify?
{{ email.email }} - {% ifnotequal user.email email.email %} -
- {% csrf_token %} - -
- {% endifnotequal %} -
- {% if email.is_optout %} -
- No, - {% csrf_token %} - - -
- {% else %} -
- Yes, - {% csrf_token %} - - -
- {% endif %} -
-
- {% csrf_token %} - {{ linkform.email }} - -
-
-
-
- -
- -
-

Bundles

- -{% if bundles %} -

You have the following bundle{{ bundle|length|pluralize }}:

- -

Visit the bundles - page to manage your bundles.

-{% else %} -

You have no bundles.

-{% endif %} -
- - -
-

Settings

- -
- {% csrf_token %} - -{{ profileform }} - - - -
- - -
-
-
- -
-

Authentication

-Change password -
- -
- -

- -{% endblock %} diff --git a/templates/patchwork/project.html b/templates/patchwork/project.html deleted file mode 100644 index be8cadc..0000000 --- a/templates/patchwork/project.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{{ project.name }}{% endblock %} -{% block heading %}{{ project.name }}{% endblock %} - -{% block body %} - - - - - - - - - - - - - - - - - -{% if project.web_url %} - - - - -{% endif %} -{% if project.webscm_url %} - - - - -{% endif %} -{% if project.scm_url %} - - - - -{% endif %} -
Name{{project.name}} -
List address{{project.listemail}}
Maintainer{{maintainers|length|pluralize}} - {% for maintainer in maintainers %} - {{ maintainer.profile.name }} - <{{maintainer.email}}> -
- {% endfor %} -
Patch count{{n_patches}} (+ {{n_archived_patches}} archived)
Website{{project.web_url}}
Source Code Web Interface{{project.webscm_url}}
Source Code Manager URL{{project.scm_url}}
- -{% if settings.ENABLE_XMLRPC %} -

Sample patchwork -client configuration for this project: .pwclientrc.

-{% endif %} - -{% endblock %} diff --git a/templates/patchwork/projects.html b/templates/patchwork/projects.html deleted file mode 100644 index 8c727ad..0000000 --- a/templates/patchwork/projects.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Project List{% endblock %} -{% block heading %}Project List{% endblock %} - -{% block body %} - -{% if projects %} -
- {% for p in projects %} -
-

- {{p.linkname}} -

-
{{p.name}}
-{% if p.web_url %} - -{% endif %} -
- {% endfor %} -
-{% else %} -

Patchwork doesn't have any projects to display!

-{% endif %} - -{% endblock %} diff --git a/templates/patchwork/pwclient b/templates/patchwork/pwclient deleted file mode 120000 index ae4faf3..0000000 --- a/templates/patchwork/pwclient +++ /dev/null @@ -1 +0,0 @@ -../../apps/patchwork/bin/pwclient \ No newline at end of file diff --git a/templates/patchwork/pwclientrc b/templates/patchwork/pwclientrc deleted file mode 100644 index d331003..0000000 --- a/templates/patchwork/pwclientrc +++ /dev/null @@ -1,15 +0,0 @@ -# Sample .pwclientrc file for the {{ project.linkname }} project, -# running on {{ site.domain }}. -# -# Just append this file to your existing ~/.pwclientrc -# If you do not already have a ~/.pwclientrc, then copy this file to -# ~/.pwclientrc, and uncomment the following two lines: -# [options] -# default={{ project.linkname }} - -[{{ project.linkname }}] -url= {{scheme}}://{{site.domain}}{% url 'patchwork.views.xmlrpc.xmlrpc' %} -{% if user.is_authenticated %} -username: {{ user.username }} -password: -{% endif %} diff --git a/templates/patchwork/register.mail b/templates/patchwork/register.mail deleted file mode 100644 index 9079203..0000000 --- a/templates/patchwork/register.mail +++ /dev/null @@ -1,11 +0,0 @@ -Hi, - -This email is to confirm your account on the patchwork patch-tracking -system. You can activate your account by visiting the url: - - http://{{site.domain}}{% url 'registration_activateactivation_key'=request.key %} - -If you didn't request a user account on patchwork, then you can ignore -this mail. - -Happy patchworking. diff --git a/templates/patchwork/registration-confirm.html b/templates/patchwork/registration-confirm.html deleted file mode 100644 index 6111401..0000000 --- a/templates/patchwork/registration-confirm.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Registration{% endblock %} -{% block heading %}Registration{% endblock %} - -{% block body %} -

Registraton confirmed!

- -

Your patchwork registration is complete. Head over to your profile to start using -patchwork's extra features.

- -{% endblock %} diff --git a/templates/patchwork/registration_form.html b/templates/patchwork/registration_form.html deleted file mode 100644 index 3a314b8..0000000 --- a/templates/patchwork/registration_form.html +++ /dev/null @@ -1,121 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Registration{% endblock %} -{% block heading %}Registration{% endblock %} - - -{% block body %} - -{% if confirmation and not error %} -

Registration successful!

-

A confirmation email has been sent to {{ confirmation.email }}. You'll - need to visit the link provided in that email to confirm your - registration.

-

-{% else %} -

By creating a patchwork account, you can:

-

    -
  • create "bundles" of patches
  • -
  • update the state of your own patches
  • -
-
-{% csrf_token %} - - - - - {% if error %} - - - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
register
{{ error }}
{{ form.first_name.label_tag }} -{% if form.first_name.errors %} - {{ form.first_name.errors }} -{% endif %} - {{ form.first_name }} -{% if form.first_name.help_text %} -
{{ form.first_name.help_text }}
-{% endif %} -
{{ form.last_name.label_tag }} -{% if form.last_name.errors %} - {{ form.last_name.errors }} -{% endif %} - {{ form.last_name }} -{% if form.last_name.help_text %} -
{{ form.last_name.help_text }}
-{% endif %} -
- Your name is used to identify you on the site -
{{ form.email.label_tag }} -{% if form.email.errors %} - {{ form.email.errors }} -{% endif %} - {{ form.email }} -{% if form.email.help_text %} -
{{ form.email.help_text }}
-{% endif %} -
- Patchwork will send a confirmation email to this address -
{{ form.username.label_tag }} -{% if form.username.errors %} - {{ form.username.errors }} -{% endif %} - {{ form.username }} -{% if form.username.help_text %} -
{{ form.username.help_text }}
-{% endif %} -
{{ form.password.label_tag }} -{% if form.password.errors %} - {{ form.password.errors }} -{% endif %} - {{ form.password }} -{% if form.password.help_text %} -
{{ form.password.help_text }}
-{% endif %} -
- -
-
-{% endif %} - -{% endblock %} diff --git a/templates/patchwork/todo-list.html b/templates/patchwork/todo-list.html deleted file mode 100644 index b301901..0000000 --- a/templates/patchwork/todo-list.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "base.html" %} - -{% load person %} - -{% block title %}{{ user }}'s todo list{% endblock %} -{% block heading %}{{user}}'s todo list for {{ project.linkname }}{% endblock %} - -{% block body %} - -

A Patchwork Todo-list contains patches that are assigned to you, and -are in an "action required" state -({% for state in action_required_states %}{% if forloop.last and not forloop.first %} or {% endif %}{{ state }}{% if not forloop.last and not forloop.first %}, {%endif %}{% endfor %}), and are not archived. -

- -{% include "patchwork/patch-list.html" %} - -{% endblock %} diff --git a/templates/patchwork/todo-lists.html b/templates/patchwork/todo-lists.html deleted file mode 100644 index e268160..0000000 --- a/templates/patchwork/todo-lists.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{{ user }}'s todo lists{% endblock %} -{% block heading %}{{ user }}'s todo lists{% endblock %} - -{% block body %} - -{% if todo_lists %} -

You have multiple todo lists. Each todo list contains patches for a single - project.

- - - - - -{% for todo_list in todo_lists %} - - - - -{% endfor %} -
projectpatches
{{ todo_list.project.name }}{{ todo_list.n_patches }}
- -{% else %} - No todo lists -{% endif %} -{% endblock %} diff --git a/templates/patchwork/user-link-confirm.html b/templates/patchwork/user-link-confirm.html deleted file mode 100644 index 449bfeb..0000000 --- a/templates/patchwork/user-link-confirm.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{{ user.username }}{% endblock %} -{% block heading %}link accounts for {{ user.username }}{% endblock %} - - -{% block body %} - -{% if errors %} -

{{ errors }}

-{% else %} -

You have sucessfully linked the email address {{ person.email }} to - your patchwork account

- -{% endif %} -

Back to your - profile.

- -{% endblock %} diff --git a/templates/patchwork/user-link.html b/templates/patchwork/user-link.html deleted file mode 100644 index e436c3a..0000000 --- a/templates/patchwork/user-link.html +++ /dev/null @@ -1,32 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{{ user.username }}{% endblock %} -{% block heading %}link accounts for {{ user.username }}{% endblock %} - - -{% block body %} - -{% if confirmation and not error %} -

A confirmation email has been sent to {{ confirmation.email }}. Click -on the link provided in the email to confirm that this address belongs to -you.

- -{% else %} - - {% if form.errors %} -

There was an error submitting your link request.

- {{ form.non_field_errors }} - {% endif %} - {% if error %} -
  • {{error}}
- {% endif %} - -
- {% csrf_token %} - {{linkform.email.errors}} - Link an email address: {{ linkform.email }} -
- -{% endif %} - -{% endblock %} diff --git a/templates/patchwork/user-link.mail b/templates/patchwork/user-link.mail deleted file mode 100644 index 8db6726..0000000 --- a/templates/patchwork/user-link.mail +++ /dev/null @@ -1,12 +0,0 @@ -Hi, - -This email is to confirm that you own the email address: - - {{ confirmation.email }} - -So that you can add it to your patchwork profile. You can confirm this -email address by visiting the url: - - http://{{site.domain}}{% url 'patchwork.views.confirm' key=confirmation.key %} - -Happy patchworking. diff --git a/tools/patchwork-update-commits b/tools/patchwork-update-commits index b2a658a..820fd1c 100755 --- a/tools/patchwork-update-commits +++ b/tools/patchwork-update-commits @@ -1,7 +1,7 @@ #!/bin/bash toolsdir="$(dirname "$0")" -pwpath="${toolsdir}"/../apps/patchwork +pwpath="${toolsdir}"/../patchwork if [ "$#" -lt 1 ] then diff --git a/tools/post-receive.hook b/tools/post-receive.hook index a38522e..8f05b8d 100755 --- a/tools/post-receive.hook +++ b/tools/post-receive.hook @@ -15,7 +15,7 @@ STATE_MAP="refs/heads/master:Accepted" # EXCLUDE="" -PWDIR=/srv/patchwork/apps/patchwork +PWDIR=/srv/patchwork/patchwork do_exit=0 trap "do_exit=1" INT diff --git a/tox.ini b/tox.ini index 8d99e6a..485f7c7 100644 --- a/tox.ini +++ b/tox.ini @@ -8,14 +8,14 @@ commands = flake8 {posargs} [flake8] ignore = E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E251,H405 -exclude = ./apps/patchwork/tests +exclude = ./patchwork/tests [testenv:lint] basepython = python2.7 deps = pylint -r{toxinidir}/docs/requirements-django-1.7-mysql.txt -commands = pylint apps --rcfile=pylint.rc +commands = pylint patchwork --rcfile=pylint.rc [testenv:coverage] basepython = python2.7 @@ -26,16 +26,16 @@ setenv = DJANGO_SETTINGS_MODULE = patchwork.settings.dev commands = coverage erase - {toxinidir}/apps/manage.py syncdb - coverage run --omit=*tox* --branch {toxinidir}/apps/manage.py test patchwork + {toxinidir}/manage.py syncdb + coverage run --omit=*tox* --branch {toxinidir}/manage.py test patchwork coverage report -m [testenv] basepython = py27: python2.7 commands = - {toxinidir}/apps/manage.py syncdb - {toxinidir}/apps/manage.py test patchwork + {toxinidir}/manage.py syncdb + {toxinidir}/manage.py test patchwork deps = python-dateutil==1.5 MySQL-python==1.2.5