]> git.ozlabs.org Git - patchwork/commitdiff
Move to a more recent django project structure
authorJeremy Kerr <jk@ozlabs.org>
Sun, 24 May 2015 08:57:33 +0000 (16:57 +0800)
committerJeremy Kerr <jk@ozlabs.org>
Wed, 27 May 2015 02:26:41 +0000 (10:26 +0800)
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 <jk@ozlabs.org>
230 files changed:
apps/__init__.py [deleted file]
apps/manage.py [deleted file]
apps/patchwork/__init__.py [deleted file]
apps/patchwork/admin.py [deleted file]
apps/patchwork/bin/__init__.py [deleted file]
apps/patchwork/bin/bash_completion [deleted file]
apps/patchwork/bin/parsemail-batch.sh [deleted file]
apps/patchwork/bin/parsemail.py [deleted file]
apps/patchwork/bin/parsemail.sh [deleted file]
apps/patchwork/bin/patchwork-cron.py [deleted file]
apps/patchwork/bin/pwclient [deleted file]
apps/patchwork/bin/rehash.py [deleted file]
apps/patchwork/bin/update-patchwork-status.py [deleted file]
apps/patchwork/context_processors.py [deleted file]
apps/patchwork/filters.py [deleted file]
apps/patchwork/fixtures/default_projects.xml [deleted file]
apps/patchwork/fixtures/initial_data.xml [deleted file]
apps/patchwork/forms.py [deleted file]
apps/patchwork/models.py [deleted file]
apps/patchwork/paginator.py [deleted file]
apps/patchwork/parser.py [deleted file]
apps/patchwork/requestcontext.py [deleted file]
apps/patchwork/settings/__init__.py [deleted file]
apps/patchwork/settings/base.py [deleted file]
apps/patchwork/settings/dev.py [deleted file]
apps/patchwork/settings/prod.py [deleted file]
apps/patchwork/templatetags/__init__.py [deleted file]
apps/patchwork/templatetags/filter.py [deleted file]
apps/patchwork/templatetags/listurl.py [deleted file]
apps/patchwork/templatetags/order.py [deleted file]
apps/patchwork/templatetags/patch.py [deleted file]
apps/patchwork/templatetags/person.py [deleted file]
apps/patchwork/templatetags/pwurl.py [deleted file]
apps/patchwork/templatetags/syntax.py [deleted file]
apps/patchwork/tests/__init__.py [deleted file]
apps/patchwork/tests/mail/0001-git-pull-request.mbox [deleted file]
apps/patchwork/tests/mail/0002-git-pull-request-wrapped.mbox [deleted file]
apps/patchwork/tests/mail/0003-git-pull-request-with-diff.mbox [deleted file]
apps/patchwork/tests/mail/0004-git-pull-request-git+ssh.mbox [deleted file]
apps/patchwork/tests/mail/0005-git-pull-request-ssh.mbox [deleted file]
apps/patchwork/tests/mail/0006-git-pull-request-http.mbox [deleted file]
apps/patchwork/tests/mail/0007-cvs-format-diff.mbox [deleted file]
apps/patchwork/tests/mail/0008-git-rename.mbox [deleted file]
apps/patchwork/tests/mail/0009-git-rename-with-diff.mbox [deleted file]
apps/patchwork/tests/mail/0010-invalid-charset.mbox [deleted file]
apps/patchwork/tests/mail/0011-no-newline-at-end-of-file.mbox [deleted file]
apps/patchwork/tests/patches/0001-add-line.patch [deleted file]
apps/patchwork/tests/patches/0002-utf-8.patch [deleted file]
apps/patchwork/tests/test_bundles.py [deleted file]
apps/patchwork/tests/test_confirm.py [deleted file]
apps/patchwork/tests/test_encodings.py [deleted file]
apps/patchwork/tests/test_expiry.py [deleted file]
apps/patchwork/tests/test_filters.py [deleted file]
apps/patchwork/tests/test_list.py [deleted file]
apps/patchwork/tests/test_mail_settings.py [deleted file]
apps/patchwork/tests/test_mboxviews.py [deleted file]
apps/patchwork/tests/test_notifications.py [deleted file]
apps/patchwork/tests/test_patchparser.py [deleted file]
apps/patchwork/tests/test_person.py [deleted file]
apps/patchwork/tests/test_registration.py [deleted file]
apps/patchwork/tests/test_updates.py [deleted file]
apps/patchwork/tests/test_user.py [deleted file]
apps/patchwork/tests/test_xmlrpc.py [deleted file]
apps/patchwork/tests/utils.py [deleted file]
apps/patchwork/urls.py [deleted file]
apps/patchwork/utils.py [deleted file]
apps/patchwork/views/__init__.py [deleted file]
apps/patchwork/views/base.py [deleted file]
apps/patchwork/views/bundle.py [deleted file]
apps/patchwork/views/mail.py [deleted file]
apps/patchwork/views/patch.py [deleted file]
apps/patchwork/views/project.py [deleted file]
apps/patchwork/views/user.py [deleted file]
apps/patchwork/views/xmlrpc.py [deleted file]
docs/HACKING
docs/INSTALL
lib/apache2/patchwork.mod_python.conf
lib/apache2/patchwork.wsgi
manage.py [new file with mode: 0755]
patchwork/__init__.py [new file with mode: 0644]
patchwork/admin.py [new file with mode: 0644]
patchwork/bin/__init__.py [new file with mode: 0644]
patchwork/bin/bash_completion [new file with mode: 0644]
patchwork/bin/parsemail-batch.sh [new file with mode: 0755]
patchwork/bin/parsemail.py [new file with mode: 0755]
patchwork/bin/parsemail.sh [new file with mode: 0755]
patchwork/bin/patchwork-cron.py [new file with mode: 0755]
patchwork/bin/pwclient [new file with mode: 0755]
patchwork/bin/rehash.py [new file with mode: 0755]
patchwork/bin/update-patchwork-status.py [new file with mode: 0755]
patchwork/context_processors.py [new file with mode: 0644]
patchwork/filters.py [new file with mode: 0644]
patchwork/fixtures/default_projects.xml [new file with mode: 0644]
patchwork/fixtures/initial_data.xml [new file with mode: 0644]
patchwork/forms.py [new file with mode: 0644]
patchwork/models.py [new file with mode: 0644]
patchwork/paginator.py [new file with mode: 0644]
patchwork/parser.py [new file with mode: 0644]
patchwork/requestcontext.py [new file with mode: 0644]
patchwork/settings/__init__.py [new file with mode: 0644]
patchwork/settings/base.py [new file with mode: 0644]
patchwork/settings/dev.py [new file with mode: 0644]
patchwork/settings/prod.py [new file with mode: 0644]
patchwork/templates/patchwork/activation_email.txt [new file with mode: 0644]
patchwork/templates/patchwork/activation_email_subject.txt [new file with mode: 0644]
patchwork/templates/patchwork/bundle.html [new file with mode: 0644]
patchwork/templates/patchwork/bundles.html [new file with mode: 0644]
patchwork/templates/patchwork/confirm-error.html [new file with mode: 0644]
patchwork/templates/patchwork/filters.html [new file with mode: 0644]
patchwork/templates/patchwork/help/about.html [new file with mode: 0644]
patchwork/templates/patchwork/help/index.html [new file with mode: 0644]
patchwork/templates/patchwork/help/pwclient.html [new file with mode: 0644]
patchwork/templates/patchwork/list.html [new file with mode: 0644]
patchwork/templates/patchwork/login.html [new file with mode: 0644]
patchwork/templates/patchwork/logout.html [new file with mode: 0644]
patchwork/templates/patchwork/mail-form.html [new file with mode: 0644]
patchwork/templates/patchwork/mail-settings.html [new file with mode: 0644]
patchwork/templates/patchwork/optin-request.html [new file with mode: 0644]
patchwork/templates/patchwork/optin-request.mail [new file with mode: 0644]
patchwork/templates/patchwork/optin.html [new file with mode: 0644]
patchwork/templates/patchwork/optout-request.html [new file with mode: 0644]
patchwork/templates/patchwork/optout-request.mail [new file with mode: 0644]
patchwork/templates/patchwork/optout.html [new file with mode: 0644]
patchwork/templates/patchwork/pagination.html [new file with mode: 0644]
patchwork/templates/patchwork/patch-change-notification-subject.text [new file with mode: 0644]
patchwork/templates/patchwork/patch-change-notification.mail [new file with mode: 0644]
patchwork/templates/patchwork/patch-list.html [new file with mode: 0644]
patchwork/templates/patchwork/patch.html [new file with mode: 0644]
patchwork/templates/patchwork/profile.html [new file with mode: 0644]
patchwork/templates/patchwork/project.html [new file with mode: 0644]
patchwork/templates/patchwork/projects.html [new file with mode: 0644]
patchwork/templates/patchwork/pwclient [new symlink]
patchwork/templates/patchwork/pwclientrc [new file with mode: 0644]
patchwork/templates/patchwork/register.mail [new file with mode: 0644]
patchwork/templates/patchwork/registration-confirm.html [new file with mode: 0644]
patchwork/templates/patchwork/registration_form.html [new file with mode: 0644]
patchwork/templates/patchwork/todo-list.html [new file with mode: 0644]
patchwork/templates/patchwork/todo-lists.html [new file with mode: 0644]
patchwork/templates/patchwork/user-link-confirm.html [new file with mode: 0644]
patchwork/templates/patchwork/user-link.html [new file with mode: 0644]
patchwork/templates/patchwork/user-link.mail [new file with mode: 0644]
patchwork/templatetags/__init__.py [new file with mode: 0644]
patchwork/templatetags/filter.py [new file with mode: 0644]
patchwork/templatetags/listurl.py [new file with mode: 0644]
patchwork/templatetags/order.py [new file with mode: 0644]
patchwork/templatetags/patch.py [new file with mode: 0644]
patchwork/templatetags/person.py [new file with mode: 0644]
patchwork/templatetags/pwurl.py [new file with mode: 0644]
patchwork/templatetags/syntax.py [new file with mode: 0644]
patchwork/tests/__init__.py [new file with mode: 0644]
patchwork/tests/mail/0001-git-pull-request.mbox [new file with mode: 0644]
patchwork/tests/mail/0002-git-pull-request-wrapped.mbox [new file with mode: 0644]
patchwork/tests/mail/0003-git-pull-request-with-diff.mbox [new file with mode: 0644]
patchwork/tests/mail/0004-git-pull-request-git+ssh.mbox [new file with mode: 0644]
patchwork/tests/mail/0005-git-pull-request-ssh.mbox [new file with mode: 0644]
patchwork/tests/mail/0006-git-pull-request-http.mbox [new file with mode: 0644]
patchwork/tests/mail/0007-cvs-format-diff.mbox [new file with mode: 0644]
patchwork/tests/mail/0008-git-rename.mbox [new file with mode: 0644]
patchwork/tests/mail/0009-git-rename-with-diff.mbox [new file with mode: 0644]
patchwork/tests/mail/0010-invalid-charset.mbox [new file with mode: 0644]
patchwork/tests/mail/0011-no-newline-at-end-of-file.mbox [new file with mode: 0644]
patchwork/tests/patches/0001-add-line.patch [new file with mode: 0644]
patchwork/tests/patches/0002-utf-8.patch [new file with mode: 0644]
patchwork/tests/test_bundles.py [new file with mode: 0644]
patchwork/tests/test_confirm.py [new file with mode: 0644]
patchwork/tests/test_encodings.py [new file with mode: 0644]
patchwork/tests/test_expiry.py [new file with mode: 0644]
patchwork/tests/test_filters.py [new file with mode: 0644]
patchwork/tests/test_list.py [new file with mode: 0644]
patchwork/tests/test_mail_settings.py [new file with mode: 0644]
patchwork/tests/test_mboxviews.py [new file with mode: 0644]
patchwork/tests/test_notifications.py [new file with mode: 0644]
patchwork/tests/test_patchparser.py [new file with mode: 0644]
patchwork/tests/test_person.py [new file with mode: 0644]
patchwork/tests/test_registration.py [new file with mode: 0644]
patchwork/tests/test_updates.py [new file with mode: 0644]
patchwork/tests/test_user.py [new file with mode: 0644]
patchwork/tests/test_xmlrpc.py [new file with mode: 0644]
patchwork/tests/utils.py [new file with mode: 0644]
patchwork/urls.py [new file with mode: 0644]
patchwork/utils.py [new file with mode: 0644]
patchwork/views/__init__.py [new file with mode: 0644]
patchwork/views/base.py [new file with mode: 0644]
patchwork/views/bundle.py [new file with mode: 0644]
patchwork/views/mail.py [new file with mode: 0644]
patchwork/views/patch.py [new file with mode: 0644]
patchwork/views/project.py [new file with mode: 0644]
patchwork/views/user.py [new file with mode: 0644]
patchwork/views/xmlrpc.py [new file with mode: 0644]
templates/patchwork/activation_email.txt [deleted file]
templates/patchwork/activation_email_subject.txt [deleted file]
templates/patchwork/bundle.html [deleted file]
templates/patchwork/bundles.html [deleted file]
templates/patchwork/confirm-error.html [deleted file]
templates/patchwork/filters.html [deleted file]
templates/patchwork/help/about.html [deleted file]
templates/patchwork/help/index.html [deleted file]
templates/patchwork/help/pwclient.html [deleted file]
templates/patchwork/list.html [deleted file]
templates/patchwork/login.html [deleted file]
templates/patchwork/logout.html [deleted file]
templates/patchwork/mail-form.html [deleted file]
templates/patchwork/mail-settings.html [deleted file]
templates/patchwork/optin-request.html [deleted file]
templates/patchwork/optin-request.mail [deleted file]
templates/patchwork/optin.html [deleted file]
templates/patchwork/optout-request.html [deleted file]
templates/patchwork/optout-request.mail [deleted file]
templates/patchwork/optout.html [deleted file]
templates/patchwork/pagination.html [deleted file]
templates/patchwork/patch-change-notification-subject.text [deleted file]
templates/patchwork/patch-change-notification.mail [deleted file]
templates/patchwork/patch-list.html [deleted file]
templates/patchwork/patch.html [deleted file]
templates/patchwork/profile.html [deleted file]
templates/patchwork/project.html [deleted file]
templates/patchwork/projects.html [deleted file]
templates/patchwork/pwclient [deleted symlink]
templates/patchwork/pwclientrc [deleted file]
templates/patchwork/register.mail [deleted file]
templates/patchwork/registration-confirm.html [deleted file]
templates/patchwork/registration_form.html [deleted file]
templates/patchwork/todo-list.html [deleted file]
templates/patchwork/todo-lists.html [deleted file]
templates/patchwork/user-link-confirm.html [deleted file]
templates/patchwork/user-link.html [deleted file]
templates/patchwork/user-link.mail [deleted file]
tools/patchwork-update-commits
tools/post-receive.hook
tox.ini

diff --git a/apps/__init__.py b/apps/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/apps/manage.py b/apps/manage.py
deleted file mode 100755 (executable)
index 04eac77..0000000
+++ /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 (file)
index e69de29..0000000
diff --git a/apps/patchwork/admin.py b/apps/patchwork/admin.py
deleted file mode 100644 (file)
index 5297903..0000000
+++ /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 (file)
index e69de29..0000000
diff --git a/apps/patchwork/bin/bash_completion b/apps/patchwork/bin/bash_completion
deleted file mode 100644 (file)
index a120a76..0000000
+++ /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 (executable)
index 31ef4f0..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/bin/sh
-#
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 <dir>" >&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 (executable)
index 19e6e57..0000000
+++ /dev/null
@@ -1,455 +0,0 @@
-#!/usr/bin/env python
-#
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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" <example@example.com> 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 (executable)
index 246c2a1..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/sh
-#
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (executable)
index 148e97c..0000000
+++ /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 (executable)
index 8d1f476..0000000
+++ /dev/null
@@ -1,744 +0,0 @@
-#!/usr/bin/env python
-#
-# Patchwork command line client
-# Copyright (C) 2008 Nate Case <ncase@xes-inc.com>
-#
-# 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 (executable)
index c44e49b..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env python
-#
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (executable)
index 2da5d23..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env python
-#
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index f4ab5a9..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 8c9690e..0000000
+++ /dev/null
@@ -1,471 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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('<input type="hidden" value="%s">%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(('<input onKeyUp="submitter_field_change(this)" ' +
-                'name="submitter" id="submitter_input" ' +
-                        'value="%s">&nbsp;' % escape(name)) +
-                '<select id="submitter_select" ' +
-                'disabled="true"></select>')
-
-    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 = '<select name="%s">' % self.param
-
-        selected = ''
-        if not self.applied:
-            selected = 'selected'
-        str += '<option %s value="%s">any</option>' % (selected, self.any_key)
-
-        selected = ''
-        if self.applied and self.state == None:
-            selected = 'selected'
-        str += '<option %s value="">%s</option>' % \
-               (selected, self.action_req_str)
-
-        for state in State.objects.all():
-            selected = ''
-            if self.state and self.state == state:
-                selected = ' selected="true"'
-
-            str += '<option value="%d" %s>%s</option>' % \
-                (state.id, selected, state.name)
-        str += '</select>'
-        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('<input name="%s" value="%s">' %\
-                (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 += ('<input type="radio" name="%(param)s" ' + \
-                   '%(selected)s value="%(value)s">%(label)s' + \
-                   '&nbsp;&nbsp;&nbsp;&nbsp;') % \
-                    {'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 = '<select name="delegate">'
-
-        selected = ''
-        if not self.applied:
-            selected = 'selected'
-
-        str += '<option %s value="">------</option>' % selected
-
-        selected = ''
-        if self.applied and self.delegate is None:
-            selected = 'selected'
-
-        str += '<option %s value="%s">%s</option>' % \
-                (selected, self.no_delegate_key, self.no_delegate_str)
-
-        for d in delegates:
-            selected = ''
-            if d == self.delegate:
-                selected = ' selected'
-
-            str += '<option %s value="%s">%s</option>' % (selected,
-                    d.id, d.profile.name())
-        str += '</select>'
-
-        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 (file)
index c67fa56..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<django-objects version="1.0">
-
-  <!-- some default projects -->
-  <object pk="1" model="patchwork.project">
-    <field type="CharField" name="linkname">cbe-oss-dev</field>
-    <field type="CharField" name="name">Cell Broadband Engine development</field>
-    <field type="CharField" name="listid">cbe-oss-dev.ozlabs.org</field>
-    <field type="CharField" name="listemail">cbe-oss-dev@ozlabs.org</field>
-  </object>
-  <object pk="2" model="patchwork.project">
-    <field type="CharField" name="linkname">linuxppc-dev</field>
-    <field type="CharField" name="name">Linux PPC development</field>
-    <field type="CharField" name="listid">linuxppc-dev.ozlabs.org</field>
-    <field type="CharField" name="listemail">linuxppc-dev@ozlabs.org</field>
-  </object>
-
-</django-objects>
diff --git a/apps/patchwork/fixtures/initial_data.xml b/apps/patchwork/fixtures/initial_data.xml
deleted file mode 100644 (file)
index 86e1105..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<django-objects version="1.0">
-
-  <!-- default states -->
-  <object pk="1" model="patchwork.state">
-    <field type="CharField" name="name">New</field>
-    <field type="IntegerField" name="ordering">0</field>
-    <field type="BooleanField" name="action_required">True</field>
-  </object>
-  <object pk="2" model="patchwork.state">
-    <field type="CharField" name="name">Under Review</field>
-    <field type="IntegerField" name="ordering">1</field>
-    <field type="BooleanField" name="action_required">True</field>
-  </object>
-  <object pk="3" model="patchwork.state">
-    <field type="CharField" name="name">Accepted</field>
-    <field type="IntegerField" name="ordering">2</field>
-    <field type="BooleanField" name="action_required">False</field>
-  </object>
-  <object pk="4" model="patchwork.state">
-    <field type="CharField" name="name">Rejected</field>
-    <field type="IntegerField" name="ordering">3</field>
-    <field type="BooleanField" name="action_required">False</field>
-  </object>
-  <object pk="5" model="patchwork.state">
-    <field type="CharField" name="name">RFC</field>
-    <field type="IntegerField" name="ordering">4</field>
-    <field type="BooleanField" name="action_required">False</field>
-  </object>
-  <object pk="6" model="patchwork.state">
-    <field type="CharField" name="name">Not Applicable</field>
-    <field type="IntegerField" name="ordering">5</field>
-    <field type="BooleanField" name="action_required">False</field>
-  </object>
-  <object pk="7" model="patchwork.state">
-    <field type="CharField" name="name">Changes Requested</field>
-    <field type="IntegerField" name="ordering">6</field>
-    <field type="BooleanField" name="action_required">False</field>
-  </object>
-  <object pk="8" model="patchwork.state">
-    <field type="CharField" name="name">Awaiting Upstream</field>
-    <field type="IntegerField" name="ordering">7</field>
-    <field type="BooleanField" name="action_required">False</field>
-  </object>
-  <object pk="9" model="patchwork.state">
-    <field type="CharField" name="name">Superseded</field>
-    <field type="IntegerField" name="ordering">8</field>
-    <field type="BooleanField" name="action_required">False</field>
-  </object>
-  <object pk="10" model="patchwork.state">
-    <field type="CharField" name="name">Deferred</field>
-    <field type="IntegerField" name="ordering">9</field>
-    <field type="BooleanField" name="action_required">False</field>
-  </object>
-</django-objects>
diff --git a/apps/patchwork/forms.py b/apps/patchwork/forms.py
deleted file mode 100644 (file)
index 0327958..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 54b8656..0000000
+++ /dev/null
@@ -1,386 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 31c0190..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index a51a7b6..0000000
+++ /dev/null
@@ -1,267 +0,0 @@
-#!/usr/bin/env python
-#
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 3b1afaf..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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([ '<input type="hidden" name="%s" value="%s"/>' % \
-                (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 (file)
index e69de29..0000000
diff --git a/apps/patchwork/settings/base.py b/apps/patchwork/settings/base.py
deleted file mode 100644 (file)
index 5440de6..0000000
+++ /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 <patchwork@patchwork.example.com>'
-
-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 (file)
index 6e373cc..0000000
+++ /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 (file)
index d71f3df..0000000
+++ /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 (file)
index e69de29..0000000
diff --git a/apps/patchwork/templatetags/filter.py b/apps/patchwork/templatetags/filter.py
deleted file mode 100644 (file)
index 7a5d9df..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 '<a href="javascript:personpopup(\'%s\')">%s</a>' % (escape(person.email), linktext)
-
diff --git a/apps/patchwork/templatetags/listurl.py b/apps/patchwork/templatetags/listurl.py
deleted file mode 100644 (file)
index 5fe03e4..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 += '<input type="hidden" name="%s" value="%s"\>' % \
-                   (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 (file)
index e392f03..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index bec0cab..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index c337c74..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 = '<a href="%s?%s=%s">%s</a>' % \
-                (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 (file)
index 98bc1ca..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index abdbb4d..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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*&gt;.*$', 'quote'),
-        ])
-
-_span = '<span class="%s">%s</span>'
-
-@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 (file)
index 85200bd..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 0dbedbe..0000000
+++ /dev/null
@@ -1,348 +0,0 @@
-From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010
-Return-Path: <linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org>
-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 <jk@ozlabs.org>; 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 <linuxppc-dev@ozlabs.org>; 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 <benh@kernel.crashing.org>
-To: Linus Torvalds <torvalds@linux-foundation.org>
-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 <linuxppc-dev@ozlabs.org>,
- Andrew Morton <akpm@linux-foundation.org>,
- Linux Kernel list <linux-kernel@vger.kernel.org>
-X-BeenThere: linuxppc-dev@lists.ozlabs.org
-X-Mailman-Version: 2.1.13
-Precedence: list
-List-Id: Linux on PowerPC Developers Mail List <cbe-oss-dev.ozlabs.org>
-List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
-       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
-List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
-List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
-List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
-List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
-       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=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_<level> uses of KERN_<level>
-
-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 (file)
index d3ccee1..0000000
+++ /dev/null
@@ -1,349 +0,0 @@
-From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010
-Return-Path: <linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org>
-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 <jk@ozlabs.org>; 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 <linuxppc-dev@ozlabs.org>; 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 <benh@kernel.crashing.org>
-To: Linus Torvalds <torvalds@linux-foundation.org>
-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 <linuxppc-dev@ozlabs.org>,
- Andrew Morton <akpm@linux-foundation.org>,
- Linux Kernel list <linux-kernel@vger.kernel.org>
-X-BeenThere: linuxppc-dev@lists.ozlabs.org
-X-Mailman-Version: 2.1.13
-Precedence: list
-List-Id: Linux on PowerPC Developers Mail List <cbe-oss-dev.ozlabs.org>
-List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
-       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
-List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
-List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
-List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
-List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
-       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=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_<level> uses of KERN_<level>
-
-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 (file)
index b4d578c..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010
-Return-Path: <linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org>
-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 <jk@ozlabs.org>; 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 <linuxppc-dev@ozlabs.org>; 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 <benh@kernel.crashing.org>
-To: Linus Torvalds <torvalds@linux-foundation.org>
-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 <linuxppc-dev@ozlabs.org>,
- Andrew Morton <akpm@linux-foundation.org>,
- Linux Kernel list <linux-kernel@vger.kernel.org>
-X-BeenThere: linuxppc-dev@lists.ozlabs.org
-X-Mailman-Version: 2.1.13
-Precedence: list
-List-Id: Linux on PowerPC Developers Mail List <cbe-oss-dev.ozlabs.org>
-List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
-     <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
-List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
-List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
-List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
-List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
-     <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=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 (file)
index da96465..0000000
+++ /dev/null
@@ -1,348 +0,0 @@
-From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010
-Return-Path: <linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org>
-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 <jk@ozlabs.org>; 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 <linuxppc-dev@ozlabs.org>; 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 <benh@kernel.crashing.org>
-To: Linus Torvalds <torvalds@linux-foundation.org>
-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 <linuxppc-dev@ozlabs.org>,
- Andrew Morton <akpm@linux-foundation.org>,
- Linux Kernel list <linux-kernel@vger.kernel.org>
-X-BeenThere: linuxppc-dev@lists.ozlabs.org
-X-Mailman-Version: 2.1.13
-Precedence: list
-List-Id: Linux on PowerPC Developers Mail List <cbe-oss-dev.ozlabs.org>
-List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
-       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
-List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
-List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
-List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
-List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
-       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=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_<level> uses of KERN_<level>
-
-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 (file)
index 7f4c93e..0000000
+++ /dev/null
@@ -1,348 +0,0 @@
-From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010
-Return-Path: <linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org>
-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 <jk@ozlabs.org>; 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 <linuxppc-dev@ozlabs.org>; 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 <benh@kernel.crashing.org>
-To: Linus Torvalds <torvalds@linux-foundation.org>
-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 <linuxppc-dev@ozlabs.org>,
- Andrew Morton <akpm@linux-foundation.org>,
- Linux Kernel list <linux-kernel@vger.kernel.org>
-X-BeenThere: linuxppc-dev@lists.ozlabs.org
-X-Mailman-Version: 2.1.13
-Precedence: list
-List-Id: Linux on PowerPC Developers Mail List <cbe-oss-dev.ozlabs.org>
-List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
-       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
-List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
-List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
-List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
-List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
-       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=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_<level> uses of KERN_<level>
-
-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 (file)
index e4f9007..0000000
+++ /dev/null
@@ -1,348 +0,0 @@
-From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010
-Return-Path: <linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org>
-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 <jk@ozlabs.org>; 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 <linuxppc-dev@ozlabs.org>; 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 <benh@kernel.crashing.org>
-To: Linus Torvalds <torvalds@linux-foundation.org>
-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 <linuxppc-dev@ozlabs.org>,
- Andrew Morton <akpm@linux-foundation.org>,
- Linux Kernel list <linux-kernel@vger.kernel.org>
-X-BeenThere: linuxppc-dev@lists.ozlabs.org
-X-Mailman-Version: 2.1.13
-Precedence: list
-List-Id: Linux on PowerPC Developers Mail List <cbe-oss-dev.ozlabs.org>
-List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
-       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
-List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
-List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
-List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
-List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
-       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=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_<level> uses of KERN_<level>
-
-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 (file)
index 99735fa..0000000
+++ /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
-        <rfc822;linux-mips@linux-mips.org>); 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 <B4edd66f80000>; 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 <david.daney@cavium.com>
-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 <binutils@sourceware.org>
-CC:     linux-mips <linux-mips@linux-mips.org>,
-        Manuel Lauss <manuel.lauss@googlemail.com>,
-        Debian MIPS <debian-mips@lists.debian.org>
-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: <David.Daney@caviumnetworks.com>
-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  <david.daney@cavium.com>
-
-       * 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 (file)
index 8277049..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-From: "Yann E. MORIN" <yann.morin.1998@free.fr>
-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" <yann.morin.1998@free.fr>
----
- ...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 (file)
index 761cfc1..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-From: "Yann E. MORIN" <yann.morin.1998@free.fr>
-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" <yann.morin.1998@free.fr>
----
- ...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 (file)
index 10b369d..0000000
+++ /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: <libc-alpha.sourceware.org>
-Sender: libc-alpha-owner@sourceware.org
-Date: Wed, 4 Jun 2014 17:50:46 +0000
-From: "Joseph S. Myers" <joseph@codesourcery.com>
-To: <libc-alpha@sourceware.org>
-Subject: Fix pow overflow in non-default rounding modes (bug 16315)
-Message-ID: <Pine.LNX.4.64.1406041749420.3719@digraph.polyomino.org.uk>
-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
-<https://sourceware.org/ml/libc-alpha/2014-06/msg00076.html> 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  <joseph@codesourcery.com>
-
-=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 <math.h>.
-=09(__ieee754_pow): Recompute overflowing and underflowing results in
-=09original rounding mode.
-=09* sysdeps/x86/fpu/powl_helper.c: Include <stdbool.h>.
-=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 <math.h>.
-=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 <init-arch.h>
-+# include <math.h>
- # include <math_private.h>
-=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 (file)
index 3ed0597..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-Subject: [PATCH v3 5/5] selftests, powerpc: Add test for VPHN
-From: Greg Kurz <gkurz@linux.vnet.ibm.com>
-To: Michael Ellerman <mpe@ellerman.id.au>
-Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>,
- 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 (file)
index c6cb9f1..0000000
+++ /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 (file)
index 71a2f24..0000000
+++ /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 (file)
index 38f3a2c..0000000
+++ /dev/null
@@ -1,646 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2009 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 &#39;%s&#39; 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 &#39;%s&#39; already in bundle' \
-                            % patch.name, count = 1, status_code = 200)
-        self.assertContains(response, 'Patch &#39;%s&#39; 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 &quot;%s&quot;' % 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 &quot;%s&quot;' % 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/<id>
-
-    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 (file)
index fad5125..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index b9032bb..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 844ed4b..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2014 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 2c464e5..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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&amp;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 (file)
index a795a5f..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2012 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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('<tr id="patch_row:(\d+)" ')
-        ids = [ int(m.group(1)) for m in id_re.finditer(response.content) ]
-        return ids
-
-    def _test_sequence(self, response, test_fn):
-        ids = self._extract_patch_ids(response)
-        self.assertTrue(bool(ids))
-        patches = [ Patch.objects.get(id = i) for i in ids ]
-        pairs = zip(patches, patches[1:])
-        [ test_fn(p1, p2) for (p1, p2) in pairs ]
-
-    def testDateOrder(self):
-        url = reverse('patchwork.views.patch.list',
-                kwargs={'project_id': defaults.project.linkname})
-        response = self.client.get(url + '?order=date')
-        def test_fn(p1, p2):
-            self.assertLessEqual(p1.date, p2.date)
-        self._test_sequence(response, test_fn)
-
-    def testDateReverseOrder(self):
-        url = reverse('patchwork.views.patch.list',
-                kwargs={'project_id': defaults.project.linkname})
-        response = self.client.get(url + '?order=-date')
-        def test_fn(p1, p2):
-            self.assertGreaterEqual(p1.date, p2.date)
-        self._test_sequence(response, test_fn)
-
-    def testSubmitterOrder(self):
-        url = reverse('patchwork.views.patch.list',
-                kwargs={'project_id': defaults.project.linkname})
-        response = self.client.get(url + '?order=submitter')
-        def test_fn(p1, p2):
-            self.assertLessEqual(p1.submitter.name.lower(),
-                                 p2.submitter.name.lower())
-        self._test_sequence(response, test_fn)
-
-    def testSubmitterReverseOrder(self):
-        url = reverse('patchwork.views.patch.list',
-                kwargs={'project_id': defaults.project.linkname})
-        response = self.client.get(url + '?order=-submitter')
-        def test_fn(p1, p2):
-            self.assertGreaterEqual(p1.submitter.name.lower(),
-                                    p2.submitter.name.lower())
-        self._test_sequence(response, test_fn)
-
diff --git a/apps/patchwork/tests/test_mail_settings.py b/apps/patchwork/tests/test_mail_settings.py
deleted file mode 100644 (file)
index a193c97..0000000
+++ /dev/null
@@ -1,299 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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('<strong>may</strong>' 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('<strong>may not</strong>' 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 = ('<form\s+[^>]*action="%(url)s"[^>]*>'
-                        '.*?<input\s+[^>]*value="%(email)s"[^>]*>.*?'
-                        '</form>')
-    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 (file)
index 0e57f42..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-# vim: set fileencoding=utf-8 :
-#
-# Patchwork - automated patch tracking system
-# Copyright (C) 2009 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 <cc@example.com>'
-        self.to_header = 'To: To Person <to@example.com>'
-        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
-        <user@doamin.tld> 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 (file)
index ed35140..0000000
+++ /dev/null
@@ -1,255 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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('&quot;' 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 (file)
index 119936a..0000000
+++ /dev/null
@@ -1,554 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 <user@example.com>'
-
-    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?= <user@example.com>'
-
-class SenderUTF8QPSplitEncodingTest(SenderEncodingTest):
-    sender_name = u'\xe9xample user'
-    from_header = '=?utf-8?q?=C3=A9xample?= user <user@example.com>'
-
-class SenderUTF8B64EncodingTest(SenderUTF8QPEncodingTest):
-    from_header = '=?utf-8?B?w6l4YW1wbGUgdXNlcg==?= <user@example.com>'
-
-class SubjectEncodingTest(PatchTest):
-    sender = 'example user <user@example.com>'
-    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 <existing@example.com>'
-    non_existing_sender = 'Non-existing Sender <nonexisting@example.com>'
-
-    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 (file)
index d948096..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2013 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 845b60b..0000000
+++ /dev/null
@@ -1,210 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 177ee78..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 0faa970..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 2b459b2..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2014 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 782ed36..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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@example.com>'
-    patch_author_person = Person(name = 'Patch Author',
-        email = 'patch-author@example.com')
-
-    comment_author = 'Comment Author <comment-author@example.com>'
-
-    sender = 'Test Author <test-author@example.com>'
-
-    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 (file)
index b28eb90..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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<project_id>[^/]+)/list/$', 'patchwork.views.patch.list'),
-    (r'^project/(?P<project_id>[^/]+)/$', 'patchwork.views.project.project'),
-
-    # patch views
-    (r'^patch/(?P<patch_id>\d+)/$', 'patchwork.views.patch.patch'),
-    (r'^patch/(?P<patch_id>\d+)/raw/$', 'patchwork.views.patch.content'),
-    (r'^patch/(?P<patch_id>\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<project_id>[^/]+)/$', 'patchwork.views.user.todo_list'),
-
-    (r'^user/bundles/$',
-        'patchwork.views.bundle.bundles'),
-
-    (r'^user/link/$', 'patchwork.views.user.link'),
-    (r'^user/unlink/(?P<person_id>[^/]+)/$', '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<username>[^/]*)/(?P<bundlename>[^/]*)/$',
-                                'patchwork.views.bundle.bundle'),
-    (r'^bundle/(?P<username>[^/]*)/(?P<bundlename>[^/]*)/mbox/$',
-                                'patchwork.views.bundle.mbox'),
-
-    (r'^confirm/(?P<key>[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<path>.*)$', 'patchwork.views.help'),
-)
-
-if settings.ENABLE_XMLRPC:
-    urlpatterns += patterns('',
-        (r'xmlrpc/$', 'patchwork.views.xmlrpc.xmlrpc'),
-        (r'^pwclient/$', 'patchwork.views.pwclient'),
-        (r'^project/(?P<project_id>[^/]+)/pwclientrc/$',
-             'patchwork.views.pwclientrc'),
-    )
-
-# redirect from old urls
-if settings.COMPAT_REDIR:
-    urlpatterns += patterns('',
-        (r'^user/bundle/(?P<bundle_id>[^/]+)/$',
-            'patchwork.views.bundle.bundle_redir'),
-        (r'^user/bundle/(?P<bundle_id>[^/]+)/mbox/$',
-            'patchwork.views.bundle.mbox_redir'),
-    )
-
diff --git a/apps/patchwork/utils.py b/apps/patchwork/utils.py
deleted file mode 100644 (file)
index 9ed9e41..0000000
+++ /dev/null
@@ -1,248 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index dfca56d..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 6d7dd13..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 3fb47e2..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index aebba34..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 62ff853..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 114dbe0..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2009 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 126ecc9..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 (file)
index 84ed408..0000000
+++ /dev/null
@@ -1,450 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
-#
-# 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 {}
index cab59eb7496f1dd347488c36e408c74069a8ca83..c1b478efe5be6734034076dd3316091337262acf 100644 (file)
@@ -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
 
index 16ab2b5983a3cc3ebc28d3c1f85728199ee0174c..b006178c5e4d63bb9aff9ed85f174bf6b9b32a74 100644 (file)
@@ -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
index 639573852441aa166648b7d36af9fa25ac5e4d0e..c46f86cda0390206704492427347ab37a3004d65 100644 (file)
@@ -7,7 +7,7 @@ NameVirtualHost patchwork.example.com:80
        <Location "/">
            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
        </Location>
 
index 869bb9de6c147e73dadc5ab998905a8ddb2948fb..52feb5863066c1972ca4a73c571d562816a916d9 100644 (file)
@@ -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 (executable)
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 (file)
index 0000000..e69de29
diff --git a/patchwork/admin.py b/patchwork/admin.py
new file mode 100644 (file)
index 0000000..5297903
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/patchwork/bin/bash_completion b/patchwork/bin/bash_completion
new file mode 100644 (file)
index 0000000..a120a76
--- /dev/null
@@ -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 (executable)
index 0000000..31ef4f0
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 <dir>" >&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 (executable)
index 0000000..19e6e57
--- /dev/null
@@ -0,0 +1,455 @@
+#!/usr/bin/env python
+#
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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" <example@example.com> 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 (executable)
index 0000000..d9ad005
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (executable)
index 0000000..148e97c
--- /dev/null
@@ -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 (executable)
index 0000000..8d1f476
--- /dev/null
@@ -0,0 +1,744 @@
+#!/usr/bin/env python
+#
+# Patchwork command line client
+# Copyright (C) 2008 Nate Case <ncase@xes-inc.com>
+#
+# 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 (executable)
index 0000000..c44e49b
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+#
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (executable)
index 0000000..2da5d23
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+#
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..f4ab5a9
--- /dev/null
@@ -0,0 +1,32 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..8c9690e
--- /dev/null
@@ -0,0 +1,471 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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('<input type="hidden" value="%s">%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(('<input onKeyUp="submitter_field_change(this)" ' +
+                'name="submitter" id="submitter_input" ' +
+                        'value="%s">&nbsp;' % escape(name)) +
+                '<select id="submitter_select" ' +
+                'disabled="true"></select>')
+
+    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 = '<select name="%s">' % self.param
+
+        selected = ''
+        if not self.applied:
+            selected = 'selected'
+        str += '<option %s value="%s">any</option>' % (selected, self.any_key)
+
+        selected = ''
+        if self.applied and self.state == None:
+            selected = 'selected'
+        str += '<option %s value="">%s</option>' % \
+               (selected, self.action_req_str)
+
+        for state in State.objects.all():
+            selected = ''
+            if self.state and self.state == state:
+                selected = ' selected="true"'
+
+            str += '<option value="%d" %s>%s</option>' % \
+                (state.id, selected, state.name)
+        str += '</select>'
+        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('<input name="%s" value="%s">' %\
+                (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 += ('<input type="radio" name="%(param)s" ' + \
+                   '%(selected)s value="%(value)s">%(label)s' + \
+                   '&nbsp;&nbsp;&nbsp;&nbsp;') % \
+                    {'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 = '<select name="delegate">'
+
+        selected = ''
+        if not self.applied:
+            selected = 'selected'
+
+        str += '<option %s value="">------</option>' % selected
+
+        selected = ''
+        if self.applied and self.delegate is None:
+            selected = 'selected'
+
+        str += '<option %s value="%s">%s</option>' % \
+                (selected, self.no_delegate_key, self.no_delegate_str)
+
+        for d in delegates:
+            selected = ''
+            if d == self.delegate:
+                selected = ' selected'
+
+            str += '<option %s value="%s">%s</option>' % (selected,
+                    d.id, d.profile.name())
+        str += '</select>'
+
+        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 (file)
index 0000000..c67fa56
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+
+  <!-- some default projects -->
+  <object pk="1" model="patchwork.project">
+    <field type="CharField" name="linkname">cbe-oss-dev</field>
+    <field type="CharField" name="name">Cell Broadband Engine development</field>
+    <field type="CharField" name="listid">cbe-oss-dev.ozlabs.org</field>
+    <field type="CharField" name="listemail">cbe-oss-dev@ozlabs.org</field>
+  </object>
+  <object pk="2" model="patchwork.project">
+    <field type="CharField" name="linkname">linuxppc-dev</field>
+    <field type="CharField" name="name">Linux PPC development</field>
+    <field type="CharField" name="listid">linuxppc-dev.ozlabs.org</field>
+    <field type="CharField" name="listemail">linuxppc-dev@ozlabs.org</field>
+  </object>
+
+</django-objects>
diff --git a/patchwork/fixtures/initial_data.xml b/patchwork/fixtures/initial_data.xml
new file mode 100644 (file)
index 0000000..86e1105
--- /dev/null
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+
+  <!-- default states -->
+  <object pk="1" model="patchwork.state">
+    <field type="CharField" name="name">New</field>
+    <field type="IntegerField" name="ordering">0</field>
+    <field type="BooleanField" name="action_required">True</field>
+  </object>
+  <object pk="2" model="patchwork.state">
+    <field type="CharField" name="name">Under Review</field>
+    <field type="IntegerField" name="ordering">1</field>
+    <field type="BooleanField" name="action_required">True</field>
+  </object>
+  <object pk="3" model="patchwork.state">
+    <field type="CharField" name="name">Accepted</field>
+    <field type="IntegerField" name="ordering">2</field>
+    <field type="BooleanField" name="action_required">False</field>
+  </object>
+  <object pk="4" model="patchwork.state">
+    <field type="CharField" name="name">Rejected</field>
+    <field type="IntegerField" name="ordering">3</field>
+    <field type="BooleanField" name="action_required">False</field>
+  </object>
+  <object pk="5" model="patchwork.state">
+    <field type="CharField" name="name">RFC</field>
+    <field type="IntegerField" name="ordering">4</field>
+    <field type="BooleanField" name="action_required">False</field>
+  </object>
+  <object pk="6" model="patchwork.state">
+    <field type="CharField" name="name">Not Applicable</field>
+    <field type="IntegerField" name="ordering">5</field>
+    <field type="BooleanField" name="action_required">False</field>
+  </object>
+  <object pk="7" model="patchwork.state">
+    <field type="CharField" name="name">Changes Requested</field>
+    <field type="IntegerField" name="ordering">6</field>
+    <field type="BooleanField" name="action_required">False</field>
+  </object>
+  <object pk="8" model="patchwork.state">
+    <field type="CharField" name="name">Awaiting Upstream</field>
+    <field type="IntegerField" name="ordering">7</field>
+    <field type="BooleanField" name="action_required">False</field>
+  </object>
+  <object pk="9" model="patchwork.state">
+    <field type="CharField" name="name">Superseded</field>
+    <field type="IntegerField" name="ordering">8</field>
+    <field type="BooleanField" name="action_required">False</field>
+  </object>
+  <object pk="10" model="patchwork.state">
+    <field type="CharField" name="name">Deferred</field>
+    <field type="IntegerField" name="ordering">9</field>
+    <field type="BooleanField" name="action_required">False</field>
+  </object>
+</django-objects>
diff --git a/patchwork/forms.py b/patchwork/forms.py
new file mode 100644 (file)
index 0000000..0327958
--- /dev/null
@@ -0,0 +1,237 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..54b8656
--- /dev/null
@@ -0,0 +1,386 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..31c0190
--- /dev/null
@@ -0,0 +1,88 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..a51a7b6
--- /dev/null
@@ -0,0 +1,267 @@
+#!/usr/bin/env python
+#
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..3b1afaf
--- /dev/null
@@ -0,0 +1,89 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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([ '<input type="hidden" name="%s" value="%s"/>' % \
+                (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 (file)
index 0000000..e69de29
diff --git a/patchwork/settings/base.py b/patchwork/settings/base.py
new file mode 100644 (file)
index 0000000..9b52989
--- /dev/null
@@ -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 <patchwork@patchwork.example.com>'
+
+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 (file)
index 0000000..6e373cc
--- /dev/null
@@ -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 (file)
index 0000000..d71f3df
--- /dev/null
@@ -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 (file)
index 0000000..caf514a
--- /dev/null
@@ -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 (file)
index 0000000..c409f38
--- /dev/null
@@ -0,0 +1 @@
+Patchwork account confirmation
diff --git a/patchwork/templates/patchwork/bundle.html b/patchwork/templates/patchwork/bundle.html
new file mode 100644 (file)
index 0000000..4a96b6b
--- /dev/null
@@ -0,0 +1,47 @@
+{% extends "base.html" %}
+
+{% load person %}
+{% load static %}
+
+{% block headers %}
+  <script type="text/javascript" src="{% static "js/jquery-1.10.1.min.js" %}"></script>
+  <script type="text/javascript" src="{% static "js/jquery.tablednd.js" %}"></script>
+  <script type="text/javascript" src="{% static "js/bundle.js" %}"></script>
+{% endblock %}
+{% block title %}{{project.name}}{% endblock %}
+{% block heading %}bundle: {{bundle.owner.username}} /
+{{bundle.name}}{% endblock %}
+
+{% block body %}
+
+<p>This bundle contains patches for the {{ bundle.project.linkname }}
+project.</p>
+
+<p><a href="{% url 'patchwork.views.bundle.mbox' username=bundle.owner.username bundlename=bundle.name %}">Download bundle as mbox</a></p>
+
+{% if bundleform %}
+<form method="post">
+ {% csrf_token %}
+ <input type="hidden" name="form" value="bundle"/>
+<table class="form">
+
+ <tr>
+  <th colspan="2" class="headerrow">Bundle settings</th>
+ </tr>
+
+{{ bundleform }}
+ <tr>
+  <td colspan="2" class="submitrow">
+   <input type="submit" name="action" value="Update"/>
+   <input type="submit" name="action" value="Delete"/>
+  </td>
+ </tr>
+</table>
+</form>
+
+<div style="clear: both; padding: 1em;"></div>
+{% 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 (file)
index 0000000..11fb89d
--- /dev/null
@@ -0,0 +1,59 @@
+{% extends "base.html" %}
+
+{% load static %}
+
+{% block title %}Bundles{% endblock %}
+{% block heading %}Bundles{% endblock %}
+
+{% block body %}
+
+{% if bundles %}
+<table class="bundlelist">
+ <tr>
+  <th>Name</th>
+  <th>Project</th>
+  <th>Public Link</th>
+  <th>Patches</td>
+  <th>Download</th>
+  <th>Delete</th>
+ </tr>
+{% for bundle in bundles %}
+ <tr>
+  <td><a href="{{ bundle.get_absolute_url }}">{{ bundle.name }}</a></td>
+  <td>{{ bundle.project.linkname }}</td>
+  <td>
+   {% if bundle.public %}
+    <a href="{{ bundle.public_url }}">{{ bundle.public_url }}</a>
+   {% endif %}
+  </td>
+  <td style="text-align: right">{{ bundle.n_patches }}</td>
+  <td style="text-align: center;"><a
+   href="{% url 'patchwork.views.bundle.mbox' username=bundle.owner.username bundlename=bundle.name %}"
+   ><img src="{% static "images/16-em-down.png" %}" width="16" height="16" alt="download"
+   title="download"/></a></td>
+  <td style="text-align: center;">
+   <form method="post"
+    onsubmit="return confirm_delete('bundle', '{{bundle.name|escapejs}}');">
+    {% csrf_token %}
+    {{ bundle.delete_form.as_p }}
+    <input type="image"
+     src="{% static "images/patchwork/16-em-cross.png" %}" width="16" height="16" alt="delete"
+     title="delete" border="0" style="border: none;"/>
+   </form>
+  </td>
+
+ </tr>
+{% endfor %}
+</table>
+{% endif %}
+
+<p>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.</p>
+
+{% if not bundles %}
+<p>You have no bundles.</p>
+{% endif %}
+{% endblock %}
diff --git a/patchwork/templates/patchwork/confirm-error.html b/patchwork/templates/patchwork/confirm-error.html
new file mode 100644 (file)
index 0000000..81292e2
--- /dev/null
@@ -0,0 +1,19 @@
+{% extends "base.html" %}
+
+{% block title %}Confirmation{% endblock %}
+{% block heading %}Confirmation{% endblock %}
+
+
+{% block body %}
+
+{% if error == 'inactive' %}
+<p>This confirmation has already been processed; you've probably visited this
+page before.</p>
+{% endif %}
+
+{% if error == 'expired' %}
+<p>The confirmation has expired. If you'd still like to perform the
+{{conf.get_type_display}} process, you'll need to resubmit the request.</p>
+{% endif %}
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/filters.html b/patchwork/templates/patchwork/filters.html
new file mode 100644 (file)
index 0000000..10ca587
--- /dev/null
@@ -0,0 +1,183 @@
+{% load static %}
+
+<script type="text/javascript" language="JavaScript">
+var filterform_displayed = false;
+function filter_click()
+{
+    var form = document.getElementById('filterform');
+    if (!form) {
+        return;
+    }
+
+    if (filterform_displayed) {
+        form.style['display'] = 'none';
+        filterform_displayed = false;
+    } else {
+        form.style['display'] = 'block';
+        filterform_displayed = true;
+    }
+
+
+}
+function enable_selected_submitter(select, input)
+{
+    select.name = 'submitter';
+    input.name = '';
+}
+function filter_form_submit(form)
+{
+    var i;
+
+    var submitter_select = document.getElementById("submitter_select");
+    var submitter_input = document.getElementById("submitter_input");
+    if (!submitter_select || !submitter_input) {
+        req = null;
+        return;
+    }
+
+    /* submitter handling. if possible, use the select box, otherwise leave
+     * as-is (and so the text box is used). */
+
+    if (submitter_select.options.length == 0) {
+        /* if there's no match, just use the input */
+
+    } else if (submitter_select.options.length == 1) {
+        /* if there's only one match, request by id */
+        submitter_select.selectedIndex = 0;
+        enable_selected_submitter(submitter_select, submitter_input);
+
+    } else if (submitter_select.selectedIndex != -1) {
+        /* if the user has explicitly selected, request by id */
+        enable_selected_submitter(submitter_select, submitter_input);
+
+    }
+
+    for (i = 0; i < form.elements.length; i++) {
+        var e = form.elements[i];
+        if (e.type == 'submit') {
+            continue;
+        }
+
+        /* handle submitter data */
+        if (e.type == 'select-one') {
+            if (e.name == '') {
+                e.disabled = true;
+            }
+            if (e.selectedIndex != -1
+                    && e.options[e.selectedIndex].value == '') {
+                e.disabled = true;
+            }
+
+            continue;
+        }
+
+        if (e.value == '') {
+            e.disabled = true;
+        }
+    }
+}
+
+var req = null;
+
+function submitter_complete_response()
+{
+    if (req.readyState != 4) {
+        return
+    }
+
+    var completions;
+    eval("completions = " + req.responseText);
+
+    if (completions.length == 0) {
+        req = null;
+        return;
+    }
+
+    var submitter_select = document.getElementById("submitter_select");
+    var submitter_input = document.getElementById("submitter_input");
+    if (!submitter_select || !submitter_input) {
+        req = null;
+        return;
+    }
+
+    for (i = 0; i < completions.length; i++) {
+        name = completions[i]['fields']['name'];
+        if (name) {
+            name = completions[i]['fields']['name'] +
+                ' <' + completions[i]['fields']['email'] + '>';
+        } else {
+            name = completions[i]['fields']['email'];
+        }
+        o = new Option(name, completions[i]['pk']);
+        submitter_select.options[i] = o;
+    }
+
+    /* remove remaining options */
+    for (; i < submitter_select.length; i++) {
+        submitter_select.options[i] = null;
+    }
+
+    submitter_select.disabled = false;
+    req = null;
+}
+
+function submitter_field_change(field)
+{
+    var limit = 20;
+    var value = field.value;
+    if (value.length < 4) {
+        return;
+    }
+
+    if (req) {
+         return;
+    }
+
+    var url = '{% url 'patchwork.views.submitter_complete' %}?q=' + value +
+                        '&l=' + limit;
+    req = new XMLHttpRequest();
+    req.onreadystatechange = submitter_complete_response;
+    req.open("GET", url, true);
+    req.send('');
+}
+</script>
+
+<div class="filters">
+ <div id="filtersummary">
+  <strong><a href="javascript:filter_click()">Filters</a>:</strong>
+ {% if filters.applied_filters %}
+  {% for filter in filters.applied_filters %}
+   {{ filter.name }} = {{ filter.condition }}
+    {% if not filter.forced %}
+     <a href="{{ filter.url_without_me }}"><img
+      width="16" height="16" alt="remove filter" title="remove filter"
+      src="{% static "images/16-circle-blue-remove.png" %}"></a>
+    {% endif %}
+   {% if not forloop.last %}&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;{% endif %}
+  {% endfor %}
+ {% else %}
+  none
+  <a href="javascript:filter_click()"><img
+  width="16" height="16" alt="add filter" title="add filter"
+  src="{% static "images/16-circle-blue-add.png" %}"></a>
+ {% endif %}
+ </div>
+ <div id="filterform" style="padding-top: 1em; display: none">
+  <form action="" method="get" onSubmit="return filter_form_submit(this)">
+    <table>
+    {% for filter in filters.available_filters %}
+     <tr>
+      <td>{{ filter.name }}</td>
+      <td>{{ filter.form }}</td>
+     </tr>
+    {% endfor %}
+     <tr>
+      <td/>
+      <td><input type="submit" value="Apply"/></td>
+     </tr>
+    </table>
+  </form>
+ </div>
+</div>
+
+
diff --git a/patchwork/templates/patchwork/help/about.html b/patchwork/templates/patchwork/help/about.html
new file mode 100644 (file)
index 0000000..7befa6b
--- /dev/null
@@ -0,0 +1,17 @@
+{% extends "base.html" %}
+
+{% block title %}About{% endblock %}
+{% block heading %} - About Patchwork{% endblock %}
+
+{% block body %}
+
+<p>Patchwork is free software, and is available from the
+<a href="http://jk.ozlabs.org/projects/patchwork/">patchwork website</a>.</p>
+
+<p>Patchwork is built on the <a href="http://djangoproject.com/">django</a>
+web framework.</p>
+
+<p>Icons from the <a href="http://sweetie.sublink.ca/">Sweetie</a> icon set.</a>
+
+{% endblock %}
+
diff --git a/patchwork/templates/patchwork/help/index.html b/patchwork/templates/patchwork/help/index.html
new file mode 100644 (file)
index 0000000..5cb6467
--- /dev/null
@@ -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 (file)
index 0000000..7101ec1
--- /dev/null
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+
+{% block title %}Command-line client{% endblock %}
+{% block heading %} - Command-line client{% endblock %}
+
+{% block body %}
+
+<p><code>pwclient</code> is the command-line client for patchwork. Currently,
+it provides access to some read-only features of patchwork, such as downloading
+and applying patches.</p>
+
+<p>To use pwclient, you will need:</p>
+<ul>
+ <li>The <a href="{% url 'patchwork.views.pwclient' %}">pwclient</a>
+  program (11kB, python script)</li>
+ <li>(optional) a <code>.pwclientrc</code> file in your home directory.</li>
+</ul>
+
+<p>You can create your own <code>.pwclientrc</code> file. Each
+<a href="{% url 'patchwork.views.projects' %}">patchwork project</a>
+provides a sample linked from the 'project info' page.</p>
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/list.html b/patchwork/templates/patchwork/list.html
new file mode 100644 (file)
index 0000000..654fe8c
--- /dev/null
@@ -0,0 +1,25 @@
+{% extends "base.html" %}
+
+{% load person %}
+{% load static %}
+
+{% block title %}{{project.name}}{% endblock %}
+{% block heading %}{{project.name}}{% endblock %}
+
+{% block body %}
+
+<h2>Incoming patches</h2>
+
+{% if errors %}
+<p>The following error{{ errors|length|pluralize:" was,s were" }} encountered
+while updating patches:</p>
+<ul class="errorlist">
+{% for error in errors %}
+ <li>{{ error }}</li>
+{% endfor %}
+</ul>
+{% 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 (file)
index 0000000..2dfc2a7
--- /dev/null
@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+
+{% block title %}Login{% endblock %}
+{% block heading %}Login{% endblock %}
+
+
+{% block body %}
+<form method="post">
+{% csrf_token %}
+<table class="form loginform">
+ <tr>
+  <th colspan="2" class="headerrow">login</th>
+ </tr>
+ {% if error %}
+  <tr>
+   <td colspan="2">{{ error }}</td>
+  </tr>
+ {% endif %}
+ {{ form }}
+ <tr>
+  <td colspan="2" class="submitrow">
+   <input type="submit" value="Login"/>
+  </td>
+ </tr>
+</table>
+</form>
+{% endblock %}
diff --git a/patchwork/templates/patchwork/logout.html b/patchwork/templates/patchwork/logout.html
new file mode 100644 (file)
index 0000000..f030aee
--- /dev/null
@@ -0,0 +1,8 @@
+{% extends "base.html" %}
+
+{% block title %}Logout{% endblock %}
+{% block heading %}Logout{% endblock %}
+
+{% block body %}
+<p>Logged out</p>
+{% endblock %}
diff --git a/patchwork/templates/patchwork/mail-form.html b/patchwork/templates/patchwork/mail-form.html
new file mode 100644 (file)
index 0000000..d71b2fb
--- /dev/null
@@ -0,0 +1,38 @@
+{% extends "base.html" %}
+
+{% block title %}mail settings{% endblock %}
+{% block heading %}mail settings{% endblock %}
+
+{% block body %}
+
+<p>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.</p>
+
+<form method="post">
+{% csrf_token %}
+<table class="form registerform">
+{% if form.errors %}
+ <tr>
+  <td colspan="2" class="error">
+   There was an error accessing your mail settings:
+  </td>
+ </tr>
+{% endif %}
+ <tr>
+  <th>{{ form.email.label_tag }}</th>
+  <td>
+   {{form.email}}
+   {{form.email.errors}}
+  </td>
+ </tr>
+ <tr>
+  <td colspan="2" class="submitrow">
+   <input type="submit" value="Access mail settings"/>
+  </td>
+ </tr>
+</table>
+</form>
+
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/mail-settings.html b/patchwork/templates/patchwork/mail-settings.html
new file mode 100644 (file)
index 0000000..440af08
--- /dev/null
@@ -0,0 +1,37 @@
+{% extends "base.html" %}
+
+{% block title %}mail settings{% endblock %}
+{% block heading %}mail settings{% endblock %}
+
+{% block body %}
+<p>Settings for <strong>{{email}}</strong>:</p>
+
+<table class="horizontal">
+ <tr>
+  <th>Opt-out list</th>
+{% if is_optout %}
+  <td>Patchwork <strong>may not</strong> send automated notifications to
+   this address.</td>
+  <td>
+   <form method="post" action="{% url 'patchwork.views.mail.optin' %}">
+    {% csrf_token %}
+    <input type="hidden" name="email" value="{{email}}"/>
+    <input type="submit" value="Opt-in"/>
+   </form>
+  </td>
+   
+{% else %}
+  <td>Patchwork <strong>may</strong> send automated notifications to
+   this address.</td>
+  <td>
+   <form method="post" action="{% url 'patchwork.views.mail.optout' %}">
+    {% csrf_token %}
+    <input type="hidden" name="email" value="{{email}}"/>
+    <input type="submit" value="Opt-out"/>
+   </form>
+  </td>
+{% endif %}
+ </tr>
+</table>
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/optin-request.html b/patchwork/templates/patchwork/optin-request.html
new file mode 100644 (file)
index 0000000..3dfb1bd
--- /dev/null
@@ -0,0 +1,50 @@
+{% extends "base.html" %}
+
+{% block title %}opt-in{% endblock %}
+{% block heading %}opt-in{% endblock %}
+
+{% block body %}
+{% if email_sent %}
+<p><strong>Opt-in confirmation email sent</strong></p>
+<p>An opt-in confirmation mail has been sent to
+<strong>{{confirmation.email}}</strong>, containing a link. Please click on
+that link to confirm your opt-in.</p>
+{% else %}
+{% if error %}
+<p class="error">{{error}}</p>
+{% endif %}
+
+{% if form %}
+<p>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.</p>
+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.</p>
+<form method="post" action="">
+{% csrf_token %}
+{{form.email.errors}}
+<div style="padding: 0.5em 1em 2em;">
+{{form.email.label_tag}}: {{form.email}}
+</div>
+<input type="submit" value="Send me an opt-in link">
+</form>
+{% endif %}
+
+{% if error and admins %}
+<p>If you are having trouble opting in, please email
+{% for admin in admins %}
+{% if admins|length > 1 and forloop.last %} or {% endif %}
+{{admin.0}} &lt;<a href="mailto:{{admin.1}}">{{admin.1}}</a
+>&gt;{% if admins|length > 2 and not forloop.last %}, {% endif %}
+{% endfor %}
+{% endif %}
+
+{% endif %}
+
+{% if user.is_authenticated %}
+<p>Return to your <a href="{% url 'patchwork.views.user.profile' %}">user
+profile</a>.</p>
+{% endif %}
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/optin-request.mail b/patchwork/templates/patchwork/optin-request.mail
new file mode 100644 (file)
index 0000000..d97c78b
--- /dev/null
@@ -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 (file)
index 0000000..01aaa0e
--- /dev/null
@@ -0,0 +1,19 @@
+{% extends "base.html" %}
+
+{% block title %}opt-in{% endblock %}
+{% block heading %}opt-in{% endblock %}
+
+{% block body %}
+
+<p><strong>Opt-in complete</strong>. You have sucessfully opted back in to
+automated email from this patchwork system, using the address
+<strong>{{email}}</strong>.</p>
+<p>If you later decide that you no longer want to receive automated mail from
+patchwork, just visit <a href="{% url 'patchwork.views.mail.settings' %}"
+>http://{{site.domain}}{% url 'patchwork.views.mail.settings' %}</a>, or
+visit the main patchwork page and navigate from there.</p>
+{% if user.is_authenticated %}
+<p>Return to your <a href="{% url 'patchwork.views.user.profile' %}">user
+profile</a>.</p>
+{% endif %}
+{% endblock %}
diff --git a/patchwork/templates/patchwork/optout-request.html b/patchwork/templates/patchwork/optout-request.html
new file mode 100644 (file)
index 0000000..092dbbb
--- /dev/null
@@ -0,0 +1,51 @@
+{% extends "base.html" %}
+
+{% block title %}opt-out{% endblock %}
+{% block heading %}opt-out{% endblock %}
+
+{% block body %}
+{% if email_sent %}
+<p><strong>Opt-out confirmation email sent</strong></p>
+<p>An opt-out confirmation mail has been sent to
+<strong>{{confirmation.email}}</strong>, containing a link. Please click on
+that link to confirm your opt-out.</p>
+{% else %}
+{% if error %}
+<p class="error">{{error}}</p>
+{% endif %}
+
+{% if form %}
+<p>This form allows you to opt-out of automated email from patchwork.</p>
+<p>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.</p>
+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.</p>
+<form method="post" action="">
+{% csrf_token %}
+{{form.email.errors}}
+<div style="padding: 0.5em 1em 2em;">
+{{form.email.label_tag}}: {{form.email}}
+</div>
+<input type="submit" value="Send me an opt-out link">
+</form>
+{% endif %}
+
+{% if error and admins %}
+<p>If you are having trouble opting out, please email
+{% for admin in admins %}
+{% if admins|length > 1 and forloop.last %} or {% endif %}
+{{admin.0}} &lt;<a href="mailto:{{admin.1}}">{{admin.1}}</a
+>&gt;{% if admins|length > 2 and not forloop.last %}, {% endif %}
+{% endfor %}
+{% endif %}
+
+{% endif %}
+
+{% if user.is_authenticated %}
+<p>Return to your <a href="{% url 'patchwork.views.user.profile' %}">user
+profile</a>.</p>
+{% endif %}
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/optout-request.mail b/patchwork/templates/patchwork/optout-request.mail
new file mode 100644 (file)
index 0000000..67203ca
--- /dev/null
@@ -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 (file)
index 0000000..b140bf4
--- /dev/null
@@ -0,0 +1,22 @@
+{% extends "base.html" %}
+
+{% block title %}opt-out{% endblock %}
+{% block heading %}opt-out{% endblock %}
+
+{% block body %}
+
+<p><strong>Opt-out complete</strong>. You have successfully opted-out of
+automated notifications from this patchwork system, from the address
+<strong>{{email}}</strong></p>
+<p>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.</p>
+<p>If you later decide to receive mail from patchwork, just visit
+<a href="{% url 'patchwork.views.mail.settings' %}"
+>http://{{site.domain}}{% url 'patchwork.views.mail.settings' %}</a>, or
+visit the main patchwork page and navigate from there.</p>
+{% if user.is_authenticated %}
+<p>Return to your <a href="{% url 'patchwork.views.user.profile' %}">user
+profile</a>.</p>
+{% endif %}
+{% endblock %}
diff --git a/patchwork/templates/patchwork/pagination.html b/patchwork/templates/patchwork/pagination.html
new file mode 100644 (file)
index 0000000..3e95126
--- /dev/null
@@ -0,0 +1,45 @@
+{% load listurl %}
+
+{% ifnotequal page.paginator.num_pages 1 %}
+<div class="paginator">
+{% if page.has_previous %}
+ <span class="prev">
+  <a href="{% listurl page=page.previous_page_number %}"
+     title="Previous Page">&laquo; Previous</a></span>
+{% else %}
+ <span class="prev-na">&laquo; Previous</span>
+{% endif %}
+{% if page.paginator.trailing_set %}
+ {% for p in page.paginator.trailing_set %}
+ <span class="page"><a href="{% listurl page=p %}" >{{ p }}</a></span>
+ {% endfor %}
+        ...
+{% endif %}
+{% for p in page.paginator.adjacent_set %}
+  {% ifequal p page.number %}
+    <span class="curr" title="Current Page">{{ p }}</span>
+  {% else %}
+    <span class="page"><a href="{% listurl page=p %}"
+     title="Page {{ p }}">{{ p }}</a></span>
+  {% endifequal %}
+{% endfor %}
+{% if page.paginator.leading_set %}
+        ...
+ {% for p in page.paginator.leading_set %}
+    <span class="page"><a href="{% listurl page=p %}">{{ p }}</a></span>
+ {% endfor %}
+{% endif %}
+{% if page.has_next %}
+ <span class="next">
+  <a href="{% listurl page=page.next_page_number %}"
+   title="Next Page">Next &raquo;</a>
+  </span>
+{% else %}
+ <span class="next-na">Next &raquo;</span>
+{% endif %}
+</div> 
+{% 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 (file)
index 0000000..c9d96d4
--- /dev/null
@@ -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 (file)
index 0000000..4246704
--- /dev/null
@@ -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 (file)
index 0000000..675f67f
--- /dev/null
@@ -0,0 +1,268 @@
+{% load person %}
+{% load listurl %}
+{% load static %}
+
+{% include "patchwork/pagination.html" %}
+
+
+<table class="patchlist">
+ <tr>
+  <td class="patchlistfilters">
+ {% include "patchwork/filters.html" %}
+  </td>
+ {% if order.editable %}
+  <td class="patchlistreorder">
+   <form method="post" id="reorderform">
+    {% csrf_token %}
+    <input type="hidden" name="form" value="reorderform"/>
+    <input type="hidden" name="order_start" value="0"/>
+    <span id="reorderhelp"></span>
+    <input id="reorder-cancel" type="button" value="Cancel"
+     onClick="order_cancel_click(this)"/>
+    <input id="reorder-change" type="button" value="Change order"
+     onClick="order_button_click(this)"/>
+    </form>
+  </td>
+ {% endif %}
+ </tr>
+</table>
+
+{% if page.paginator.long_page and user.is_authenticated %}
+<div class="floaty">
+ <a title="jump to form" href="#patchforms"><span
+  style="font-size: 120%">&#9662;</span></a>
+</div>
+{% endif %}
+
+<form method="post">
+{% csrf_token %}
+<input type="hidden" name="form" value="patchlistform"/>
+<input type="hidden" name="project" value="{{project.id}}"/>
+<table class="patchlist" id="patchlist">
+ <thead>
+  <tr>
+   {% if user.is_authenticated %}
+   <th>
+    <input type="checkbox" onChange="select_all(this)"/>
+   </th>
+   {% endif %}
+
+   <th>
+    {% ifequal order.name "name" %}
+     <a class="colactive"
+      href="{% listurl order=order.reversed_name %}"><img
+      {% if order.reversed %}
+      src="{% static "images/16-arrow-up.png" %}"
+      {% else %}
+      src="{% static "images/16-arrow-down.png" %}"
+      {%endif%}
+      width="16" height="16"
+     ></a> <a class="colactive"
+      href="{% listurl order=order.reversed_name %}">Patch</a>
+    {% else %}
+     {% if not order.editable %}
+     <a class="colinactive" href="{% listurl order="name" %}">Patch</a>
+     {% else %}
+     <span class="colinactive">Patch</span>
+     {% endif %}
+    {% endifequal %}
+   </th>
+
+   <th>
+    {% ifequal order.name "date" %}
+     <a class="colactive"
+      href="{% listurl order=order.reversed_name %}"><img
+      {% if order.reversed %}
+      src="{% static "images/16-arrow-up.png" %}"
+      {% else %}
+      src="{% static "images/16-arrow-down.png" %}"
+      {%endif%}
+      width="16" height="16"
+     ></a> <a class="colactive"
+      href="{% listurl order=order.reversed_name %}">Date</a>
+    {% else %}
+     {% if not order.editable %}
+     <a class="colinactive" href="{% listurl order="date" %}">Date</a>
+     {% else %}
+     <span class="colinactive">Date</span>
+     {% endif %}
+    {% endifequal %}
+   </th>
+
+   <th>
+    {% ifequal order.name "submitter" %}
+     <a class="colactive"
+      href="{% listurl order=order.reversed_name %}"><img
+      {% if order.reversed %}
+      src="{% static "images/16-arrow-up.png" %}"
+      {% else %}
+      src="{% static "images/16-arrow-down.png" %}"
+      {%endif%}
+      width="16" height="16"
+     ></a> <a class="colactive"
+      href="{% listurl order=order.reversed_name %}">Submitter</a>
+    {% else %}
+     {% if not order.editable %}
+     <a class="colinactive" href="{% listurl order="submitter" %}">Submitter</a>
+     {% else %}
+     <span class="colinactive">Submitter</span>
+     {% endif %}
+    {% endifequal %}
+   </th>
+
+   <th>
+    {% ifequal order.name "delegate" %}
+     <a class="colactive"
+      href="{% listurl order=order.reversed_name %}"><img
+      {% if order.reversed %}
+      src="{% static "images/16-arrow-up.png" %}"
+      {% else %}
+      src="{% static "images/16-arrow-down.png" %}"
+      {%endif%}
+      width="16" height="16"
+     ></a> <a class="colactive"
+      href="{% listurl order=order.reversed_name %}">Delegate</a>
+    {% else %}
+     {% if not order.editable %}
+     <a class="colinactive" href="{% listurl order="delegate" %}">Delegate</a>
+     {% else %}
+     <span class="colinactive">Delegate</span>
+     {% endif %}
+    {% endifequal %}
+   </th>
+
+   <th>
+    {% ifequal order.name "state" %}
+     <a class="colactive"
+      href="{% listurl order=order.reversed_name %}"><img
+      {% if order.reversed %}
+      src="{% static "images/16-arrow-up.png" %}"
+      {% else %}
+      src="{% static "images/16-arrow-down.png" %}"
+      {%endif%}
+      width="16" height="16"
+     ></a> <a class="colactive"
+      href="{% listurl order=order.reversed_name %}">State</a>
+    {% else %}
+     {% if not order.editable %}
+     <a class="colinactive" href="{% listurl order="state" %}">State</a>
+     {% else %}
+     <span class="colinactive">State</span>
+     {% endif %}
+    {% endifequal %}
+   </th>
+
+  </tr>
+ </thead>
+
+{% if page.paginator.count %}
+ <tbody>
+ {% for patch in page.object_list %}
+  <tr id="patch_row:{{patch.id}}" class="{% cycle 'odd' 'even' %}">
+    {% if user.is_authenticated %}
+    <td>
+    <input type="checkbox" name="patch_id:{{patch.id}}"/>
+    </td>
+    {% endif %}
+   <td><a href="{% url 'patchwork.views.patch.patch' patch_id=patch.id %}"
+     >{{ patch.name|default:"[no subject]" }}</a></td>
+   <td>{{ patch.date|date:"Y-m-d" }}</td>
+   <td>{{ patch.submitter|personify:project }}</td>
+   <td>{{ patch.delegate.username }}</td>
+   <td>{{ patch.state }}</td>
+  </tr>
+ {% endfor %}
+ </tbody>
+</table>
+
+{% include "patchwork/pagination.html" %}
+
+<div class="patchforms" id="patchforms" name="patchforms">
+
+{% if patchform %}
+ <div class="patchform patchform-properties">
+  <h3>Properties</h3>
+    <table class="form">
+     <tr>
+      <th>Change state:</th>
+      <td>
+       {{ patchform.state }}
+       {{ patchform.state.errors }}
+      </td>
+     </tr>
+     <tr>
+      <th>Delegate to:</td>
+      <td>
+       {{ patchform.delegate }}
+       {{ patchform.delegate.errors }}
+      </td>
+     </tr>
+     <tr>
+      <th>Archive:</td>
+      <td>
+       {{ patchform.archived }}
+       {{ patchform.archived.errors }}
+      </td>
+     </tr>
+     <tr>
+      <td></td>
+      <td>
+       <input type="submit" name="action" value="{{patchform.action}}"/>
+      </td>
+     </tr>
+    </table>
+ </div>
+
+{% endif %}
+
+{% if user.is_authenticated %}
+ <div class="patchform patchform-bundle">
+  <h3>Bundling</h3>
+   <table class="form">
+    <tr>
+     <td>Create bundle:</td>
+     <td>
+      <input type="text" name="bundle_name"/>
+      <input name="action" value="Create" type="submit"/>
+      </td>
+    </tr>
+  {% if bundles %}
+    <tr>
+     <td>Add to bundle:</td>
+     <td>
+       <select name="bundle_id"/>
+        {% for bundle in bundles %}
+         <option value="{{bundle.id}}">{{bundle.name}}</option>
+        {% endfor %}
+        </select>
+       <input name="action" value="Add" type="submit"/>
+     </td>
+    </tr>
+  {% endif %}
+  {% if bundle %}
+   <tr>
+     <td>Remove from bundle:</td>
+     <td>
+       <input type="hidden" name="removed_bundle_id" value="{{bundle.id}}"/>
+       <input name="action" value="Remove" type="submit"/>
+     </td>
+    </tr>
+  {% endif %}
+  </table>
+ </div>
+{% endif %}
+
+
+ <div style="clear: both;">
+ </div>
+</div>
+
+{% else %}
+ <tr>
+  <td colspan="5">No patches to display</td>
+ </tr>
+{% endif %}
+
+ </table>
+</form>
+
diff --git a/patchwork/templates/patchwork/patch.html b/patchwork/templates/patchwork/patch.html
new file mode 100644 (file)
index 0000000..f18ee3b
--- /dev/null
@@ -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 %}
+<script language="JavaScript" type="text/javascript">
+function toggle_headers(link_id, headers_id)
+{
+    var link = document.getElementById(link_id)
+    var headers = document.getElementById(headers_id)
+
+    var hidden = headers.style['display'] == 'none';
+
+    if (hidden) {
+        link.innerHTML = 'hide';
+        headers.style['display'] = 'block';
+    } else {
+        link.innerHTML = 'show';
+        headers.style['display'] = 'none';
+    }
+
+}
+</script>
+
+<table class="patchmeta">
+ <tr>
+  <th>Submitter</th>
+  <td>{{ patch.submitter|personify:project }}</td>
+ </tr>
+ <tr>
+  <th>Date</th>
+  <td>{{ patch.date }}</td>
+ </tr>
+ <tr>
+  <th>Message ID</th>
+  <td>{{ patch.msgid }}</td>
+ </tr>
+ <tr>
+  <th>Download</th>
+  <td>
+   <a href="{% url 'patchwork.views.patch.mbox' patch_id=patch.id %}"
+   >mbox</a>
+{% if patch.content %}|
+   <a href="{% url 'patchwork.views.patch.content' patch_id=patch.id %}"
+   >patch</a>
+{% endif %}
+   </td>
+ </tr>
+ <tr>
+  <th>Permalink</th>
+  <td><a href="{{ patch.get_absolute_url }}">{{ patch.get_absolute_url }}</a>
+ </tr>
+  <tr>
+   <th>State</th>
+   <td>{{ patch.state.name }}{% if patch.archived %}, archived{% endif %}</td>
+  </tr>
+{% if patch.commit_ref %}
+  <tr>
+   <th>Commit</th>
+   <td>{{ patch.commit_ref }}</td>
+  </tr>
+{% endif %}
+{% if patch.delegate %}
+  <tr>
+   <th>Delegated to:</th>
+   <td>{{ patch.delegate.profile.name }}</td>
+  </tr>
+{% endif %}
+ <tr>
+  <th>Headers</th>
+  <td><a id="togglepatchheaders"
+   href="javascript:toggle_headers('togglepatchheaders', 'patchheaders')"
+   >show</a>
+   <div id="patchheaders" class="patchheaders" style="display:none;">
+    <pre>{{patch.headers}}</pre>
+   </div>
+  </td>
+ </tr>
+</table>
+
+<div class="patchforms">
+
+{% if patchform %}
+ <div class="patchform patchform-properties">
+  <h3>Patch Properties</h3>
+   <form method="post">
+    {% csrf_token %}
+    <table class="form">
+     <tr>
+      <th>Change state:</th>
+      <td>
+       {{ patchform.state }}
+       {{ patchform.state.errors }}
+      </td>
+     </tr>
+     <tr>
+      <th>Delegate to:</th>
+      <td>
+       {{ patchform.delegate }}
+       {{ patchform.delegate.errors }}
+      </td>
+     </tr>
+     <tr>
+      <th>Archived:</th>
+      <td>
+       {{ patchform.archived }}
+       {{ patchform.archived.errors }}
+      </td>
+     </tr>
+     <tr>
+      <td></td>
+      <td>
+       <input type="submit" value="Update">
+      </td>
+     </tr>
+    </table>
+  </form>
+ </div>
+{% endif %}
+
+{% if createbundleform %}
+ <div class="patchform patchform-bundle">
+  <h3>Bundling</h3>
+   <table class="form">
+    <tr>
+     <td>Create bundle:</td>
+     <td>
+       {% if createbundleform.non_field_errors %}
+       <dd class="errors">{{createbundleform.non_field_errors}}</dd>
+       {% endif %}
+      <form method="post">
+       {% csrf_token %}
+       <input type="hidden" name="action" value="createbundle"/>
+       {% if createbundleform.name.errors %}
+       <dd class="errors">{{createbundleform.name.errors}}</dd>
+       {% endif %}
+        {{ createbundleform.name }}
+       <input value="Create" type="submit"/>
+      </form>
+      </td>
+    </tr>
+{% if bundles %}
+    <tr>
+     <td>Add to bundle:</td>
+     <td>
+      <form method="post">
+       {% csrf_token %}
+       <input type="hidden" name="action" value="addtobundle"/>
+       <select name="bundle_id"/>
+        {% for bundle in bundles %}
+         <option value="{{bundle.id}}">{{bundle.name}}</option>
+        {% endfor %}
+        </select>
+       <input value="Add" type="submit"/>
+      </form>
+     </td>
+    </tr>
+{% endif %}
+   </table>
+
+ </div>
+{% endif %}
+
+ <div style="clear: both;">
+ </div>
+</div>
+
+{% if patch.pull_url %}
+<h2>Pull-request</h2>
+<a class="patch-pull-url" href="{{patch.pull_url}}"
+ >{{ patch.pull_url }}</a>
+{% endif %}
+
+<h2>Comments</h2>
+{% for comment in patch.comments %}
+<div class="comment">
+<div class="meta">{{ comment.submitter|personify:project }} - {{comment.date}}</div>
+<pre class="content">
+{{ comment|commentsyntax }}
+</pre>
+</div>
+{% endfor %}
+
+{% if patch.content %}
+<h2>Patch</h2>
+<div class="patch">
+<pre class="content">
+{{ patch|patchsyntax }}
+</pre>
+</div>
+{% endif %}
+
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/profile.html b/patchwork/templates/patchwork/profile.html
new file mode 100644 (file)
index 0000000..116d6d6
--- /dev/null
@@ -0,0 +1,144 @@
+{% extends "base.html" %}
+
+{% block title %}User Profile: {{ user.username }}{% endblock %}
+{% block heading %}User Profile: {{ user.username }}{% endblock %}
+
+
+{% block body %}
+
+<p>
+{% if user.profile.maintainer_projects.count %}
+Maintainer of
+{% for project in user.profile.maintainer_projects.all %}
+<a href="{% url 'patchwork.views.patch.list' project_id=project.linkname %}"
+>{{ project.linkname }}</a>{% if not forloop.last %},{% endif %}{% endfor %}.
+{% endif %}
+
+{% if user.profile.contributor_projects.count %}
+Contributor to
+{% for project in user.profile.contributor_projects.all %}
+<a href="{% url 'patchwork.views.patch.list' project_id=project.linkname %}"
+>{{ project.linkname }}</a>{% if not forloop.last %},{% endif %}{% endfor %}.
+{% endif %}
+</p>
+
+<div class="leftcol">
+<div class="box">
+ <h2>Todo</h2>
+{% if user.profile.n_todo_patches %}
+ <p>Your <a href="{% url 'patchwork.views.user.todo_lists' %}">todo
+  list</a> contains {{ user.profile.n_todo_patches }}
+  patch{{ user.profile.n_todo_patches|pluralize:"es" }}.</p>
+{% else %}
+ <p>Your todo list contains patches that have been delegated to you. You
+  have no items in your todo list at present.</p>
+{% endif %}
+</div>
+
+<div class="box">
+<h2>Linked email addresses</h2>
+<p>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.</p>
+<p>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.</p>
+<p>Adding a new email address will send a confirmation email to that
+address.</p>
+<table class="vertical">
+ <tr>
+  <th>email</th>
+  <th>action</th>
+  <th>notify?</th>
+ </tr>
+{% for email in linked_emails %}
+ <tr>
+  <td>{{ email.email }}</td>
+  <td>
+  {% ifnotequal user.email email.email %}
+   <form action="{% url 'patchwork.views.user.unlink' person_id=email.id %}"
+    method="post">
+    {% csrf_token %}
+    <input type="submit" value="Unlink"/>
+   </form>
+    {% endifnotequal %}
+  </td>
+  <td>
+   {% if email.is_optout %}
+   <form method="post" action="{% url 'patchwork.views.mail.optin' %}">
+    No,
+     {% csrf_token %}
+     <input type="hidden" name="email" value="{{email.email}}"/>
+     <input type="submit" value="Opt-in"/>
+    </form>
+   {% else %}
+    <form method="post" action="{% url 'patchwork.views.mail.optout' %}">
+    Yes,
+     {% csrf_token %}
+     <input type="hidden" name="email" value="{{email.email}}"/>
+     <input type="submit" value="Opt-out"/>
+    </form>
+   {% endif %}
+  </td>
+ </tr>
+{% endfor %}
+ <tr>
+  <td colspan="3">
+   <form action="{% url 'patchwork.views.user.link' %}" method="post">
+    {% csrf_token %}
+    {{ linkform.email }}
+    <input type="submit" value="Add"/>
+   </form>
+  </td>
+ </tr>
+</table>
+</div>
+</div>
+
+<div class="rightcol">
+
+<div class="box">
+<h2>Bundles</h2>
+
+{% if bundles %}
+<p>You have the following bundle{{ bundle|length|pluralize }}:</p>
+<ul>
+{% for bundle in bundles %}
+ <li><a href="{{ bundle.get_absolute_url }}">{{ bundle.name }}</a></li>
+{% endfor %}
+</ul>
+<p>Visit the <a href="{%url 'patchwork.views.bundle.bundles' %}">bundles
+ page</a> to manage your bundles.</p>
+{% else %}
+<p>You have no bundles.</p>
+{% endif %}
+</div>
+
+
+<div class="box">
+<h2>Settings</h2>
+
+<form method="post">
+ {% csrf_token %}
+ <table class="form">
+{{ profileform }}
+  <tr>
+   <td/>
+   <td>
+    <input type="submit" value="Apply"/>
+   </td>
+  </tr>
+ </table>
+</form>
+</div>
+
+<div class="box">
+<h2>Authentication</h2>
+<a href="{% url 'django.contrib.auth.views.password_change' %}">Change password</a>
+</div>
+
+</div>
+
+<p style="clear: both"></p>
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/project.html b/patchwork/templates/patchwork/project.html
new file mode 100644 (file)
index 0000000..be8cadc
--- /dev/null
@@ -0,0 +1,58 @@
+{% extends "base.html" %}
+
+{% block title %}{{ project.name }}{% endblock %}
+{% block heading %}{{ project.name }}{% endblock %}
+
+{% block body %}
+
+<table class="horizontal">
+ <tr>
+  <th>Name</th>
+  <td>{{project.name}}
+ </tr>
+ <tr>
+  <th>List address</th>
+  <td>{{project.listemail}}</td>
+ </tr>
+ <tr>
+  <th>Maintainer{{maintainers|length|pluralize}}</th>
+  <td>
+   {% for maintainer in maintainers %}
+    {{ maintainer.profile.name }}
+     &lt;<a href="mailto:{{maintainer.email}}">{{maintainer.email}}</a>&gt;
+     <br />
+   {% endfor %}
+  </td>
+ </tr>
+ <tr>
+  <th>Patch count</th>
+  <td>{{n_patches}} (+ {{n_archived_patches}} archived)</td>
+ </tr>
+{% if project.web_url %}
+ <tr>
+  <th>Website</th>
+  <td><a href="{{project.web_url}}">{{project.web_url}}</a></td>
+ </tr>
+{% endif %}
+{% if project.webscm_url %}
+ <tr>
+  <th>Source Code Web Interface</th>
+  <td><a href="{{project.webscm_url}}">{{project.webscm_url}}</a></td>
+ </tr>
+{% endif %}
+{% if project.scm_url %}
+ <tr>
+  <th>Source Code Manager URL</th>
+  <td><a href="{{project.scm_url}}">{{project.scm_url}}</a></td>
+ </tr>
+{% endif %}
+</table>
+
+{% if settings.ENABLE_XMLRPC %}
+<p>Sample <a href="{% url 'patchwork.views.help' "pwclient/" %}">patchwork
+client</a> configuration for this project: <a
+href="{% url 'patchwork.views.pwclientrc' project.linkname %}"
+>.pwclientrc</a>.</p>
+{% endif %}
+  
+{% endblock %}
diff --git a/patchwork/templates/patchwork/projects.html b/patchwork/templates/patchwork/projects.html
new file mode 100644 (file)
index 0000000..8c727ad
--- /dev/null
@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+
+{% block title %}Project List{% endblock %}
+{% block heading %}Project List{% endblock %}
+
+{% block body %}
+
+{% if projects %}
+ <div class="project-set">
+ {% for p in projects %}
+ <div class="project">
+  <h2 class="project-title">
+   <a href="{% url 'patchwork.views.patch.list' project_id=p.linkname %}"
+    >{{p.linkname}}</a>
+  </h2>
+  <div class="project-name">{{p.name}}</div>
+{% if p.web_url %}
+  <div class="project-url"><a href="{{p.web_url}}">website</a></div>
+{% endif %}
+  </div>
+ {% endfor %}
+ </div>
+{% else %}
+ <p>Patchwork doesn't have any projects to display!</p>
+{% endif %}
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/pwclient b/patchwork/templates/patchwork/pwclient
new file mode 120000 (symlink)
index 0000000..5ce255f
--- /dev/null
@@ -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 (file)
index 0000000..d331003
--- /dev/null
@@ -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: <add your patchwork password here>
+{% endif %}
diff --git a/patchwork/templates/patchwork/register.mail b/patchwork/templates/patchwork/register.mail
new file mode 100644 (file)
index 0000000..9079203
--- /dev/null
@@ -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 (file)
index 0000000..6111401
--- /dev/null
@@ -0,0 +1,13 @@
+{% extends "base.html" %}
+
+{% block title %}Registration{% endblock %}
+{% block heading %}Registration{% endblock %}
+
+{% block body %}
+<p>Registraton confirmed!</p>
+
+<p>Your patchwork registration is complete. Head over to your <a
+ href="{% url 'patchwork.views.user.profile' %}">profile</a> to start using
+patchwork's extra features.</p>
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/registration_form.html b/patchwork/templates/patchwork/registration_form.html
new file mode 100644 (file)
index 0000000..3a314b8
--- /dev/null
@@ -0,0 +1,121 @@
+{% extends "base.html" %}
+
+{% block title %}Registration{% endblock %}
+{% block heading %}Registration{% endblock %}
+
+
+{% block body %}
+
+{% if confirmation and not error %}
+ <p>Registration successful!</p>
+ <p>A confirmation email has been sent to {{ confirmation.email }}. You'll
+ need to visit the link provided in that email to confirm your
+ registration.</p>
+</p>
+{% else %}
+<p>By creating a patchwork account, you can:<p>
+<ul>
+ <li>create "bundles" of patches</li>
+ <li>update the state of your own patches</li>
+</ul>
+<form method="post">
+{% csrf_token %}
+<table class="form registerform">
+ <tr>
+  <th colspan="2" class="headerrow">register</th>
+ </tr>
+ {% if error %}
+  <tr>
+   <td colspan="2">{{ error }}</td>
+  </tr>
+ {% endif %}
+
+  <tr>
+   <td>{{ form.first_name.label_tag }}</td>
+   <td>
+{% if form.first_name.errors %}
+    {{ form.first_name.errors }}
+{% endif %}
+    {{ form.first_name }}
+{% if form.first_name.help_text %}
+    <div class="help_text"/>{{ form.first_name.help_text }}</div>
+{% endif %}
+   </td>
+  </tr>
+   
+  <tr>
+   <td>{{ form.last_name.label_tag }}</td>
+   <td>
+{% if form.last_name.errors %}
+    {{ form.last_name.errors }}
+{% endif %}
+    {{ form.last_name }}
+{% if form.last_name.help_text %}
+    <div class="help_text"/>{{ form.last_name.help_text }}</div>
+{% endif %}
+   </td>
+  </tr>
+
+  <tr>
+   <td></td>
+   <td class="form-help">
+    Your name is used to identify you on the site
+   </td>
+  </tr>
+   
+  <tr>
+   <td>{{ form.email.label_tag }}</td>
+   <td>
+{% if form.email.errors %}
+    {{ form.email.errors }}
+{% endif %}
+    {{ form.email }}
+{% if form.email.help_text %}
+    <div class="help_text"/>{{ form.email.help_text }}</div>
+{% endif %}
+   </td>
+  </tr>
+   
+  <tr>
+   <td></td>
+   <td class="form-help">
+    Patchwork will send a confirmation email to this address
+   </td>
+  </tr>
+
+  <tr>
+   <td>{{ form.username.label_tag }}</td>
+   <td>
+{% if form.username.errors %}
+    {{ form.username.errors }}
+{% endif %}
+    {{ form.username }}
+{% if form.username.help_text %}
+    <div class="help_text"/>{{ form.username.help_text }}</div>
+{% endif %}
+   </td>
+  </tr>
+   
+  <tr>
+   <td>{{ form.password.label_tag }}</td>
+   <td>
+{% if form.password.errors %}
+    {{ form.password.errors }}
+{% endif %}
+    {{ form.password }}
+{% if form.password.help_text %}
+    <div class="help_text"/>{{ form.password.help_text }}</div>
+{% endif %}
+   </td>
+  </tr>
+
+   <tr>
+  <td colspan="2" class="submitrow">
+   <input type="submit" value="Register"/>
+  </td>
+ </tr>
+</table>
+</form>
+{% endif %}
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/todo-list.html b/patchwork/templates/patchwork/todo-list.html
new file mode 100644 (file)
index 0000000..b301901
--- /dev/null
@@ -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 %}
+
+<p>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.
+</p>
+
+{% 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 (file)
index 0000000..e268160
--- /dev/null
@@ -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 %}
+<p>You have multiple todo lists. Each todo list contains patches for a single
+ project.</p>
+<table class="vertical">
+ <tr>
+  <th>project</th>
+  <th>patches</th>
+ </tr>
+{% for todo_list in todo_lists %}
+ <tr>
+  <td><a
+   href="{% url 'patchwork.views.user.todo_list' project_id=todo_list.project.linkname %}"
+    >{{ todo_list.project.name }}</a></td>
+  <td class="numberformat">{{ todo_list.n_patches }}</td>
+ </tr>
+{% endfor %}
+</table>
+
+{% 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 (file)
index 0000000..449bfeb
--- /dev/null
@@ -0,0 +1,19 @@
+{% extends "base.html" %}
+
+{% block title %}{{ user.username }}{% endblock %}
+{% block heading %}link accounts for {{ user.username }}{% endblock %}
+
+
+{% block body %}
+
+{% if errors %}
+<p>{{ errors }}</p>
+{% else %}
+ <p>You have sucessfully linked the email address {{ person.email }} to
+  your patchwork account</p>
+
+{% endif %}
+<p>Back to <a href="{% url 'patchwork.views.user.profile' %}">your
+ profile</a>.</p>
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/user-link.html b/patchwork/templates/patchwork/user-link.html
new file mode 100644 (file)
index 0000000..e436c3a
--- /dev/null
@@ -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 %}
+<p>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.</p>
+
+{% else %}
+
+   {% if form.errors %}
+   <p>There was an error submitting your link request.</p>
+    {{ form.non_field_errors }}
+   {% endif %}
+   {% if error %}
+    <ul class="errorlist"><li>{{error}}</li></ul>
+   {% endif %}
+
+   <form action="{% url 'patchwork.views.user.link' %}" method="post">
+    {% csrf_token %}
+    {{linkform.email.errors}}
+    Link an email address: {{ linkform.email }}
+   </form>
+
+{% endif %}
+
+{% endblock %}
diff --git a/patchwork/templates/patchwork/user-link.mail b/patchwork/templates/patchwork/user-link.mail
new file mode 100644 (file)
index 0000000..8db6726
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/patchwork/templatetags/filter.py b/patchwork/templatetags/filter.py
new file mode 100644 (file)
index 0000000..7a5d9df
--- /dev/null
@@ -0,0 +1,36 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 '<a href="javascript:personpopup(\'%s\')">%s</a>' % (escape(person.email), linktext)
+
diff --git a/patchwork/templatetags/listurl.py b/patchwork/templatetags/listurl.py
new file mode 100644 (file)
index 0000000..5fe03e4
--- /dev/null
@@ -0,0 +1,136 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 += '<input type="hidden" name="%s" value="%s"\>' % \
+                   (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 (file)
index 0000000..e392f03
--- /dev/null
@@ -0,0 +1,66 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..bec0cab
--- /dev/null
@@ -0,0 +1,65 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..c337c74
--- /dev/null
@@ -0,0 +1,43 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 = '<a href="%s?%s=%s">%s</a>' % \
+                (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 (file)
index 0000000..98bc1ca
--- /dev/null
@@ -0,0 +1,76 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..abdbb4d
--- /dev/null
@@ -0,0 +1,75 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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*&gt;.*$', 'quote'),
+        ])
+
+_span = '<span class="%s">%s</span>'
+
+@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 (file)
index 0000000..85200bd
--- /dev/null
@@ -0,0 +1,34 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..0dbedbe
--- /dev/null
@@ -0,0 +1,348 @@
+From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010
+Return-Path: <linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org>
+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 <jk@ozlabs.org>; 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 <linuxppc-dev@ozlabs.org>; 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 <benh@kernel.crashing.org>
+To: Linus Torvalds <torvalds@linux-foundation.org>
+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 <linuxppc-dev@ozlabs.org>,
+ Andrew Morton <akpm@linux-foundation.org>,
+ Linux Kernel list <linux-kernel@vger.kernel.org>
+X-BeenThere: linuxppc-dev@lists.ozlabs.org
+X-Mailman-Version: 2.1.13
+Precedence: list
+List-Id: Linux on PowerPC Developers Mail List <cbe-oss-dev.ozlabs.org>
+List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
+List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
+List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
+List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
+List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=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_<level> uses of KERN_<level>
+
+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 (file)
index 0000000..d3ccee1
--- /dev/null
@@ -0,0 +1,349 @@
+From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010
+Return-Path: <linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org>
+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 <jk@ozlabs.org>; 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 <linuxppc-dev@ozlabs.org>; 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 <benh@kernel.crashing.org>
+To: Linus Torvalds <torvalds@linux-foundation.org>
+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 <linuxppc-dev@ozlabs.org>,
+ Andrew Morton <akpm@linux-foundation.org>,
+ Linux Kernel list <linux-kernel@vger.kernel.org>
+X-BeenThere: linuxppc-dev@lists.ozlabs.org
+X-Mailman-Version: 2.1.13
+Precedence: list
+List-Id: Linux on PowerPC Developers Mail List <cbe-oss-dev.ozlabs.org>
+List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
+List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
+List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
+List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
+List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=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_<level> uses of KERN_<level>
+
+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 (file)
index 0000000..b4d578c
--- /dev/null
@@ -0,0 +1,141 @@
+From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010
+Return-Path: <linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org>
+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 <jk@ozlabs.org>; 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 <linuxppc-dev@ozlabs.org>; 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 <benh@kernel.crashing.org>
+To: Linus Torvalds <torvalds@linux-foundation.org>
+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 <linuxppc-dev@ozlabs.org>,
+ Andrew Morton <akpm@linux-foundation.org>,
+ Linux Kernel list <linux-kernel@vger.kernel.org>
+X-BeenThere: linuxppc-dev@lists.ozlabs.org
+X-Mailman-Version: 2.1.13
+Precedence: list
+List-Id: Linux on PowerPC Developers Mail List <cbe-oss-dev.ozlabs.org>
+List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
+     <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
+List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
+List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
+List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
+List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
+     <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=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 (file)
index 0000000..da96465
--- /dev/null
@@ -0,0 +1,348 @@
+From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010
+Return-Path: <linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org>
+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 <jk@ozlabs.org>; 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 <linuxppc-dev@ozlabs.org>; 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 <benh@kernel.crashing.org>
+To: Linus Torvalds <torvalds@linux-foundation.org>
+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 <linuxppc-dev@ozlabs.org>,
+ Andrew Morton <akpm@linux-foundation.org>,
+ Linux Kernel list <linux-kernel@vger.kernel.org>
+X-BeenThere: linuxppc-dev@lists.ozlabs.org
+X-Mailman-Version: 2.1.13
+Precedence: list
+List-Id: Linux on PowerPC Developers Mail List <cbe-oss-dev.ozlabs.org>
+List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
+List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
+List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
+List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
+List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=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_<level> uses of KERN_<level>
+
+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 (file)
index 0000000..7f4c93e
--- /dev/null
@@ -0,0 +1,348 @@
+From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010
+Return-Path: <linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org>
+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 <jk@ozlabs.org>; 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 <linuxppc-dev@ozlabs.org>; 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 <benh@kernel.crashing.org>
+To: Linus Torvalds <torvalds@linux-foundation.org>
+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 <linuxppc-dev@ozlabs.org>,
+ Andrew Morton <akpm@linux-foundation.org>,
+ Linux Kernel list <linux-kernel@vger.kernel.org>
+X-BeenThere: linuxppc-dev@lists.ozlabs.org
+X-Mailman-Version: 2.1.13
+Precedence: list
+List-Id: Linux on PowerPC Developers Mail List <cbe-oss-dev.ozlabs.org>
+List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
+List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
+List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
+List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
+List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=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_<level> uses of KERN_<level>
+
+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 (file)
index 0000000..e4f9007
--- /dev/null
@@ -0,0 +1,348 @@
+From benh@kernel.crashing.org Fri Oct 22 11:51:02 2010
+Return-Path: <linuxppc-dev-bounces+jk=ozlabs.org@lists.ozlabs.org>
+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 <jk@ozlabs.org>; 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 <linuxppc-dev@ozlabs.org>; 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 <benh@kernel.crashing.org>
+To: Linus Torvalds <torvalds@linux-foundation.org>
+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 <linuxppc-dev@ozlabs.org>,
+ Andrew Morton <akpm@linux-foundation.org>,
+ Linux Kernel list <linux-kernel@vger.kernel.org>
+X-BeenThere: linuxppc-dev@lists.ozlabs.org
+X-Mailman-Version: 2.1.13
+Precedence: list
+List-Id: Linux on PowerPC Developers Mail List <cbe-oss-dev.ozlabs.org>
+List-Unsubscribe: <https://lists.ozlabs.org/options/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=unsubscribe>
+List-Archive: <http://lists.ozlabs.org/pipermail/linuxppc-dev>
+List-Post: <mailto:linuxppc-dev@lists.ozlabs.org>
+List-Help: <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=help>
+List-Subscribe: <https://lists.ozlabs.org/listinfo/linuxppc-dev>,
+       <mailto:linuxppc-dev-request@lists.ozlabs.org?subject=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_<level> uses of KERN_<level>
+
+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 (file)
index 0000000..99735fa
--- /dev/null
@@ -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
+        <rfc822;linux-mips@linux-mips.org>); 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 <B4edd66f80000>; 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 <david.daney@cavium.com>
+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 <binutils@sourceware.org>
+CC:     linux-mips <linux-mips@linux-mips.org>,
+        Manuel Lauss <manuel.lauss@googlemail.com>,
+        Debian MIPS <debian-mips@lists.debian.org>
+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: <David.Daney@caviumnetworks.com>
+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  <david.daney@cavium.com>
+
+       * 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 (file)
index 0000000..8277049
--- /dev/null
@@ -0,0 +1,24 @@
+From: "Yann E. MORIN" <yann.morin.1998@free.fr>
+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" <yann.morin.1998@free.fr>
+---
+ ...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 (file)
index 0000000..761cfc1
--- /dev/null
@@ -0,0 +1,32 @@
+From: "Yann E. MORIN" <yann.morin.1998@free.fr>
+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" <yann.morin.1998@free.fr>
+---
+ ...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 (file)
index 0000000..10b369d
--- /dev/null
@@ -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: <libc-alpha.sourceware.org>
+Sender: libc-alpha-owner@sourceware.org
+Date: Wed, 4 Jun 2014 17:50:46 +0000
+From: "Joseph S. Myers" <joseph@codesourcery.com>
+To: <libc-alpha@sourceware.org>
+Subject: Fix pow overflow in non-default rounding modes (bug 16315)
+Message-ID: <Pine.LNX.4.64.1406041749420.3719@digraph.polyomino.org.uk>
+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
+<https://sourceware.org/ml/libc-alpha/2014-06/msg00076.html> 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  <joseph@codesourcery.com>
+
+=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 <math.h>.
+=09(__ieee754_pow): Recompute overflowing and underflowing results in
+=09original rounding mode.
+=09* sysdeps/x86/fpu/powl_helper.c: Include <stdbool.h>.
+=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 <math.h>.
+=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 <init-arch.h>
++# include <math.h>
+ # include <math_private.h>
+=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 (file)
index 0000000..3ed0597
--- /dev/null
@@ -0,0 +1,45 @@
+Subject: [PATCH v3 5/5] selftests, powerpc: Add test for VPHN
+From: Greg Kurz <gkurz@linux.vnet.ibm.com>
+To: Michael Ellerman <mpe@ellerman.id.au>
+Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>,
+ 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 (file)
index 0000000..c6cb9f1
--- /dev/null
@@ -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 (file)
index 0000000..71a2f24
--- /dev/null
@@ -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 (file)
index 0000000..38f3a2c
--- /dev/null
@@ -0,0 +1,646 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2009 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 &#39;%s&#39; 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 &#39;%s&#39; already in bundle' \
+                            % patch.name, count = 1, status_code = 200)
+        self.assertContains(response, 'Patch &#39;%s&#39; 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 &quot;%s&quot;' % 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 &quot;%s&quot;' % 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/<id>
+
+    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 (file)
index 0000000..fad5125
--- /dev/null
@@ -0,0 +1,67 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..b9032bb
--- /dev/null
@@ -0,0 +1,87 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..844ed4b
--- /dev/null
@@ -0,0 +1,121 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2014 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..2c464e5
--- /dev/null
@@ -0,0 +1,45 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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&amp;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 (file)
index 0000000..a795a5f
--- /dev/null
@@ -0,0 +1,116 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2012 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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('<tr id="patch_row:(\d+)" ')
+        ids = [ int(m.group(1)) for m in id_re.finditer(response.content) ]
+        return ids
+
+    def _test_sequence(self, response, test_fn):
+        ids = self._extract_patch_ids(response)
+        self.assertTrue(bool(ids))
+        patches = [ Patch.objects.get(id = i) for i in ids ]
+        pairs = zip(patches, patches[1:])
+        [ test_fn(p1, p2) for (p1, p2) in pairs ]
+
+    def testDateOrder(self):
+        url = reverse('patchwork.views.patch.list',
+                kwargs={'project_id': defaults.project.linkname})
+        response = self.client.get(url + '?order=date')
+        def test_fn(p1, p2):
+            self.assertLessEqual(p1.date, p2.date)
+        self._test_sequence(response, test_fn)
+
+    def testDateReverseOrder(self):
+        url = reverse('patchwork.views.patch.list',
+                kwargs={'project_id': defaults.project.linkname})
+        response = self.client.get(url + '?order=-date')
+        def test_fn(p1, p2):
+            self.assertGreaterEqual(p1.date, p2.date)
+        self._test_sequence(response, test_fn)
+
+    def testSubmitterOrder(self):
+        url = reverse('patchwork.views.patch.list',
+                kwargs={'project_id': defaults.project.linkname})
+        response = self.client.get(url + '?order=submitter')
+        def test_fn(p1, p2):
+            self.assertLessEqual(p1.submitter.name.lower(),
+                                 p2.submitter.name.lower())
+        self._test_sequence(response, test_fn)
+
+    def testSubmitterReverseOrder(self):
+        url = reverse('patchwork.views.patch.list',
+                kwargs={'project_id': defaults.project.linkname})
+        response = self.client.get(url + '?order=-submitter')
+        def test_fn(p1, p2):
+            self.assertGreaterEqual(p1.submitter.name.lower(),
+                                    p2.submitter.name.lower())
+        self._test_sequence(response, test_fn)
+
diff --git a/patchwork/tests/test_mail_settings.py b/patchwork/tests/test_mail_settings.py
new file mode 100644 (file)
index 0000000..a193c97
--- /dev/null
@@ -0,0 +1,299 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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('<strong>may</strong>' 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('<strong>may not</strong>' 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 = ('<form\s+[^>]*action="%(url)s"[^>]*>'
+                        '.*?<input\s+[^>]*value="%(email)s"[^>]*>.*?'
+                        '</form>')
+    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 (file)
index 0000000..0e57f42
--- /dev/null
@@ -0,0 +1,209 @@
+# vim: set fileencoding=utf-8 :
+#
+# Patchwork - automated patch tracking system
+# Copyright (C) 2009 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 <cc@example.com>'
+        self.to_header = 'To: To Person <to@example.com>'
+        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
+        <user@doamin.tld> 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 (file)
index 0000000..ed35140
--- /dev/null
@@ -0,0 +1,255 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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('&quot;' 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 (file)
index 0000000..119936a
--- /dev/null
@@ -0,0 +1,554 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 <user@example.com>'
+
+    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?= <user@example.com>'
+
+class SenderUTF8QPSplitEncodingTest(SenderEncodingTest):
+    sender_name = u'\xe9xample user'
+    from_header = '=?utf-8?q?=C3=A9xample?= user <user@example.com>'
+
+class SenderUTF8B64EncodingTest(SenderUTF8QPEncodingTest):
+    from_header = '=?utf-8?B?w6l4YW1wbGUgdXNlcg==?= <user@example.com>'
+
+class SubjectEncodingTest(PatchTest):
+    sender = 'example user <user@example.com>'
+    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 <existing@example.com>'
+    non_existing_sender = 'Non-existing Sender <nonexisting@example.com>'
+
+    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 (file)
index 0000000..d948096
--- /dev/null
@@ -0,0 +1,55 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2013 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..845b60b
--- /dev/null
@@ -0,0 +1,210 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..177ee78
--- /dev/null
@@ -0,0 +1,118 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..0faa970
--- /dev/null
@@ -0,0 +1,195 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..2b459b2
--- /dev/null
@@ -0,0 +1,55 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2014 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..782ed36
--- /dev/null
@@ -0,0 +1,138 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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@example.com>'
+    patch_author_person = Person(name = 'Patch Author',
+        email = 'patch-author@example.com')
+
+    comment_author = 'Comment Author <comment-author@example.com>'
+
+    sender = 'Test Author <test-author@example.com>'
+
+    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 (file)
index 0000000..b28eb90
--- /dev/null
@@ -0,0 +1,103 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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<project_id>[^/]+)/list/$', 'patchwork.views.patch.list'),
+    (r'^project/(?P<project_id>[^/]+)/$', 'patchwork.views.project.project'),
+
+    # patch views
+    (r'^patch/(?P<patch_id>\d+)/$', 'patchwork.views.patch.patch'),
+    (r'^patch/(?P<patch_id>\d+)/raw/$', 'patchwork.views.patch.content'),
+    (r'^patch/(?P<patch_id>\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<project_id>[^/]+)/$', 'patchwork.views.user.todo_list'),
+
+    (r'^user/bundles/$',
+        'patchwork.views.bundle.bundles'),
+
+    (r'^user/link/$', 'patchwork.views.user.link'),
+    (r'^user/unlink/(?P<person_id>[^/]+)/$', '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<username>[^/]*)/(?P<bundlename>[^/]*)/$',
+                                'patchwork.views.bundle.bundle'),
+    (r'^bundle/(?P<username>[^/]*)/(?P<bundlename>[^/]*)/mbox/$',
+                                'patchwork.views.bundle.mbox'),
+
+    (r'^confirm/(?P<key>[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<path>.*)$', 'patchwork.views.help'),
+)
+
+if settings.ENABLE_XMLRPC:
+    urlpatterns += patterns('',
+        (r'xmlrpc/$', 'patchwork.views.xmlrpc.xmlrpc'),
+        (r'^pwclient/$', 'patchwork.views.pwclient'),
+        (r'^project/(?P<project_id>[^/]+)/pwclientrc/$',
+             'patchwork.views.pwclientrc'),
+    )
+
+# redirect from old urls
+if settings.COMPAT_REDIR:
+    urlpatterns += patterns('',
+        (r'^user/bundle/(?P<bundle_id>[^/]+)/$',
+            'patchwork.views.bundle.bundle_redir'),
+        (r'^user/bundle/(?P<bundle_id>[^/]+)/mbox/$',
+            'patchwork.views.bundle.mbox_redir'),
+    )
+
diff --git a/patchwork/utils.py b/patchwork/utils.py
new file mode 100644 (file)
index 0000000..9ed9e41
--- /dev/null
@@ -0,0 +1,248 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..dfca56d
--- /dev/null
@@ -0,0 +1,220 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..6d7dd13
--- /dev/null
@@ -0,0 +1,122 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..3fb47e2
--- /dev/null
@@ -0,0 +1,221 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..aebba34
--- /dev/null
@@ -0,0 +1,119 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2010 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..62ff853
--- /dev/null
@@ -0,0 +1,107 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..114dbe0
--- /dev/null
@@ -0,0 +1,38 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2009 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..126ecc9
--- /dev/null
@@ -0,0 +1,216 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index 0000000..84ed408
--- /dev/null
@@ -0,0 +1,450 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2008 Jeremy Kerr <jk@ozlabs.org>
+#
+# 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 (file)
index caf514a..0000000
+++ /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 (file)
index c409f38..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Patchwork account confirmation
diff --git a/templates/patchwork/bundle.html b/templates/patchwork/bundle.html
deleted file mode 100644 (file)
index 4a96b6b..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-{% extends "base.html" %}
-
-{% load person %}
-{% load static %}
-
-{% block headers %}
-  <script type="text/javascript" src="{% static "js/jquery-1.10.1.min.js" %}"></script>
-  <script type="text/javascript" src="{% static "js/jquery.tablednd.js" %}"></script>
-  <script type="text/javascript" src="{% static "js/bundle.js" %}"></script>
-{% endblock %}
-{% block title %}{{project.name}}{% endblock %}
-{% block heading %}bundle: {{bundle.owner.username}} /
-{{bundle.name}}{% endblock %}
-
-{% block body %}
-
-<p>This bundle contains patches for the {{ bundle.project.linkname }}
-project.</p>
-
-<p><a href="{% url 'patchwork.views.bundle.mbox' username=bundle.owner.username bundlename=bundle.name %}">Download bundle as mbox</a></p>
-
-{% if bundleform %}
-<form method="post">
- {% csrf_token %}
- <input type="hidden" name="form" value="bundle"/>
-<table class="form">
-
- <tr>
-  <th colspan="2" class="headerrow">Bundle settings</th>
- </tr>
-
-{{ bundleform }}
- <tr>
-  <td colspan="2" class="submitrow">
-   <input type="submit" name="action" value="Update"/>
-   <input type="submit" name="action" value="Delete"/>
-  </td>
- </tr>
-</table>
-</form>
-
-<div style="clear: both; padding: 1em;"></div>
-{% endif %}
-
-{% include "patchwork/patch-list.html" %}
-
-{% endblock %}
diff --git a/templates/patchwork/bundles.html b/templates/patchwork/bundles.html
deleted file mode 100644 (file)
index 11fb89d..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-{% extends "base.html" %}
-
-{% load static %}
-
-{% block title %}Bundles{% endblock %}
-{% block heading %}Bundles{% endblock %}
-
-{% block body %}
-
-{% if bundles %}
-<table class="bundlelist">
- <tr>
-  <th>Name</th>
-  <th>Project</th>
-  <th>Public Link</th>
-  <th>Patches</td>
-  <th>Download</th>
-  <th>Delete</th>
- </tr>
-{% for bundle in bundles %}
- <tr>
-  <td><a href="{{ bundle.get_absolute_url }}">{{ bundle.name }}</a></td>
-  <td>{{ bundle.project.linkname }}</td>
-  <td>
-   {% if bundle.public %}
-    <a href="{{ bundle.public_url }}">{{ bundle.public_url }}</a>
-   {% endif %}
-  </td>
-  <td style="text-align: right">{{ bundle.n_patches }}</td>
-  <td style="text-align: center;"><a
-   href="{% url 'patchwork.views.bundle.mbox' username=bundle.owner.username bundlename=bundle.name %}"
-   ><img src="{% static "images/16-em-down.png" %}" width="16" height="16" alt="download"
-   title="download"/></a></td>
-  <td style="text-align: center;">
-   <form method="post"
-    onsubmit="return confirm_delete('bundle', '{{bundle.name|escapejs}}');">
-    {% csrf_token %}
-    {{ bundle.delete_form.as_p }}
-    <input type="image"
-     src="{% static "images/patchwork/16-em-cross.png" %}" width="16" height="16" alt="delete"
-     title="delete" border="0" style="border: none;"/>
-   </form>
-  </td>
-
- </tr>
-{% endfor %}
-</table>
-{% endif %}
-
-<p>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.</p>
-
-{% if not bundles %}
-<p>You have no bundles.</p>
-{% endif %}
-{% endblock %}
diff --git a/templates/patchwork/confirm-error.html b/templates/patchwork/confirm-error.html
deleted file mode 100644 (file)
index 81292e2..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Confirmation{% endblock %}
-{% block heading %}Confirmation{% endblock %}
-
-
-{% block body %}
-
-{% if error == 'inactive' %}
-<p>This confirmation has already been processed; you've probably visited this
-page before.</p>
-{% endif %}
-
-{% if error == 'expired' %}
-<p>The confirmation has expired. If you'd still like to perform the
-{{conf.get_type_display}} process, you'll need to resubmit the request.</p>
-{% endif %}
-
-{% endblock %}
diff --git a/templates/patchwork/filters.html b/templates/patchwork/filters.html
deleted file mode 100644 (file)
index 10ca587..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-{% load static %}
-
-<script type="text/javascript" language="JavaScript">
-var filterform_displayed = false;
-function filter_click()
-{
-    var form = document.getElementById('filterform');
-    if (!form) {
-        return;
-    }
-
-    if (filterform_displayed) {
-        form.style['display'] = 'none';
-        filterform_displayed = false;
-    } else {
-        form.style['display'] = 'block';
-        filterform_displayed = true;
-    }
-
-
-}
-function enable_selected_submitter(select, input)
-{
-    select.name = 'submitter';
-    input.name = '';
-}
-function filter_form_submit(form)
-{
-    var i;
-
-    var submitter_select = document.getElementById("submitter_select");
-    var submitter_input = document.getElementById("submitter_input");
-    if (!submitter_select || !submitter_input) {
-        req = null;
-        return;
-    }
-
-    /* submitter handling. if possible, use the select box, otherwise leave
-     * as-is (and so the text box is used). */
-
-    if (submitter_select.options.length == 0) {
-        /* if there's no match, just use the input */
-
-    } else if (submitter_select.options.length == 1) {
-        /* if there's only one match, request by id */
-        submitter_select.selectedIndex = 0;
-        enable_selected_submitter(submitter_select, submitter_input);
-
-    } else if (submitter_select.selectedIndex != -1) {
-        /* if the user has explicitly selected, request by id */
-        enable_selected_submitter(submitter_select, submitter_input);
-
-    }
-
-    for (i = 0; i < form.elements.length; i++) {
-        var e = form.elements[i];
-        if (e.type == 'submit') {
-            continue;
-        }
-
-        /* handle submitter data */
-        if (e.type == 'select-one') {
-            if (e.name == '') {
-                e.disabled = true;
-            }
-            if (e.selectedIndex != -1
-                    && e.options[e.selectedIndex].value == '') {
-                e.disabled = true;
-            }
-
-            continue;
-        }
-
-        if (e.value == '') {
-            e.disabled = true;
-        }
-    }
-}
-
-var req = null;
-
-function submitter_complete_response()
-{
-    if (req.readyState != 4) {
-        return
-    }
-
-    var completions;
-    eval("completions = " + req.responseText);
-
-    if (completions.length == 0) {
-        req = null;
-        return;
-    }
-
-    var submitter_select = document.getElementById("submitter_select");
-    var submitter_input = document.getElementById("submitter_input");
-    if (!submitter_select || !submitter_input) {
-        req = null;
-        return;
-    }
-
-    for (i = 0; i < completions.length; i++) {
-        name = completions[i]['fields']['name'];
-        if (name) {
-            name = completions[i]['fields']['name'] +
-                ' <' + completions[i]['fields']['email'] + '>';
-        } else {
-            name = completions[i]['fields']['email'];
-        }
-        o = new Option(name, completions[i]['pk']);
-        submitter_select.options[i] = o;
-    }
-
-    /* remove remaining options */
-    for (; i < submitter_select.length; i++) {
-        submitter_select.options[i] = null;
-    }
-
-    submitter_select.disabled = false;
-    req = null;
-}
-
-function submitter_field_change(field)
-{
-    var limit = 20;
-    var value = field.value;
-    if (value.length < 4) {
-        return;
-    }
-
-    if (req) {
-         return;
-    }
-
-    var url = '{% url 'patchwork.views.submitter_complete' %}?q=' + value +
-                        '&l=' + limit;
-    req = new XMLHttpRequest();
-    req.onreadystatechange = submitter_complete_response;
-    req.open("GET", url, true);
-    req.send('');
-}
-</script>
-
-<div class="filters">
- <div id="filtersummary">
-  <strong><a href="javascript:filter_click()">Filters</a>:</strong>
- {% if filters.applied_filters %}
-  {% for filter in filters.applied_filters %}
-   {{ filter.name }} = {{ filter.condition }}
-    {% if not filter.forced %}
-     <a href="{{ filter.url_without_me }}"><img
-      width="16" height="16" alt="remove filter" title="remove filter"
-      src="{% static "images/16-circle-blue-remove.png" %}"></a>
-    {% endif %}
-   {% if not forloop.last %}&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;{% endif %}
-  {% endfor %}
- {% else %}
-  none
-  <a href="javascript:filter_click()"><img
-  width="16" height="16" alt="add filter" title="add filter"
-  src="{% static "images/16-circle-blue-add.png" %}"></a>
- {% endif %}
- </div>
- <div id="filterform" style="padding-top: 1em; display: none">
-  <form action="" method="get" onSubmit="return filter_form_submit(this)">
-    <table>
-    {% for filter in filters.available_filters %}
-     <tr>
-      <td>{{ filter.name }}</td>
-      <td>{{ filter.form }}</td>
-     </tr>
-    {% endfor %}
-     <tr>
-      <td/>
-      <td><input type="submit" value="Apply"/></td>
-     </tr>
-    </table>
-  </form>
- </div>
-</div>
-
-
diff --git a/templates/patchwork/help/about.html b/templates/patchwork/help/about.html
deleted file mode 100644 (file)
index 7befa6b..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}About{% endblock %}
-{% block heading %} - About Patchwork{% endblock %}
-
-{% block body %}
-
-<p>Patchwork is free software, and is available from the
-<a href="http://jk.ozlabs.org/projects/patchwork/">patchwork website</a>.</p>
-
-<p>Patchwork is built on the <a href="http://djangoproject.com/">django</a>
-web framework.</p>
-
-<p>Icons from the <a href="http://sweetie.sublink.ca/">Sweetie</a> icon set.</a>
-
-{% endblock %}
-
diff --git a/templates/patchwork/help/index.html b/templates/patchwork/help/index.html
deleted file mode 100644 (file)
index 5cb6467..0000000
+++ /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 (file)
index 7101ec1..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Command-line client{% endblock %}
-{% block heading %} - Command-line client{% endblock %}
-
-{% block body %}
-
-<p><code>pwclient</code> is the command-line client for patchwork. Currently,
-it provides access to some read-only features of patchwork, such as downloading
-and applying patches.</p>
-
-<p>To use pwclient, you will need:</p>
-<ul>
- <li>The <a href="{% url 'patchwork.views.pwclient' %}">pwclient</a>
-  program (11kB, python script)</li>
- <li>(optional) a <code>.pwclientrc</code> file in your home directory.</li>
-</ul>
-
-<p>You can create your own <code>.pwclientrc</code> file. Each
-<a href="{% url 'patchwork.views.projects' %}">patchwork project</a>
-provides a sample linked from the 'project info' page.</p>
-
-{% endblock %}
diff --git a/templates/patchwork/list.html b/templates/patchwork/list.html
deleted file mode 100644 (file)
index 654fe8c..0000000
+++ /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 %}
-
-<h2>Incoming patches</h2>
-
-{% if errors %}
-<p>The following error{{ errors|length|pluralize:" was,s were" }} encountered
-while updating patches:</p>
-<ul class="errorlist">
-{% for error in errors %}
- <li>{{ error }}</li>
-{% endfor %}
-</ul>
-{% endif %}
-
-{% include "patchwork/patch-list.html" %}
-
-{% endblock %}
diff --git a/templates/patchwork/login.html b/templates/patchwork/login.html
deleted file mode 100644 (file)
index 2dfc2a7..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Login{% endblock %}
-{% block heading %}Login{% endblock %}
-
-
-{% block body %}
-<form method="post">
-{% csrf_token %}
-<table class="form loginform">
- <tr>
-  <th colspan="2" class="headerrow">login</th>
- </tr>
- {% if error %}
-  <tr>
-   <td colspan="2">{{ error }}</td>
-  </tr>
- {% endif %}
- {{ form }}
- <tr>
-  <td colspan="2" class="submitrow">
-   <input type="submit" value="Login"/>
-  </td>
- </tr>
-</table>
-</form>
-{% endblock %}
diff --git a/templates/patchwork/logout.html b/templates/patchwork/logout.html
deleted file mode 100644 (file)
index f030aee..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Logout{% endblock %}
-{% block heading %}Logout{% endblock %}
-
-{% block body %}
-<p>Logged out</p>
-{% endblock %}
diff --git a/templates/patchwork/mail-form.html b/templates/patchwork/mail-form.html
deleted file mode 100644 (file)
index d71b2fb..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}mail settings{% endblock %}
-{% block heading %}mail settings{% endblock %}
-
-{% block body %}
-
-<p>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.</p>
-
-<form method="post">
-{% csrf_token %}
-<table class="form registerform">
-{% if form.errors %}
- <tr>
-  <td colspan="2" class="error">
-   There was an error accessing your mail settings:
-  </td>
- </tr>
-{% endif %}
- <tr>
-  <th>{{ form.email.label_tag }}</th>
-  <td>
-   {{form.email}}
-   {{form.email.errors}}
-  </td>
- </tr>
- <tr>
-  <td colspan="2" class="submitrow">
-   <input type="submit" value="Access mail settings"/>
-  </td>
- </tr>
-</table>
-</form>
-
-
-{% endblock %}
diff --git a/templates/patchwork/mail-settings.html b/templates/patchwork/mail-settings.html
deleted file mode 100644 (file)
index 440af08..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}mail settings{% endblock %}
-{% block heading %}mail settings{% endblock %}
-
-{% block body %}
-<p>Settings for <strong>{{email}}</strong>:</p>
-
-<table class="horizontal">
- <tr>
-  <th>Opt-out list</th>
-{% if is_optout %}
-  <td>Patchwork <strong>may not</strong> send automated notifications to
-   this address.</td>
-  <td>
-   <form method="post" action="{% url 'patchwork.views.mail.optin' %}">
-    {% csrf_token %}
-    <input type="hidden" name="email" value="{{email}}"/>
-    <input type="submit" value="Opt-in"/>
-   </form>
-  </td>
-   
-{% else %}
-  <td>Patchwork <strong>may</strong> send automated notifications to
-   this address.</td>
-  <td>
-   <form method="post" action="{% url 'patchwork.views.mail.optout' %}">
-    {% csrf_token %}
-    <input type="hidden" name="email" value="{{email}}"/>
-    <input type="submit" value="Opt-out"/>
-   </form>
-  </td>
-{% endif %}
- </tr>
-</table>
-
-{% endblock %}
diff --git a/templates/patchwork/optin-request.html b/templates/patchwork/optin-request.html
deleted file mode 100644 (file)
index 3dfb1bd..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}opt-in{% endblock %}
-{% block heading %}opt-in{% endblock %}
-
-{% block body %}
-{% if email_sent %}
-<p><strong>Opt-in confirmation email sent</strong></p>
-<p>An opt-in confirmation mail has been sent to
-<strong>{{confirmation.email}}</strong>, containing a link. Please click on
-that link to confirm your opt-in.</p>
-{% else %}
-{% if error %}
-<p class="error">{{error}}</p>
-{% endif %}
-
-{% if form %}
-<p>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.</p>
-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.</p>
-<form method="post" action="">
-{% csrf_token %}
-{{form.email.errors}}
-<div style="padding: 0.5em 1em 2em;">
-{{form.email.label_tag}}: {{form.email}}
-</div>
-<input type="submit" value="Send me an opt-in link">
-</form>
-{% endif %}
-
-{% if error and admins %}
-<p>If you are having trouble opting in, please email
-{% for admin in admins %}
-{% if admins|length > 1 and forloop.last %} or {% endif %}
-{{admin.0}} &lt;<a href="mailto:{{admin.1}}">{{admin.1}}</a
->&gt;{% if admins|length > 2 and not forloop.last %}, {% endif %}
-{% endfor %}
-{% endif %}
-
-{% endif %}
-
-{% if user.is_authenticated %}
-<p>Return to your <a href="{% url 'patchwork.views.user.profile' %}">user
-profile</a>.</p>
-{% endif %}
-
-{% endblock %}
diff --git a/templates/patchwork/optin-request.mail b/templates/patchwork/optin-request.mail
deleted file mode 100644 (file)
index d97c78b..0000000
+++ /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 (file)
index 01aaa0e..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}opt-in{% endblock %}
-{% block heading %}opt-in{% endblock %}
-
-{% block body %}
-
-<p><strong>Opt-in complete</strong>. You have sucessfully opted back in to
-automated email from this patchwork system, using the address
-<strong>{{email}}</strong>.</p>
-<p>If you later decide that you no longer want to receive automated mail from
-patchwork, just visit <a href="{% url 'patchwork.views.mail.settings' %}"
->http://{{site.domain}}{% url 'patchwork.views.mail.settings' %}</a>, or
-visit the main patchwork page and navigate from there.</p>
-{% if user.is_authenticated %}
-<p>Return to your <a href="{% url 'patchwork.views.user.profile' %}">user
-profile</a>.</p>
-{% endif %}
-{% endblock %}
diff --git a/templates/patchwork/optout-request.html b/templates/patchwork/optout-request.html
deleted file mode 100644 (file)
index 092dbbb..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}opt-out{% endblock %}
-{% block heading %}opt-out{% endblock %}
-
-{% block body %}
-{% if email_sent %}
-<p><strong>Opt-out confirmation email sent</strong></p>
-<p>An opt-out confirmation mail has been sent to
-<strong>{{confirmation.email}}</strong>, containing a link. Please click on
-that link to confirm your opt-out.</p>
-{% else %}
-{% if error %}
-<p class="error">{{error}}</p>
-{% endif %}
-
-{% if form %}
-<p>This form allows you to opt-out of automated email from patchwork.</p>
-<p>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.</p>
-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.</p>
-<form method="post" action="">
-{% csrf_token %}
-{{form.email.errors}}
-<div style="padding: 0.5em 1em 2em;">
-{{form.email.label_tag}}: {{form.email}}
-</div>
-<input type="submit" value="Send me an opt-out link">
-</form>
-{% endif %}
-
-{% if error and admins %}
-<p>If you are having trouble opting out, please email
-{% for admin in admins %}
-{% if admins|length > 1 and forloop.last %} or {% endif %}
-{{admin.0}} &lt;<a href="mailto:{{admin.1}}">{{admin.1}}</a
->&gt;{% if admins|length > 2 and not forloop.last %}, {% endif %}
-{% endfor %}
-{% endif %}
-
-{% endif %}
-
-{% if user.is_authenticated %}
-<p>Return to your <a href="{% url 'patchwork.views.user.profile' %}">user
-profile</a>.</p>
-{% endif %}
-
-{% endblock %}
diff --git a/templates/patchwork/optout-request.mail b/templates/patchwork/optout-request.mail
deleted file mode 100644 (file)
index 67203ca..0000000
+++ /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 (file)
index b140bf4..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}opt-out{% endblock %}
-{% block heading %}opt-out{% endblock %}
-
-{% block body %}
-
-<p><strong>Opt-out complete</strong>. You have successfully opted-out of
-automated notifications from this patchwork system, from the address
-<strong>{{email}}</strong></p>
-<p>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.</p>
-<p>If you later decide to receive mail from patchwork, just visit
-<a href="{% url 'patchwork.views.mail.settings' %}"
->http://{{site.domain}}{% url 'patchwork.views.mail.settings' %}</a>, or
-visit the main patchwork page and navigate from there.</p>
-{% if user.is_authenticated %}
-<p>Return to your <a href="{% url 'patchwork.views.user.profile' %}">user
-profile</a>.</p>
-{% endif %}
-{% endblock %}
diff --git a/templates/patchwork/pagination.html b/templates/patchwork/pagination.html
deleted file mode 100644 (file)
index 3e95126..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-{% load listurl %}
-
-{% ifnotequal page.paginator.num_pages 1 %}
-<div class="paginator">
-{% if page.has_previous %}
- <span class="prev">
-  <a href="{% listurl page=page.previous_page_number %}"
-     title="Previous Page">&laquo; Previous</a></span>
-{% else %}
- <span class="prev-na">&laquo; Previous</span>
-{% endif %}
-{% if page.paginator.trailing_set %}
- {% for p in page.paginator.trailing_set %}
- <span class="page"><a href="{% listurl page=p %}" >{{ p }}</a></span>
- {% endfor %}
-        ...
-{% endif %}
-{% for p in page.paginator.adjacent_set %}
-  {% ifequal p page.number %}
-    <span class="curr" title="Current Page">{{ p }}</span>
-  {% else %}
-    <span class="page"><a href="{% listurl page=p %}"
-     title="Page {{ p }}">{{ p }}</a></span>
-  {% endifequal %}
-{% endfor %}
-{% if page.paginator.leading_set %}
-        ...
- {% for p in page.paginator.leading_set %}
-    <span class="page"><a href="{% listurl page=p %}">{{ p }}</a></span>
- {% endfor %}
-{% endif %}
-{% if page.has_next %}
- <span class="next">
-  <a href="{% listurl page=page.next_page_number %}"
-   title="Next Page">Next &raquo;</a>
-  </span>
-{% else %}
- <span class="next-na">Next &raquo;</span>
-{% endif %}
-</div> 
-{% endifnotequal %}
diff --git a/templates/patchwork/patch-change-notification-subject.text b/templates/patchwork/patch-change-notification-subject.text
deleted file mode 100644 (file)
index c9d96d4..0000000
+++ /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 (file)
index 4246704..0000000
+++ /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 (file)
index 675f67f..0000000
+++ /dev/null
@@ -1,268 +0,0 @@
-{% load person %}
-{% load listurl %}
-{% load static %}
-
-{% include "patchwork/pagination.html" %}
-
-
-<table class="patchlist">
- <tr>
-  <td class="patchlistfilters">
- {% include "patchwork/filters.html" %}
-  </td>
- {% if order.editable %}
-  <td class="patchlistreorder">
-   <form method="post" id="reorderform">
-    {% csrf_token %}
-    <input type="hidden" name="form" value="reorderform"/>
-    <input type="hidden" name="order_start" value="0"/>
-    <span id="reorderhelp"></span>
-    <input id="reorder-cancel" type="button" value="Cancel"
-     onClick="order_cancel_click(this)"/>
-    <input id="reorder-change" type="button" value="Change order"
-     onClick="order_button_click(this)"/>
-    </form>
-  </td>
- {% endif %}
- </tr>
-</table>
-
-{% if page.paginator.long_page and user.is_authenticated %}
-<div class="floaty">
- <a title="jump to form" href="#patchforms"><span
-  style="font-size: 120%">&#9662;</span></a>
-</div>
-{% endif %}
-
-<form method="post">
-{% csrf_token %}
-<input type="hidden" name="form" value="patchlistform"/>
-<input type="hidden" name="project" value="{{project.id}}"/>
-<table class="patchlist" id="patchlist">
- <thead>
-  <tr>
-   {% if user.is_authenticated %}
-   <th>
-    <input type="checkbox" onChange="select_all(this)"/>
-   </th>
-   {% endif %}
-
-   <th>
-    {% ifequal order.name "name" %}
-     <a class="colactive"
-      href="{% listurl order=order.reversed_name %}"><img
-      {% if order.reversed %}
-      src="{% static "images/16-arrow-up.png" %}"
-      {% else %}
-      src="{% static "images/16-arrow-down.png" %}"
-      {%endif%}
-      width="16" height="16"
-     ></a> <a class="colactive"
-      href="{% listurl order=order.reversed_name %}">Patch</a>
-    {% else %}
-     {% if not order.editable %}
-     <a class="colinactive" href="{% listurl order="name" %}">Patch</a>
-     {% else %}
-     <span class="colinactive">Patch</span>
-     {% endif %}
-    {% endifequal %}
-   </th>
-
-   <th>
-    {% ifequal order.name "date" %}
-     <a class="colactive"
-      href="{% listurl order=order.reversed_name %}"><img
-      {% if order.reversed %}
-      src="{% static "images/16-arrow-up.png" %}"
-      {% else %}
-      src="{% static "images/16-arrow-down.png" %}"
-      {%endif%}
-      width="16" height="16"
-     ></a> <a class="colactive"
-      href="{% listurl order=order.reversed_name %}">Date</a>
-    {% else %}
-     {% if not order.editable %}
-     <a class="colinactive" href="{% listurl order="date" %}">Date</a>
-     {% else %}
-     <span class="colinactive">Date</span>
-     {% endif %}
-    {% endifequal %}
-   </th>
-
-   <th>
-    {% ifequal order.name "submitter" %}
-     <a class="colactive"
-      href="{% listurl order=order.reversed_name %}"><img
-      {% if order.reversed %}
-      src="{% static "images/16-arrow-up.png" %}"
-      {% else %}
-      src="{% static "images/16-arrow-down.png" %}"
-      {%endif%}
-      width="16" height="16"
-     ></a> <a class="colactive"
-      href="{% listurl order=order.reversed_name %}">Submitter</a>
-    {% else %}
-     {% if not order.editable %}
-     <a class="colinactive" href="{% listurl order="submitter" %}">Submitter</a>
-     {% else %}
-     <span class="colinactive">Submitter</span>
-     {% endif %}
-    {% endifequal %}
-   </th>
-
-   <th>
-    {% ifequal order.name "delegate" %}
-     <a class="colactive"
-      href="{% listurl order=order.reversed_name %}"><img
-      {% if order.reversed %}
-      src="{% static "images/16-arrow-up.png" %}"
-      {% else %}
-      src="{% static "images/16-arrow-down.png" %}"
-      {%endif%}
-      width="16" height="16"
-     ></a> <a class="colactive"
-      href="{% listurl order=order.reversed_name %}">Delegate</a>
-    {% else %}
-     {% if not order.editable %}
-     <a class="colinactive" href="{% listurl order="delegate" %}">Delegate</a>
-     {% else %}
-     <span class="colinactive">Delegate</span>
-     {% endif %}
-    {% endifequal %}
-   </th>
-
-   <th>
-    {% ifequal order.name "state" %}
-     <a class="colactive"
-      href="{% listurl order=order.reversed_name %}"><img
-      {% if order.reversed %}
-      src="{% static "images/16-arrow-up.png" %}"
-      {% else %}
-      src="{% static "images/16-arrow-down.png" %}"
-      {%endif%}
-      width="16" height="16"
-     ></a> <a class="colactive"
-      href="{% listurl order=order.reversed_name %}">State</a>
-    {% else %}
-     {% if not order.editable %}
-     <a class="colinactive" href="{% listurl order="state" %}">State</a>
-     {% else %}
-     <span class="colinactive">State</span>
-     {% endif %}
-    {% endifequal %}
-   </th>
-
-  </tr>
- </thead>
-
-{% if page.paginator.count %}
- <tbody>
- {% for patch in page.object_list %}
-  <tr id="patch_row:{{patch.id}}" class="{% cycle 'odd' 'even' %}">
-    {% if user.is_authenticated %}
-    <td>
-    <input type="checkbox" name="patch_id:{{patch.id}}"/>
-    </td>
-    {% endif %}
-   <td><a href="{% url 'patchwork.views.patch.patch' patch_id=patch.id %}"
-     >{{ patch.name|default:"[no subject]" }}</a></td>
-   <td>{{ patch.date|date:"Y-m-d" }}</td>
-   <td>{{ patch.submitter|personify:project }}</td>
-   <td>{{ patch.delegate.username }}</td>
-   <td>{{ patch.state }}</td>
-  </tr>
- {% endfor %}
- </tbody>
-</table>
-
-{% include "patchwork/pagination.html" %}
-
-<div class="patchforms" id="patchforms" name="patchforms">
-
-{% if patchform %}
- <div class="patchform patchform-properties">
-  <h3>Properties</h3>
-    <table class="form">
-     <tr>
-      <th>Change state:</th>
-      <td>
-       {{ patchform.state }}
-       {{ patchform.state.errors }}
-      </td>
-     </tr>
-     <tr>
-      <th>Delegate to:</td>
-      <td>
-       {{ patchform.delegate }}
-       {{ patchform.delegate.errors }}
-      </td>
-     </tr>
-     <tr>
-      <th>Archive:</td>
-      <td>
-       {{ patchform.archived }}
-       {{ patchform.archived.errors }}
-      </td>
-     </tr>
-     <tr>
-      <td></td>
-      <td>
-       <input type="submit" name="action" value="{{patchform.action}}"/>
-      </td>
-     </tr>
-    </table>
- </div>
-
-{% endif %}
-
-{% if user.is_authenticated %}
- <div class="patchform patchform-bundle">
-  <h3>Bundling</h3>
-   <table class="form">
-    <tr>
-     <td>Create bundle:</td>
-     <td>
-      <input type="text" name="bundle_name"/>
-      <input name="action" value="Create" type="submit"/>
-      </td>
-    </tr>
-  {% if bundles %}
-    <tr>
-     <td>Add to bundle:</td>
-     <td>
-       <select name="bundle_id"/>
-        {% for bundle in bundles %}
-         <option value="{{bundle.id}}">{{bundle.name}}</option>
-        {% endfor %}
-        </select>
-       <input name="action" value="Add" type="submit"/>
-     </td>
-    </tr>
-  {% endif %}
-  {% if bundle %}
-   <tr>
-     <td>Remove from bundle:</td>
-     <td>
-       <input type="hidden" name="removed_bundle_id" value="{{bundle.id}}"/>
-       <input name="action" value="Remove" type="submit"/>
-     </td>
-    </tr>
-  {% endif %}
-  </table>
- </div>
-{% endif %}
-
-
- <div style="clear: both;">
- </div>
-</div>
-
-{% else %}
- <tr>
-  <td colspan="5">No patches to display</td>
- </tr>
-{% endif %}
-
- </table>
-</form>
-
diff --git a/templates/patchwork/patch.html b/templates/patchwork/patch.html
deleted file mode 100644 (file)
index f18ee3b..0000000
+++ /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 %}
-<script language="JavaScript" type="text/javascript">
-function toggle_headers(link_id, headers_id)
-{
-    var link = document.getElementById(link_id)
-    var headers = document.getElementById(headers_id)
-
-    var hidden = headers.style['display'] == 'none';
-
-    if (hidden) {
-        link.innerHTML = 'hide';
-        headers.style['display'] = 'block';
-    } else {
-        link.innerHTML = 'show';
-        headers.style['display'] = 'none';
-    }
-
-}
-</script>
-
-<table class="patchmeta">
- <tr>
-  <th>Submitter</th>
-  <td>{{ patch.submitter|personify:project }}</td>
- </tr>
- <tr>
-  <th>Date</th>
-  <td>{{ patch.date }}</td>
- </tr>
- <tr>
-  <th>Message ID</th>
-  <td>{{ patch.msgid }}</td>
- </tr>
- <tr>
-  <th>Download</th>
-  <td>
-   <a href="{% url 'patchwork.views.patch.mbox' patch_id=patch.id %}"
-   >mbox</a>
-{% if patch.content %}|
-   <a href="{% url 'patchwork.views.patch.content' patch_id=patch.id %}"
-   >patch</a>
-{% endif %}
-   </td>
- </tr>
- <tr>
-  <th>Permalink</th>
-  <td><a href="{{ patch.get_absolute_url }}">{{ patch.get_absolute_url }}</a>
- </tr>
-  <tr>
-   <th>State</th>
-   <td>{{ patch.state.name }}{% if patch.archived %}, archived{% endif %}</td>
-  </tr>
-{% if patch.commit_ref %}
-  <tr>
-   <th>Commit</th>
-   <td>{{ patch.commit_ref }}</td>
-  </tr>
-{% endif %}
-{% if patch.delegate %}
-  <tr>
-   <th>Delegated to:</th>
-   <td>{{ patch.delegate.profile.name }}</td>
-  </tr>
-{% endif %}
- <tr>
-  <th>Headers</th>
-  <td><a id="togglepatchheaders"
-   href="javascript:toggle_headers('togglepatchheaders', 'patchheaders')"
-   >show</a>
-   <div id="patchheaders" class="patchheaders" style="display:none;">
-    <pre>{{patch.headers}}</pre>
-   </div>
-  </td>
- </tr>
-</table>
-
-<div class="patchforms">
-
-{% if patchform %}
- <div class="patchform patchform-properties">
-  <h3>Patch Properties</h3>
-   <form method="post">
-    {% csrf_token %}
-    <table class="form">
-     <tr>
-      <th>Change state:</th>
-      <td>
-       {{ patchform.state }}
-       {{ patchform.state.errors }}
-      </td>
-     </tr>
-     <tr>
-      <th>Delegate to:</th>
-      <td>
-       {{ patchform.delegate }}
-       {{ patchform.delegate.errors }}
-      </td>
-     </tr>
-     <tr>
-      <th>Archived:</th>
-      <td>
-       {{ patchform.archived }}
-       {{ patchform.archived.errors }}
-      </td>
-     </tr>
-     <tr>
-      <td></td>
-      <td>
-       <input type="submit" value="Update">
-      </td>
-     </tr>
-    </table>
-  </form>
- </div>
-{% endif %}
-
-{% if createbundleform %}
- <div class="patchform patchform-bundle">
-  <h3>Bundling</h3>
-   <table class="form">
-    <tr>
-     <td>Create bundle:</td>
-     <td>
-       {% if createbundleform.non_field_errors %}
-       <dd class="errors">{{createbundleform.non_field_errors}}</dd>
-       {% endif %}
-      <form method="post">
-       {% csrf_token %}
-       <input type="hidden" name="action" value="createbundle"/>
-       {% if createbundleform.name.errors %}
-       <dd class="errors">{{createbundleform.name.errors}}</dd>
-       {% endif %}
-        {{ createbundleform.name }}
-       <input value="Create" type="submit"/>
-      </form>
-      </td>
-    </tr>
-{% if bundles %}
-    <tr>
-     <td>Add to bundle:</td>
-     <td>
-      <form method="post">
-       {% csrf_token %}
-       <input type="hidden" name="action" value="addtobundle"/>
-       <select name="bundle_id"/>
-        {% for bundle in bundles %}
-         <option value="{{bundle.id}}">{{bundle.name}}</option>
-        {% endfor %}
-        </select>
-       <input value="Add" type="submit"/>
-      </form>
-     </td>
-    </tr>
-{% endif %}
-   </table>
-
- </div>
-{% endif %}
-
- <div style="clear: both;">
- </div>
-</div>
-
-{% if patch.pull_url %}
-<h2>Pull-request</h2>
-<a class="patch-pull-url" href="{{patch.pull_url}}"
- >{{ patch.pull_url }}</a>
-{% endif %}
-
-<h2>Comments</h2>
-{% for comment in patch.comments %}
-<div class="comment">
-<div class="meta">{{ comment.submitter|personify:project }} - {{comment.date}}</div>
-<pre class="content">
-{{ comment|commentsyntax }}
-</pre>
-</div>
-{% endfor %}
-
-{% if patch.content %}
-<h2>Patch</h2>
-<div class="patch">
-<pre class="content">
-{{ patch|patchsyntax }}
-</pre>
-</div>
-{% endif %}
-
-
-{% endblock %}
diff --git a/templates/patchwork/profile.html b/templates/patchwork/profile.html
deleted file mode 100644 (file)
index 116d6d6..0000000
+++ /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 %}
-
-<p>
-{% if user.profile.maintainer_projects.count %}
-Maintainer of
-{% for project in user.profile.maintainer_projects.all %}
-<a href="{% url 'patchwork.views.patch.list' project_id=project.linkname %}"
->{{ project.linkname }}</a>{% if not forloop.last %},{% endif %}{% endfor %}.
-{% endif %}
-
-{% if user.profile.contributor_projects.count %}
-Contributor to
-{% for project in user.profile.contributor_projects.all %}
-<a href="{% url 'patchwork.views.patch.list' project_id=project.linkname %}"
->{{ project.linkname }}</a>{% if not forloop.last %},{% endif %}{% endfor %}.
-{% endif %}
-</p>
-
-<div class="leftcol">
-<div class="box">
- <h2>Todo</h2>
-{% if user.profile.n_todo_patches %}
- <p>Your <a href="{% url 'patchwork.views.user.todo_lists' %}">todo
-  list</a> contains {{ user.profile.n_todo_patches }}
-  patch{{ user.profile.n_todo_patches|pluralize:"es" }}.</p>
-{% else %}
- <p>Your todo list contains patches that have been delegated to you. You
-  have no items in your todo list at present.</p>
-{% endif %}
-</div>
-
-<div class="box">
-<h2>Linked email addresses</h2>
-<p>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.</p>
-<p>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.</p>
-<p>Adding a new email address will send a confirmation email to that
-address.</p>
-<table class="vertical">
- <tr>
-  <th>email</th>
-  <th>action</th>
-  <th>notify?</th>
- </tr>
-{% for email in linked_emails %}
- <tr>
-  <td>{{ email.email }}</td>
-  <td>
-  {% ifnotequal user.email email.email %}
-   <form action="{% url 'patchwork.views.user.unlink' person_id=email.id %}"
-    method="post">
-    {% csrf_token %}
-    <input type="submit" value="Unlink"/>
-   </form>
-    {% endifnotequal %}
-  </td>
-  <td>
-   {% if email.is_optout %}
-   <form method="post" action="{% url 'patchwork.views.mail.optin' %}">
-    No,
-     {% csrf_token %}
-     <input type="hidden" name="email" value="{{email.email}}"/>
-     <input type="submit" value="Opt-in"/>
-    </form>
-   {% else %}
-    <form method="post" action="{% url 'patchwork.views.mail.optout' %}">
-    Yes,
-     {% csrf_token %}
-     <input type="hidden" name="email" value="{{email.email}}"/>
-     <input type="submit" value="Opt-out"/>
-    </form>
-   {% endif %}
-  </td>
- </tr>
-{% endfor %}
- <tr>
-  <td colspan="3">
-   <form action="{% url 'patchwork.views.user.link' %}" method="post">
-    {% csrf_token %}
-    {{ linkform.email }}
-    <input type="submit" value="Add"/>
-   </form>
-  </td>
- </tr>
-</table>
-</div>
-</div>
-
-<div class="rightcol">
-
-<div class="box">
-<h2>Bundles</h2>
-
-{% if bundles %}
-<p>You have the following bundle{{ bundle|length|pluralize }}:</p>
-<ul>
-{% for bundle in bundles %}
- <li><a href="{{ bundle.get_absolute_url }}">{{ bundle.name }}</a></li>
-{% endfor %}
-</ul>
-<p>Visit the <a href="{%url 'patchwork.views.bundle.bundles' %}">bundles
- page</a> to manage your bundles.</p>
-{% else %}
-<p>You have no bundles.</p>
-{% endif %}
-</div>
-
-
-<div class="box">
-<h2>Settings</h2>
-
-<form method="post">
- {% csrf_token %}
- <table class="form">
-{{ profileform }}
-  <tr>
-   <td/>
-   <td>
-    <input type="submit" value="Apply"/>
-   </td>
-  </tr>
- </table>
-</form>
-</div>
-
-<div class="box">
-<h2>Authentication</h2>
-<a href="{% url 'django.contrib.auth.views.password_change' %}">Change password</a>
-</div>
-
-</div>
-
-<p style="clear: both"></p>
-
-{% endblock %}
diff --git a/templates/patchwork/project.html b/templates/patchwork/project.html
deleted file mode 100644 (file)
index be8cadc..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}{{ project.name }}{% endblock %}
-{% block heading %}{{ project.name }}{% endblock %}
-
-{% block body %}
-
-<table class="horizontal">
- <tr>
-  <th>Name</th>
-  <td>{{project.name}}
- </tr>
- <tr>
-  <th>List address</th>
-  <td>{{project.listemail}}</td>
- </tr>
- <tr>
-  <th>Maintainer{{maintainers|length|pluralize}}</th>
-  <td>
-   {% for maintainer in maintainers %}
-    {{ maintainer.profile.name }}
-     &lt;<a href="mailto:{{maintainer.email}}">{{maintainer.email}}</a>&gt;
-     <br />
-   {% endfor %}
-  </td>
- </tr>
- <tr>
-  <th>Patch count</th>
-  <td>{{n_patches}} (+ {{n_archived_patches}} archived)</td>
- </tr>
-{% if project.web_url %}
- <tr>
-  <th>Website</th>
-  <td><a href="{{project.web_url}}">{{project.web_url}}</a></td>
- </tr>
-{% endif %}
-{% if project.webscm_url %}
- <tr>
-  <th>Source Code Web Interface</th>
-  <td><a href="{{project.webscm_url}}">{{project.webscm_url}}</a></td>
- </tr>
-{% endif %}
-{% if project.scm_url %}
- <tr>
-  <th>Source Code Manager URL</th>
-  <td><a href="{{project.scm_url}}">{{project.scm_url}}</a></td>
- </tr>
-{% endif %}
-</table>
-
-{% if settings.ENABLE_XMLRPC %}
-<p>Sample <a href="{% url 'patchwork.views.help' "pwclient/" %}">patchwork
-client</a> configuration for this project: <a
-href="{% url 'patchwork.views.pwclientrc' project.linkname %}"
->.pwclientrc</a>.</p>
-{% endif %}
-  
-{% endblock %}
diff --git a/templates/patchwork/projects.html b/templates/patchwork/projects.html
deleted file mode 100644 (file)
index 8c727ad..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Project List{% endblock %}
-{% block heading %}Project List{% endblock %}
-
-{% block body %}
-
-{% if projects %}
- <div class="project-set">
- {% for p in projects %}
- <div class="project">
-  <h2 class="project-title">
-   <a href="{% url 'patchwork.views.patch.list' project_id=p.linkname %}"
-    >{{p.linkname}}</a>
-  </h2>
-  <div class="project-name">{{p.name}}</div>
-{% if p.web_url %}
-  <div class="project-url"><a href="{{p.web_url}}">website</a></div>
-{% endif %}
-  </div>
- {% endfor %}
- </div>
-{% else %}
- <p>Patchwork doesn't have any projects to display!</p>
-{% endif %}
-
-{% endblock %}
diff --git a/templates/patchwork/pwclient b/templates/patchwork/pwclient
deleted file mode 120000 (symlink)
index ae4faf3..0000000
+++ /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 (file)
index d331003..0000000
+++ /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: <add your patchwork password here>
-{% endif %}
diff --git a/templates/patchwork/register.mail b/templates/patchwork/register.mail
deleted file mode 100644 (file)
index 9079203..0000000
+++ /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 (file)
index 6111401..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Registration{% endblock %}
-{% block heading %}Registration{% endblock %}
-
-{% block body %}
-<p>Registraton confirmed!</p>
-
-<p>Your patchwork registration is complete. Head over to your <a
- href="{% url 'patchwork.views.user.profile' %}">profile</a> to start using
-patchwork's extra features.</p>
-
-{% endblock %}
diff --git a/templates/patchwork/registration_form.html b/templates/patchwork/registration_form.html
deleted file mode 100644 (file)
index 3a314b8..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Registration{% endblock %}
-{% block heading %}Registration{% endblock %}
-
-
-{% block body %}
-
-{% if confirmation and not error %}
- <p>Registration successful!</p>
- <p>A confirmation email has been sent to {{ confirmation.email }}. You'll
- need to visit the link provided in that email to confirm your
- registration.</p>
-</p>
-{% else %}
-<p>By creating a patchwork account, you can:<p>
-<ul>
- <li>create "bundles" of patches</li>
- <li>update the state of your own patches</li>
-</ul>
-<form method="post">
-{% csrf_token %}
-<table class="form registerform">
- <tr>
-  <th colspan="2" class="headerrow">register</th>
- </tr>
- {% if error %}
-  <tr>
-   <td colspan="2">{{ error }}</td>
-  </tr>
- {% endif %}
-
-  <tr>
-   <td>{{ form.first_name.label_tag }}</td>
-   <td>
-{% if form.first_name.errors %}
-    {{ form.first_name.errors }}
-{% endif %}
-    {{ form.first_name }}
-{% if form.first_name.help_text %}
-    <div class="help_text"/>{{ form.first_name.help_text }}</div>
-{% endif %}
-   </td>
-  </tr>
-   
-  <tr>
-   <td>{{ form.last_name.label_tag }}</td>
-   <td>
-{% if form.last_name.errors %}
-    {{ form.last_name.errors }}
-{% endif %}
-    {{ form.last_name }}
-{% if form.last_name.help_text %}
-    <div class="help_text"/>{{ form.last_name.help_text }}</div>
-{% endif %}
-   </td>
-  </tr>
-
-  <tr>
-   <td></td>
-   <td class="form-help">
-    Your name is used to identify you on the site
-   </td>
-  </tr>
-   
-  <tr>
-   <td>{{ form.email.label_tag }}</td>
-   <td>
-{% if form.email.errors %}
-    {{ form.email.errors }}
-{% endif %}
-    {{ form.email }}
-{% if form.email.help_text %}
-    <div class="help_text"/>{{ form.email.help_text }}</div>
-{% endif %}
-   </td>
-  </tr>
-   
-  <tr>
-   <td></td>
-   <td class="form-help">
-    Patchwork will send a confirmation email to this address
-   </td>
-  </tr>
-
-  <tr>
-   <td>{{ form.username.label_tag }}</td>
-   <td>
-{% if form.username.errors %}
-    {{ form.username.errors }}
-{% endif %}
-    {{ form.username }}
-{% if form.username.help_text %}
-    <div class="help_text"/>{{ form.username.help_text }}</div>
-{% endif %}
-   </td>
-  </tr>
-   
-  <tr>
-   <td>{{ form.password.label_tag }}</td>
-   <td>
-{% if form.password.errors %}
-    {{ form.password.errors }}
-{% endif %}
-    {{ form.password }}
-{% if form.password.help_text %}
-    <div class="help_text"/>{{ form.password.help_text }}</div>
-{% endif %}
-   </td>
-  </tr>
-
-   <tr>
-  <td colspan="2" class="submitrow">
-   <input type="submit" value="Register"/>
-  </td>
- </tr>
-</table>
-</form>
-{% endif %}
-
-{% endblock %}
diff --git a/templates/patchwork/todo-list.html b/templates/patchwork/todo-list.html
deleted file mode 100644 (file)
index b301901..0000000
+++ /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 %}
-
-<p>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.
-</p>
-
-{% include "patchwork/patch-list.html" %}
-
-{% endblock %}
diff --git a/templates/patchwork/todo-lists.html b/templates/patchwork/todo-lists.html
deleted file mode 100644 (file)
index e268160..0000000
+++ /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 %}
-<p>You have multiple todo lists. Each todo list contains patches for a single
- project.</p>
-<table class="vertical">
- <tr>
-  <th>project</th>
-  <th>patches</th>
- </tr>
-{% for todo_list in todo_lists %}
- <tr>
-  <td><a
-   href="{% url 'patchwork.views.user.todo_list' project_id=todo_list.project.linkname %}"
-    >{{ todo_list.project.name }}</a></td>
-  <td class="numberformat">{{ todo_list.n_patches }}</td>
- </tr>
-{% endfor %}
-</table>
-
-{% 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 (file)
index 449bfeb..0000000
+++ /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 %}
-<p>{{ errors }}</p>
-{% else %}
- <p>You have sucessfully linked the email address {{ person.email }} to
-  your patchwork account</p>
-
-{% endif %}
-<p>Back to <a href="{% url 'patchwork.views.user.profile' %}">your
- profile</a>.</p>
-
-{% endblock %}
diff --git a/templates/patchwork/user-link.html b/templates/patchwork/user-link.html
deleted file mode 100644 (file)
index e436c3a..0000000
+++ /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 %}
-<p>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.</p>
-
-{% else %}
-
-   {% if form.errors %}
-   <p>There was an error submitting your link request.</p>
-    {{ form.non_field_errors }}
-   {% endif %}
-   {% if error %}
-    <ul class="errorlist"><li>{{error}}</li></ul>
-   {% endif %}
-
-   <form action="{% url 'patchwork.views.user.link' %}" method="post">
-    {% csrf_token %}
-    {{linkform.email.errors}}
-    Link an email address: {{ linkform.email }}
-   </form>
-
-{% endif %}
-
-{% endblock %}
diff --git a/templates/patchwork/user-link.mail b/templates/patchwork/user-link.mail
deleted file mode 100644 (file)
index 8db6726..0000000
+++ /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.
index b2a658afdb3947c03eb7b32373621d28aac13af8..820fd1cabf9b1dfdb2ff4f9b0b945c5db3803885 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 toolsdir="$(dirname "$0")"
-pwpath="${toolsdir}"/../apps/patchwork
+pwpath="${toolsdir}"/../patchwork
 
 if [ "$#" -lt 1 ]
 then
index a38522e22f350693957281659347f39cbcca44da..8f05b8d85dc3e98c3c63a4a3ca0b813ae26e79c8 100755 (executable)
@@ -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 8d99e6a8176ee46e0936a47b427f1487862881e1..485f7c7acfa292847ab297e442fce0f11f3eba24 100644 (file)
--- 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