Show patches with:
{% if filters.applied_filters %}
- {% for filter in filters.applied_filters %}
+ {% for filter in filters.applied_filters.values %}
{{ filter.name }} = {{ filter.condition }}
{% if not filter.forced %}
-
-
diff --git a/patchwork/templates/patchwork/patch-list.html b/patchwork/templates/patchwork/patch-list.html
index 71c1ba927..f0f12cc81 100644
--- a/patchwork/templates/patchwork/patch-list.html
+++ b/patchwork/templates/patchwork/patch-list.html
@@ -41,9 +41,9 @@
$('#check-all').change(function(e) {
if(this.checked) {
- $('#patchlist').checkboxes('check');
+ $('#patchlist > tbody').checkboxes('check');
} else {
- $('#patchlist').checkboxes('uncheck');
+ $('#patchlist > tbody').checkboxes('uncheck');
}
e.preventDefault();
});
diff --git a/patchwork/templates/patchwork/pwclientrc b/patchwork/templates/patchwork/pwclientrc
index 96464c1be..7d466d890 100644
--- a/patchwork/templates/patchwork/pwclientrc
+++ b/patchwork/templates/patchwork/pwclientrc
@@ -8,8 +8,8 @@
# default={{ project.linkname }}
[{{ project.linkname }}]
-url= {{scheme}}://{{site.domain}}{% url 'xmlrpc' %}
+url = {{ scheme }}://{{ site.domain }}{% url 'xmlrpc' %}
{% if user.is_authenticated %}
-username: {{ user.username }}
-password:
+username = {{ user.username }}
+password =
{% endif %}
diff --git a/patchwork/templatetags/patch.py b/patchwork/templatetags/patch.py
index 4350e092e..577c78375 100644
--- a/patchwork/templatetags/patch.py
+++ b/patchwork/templatetags/patch.py
@@ -21,6 +21,7 @@
from __future__ import absolute_import
from django import template
+from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.template.defaultfilters import stringfilter
@@ -65,4 +66,4 @@ def state_class(state):
@register.filter
@stringfilter
def msgid(value):
- return mark_safe(value.strip('<>'))
+ return escape(value.strip('<>'))
diff --git a/patchwork/tests/api/test_check.py b/patchwork/tests/api/test_check.py
index 43181af3d..e3ad099cf 100644
--- a/patchwork/tests/api/test_check.py
+++ b/patchwork/tests/api/test_check.py
@@ -54,9 +54,9 @@ def setUp(self):
self.user = create_maintainer(project)
self.patch = create_patch(project=project)
- def _create_check(self):
+ def _create_check(self, patch=None):
values = {
- 'patch': self.patch,
+ 'patch': patch if patch else self.patch,
'user': self.user,
}
return create_check(**values)
@@ -67,6 +67,7 @@ def assertSerialized(self, check_obj, check_json):
self.assertEqual(check_obj.target_url, check_json['target_url'])
self.assertEqual(check_obj.context, check_json['context'])
self.assertEqual(check_obj.description, check_json['description'])
+ self.assertEqual(check_obj.user.id, check_json['user']['id'])
def test_list(self):
"""Validate we can list checks on a patch."""
@@ -75,6 +76,7 @@ def test_list(self):
self.assertEqual(0, len(resp.data))
check_obj = self._create_check()
+ self._create_check(create_patch()) # second, unrelated patch
resp = self.client.get(self.api_url())
self.assertEqual(status.HTTP_200_OK, resp.status_code)
@@ -89,6 +91,12 @@ def test_list(self):
resp = self.client.get(self.api_url(), {'user': 'otheruser'})
self.assertEqual(0, len(resp.data))
+ def test_list_invalid_patch(self):
+ """Ensure we get a 404 for a non-existent patch."""
+ resp = self.client.get(
+ reverse('api-check-list', kwargs={'patch_id': '99999'}))
+ self.assertEqual(status.HTTP_404_NOT_FOUND, resp.status_code)
+
def test_detail(self):
"""Validate we can get a specific check."""
check = self._create_check()
@@ -111,12 +119,21 @@ def test_create(self):
self.assertEqual(1, Check.objects.all().count())
self.assertSerialized(Check.objects.first(), resp.data)
+ def test_create_no_permissions(self):
+ """Ensure creations are rejected by standard users."""
+ check = {
+ 'state': 'success',
+ 'target_url': 'http://t.co',
+ 'description': 'description',
+ 'context': 'context',
+ }
+
user = create_user()
self.client.force_authenticate(user=user)
resp = self.client.post(self.api_url(), check)
self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)
- def test_create_invalid(self):
+ def test_create_invalid_state(self):
"""Ensure we handle invalid check states."""
check = {
'state': 'this-is-not-a-valid-state',
@@ -130,6 +147,36 @@ def test_create_invalid(self):
self.assertEqual(status.HTTP_400_BAD_REQUEST, resp.status_code)
self.assertEqual(0, Check.objects.all().count())
+ def test_create_missing_state(self):
+ """Create a check using invalid values.
+
+ Ensure we handle the state being absent.
+ """
+ check = {
+ 'target_url': 'http://t.co',
+ 'description': 'description',
+ 'context': 'context',
+ }
+
+ self.client.force_authenticate(user=self.user)
+ resp = self.client.post(self.api_url(), check)
+ self.assertEqual(status.HTTP_400_BAD_REQUEST, resp.status_code)
+ self.assertEqual(0, Check.objects.all().count())
+
+ def test_create_invalid_patch(self):
+ """Ensure we handle non-existent patches."""
+ check = {
+ 'state': 'success',
+ 'target_url': 'http://t.co',
+ 'description': 'description',
+ 'context': 'context',
+ }
+
+ self.client.force_authenticate(user=self.user)
+ resp = self.client.post(
+ reverse('api-check-list', kwargs={'patch_id': '99999'}), check)
+ self.assertEqual(status.HTTP_404_NOT_FOUND, resp.status_code)
+
def test_update_delete(self):
"""Ensure updates and deletes aren't allowed"""
check = self._create_check()
diff --git a/patchwork/tests/api/test_comment.py b/patchwork/tests/api/test_comment.py
index f79ea4695..5fcb9463b 100644
--- a/patchwork/tests/api/test_comment.py
+++ b/patchwork/tests/api/test_comment.py
@@ -75,6 +75,12 @@ def test_list(self):
with self.assertRaises(NoReverseMatch):
self.client.get(self.api_url(cover_obj, version='1.0'))
+ def test_list_invalid_cover(self):
+ """Ensure we get a 404 for a non-existent cover letter."""
+ resp = self.client.get(
+ reverse('api-cover-comment-list', kwargs={'pk': '99999'}))
+ self.assertEqual(status.HTTP_404_NOT_FOUND, resp.status_code)
+
@unittest.skipUnless(settings.ENABLE_REST_API, 'requires ENABLE_REST_API')
class TestPatchComments(APITestCase):
@@ -113,3 +119,9 @@ def test_list(self):
# check we can't access comments using the old version of the API
with self.assertRaises(NoReverseMatch):
self.client.get(self.api_url(patch_obj, version='1.0'))
+
+ def test_list_invalid_patch(self):
+ """Ensure we get a 404 for a non-existent patch."""
+ resp = self.client.get(
+ reverse('api-patch-comment-list', kwargs={'pk': '99999'}))
+ self.assertEqual(status.HTTP_404_NOT_FOUND, resp.status_code)
diff --git a/patchwork/tests/api/test_patch.py b/patchwork/tests/api/test_patch.py
index 27b992484..f183ca9a1 100644
--- a/patchwork/tests/api/test_patch.py
+++ b/patchwork/tests/api/test_patch.py
@@ -72,73 +72,96 @@ def assertSerialized(self, patch_obj, patch_json):
self.assertEqual(patch_obj.project.id,
patch_json['project']['id'])
- def test_list(self):
- """Validate we can list a patch."""
+ def test_list_empty(self):
+ """List patches when none are present."""
resp = self.client.get(self.api_url())
self.assertEqual(status.HTTP_200_OK, resp.status_code)
self.assertEqual(0, len(resp.data))
+ def _create_patch(self):
person_obj = create_person(email='test@example.com')
project_obj = create_project(linkname='myproject')
state_obj = create_state(name='Under Review')
patch_obj = create_patch(state=state_obj, project=project_obj,
submitter=person_obj)
- # anonymous user
+ return patch_obj
+
+ def test_list_anonymous(self):
+ """List patches as anonymous user."""
+ patch = self._create_patch()
+
resp = self.client.get(self.api_url())
self.assertEqual(status.HTTP_200_OK, resp.status_code)
self.assertEqual(1, len(resp.data))
patch_rsp = resp.data[0]
- self.assertSerialized(patch_obj, patch_rsp)
+ self.assertSerialized(patch, patch_rsp)
self.assertNotIn('headers', patch_rsp)
self.assertNotIn('content', patch_rsp)
self.assertNotIn('diff', patch_rsp)
- # authenticated user
+ def test_list_authenticated(self):
+ """List patches as an authenticated user."""
+ patch = self._create_patch()
user = create_user()
+
self.client.force_authenticate(user=user)
resp = self.client.get(self.api_url())
self.assertEqual(status.HTTP_200_OK, resp.status_code)
self.assertEqual(1, len(resp.data))
patch_rsp = resp.data[0]
- self.assertSerialized(patch_obj, patch_rsp)
+ self.assertSerialized(patch, patch_rsp)
- # test filtering by state
- resp = self.client.get(self.api_url(), {'state': 'under-review'})
- self.assertEqual([patch_obj.id], [x['id'] for x in resp.data])
- resp = self.client.get(self.api_url(), {'state': 'missing-state'})
- self.assertEqual(0, len(resp.data))
+ def test_list_filter_state(self):
+ """Filter patches by state."""
+ self._create_patch()
+ user = create_user()
+
+ state_obj_b = create_state(name='New')
+ create_patch(state=state_obj_b)
+ state_obj_c = create_state(name='RFC')
+ create_patch(state=state_obj_c)
+
+ self.client.force_authenticate(user=user)
+ resp = self.client.get(self.api_url(), [('state', 'under-review'),
+ ('state', 'new')])
+ self.assertEqual(2, len(resp.data))
+
+ def test_list_filter_project(self):
+ """Filter patches by project."""
+ patch = self._create_patch()
+ user = create_user()
+
+ self.client.force_authenticate(user=user)
- # test filtering by project
resp = self.client.get(self.api_url(), {'project': 'myproject'})
- self.assertEqual([patch_obj.id], [x['id'] for x in resp.data])
+ self.assertEqual([patch.id], [x['id'] for x in resp.data])
+
resp = self.client.get(self.api_url(), {'project': 'invalidproject'})
self.assertEqual(0, len(resp.data))
+ def test_list_filter_submitter(self):
+ """Filter patches by submitter."""
+ patch = self._create_patch()
+ submitter = patch.submitter
+ user = create_user()
+
+ self.client.force_authenticate(user=user)
+
# test filtering by submitter, both ID and email
- resp = self.client.get(self.api_url(), {'submitter': person_obj.id})
- self.assertEqual([patch_obj.id], [x['id'] for x in resp.data])
+ resp = self.client.get(self.api_url(), {'submitter': submitter.id})
+ self.assertEqual([patch.id], [x['id'] for x in resp.data])
+
resp = self.client.get(self.api_url(), {
'submitter': 'test@example.com'})
- self.assertEqual([patch_obj.id], [x['id'] for x in resp.data])
+ self.assertEqual([patch.id], [x['id'] for x in resp.data])
+
resp = self.client.get(self.api_url(), {
'submitter': 'test@example.org'})
self.assertEqual(0, len(resp.data))
- state_obj_b = create_state(name='New')
- create_patch(state=state_obj_b)
- state_obj_c = create_state(name='RFC')
- create_patch(state=state_obj_c)
-
- resp = self.client.get(self.api_url())
- self.assertEqual(3, len(resp.data))
- resp = self.client.get(self.api_url(), [('state', 'under-review')])
- self.assertEqual(1, len(resp.data))
- resp = self.client.get(self.api_url(), [('state', 'under-review'),
- ('state', 'new')])
- self.assertEqual(2, len(resp.data))
-
def test_list_version_1_0(self):
+ """List patches using API v1.0."""
create_patch()
resp = self.client.get(self.api_url(version='1.0'))
@@ -148,7 +171,7 @@ def test_list_version_1_0(self):
self.assertNotIn('web_url', resp.data[0])
def test_detail(self):
- """Validate we can get a specific patch."""
+ """Show a specific patch."""
patch = create_patch(
content='Reviewed-by: Test User \n',
headers='Received: from somewhere\nReceived: from another place'
@@ -199,43 +222,112 @@ def test_create(self):
resp = self.client.post(self.api_url(), patch)
self.assertEqual(status.HTTP_405_METHOD_NOT_ALLOWED, resp.status_code)
- def test_update(self):
- """Ensure updates can be performed by maintainers."""
- project = create_project()
- patch = create_patch(project=project)
+ def test_update_anonymous(self):
+ """Update patch as anonymous user.
+
+ Ensure updates can be performed by maintainers.
+ """
+ patch = create_patch()
state = create_state()
- # anonymous user
resp = self.client.patch(self.api_url(patch.id), {'state': state.name})
self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)
- # authenticated user
+ def test_update_non_maintainer(self):
+ """Update patch as non-maintainer.
+
+ Ensure updates can be performed by maintainers.
+ """
+ patch = create_patch()
+ state = create_state()
user = create_user()
+
self.client.force_authenticate(user=user)
resp = self.client.patch(self.api_url(patch.id), {'state': state.name})
self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)
- # maintainer
+ def test_update_maintainer(self):
+ """Update patch as maintainer.
+
+ Ensure updates can be performed by maintainers.
+ """
+ project = create_project()
+ patch = create_patch(project=project)
+ state = create_state()
user = create_maintainer(project)
+
self.client.force_authenticate(user=user)
- resp = self.client.patch(self.api_url(patch.id), {'state': state.name})
- self.assertEqual(status.HTTP_200_OK, resp.status_code)
+ resp = self.client.patch(self.api_url(patch.id),
+ {'state': state.name, 'delegate': user.id})
+ self.assertEqual(status.HTTP_200_OK, resp.status_code, resp)
self.assertEqual(Patch.objects.get(id=patch.id).state, state)
+ self.assertEqual(Patch.objects.get(id=patch.id).delegate, user)
+
+ # (who can unset fields too)
+ # we need to send as JSON due to https://stackoverflow.com/q/30677216/
+ resp = self.client.patch(self.api_url(patch.id), {'delegate': None},
+ format='json')
+ self.assertEqual(status.HTTP_200_OK, resp.status_code, resp)
+ self.assertIsNone(Patch.objects.get(id=patch.id).delegate)
- def test_update_invalid(self):
- """Ensure we handle invalid Patch states."""
+ def test_update_invalid_state(self):
+ """Update patch with invalid fields.
+
+ Ensure we handle invalid Patch updates.
+ """
project = create_project()
state = create_state()
patch = create_patch(project=project, state=state)
user = create_maintainer(project)
- # invalid state
self.client.force_authenticate(user=user)
resp = self.client.patch(self.api_url(patch.id), {'state': 'foobar'})
self.assertEqual(status.HTTP_400_BAD_REQUEST, resp.status_code)
self.assertContains(resp, 'Expected one of: %s.' % state.name,
status_code=status.HTTP_400_BAD_REQUEST)
+ def test_update_legacy_delegate(self):
+ """Regression test for bug #313."""
+ project = create_project()
+ state = create_state()
+ patch = create_patch(project=project, state=state)
+ user_a = create_maintainer(project)
+
+ # create a user (User), then delete the associated UserProfile and save
+ # the user to ensure a new profile is generated
+ user_b = create_user()
+ self.assertEqual(user_b.id, user_b.profile.id)
+ user_b.profile.delete()
+ user_b.save()
+ user_b.profile.maintainer_projects.add(project)
+ user_b.profile.save()
+ self.assertNotEqual(user_b.id, user_b.profile.id)
+
+ self.client.force_authenticate(user=user_a)
+ resp = self.client.patch(self.api_url(patch.id),
+ {'delegate': user_b.id})
+ self.assertEqual(status.HTTP_200_OK, resp.status_code, resp)
+ self.assertEqual(Patch.objects.get(id=patch.id).state, state)
+ self.assertEqual(Patch.objects.get(id=patch.id).delegate, user_b)
+
+ def test_update_invalid_delegate(self):
+ """Update patch with invalid fields.
+
+ Ensure we handle invalid Patch updates.
+ """
+ project = create_project()
+ state = create_state()
+ patch = create_patch(project=project, state=state)
+ user_a = create_maintainer(project)
+ user_b = create_user()
+
+ self.client.force_authenticate(user=user_a)
+ resp = self.client.patch(self.api_url(patch.id),
+ {'delegate': user_b.id})
+ self.assertEqual(status.HTTP_400_BAD_REQUEST, resp.status_code)
+ self.assertContains(resp, "User '%s' is not a maintainer" % user_b,
+ status_code=status.HTTP_400_BAD_REQUEST)
+
def test_delete(self):
"""Ensure deletions are always rejected."""
project = create_project()
diff --git a/patchwork/tests/api/test_project.py b/patchwork/tests/api/test_project.py
index 129cedb74..10044de4e 100644
--- a/patchwork/tests/api/test_project.py
+++ b/patchwork/tests/api/test_project.py
@@ -143,7 +143,7 @@ def test_create(self):
def test_update(self):
"""Ensure updates can be performed by maintainers."""
project = create_project()
- data = {'linkname': 'TEST'}
+ data = {'web_url': 'TEST'}
# an anonymous user
resp = self.client.patch(self.api_url(project.id), data)
@@ -160,6 +160,15 @@ def test_update(self):
self.client.force_authenticate(user=user)
resp = self.client.patch(self.api_url(project.id), data)
self.assertEqual(status.HTTP_200_OK, resp.status_code)
+ self.assertEqual(resp.data['web_url'], 'TEST')
+
+ # ...with the exception of some read-only fields
+ resp = self.client.patch(self.api_url(project.id), {
+ 'link_name': 'test'})
+ # NOTE(stephenfin): This actually returns HTTP 200 due to
+ # https://github.com/encode/django-rest-framework/issues/1655
+ self.assertEqual(status.HTTP_200_OK, resp.status_code)
+ self.assertNotEqual(resp.data['link_name'], 'test')
def test_delete(self):
"""Ensure deletions are rejected."""
diff --git a/patchwork/tests/api/test_series.py b/patchwork/tests/api/test_series.py
index 11324bc3f..23d8cd28f 100644
--- a/patchwork/tests/api/test_series.py
+++ b/patchwork/tests/api/test_series.py
@@ -122,13 +122,19 @@ def test_list(self):
def test_list_old_version(self):
"""Validate that newer fields are dropped for older API versions."""
- create_series()
+ cover_obj = create_cover()
+ series_obj = create_series()
+ series_obj.add_cover_letter(cover_obj)
+ create_series_patch(series=series_obj)
resp = self.client.get(self.api_url(version='1.0'))
self.assertEqual(status.HTTP_200_OK, resp.status_code)
self.assertEqual(1, len(resp.data))
self.assertIn('url', resp.data[0])
self.assertNotIn('web_url', resp.data[0])
+ self.assertNotIn('web_url', resp.data[0]['cover_letter'])
+ self.assertNotIn('mbox', resp.data[0]['cover_letter'])
+ self.assertNotIn('web_url', resp.data[0]['patches'][0])
def test_detail(self):
"""Validate we can get a specific series."""
@@ -141,11 +147,17 @@ def test_detail(self):
self.assertSerialized(series, resp.data)
def test_detail_version_1_0(self):
- series = create_series()
+ cover_obj = create_cover()
+ series_obj = create_series()
+ series_obj.add_cover_letter(cover_obj)
+ create_series_patch(series=series_obj)
- resp = self.client.get(self.api_url(series.id, version='1.0'))
+ resp = self.client.get(self.api_url(series_obj.id, version='1.0'))
self.assertIn('url', resp.data)
self.assertNotIn('web_url', resp.data)
+ self.assertNotIn('web_url', resp.data['cover_letter'])
+ self.assertNotIn('mbox', resp.data['cover_letter'])
+ self.assertNotIn('web_url', resp.data['patches'][0])
def test_create_update_delete(self):
"""Ensure creates, updates and deletes aren't allowed"""
diff --git a/patchwork/tests/mail/0019-multipart-patch.mbox b/patchwork/tests/mail/0019-multipart-patch.mbox
new file mode 100644
index 000000000..99d23a833
--- /dev/null
+++ b/patchwork/tests/mail/0019-multipart-patch.mbox
@@ -0,0 +1,55 @@
+From yuri.volchkov@gmail.com Wed Jun 20 12:22:05 2018
+From: Yuri Volchkov
+To: patchwork@lists.ozlabs.org
+Cc: stephen@that.guru
+Subject: [PATCH] parsemail: ignore html part of multi-part comments
+Date: Wed, 20 Jun 2018 14:21:42 +0200
+Message-Id: <20180620122142.9917-1-yuri.volchkov@gmail.com>
+Content-Type: multipart/alternative; boundary="000000000000f93f23056f12c80c"
+
+
+--000000000000f93f23056f12c80c
+Content-Type: text/plain; charset="UTF-8"
+Content-Transfer-Encoding: 8bit
+
+Currently an html-protection present only for patch-emails. If a
+multi-part comment-email arrives, it messes up patchwork. In my case,
+the symptom was a non intended 'Signed-off-by' in the downloaded
+patches, with html-like junk.
+
+This patch makes parsemail skip all parts of comment which are not
+text/plain.
+
+Of course, this will drop html-only emails completely. But they can
+not be parsed anyways.
+
+Signed-off-by: Yuri Volchkov
+---
+ patchwork/parser.py | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/patchwork/parser.py b/patchwork/parser.py
+index 8f9af811..b1fb7b9c 100644
+--- a/patchwork/parser.py
++++ b/patchwork/parser.py
+@@ -576,9 +576,11 @@ def find_comment_content(mail):
+ """Extract content from a mail."""
+ commentbuf = ''
+
+- for payload, _ in _find_content(mail):
++ for payload, subtype in _find_content(mail):
+ if not payload:
+ continue
++ if subtype != 'plain':
++ continue
+
+ commentbuf += payload.strip() + '\n'
+
+--000000000000f93f23056f12c80c
+Content-Type: text/html; charset="UTF-8"
+Content-Transfer-Encoding: 8bit
+
+
Currently an html-protection present only for patch-emails. If a multi-part comment-email arrives, it messes up patchwork. In my case, the symptom was a non intended 'Signed-off-by' in the downloaded patches, with html-like junk.
This patch makes parsemail skip all parts of comment which are not text/plain.
Of course, this will drop html-only emails completely. But they can not be parsed anyways.
diff --git a/patchwork/parser.py b/patchwork/parser.py index 8f9af811..b1fb7b9c 100644 --- a/patchwork/parser.py +++ b/patchwork/parser.py @@ -576,9 +576,11 @@ def find_comment_content(mail): """Extract content from a mail.""" commentbuf = ''
- for payload, _ in _find_content(mail): + for payload, subtype in _find_content(mail): if not payload: continue + if subtype != 'plain': + continue
commentbuf += payload.strip() + '\n'
-- 2.17.1
+
+--000000000000f93f23056f12c80c--
+
diff --git a/patchwork/tests/mail/0020-multipart-comment.mbox b/patchwork/tests/mail/0020-multipart-comment.mbox
new file mode 100644
index 000000000..7a696a559
--- /dev/null
+++ b/patchwork/tests/mail/0020-multipart-comment.mbox
@@ -0,0 +1,49 @@
+From stephenfinucane@hotmail.com Wed Jun 20 13:35:48 2018
+From: Stephen Finucane
+To: "stephen@that.guru"
+Subject: Re: [PATCH] parsemail: ignore html part of multi-part comments
+Date: Wed, 20 Jun 2018 13:35:37 +0000
+Message-ID:
+References: <20180620122142.9917-1-yuri.volchkov@gmail.com>
+In-Reply-To: <20180620122142.9917-1-yuri.volchkov@gmail.com>
+Content-Type: multipart/alternative;
+ boundary="_000_DB5PR03MB18774049A0E62D211988EC8CA3770DB5PR03MB1877eurp_"
+MIME-Version: 1.0
+
+
+--_000_DB5PR03MB18774049A0E62D211988EC8CA3770DB5PR03MB1877eurp_
+Content-Type: text/plain; charset="iso-8859-1"
+Content-Transfer-Encoding: 8bit
+
+Yup, this looks sensible to me. Replying from Outlook's awful HTML editor to get
+a sample comment to test with.
+
+Stephen
+
+
+--_000_DB5PR03MB18774049A0E62D211988EC8CA3770DB5PR03MB1877eurp_
+Content-Type: text/html; charset="iso-8859-1"
+Content-Transfer-Encoding: 8bit
+
+
+
+
+
+
+
+
+Yup, this looks sensible to me. Replying from Outlook's awful HTML editor to get a sample comment to test with.
+
+
+
+
+Stephen
+
+
+
+
+
+
+
+--_000_DB5PR03MB18774049A0E62D211988EC8CA3770DB5PR03MB1877eurp_--
+
diff --git a/patchwork/tests/mail/0021-git-empty-new-file.mbox b/patchwork/tests/mail/0021-git-empty-new-file.mbox
new file mode 100644
index 000000000..c3be48e6e
--- /dev/null
+++ b/patchwork/tests/mail/0021-git-empty-new-file.mbox
@@ -0,0 +1,32 @@
+From andrew.donnellan@au1.ibm.com Thu Feb 28 00:37:42 2019
+Delivered-To: dja@axtens.net
+Received: by 2002:a4a:2812:0:0:0:0:0 with SMTP id h18csp2242ooa;
+ Wed, 27 Feb 2019 16:37:59 -0800 (PST)
+From: Andrew Donnellan
+Subject: [snowpatch] [PATCH 1/3] Test commit; please ignore
+To: Daniel Axtens
+Date: Thu, 28 Feb 2019 11:37:42 +1100
+User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101
+ Thunderbird/60.5.1
+MIME-Version: 1.0
+Content-Language: en-AU
+
+
+Doing some snowpatching.
+---
+ banana | 0
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+ create mode 100644 banana
+
+diff --git a/banana b/banana
+new file mode 100644
+index 000000000000..e69de29bb2d1
+--
+2.11.0
+
+_______________________________________________
+snowpatch mailing list
+snowpatch@lists.ozlabs.org
+https://lists.ozlabs.org/listinfo/snowpatch
+
+
diff --git a/patchwork/tests/mail/0022-git-mode-change.mbox b/patchwork/tests/mail/0022-git-mode-change.mbox
new file mode 100644
index 000000000..bf280bb8c
--- /dev/null
+++ b/patchwork/tests/mail/0022-git-mode-change.mbox
@@ -0,0 +1,23 @@
+From linux-kbuild Sun Apr 07 23:09:09 2019
+From: Petr Vorel
+Date: Sun, 07 Apr 2019 23:09:09 +0000
+To: linux-kbuild
+Subject: [PATCH 1/1] kconfig: Make nconf-cfg.sh executable
+Message-Id: <20190407230909.20668-1-pvorel@suse.cz>
+X-MARC-Message: https://marc.info/?l=linux-kbuild&m=155467856208923
+
+Although it's not required for the build *conf-cfg.sh scripts to be
+executable (they're run by CONFIG_SHELL), let's be consistent with other
+scripts.
+
+Signed-off-by: Petr Vorel
+---
+ scripts/kconfig/nconf-cfg.sh | 0
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+ mode change 100644 => 100755 scripts/kconfig/nconf-cfg.sh
+
+diff --git a/scripts/kconfig/nconf-cfg.sh b/scripts/kconfig/nconf-cfg.sh
+old mode 100644
+new mode 100755
+--
+2.20.1
diff --git a/patchwork/tests/mail/0023-git-pull-request-newline-in-url.mbox b/patchwork/tests/mail/0023-git-pull-request-newline-in-url.mbox
new file mode 100644
index 000000000..74c29ce47
--- /dev/null
+++ b/patchwork/tests/mail/0023-git-pull-request-newline-in-url.mbox
@@ -0,0 +1,48 @@
+From mboxrd@z Thu Jan 1 00:00:00 1970
+To: soc@kernel.org
+From: Matthias Brugger
+Subject: [GIT PULL] soc: updates for v5.5
+Message-ID: <294422a4-37b2-def5-5d32-8988f27c3a5b@gmail.com>
+Date: Mon, 11 Nov 2019 13:23:51 +0100
+
+Hi Olof and Arnd,
+
+Please have a look on the following updates of drivers/soc for v5.5
+
+Thanks a lot,
+Matthias
+
+---
+
+The following changes since commit 54ecb8f7028c5eb3d740bb82b0f1d90f2df63c5c:
+
+ Linux 5.4-rc1 (2019-09-30 10:35:40 -0700)
+
+are available in the Git repository at:
+
+ https://git.kernel.org/pub/scm/linux/kernel/git/matthias.bgg/linux.git/
+tags/v5.4-next-soc
+
+for you to fetch changes up to 662c9d55c5ccb37f3920ecab9720f2ebf2a6ca18:
+
+ soc: mediatek: Refactor bus protection control (2019-11-07 10:11:04 +0100)
+
+----------------------------------------------------------------
+refactor code of mtk-scpsys
+
+----------------------------------------------------------------
+Weiyi Lu (5):
+ soc: mediatek: Refactor polling timeout and documentation
+ soc: mediatek: Refactor regulator control
+ soc: mediatek: Refactor clock control
+ soc: mediatek: Refactor sram control
+ soc: mediatek: Refactor bus protection control
+
+ drivers/soc/mediatek/mtk-scpsys.c | 214 ++++++++++++++++++++++++++------------
+ 1 file changed, 146 insertions(+), 68 deletions(-)
+
+_______________________________________________
+linux-arm-kernel mailing list
+linux-arm-kernel@lists.infradead.org
+http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
+
diff --git a/patchwork/tests/mail/0024-git-pull-request-trailing-space.mbox b/patchwork/tests/mail/0024-git-pull-request-trailing-space.mbox
new file mode 100644
index 000000000..d62d070b8
--- /dev/null
+++ b/patchwork/tests/mail/0024-git-pull-request-trailing-space.mbox
@@ -0,0 +1,60 @@
+From mboxrd@z Thu Jan 1 00:00:00 1970
+To: Linux ARM Kernel List
+From: XXX XXX
+Subject: [GIT PULL] DaVinci SoC updates for v5.6
+Message-ID: <043eb5b2-a302-4de6-a3e8-8238e49483b1@ti.com>
+Date: Tue, 14 Jan 2020 23:48:54 +0530
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: 7bit
+
+The following changes since commit e42617b825f8073569da76dc4510bfa019b1c35a:
+
+ Linux 5.5-rc1 (2019-12-08 14:57:55 -0800)
+
+are available in the Git repository at:
+
+ git://git.kernel.org/pub/scm/linux/kernel/git/nsekhar/linux-davinci.git tags/davinci-for-v5.6/soc
+
+for you to fetch changes up to 5e06d19694a463a012c2589e29078196eb209448:
+
+ ARM: davinci: dm644x-evm: Add Fixed regulators needed for tlv320aic33 (2020-01-13 17:36:26 +0530)
+
+----------------------------------------------------------------
+DaVinci SoC updates for v5.6 include migrating DM365 SoC to use
+drivers/clocksource based driver for timer. This leads to removal
+of machine specific timer driver.
+
+There are two patches adding missing fixed regulators for audio codecs
+on DM365 and DM644x EVMs.
+
+----------------------------------------------------------------
+Bartosz Golaszewski (3):
+ clocksource: davinci: only enable clockevents once tim34 is initialized
+ ARM: davinci: dm365: switch to using the clocksource driver
+ ARM: davinci: remove legacy timer support
+
+Peter Ujfalusi (2):
+ ARM: davinci: dm365-evm: Add Fixed regulators needed for tlv320aic3101
+ ARM: davinci: dm644x-evm: Add Fixed regulators needed for tlv320aic33
+
+ arch/arm/mach-davinci/Makefile | 3 +-
+ arch/arm/mach-davinci/board-dm365-evm.c | 20 ++
+ arch/arm/mach-davinci/board-dm644x-evm.c | 20 ++
+ arch/arm/mach-davinci/devices-da8xx.c | 1 -
+ arch/arm/mach-davinci/devices.c | 19 --
+ arch/arm/mach-davinci/dm365.c | 22 +-
+ arch/arm/mach-davinci/include/mach/common.h | 17 --
+ arch/arm/mach-davinci/include/mach/time.h | 33 ---
+ arch/arm/mach-davinci/time.c | 400 ----------------------------
+ drivers/clocksource/timer-davinci.c | 8 +-
+ 10 files changed, 60 insertions(+), 483 deletions(-)
+ delete mode 100644 arch/arm/mach-davinci/include/mach/time.h
+ delete mode 100644 arch/arm/mach-davinci/time.c
+~
+~
+
+_______________________________________________
+linux-arm-kernel mailing list
+linux-arm-kernel@lists.infradead.org
+http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
+
diff --git a/patchwork/tests/series/bugs-multiple-content-types.mbox b/patchwork/tests/series/bugs-multiple-content-types.mbox
new file mode 100644
index 000000000..f7006b447
--- /dev/null
+++ b/patchwork/tests/series/bugs-multiple-content-types.mbox
@@ -0,0 +1,172 @@
+From mboxrd@z Thu Jan 1 00:00:00 1970
+Return-Path:
+X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on
+ aws-us-west-2-korg-lkml-1.web.codeaurora.org
+X-Spam-Level:
+X-Spam-Status: No, score=-9.7 required=3.0 tests=HEADER_FROM_DIFFERENT_DOMAINS,
+ INCLUDES_PATCH,MAILING_LIST_MULTI,SIGNED_OFF_BY,SPF_HELO_NONE,SPF_PASS,
+ URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no
+ version=3.4.0
+Received: from mail.kernel.org (mail.kernel.org [198.145.29.99])
+ by smtp.lore.kernel.org (Postfix) with ESMTP id A702DC3A5A2
+ for ; Tue, 20 Aug 2019 01:33:12 +0000 (UTC)
+Received: from vger.kernel.org (vger.kernel.org [209.132.180.67])
+ by mail.kernel.org (Postfix) with ESMTP id 8717B22DA7
+ for ; Tue, 20 Aug 2019 01:33:12 +0000 (UTC)
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+ id S1728887AbfHTBdL (ORCPT );
+ Mon, 19 Aug 2019 21:33:11 -0400
+Received: from szxga05-in.huawei.com ([45.249.212.191]:4731 "EHLO huawei.com"
+ rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP
+ id S1728627AbfHTBdL (ORCPT );
+ Mon, 19 Aug 2019 21:33:11 -0400
+Received: from DGGEMS411-HUB.china.huawei.com (unknown [172.30.72.59])
+ by Forcepoint Email with ESMTP id EF227A58CA1FC4ADCFA3;
+ Tue, 20 Aug 2019 09:33:03 +0800 (CST)
+Received: from localhost.localdomain.localdomain (10.175.113.25) by
+ DGGEMS411-HUB.china.huawei.com (10.3.19.211) with Microsoft SMTP Server id
+ 14.3.439.0; Tue, 20 Aug 2019 09:32:56 +0800
+From: YueHaibing
+To: , ,
+ , ,
+ , , ,
+ ,
+CC: YueHaibing , ,
+ ,
+Subject: [PATCH -next] bpf: Use PTR_ERR_OR_ZERO in xsk_map_inc()
+Date: Tue, 20 Aug 2019 01:36:52 +0000
+Message-ID: <20190820013652.147041-1-yuehaibing@huawei.com>
+X-Mailer: git-send-email 2.20.1
+MIME-Version: 1.0
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7BIT
+X-Originating-IP: [10.175.113.25]
+X-CFilter-Loop: Reflected
+Sender: netdev-owner@vger.kernel.org
+Precedence: bulk
+List-ID:
+X-Mailing-List: netdev@vger.kernel.org
+Archived-At:
+List-Archive:
+List-Post:
+
+Use PTR_ERR_OR_ZERO rather than if(IS_ERR(...)) + PTR_ERR
+
+Signed-off-by: YueHaibing
+---
+ kernel/bpf/xskmap.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/kernel/bpf/xskmap.c b/kernel/bpf/xskmap.c
+index 4cc28e226398..942c662e2eed 100644
+--- a/kernel/bpf/xskmap.c
++++ b/kernel/bpf/xskmap.c
+@@ -21,7 +21,7 @@ int xsk_map_inc(struct xsk_map *map)
+ struct bpf_map *m = &map->map;
+
+ m = bpf_map_inc(m, false);
+- return IS_ERR(m) ? PTR_ERR(m) : 0;
++ return PTR_ERR_OR_ZERO(m);
+ }
+
+ void xsk_map_put(struct xsk_map *map)
+
+
+From mboxrd@z Thu Jan 1 00:00:00 1970
+Return-Path:
+X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on
+ aws-us-west-2-korg-lkml-1.web.codeaurora.org
+X-Spam-Level:
+X-Spam-Status: No, score=-8.2 required=3.0 tests=FROM_EXCESS_BASE64,
+ HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_PATCH,MAILING_LIST_MULTI,SIGNED_OFF_BY,
+ SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_SANE_1 autolearn=unavailable
+ autolearn_force=no version=3.4.0
+Received: from mail.kernel.org (mail.kernel.org [198.145.29.99])
+ by smtp.lore.kernel.org (Postfix) with ESMTP id 02AB1C3A59E
+ for ; Tue, 20 Aug 2019 07:28:35 +0000 (UTC)
+Received: from vger.kernel.org (vger.kernel.org [209.132.180.67])
+ by mail.kernel.org (Postfix) with ESMTP id CC7942082F
+ for ; Tue, 20 Aug 2019 07:28:34 +0000 (UTC)
+Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand
+ id S1729348AbfHTH2e (ORCPT );
+ Tue, 20 Aug 2019 03:28:34 -0400
+Received: from mga18.intel.com ([134.134.136.126]:58020 "EHLO mga18.intel.com"
+ rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP
+ id S1729047AbfHTH2e (ORCPT );
+ Tue, 20 Aug 2019 03:28:34 -0400
+X-Amp-Result: SKIPPED(no attachment in message)
+X-Amp-File-Uploaded: False
+Received: from orsmga007.jf.intel.com ([10.7.209.58])
+ by orsmga106.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 20 Aug 2019 00:28:33 -0700
+X-ExtLoop1: 1
+X-IronPort-AV: E=Sophos;i="5.64,408,1559545200";
+ d="scan'208";a="169001452"
+Received: from arappl-mobl2.ger.corp.intel.com (HELO btopel-mobl.ger.intel.com) ([10.252.53.140])
+ by orsmga007.jf.intel.com with ESMTP; 20 Aug 2019 00:28:27 -0700
+Subject: Re: [PATCH -next] bpf: Use PTR_ERR_OR_ZERO in xsk_map_inc()
+To: YueHaibing , magnus.karlsson@intel.com,
+ jonathan.lemon@gmail.com, ast@kernel.org, daniel@iogearbox.net,
+ kafai@fb.com, songliubraving@fb.com, yhs@fb.com,
+ john.fastabend@gmail.com
+Cc: netdev@vger.kernel.org, bpf@vger.kernel.org,
+ kernel-janitors@vger.kernel.org
+References: <20190820013652.147041-1-yuehaibing@huawei.com>
+From: =?UTF-8?B?QmrDtnJuIFTDtnBlbA==?=
+Message-ID: <93fafdab-8fb3-0f2b-8f36-0cf297db3cd9@intel.com>
+Date: Tue, 20 Aug 2019 09:28:26 +0200
+User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101
+ Thunderbird/60.8.0
+MIME-Version: 1.0
+In-Reply-To: <20190820013652.147041-1-yuehaibing@huawei.com>
+Content-Type: text/plain; charset=utf-8; format=flowed
+Content-Language: en-US
+Content-Transfer-Encoding: 8bit
+Sender: netdev-owner@vger.kernel.org
+Precedence: bulk
+List-ID:
+X-Mailing-List: netdev@vger.kernel.org
+Archived-At:
+List-Archive:
+List-Post:
+
+On 2019-08-20 03:36, YueHaibing wrote:
+> Use PTR_ERR_OR_ZERO rather than if(IS_ERR(...)) + PTR_ERR
+>
+> Signed-off-by: YueHaibing
+> ---
+> kernel/bpf/xskmap.c | 2 +-
+> 1 file changed, 1 insertion(+), 1 deletion(-)
+>
+> diff --git a/kernel/bpf/xskmap.c b/kernel/bpf/xskmap.c
+> index 4cc28e226398..942c662e2eed 100644
+> --- a/kernel/bpf/xskmap.c
+> +++ b/kernel/bpf/xskmap.c
+> @@ -21,7 +21,7 @@ int xsk_map_inc(struct xsk_map *map)
+> struct bpf_map *m = &map->map;
+>
+> m = bpf_map_inc(m, false);
+> - return IS_ERR(m) ? PTR_ERR(m) : 0;
+> + return PTR_ERR_OR_ZERO(m);
+> }
+>
+> void xsk_map_put(struct xsk_map *map)
+>
+
+Acked-by: Björn Töpel
+
+Thanks for the patch!
+
+For future patches: Prefix AF_XDP socket work with "xsk:" and use "PATCH
+bpf-next" to let the developers know what tree you're aiming for.
+
+
+
+Cheers!
+Björn
+
+
+>
+>
+
+
+
diff --git a/patchwork/tests/test_detail.py b/patchwork/tests/test_detail.py
index 5d8534eae..fa3207cfa 100644
--- a/patchwork/tests/test_detail.py
+++ b/patchwork/tests/test_detail.py
@@ -66,6 +66,23 @@ def test_series_dropdown(self):
response,
reverse('series-mbox', kwargs={'series_id': series_.id}))
+ def test_escaping(self):
+ # Warning: this test doesn't guarantee anything - it only tests some
+ # fields
+ unescaped_string = 'blahTESTblah'
+ patch = create_patch()
+ patch.diff = unescaped_string
+ patch.commit_ref = unescaped_string
+ patch.pull_url = unescaped_string
+ patch.name = unescaped_string
+ patch.msgid = unescaped_string
+ patch.headers = unescaped_string
+ patch.content = unescaped_string
+ patch.save()
+ requested_url = reverse('patch-detail', kwargs={'patch_id': patch.id})
+ response = self.client.get(requested_url)
+ self.assertNotIn('TEST'.encode('utf-8'), response.content)
+
class CommentRedirectTest(TestCase):
diff --git a/patchwork/tests/test_events.py b/patchwork/tests/test_events.py
index 70d563de3..c5543bb85 100644
--- a/patchwork/tests/test_events.py
+++ b/patchwork/tests/test_events.py
@@ -42,7 +42,7 @@ def assertEventFields(self, event, parent_type='patch', **fields):
self.assertIsNone(field)
-class PatchCreateTest(_BaseTestCase):
+class PatchCreatedTest(_BaseTestCase):
def test_patch_created(self):
"""No series, so patch dependencies implicitly exist."""
@@ -170,7 +170,7 @@ def test_patch_delegated(self):
self.assertEventFields(events[3], previous_delegate=delegate_b)
-class CheckCreateTest(_BaseTestCase):
+class CheckCreatedTest(_BaseTestCase):
def test_check_created(self):
check = utils.create_check()
@@ -181,7 +181,7 @@ def test_check_created(self):
self.assertEventFields(events[0])
-class CoverCreateTest(_BaseTestCase):
+class CoverCreatedTest(_BaseTestCase):
def test_cover_created(self):
cover = utils.create_cover()
@@ -192,7 +192,7 @@ def test_cover_created(self):
self.assertEventFields(events[0])
-class SeriesCreateTest(_BaseTestCase):
+class SeriesCreatedTest(_BaseTestCase):
def test_series_created(self):
series = utils.create_series()
@@ -201,3 +201,28 @@ def test_series_created(self):
self.assertEqual(events[0].category, Event.CATEGORY_SERIES_CREATED)
self.assertEqual(events[0].project, series.project)
self.assertEventFields(events[0])
+
+
+class SeriesChangedTest(_BaseTestCase):
+
+ def test_series_completed(self):
+ """Validate 'series-completed' events."""
+ series = utils.create_series(total=2)
+
+ # the series has no patches associated with it so it's not yet complete
+ events = _get_events(series=series)
+ self.assertNotIn(Event.CATEGORY_SERIES_COMPLETED,
+ [x.category for x in events])
+
+ # create the second of two patches in the series; series is still not
+ # complete
+ utils.create_series_patch(series=series, number=2)
+ events = _get_events(series=series)
+ self.assertNotIn(Event.CATEGORY_SERIES_COMPLETED,
+ [x.category for x in events])
+
+ # now create the first patch, which will "complete" the series
+ utils.create_series_patch(series=series, number=1)
+ events = _get_events(series=series)
+ self.assertIn(Event.CATEGORY_SERIES_COMPLETED,
+ [x.category for x in events])
diff --git a/patchwork/tests/test_mboxviews.py b/patchwork/tests/test_mboxviews.py
index 8eb3581ad..dabbb99ca 100644
--- a/patchwork/tests/test_mboxviews.py
+++ b/patchwork/tests/test_mboxviews.py
@@ -125,6 +125,21 @@ def test_header_passthrough_listid(self):
header = 'List-Id: Patchwork development '
self._test_header_passthrough(header)
+ def _test_header_dropped(self, header):
+ patch = create_patch(headers=header + '\n')
+ response = self.client.get(reverse('patch-mbox', args=[patch.id]))
+ self.assertNotContains(response, header)
+
+ def test_header_dropped_content_transfer_encoding(self):
+ """Validate dropping of 'Content-Transfer-Encoding' header."""
+ header = 'Content-Transfer-Encoding: quoted-printable'
+ self._test_header_dropped(header)
+
+ def test_header_dropped_content_type_multipart_signed(self):
+ """Validate dropping of 'Content-Type=multipart/signed' header."""
+ header = 'Content-Type: multipart/signed'
+ self._test_header_dropped(header)
+
def test_patchwork_id_header(self):
"""Validate inclusion of generated 'X-Patchwork-Id' header."""
patch = create_patch()
diff --git a/patchwork/tests/test_parser.py b/patchwork/tests/test_parser.py
index 5ba06c0f3..216ab4813 100644
--- a/patchwork/tests/test_parser.py
+++ b/patchwork/tests/test_parser.py
@@ -36,6 +36,7 @@
from patchwork.parser import clean_subject
from patchwork.parser import get_or_create_author
from patchwork.parser import find_patch_content as find_content
+from patchwork.parser import find_comment_content
from patchwork.parser import find_project
from patchwork.parser import find_series
from patchwork.parser import parse_mail as _parse_mail
@@ -582,6 +583,24 @@ def test_git_pull_with_diff(self):
diff.startswith('diff --git a/arch/x86/include/asm/smp.h'),
diff)
+ def test_git_pull_newline_in_url(self):
+ diff, message = self._find_content(
+ '0023-git-pull-request-newline-in-url.mbox')
+ pull_url = parse_pull_request(message)
+ self.assertEqual(
+ 'https://git.kernel.org/pub/scm/linux/kernel/git/matthias.bgg/'
+ 'linux.git/ tags/v5.4-next-soc',
+ pull_url)
+
+ def test_git_pull_trailing_space(self):
+ diff, message = self._find_content(
+ '0024-git-pull-request-trailing-space.mbox')
+ pull_url = parse_pull_request(message)
+ self.assertEqual(
+ 'git://git.kernel.org/pub/scm/linux/kernel/git/nsekhar/'
+ 'linux-davinci.git tags/davinci-for-v5.6/soc',
+ pull_url)
+
def test_git_rename(self):
diff, _ = self._find_content('0008-git-rename.mbox')
self.assertTrue(diff is not None)
@@ -596,6 +615,16 @@ def test_git_rename_with_diff(self):
self.assertEqual(diff.count("\nrename to "), 2)
self.assertEqual(diff.count('\n-a\n+b'), 1)
+ def test_git_new_empty_file(self):
+ diff, message = self._find_content('0021-git-empty-new-file.mbox')
+ self.assertTrue(diff is not None)
+ self.assertTrue(message is not None)
+
+ def test_git_mode_change(self):
+ diff, message = self._find_content('0022-git-mode-change.mbox')
+ self.assertTrue(diff is not None)
+ self.assertTrue(message is not None)
+
def test_cvs_format(self):
diff, message = self._find_content('0007-cvs-format-diff.mbox')
self.assertTrue(diff.startswith('Index'))
@@ -632,6 +661,14 @@ def test_no_subject(self):
self.assertTrue(diff is not None)
self.assertTrue(message is not None)
+ def test_html_multipart(self):
+ """Validate parsing a mail with multiple parts."""
+ diff, message = self._find_content('0019-multipart-patch.mbox')
+ self.assertTrue(diff is not None)
+ self.assertTrue(message is not None)
+ self.assertFalse('
`_ is
+ now supported.
diff --git a/releasenotes/notes/django-rest-framework-3-7-bc6ad5df8bc54afc.yaml b/releasenotes/notes/django-rest-framework-3-7-bc6ad5df8bc54afc.yaml
new file mode 100644
index 000000000..4bf92c999
--- /dev/null
+++ b/releasenotes/notes/django-rest-framework-3-7-bc6ad5df8bc54afc.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ `Django REST Framework 3.7
+ `_ is now
+ supported.
diff --git a/releasenotes/notes/django-rest-framework-3-8-23865db833b4d188.yaml b/releasenotes/notes/django-rest-framework-3-8-23865db833b4d188.yaml
new file mode 100644
index 000000000..dc2d2c8f8
--- /dev/null
+++ b/releasenotes/notes/django-rest-framework-3-8-23865db833b4d188.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ `Django REST Framework 3.8
+ `_ is now
+ supported.
diff --git a/releasenotes/notes/django-rest-framework-3-9-0afb78322dd82367.yaml b/releasenotes/notes/django-rest-framework-3-9-0afb78322dd82367.yaml
new file mode 100644
index 000000000..e65531808
--- /dev/null
+++ b/releasenotes/notes/django-rest-framework-3-9-0afb78322dd82367.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ `Django REST Framework 3.9
+ `_ is now
+ supported.
diff --git a/releasenotes/notes/faster-api-db-queries-a1b5face736fe5b8.yaml b/releasenotes/notes/faster-api-db-queries-a1b5face736fe5b8.yaml
new file mode 100644
index 000000000..6c4f61140
--- /dev/null
+++ b/releasenotes/notes/faster-api-db-queries-a1b5face736fe5b8.yaml
@@ -0,0 +1,4 @@
+fixes:
+ - |
+ Queries to the REST API with filters are now significantly faster: slow
+ database queries were reworked.
diff --git a/releasenotes/notes/issue-110-a5bb3184bf831280.yaml b/releasenotes/notes/issue-110-a5bb3184bf831280.yaml
new file mode 100644
index 000000000..16a0fa5f0
--- /dev/null
+++ b/releasenotes/notes/issue-110-a5bb3184bf831280.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ Assigning maintained projects when creating a new user in the admin page
+ was causing an error. This is now resolved.
diff --git a/releasenotes/notes/issue-197-4f7594db1e4c9887.yaml b/releasenotes/notes/issue-197-4f7594db1e4c9887.yaml
new file mode 100644
index 000000000..2777fbc2f
--- /dev/null
+++ b/releasenotes/notes/issue-197-4f7594db1e4c9887.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - |
+ Long headers can be wrapped using CRLF followed by WSP (whitespace). This
+ whitespace was not being stripped, resulting in errant whitespace being
+ saved for the patch subject. This is resolved though existing patches and
+ cover letters will need to be updated manually.
diff --git a/releasenotes/notes/issue-203-ece01ae49ceac712.yaml b/releasenotes/notes/issue-203-ece01ae49ceac712.yaml
new file mode 100644
index 000000000..d8609cc0e
--- /dev/null
+++ b/releasenotes/notes/issue-203-ece01ae49ceac712.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ An issue that resulted in checks for all patches being listed for each
+ patch is resolved.
+ (`#203 `__)
diff --git a/releasenotes/notes/issue-216-d3bf9d1baa100f74.yaml b/releasenotes/notes/issue-216-d3bf9d1baa100f74.yaml
new file mode 100644
index 000000000..c3756aa00
--- /dev/null
+++ b/releasenotes/notes/issue-216-d3bf9d1baa100f74.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ An issue that prevented updating of delegates using the REST API is
+ resolved. (`#216 `__)
diff --git a/releasenotes/notes/issue-217-676f3f737e46320e.yaml b/releasenotes/notes/issue-217-676f3f737e46320e.yaml
new file mode 100644
index 000000000..ecf4a118f
--- /dev/null
+++ b/releasenotes/notes/issue-217-676f3f737e46320e.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - |
+ A project's ``list_email``, ``list_id`` and ``link_name`` fields can no
+ longer be updated via the REST API. This is a superuser-only operation
+ that, for now, should only be done via the admin interface.
+ (`#217 `__)
diff --git a/releasenotes/notes/issue-223-0757db6ac886374f.yaml b/releasenotes/notes/issue-223-0757db6ac886374f.yaml
new file mode 100644
index 000000000..e7a5c5509
--- /dev/null
+++ b/releasenotes/notes/issue-223-0757db6ac886374f.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ It's now possible to assign patches to existing bundles from a user's TODO
+ page.
+ (`#213 `__)
diff --git a/releasenotes/notes/issue-224-8f1c4207aa273ac6.yaml b/releasenotes/notes/issue-224-8f1c4207aa273ac6.yaml
new file mode 100644
index 000000000..f6e9ccaa1
--- /dev/null
+++ b/releasenotes/notes/issue-224-8f1c4207aa273ac6.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ API resources with embedded series were not showing the ``web_url`` value
+ for these series. This is now shown.
diff --git a/releasenotes/notes/issue-225-94215600c1b23f6e.yaml b/releasenotes/notes/issue-225-94215600c1b23f6e.yaml
new file mode 100644
index 000000000..035e38d83
--- /dev/null
+++ b/releasenotes/notes/issue-225-94215600c1b23f6e.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ Showing comments for a non-existant patch or cover letter was returning an
+ empty response instead of a HTTP 404. This issue is resolved for both
+ resources.
diff --git a/releasenotes/notes/issue-226-27ea72266d3ee9ac.yaml b/releasenotes/notes/issue-226-27ea72266d3ee9ac.yaml
new file mode 100644
index 000000000..8f891e04b
--- /dev/null
+++ b/releasenotes/notes/issue-226-27ea72266d3ee9ac.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - |
+ Showing checks for a non-existant patch was returning an empty response
+ instead of a HTTP 404. Similarly, attempting to create a new check against
+ this patch would result in a HTTP 5xx error instead of a HTTP 404. Both
+ issues are now resolved.
diff --git a/releasenotes/notes/issue-237-48b9442c31e74b9d.yaml b/releasenotes/notes/issue-237-48b9442c31e74b9d.yaml
new file mode 100644
index 000000000..541f44a0c
--- /dev/null
+++ b/releasenotes/notes/issue-237-48b9442c31e74b9d.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ Fields added in API v1.1 are now consistently excluded when requesting API
+ v1.0, as was intended.
diff --git a/releasenotes/notes/issue-273-2bb8d2bf5fa9a57e.yaml b/releasenotes/notes/issue-273-2bb8d2bf5fa9a57e.yaml
new file mode 100644
index 000000000..506de0db9
--- /dev/null
+++ b/releasenotes/notes/issue-273-2bb8d2bf5fa9a57e.yaml
@@ -0,0 +1,8 @@
+---
+fixes:
+ - |
+ `#197`__ was the result of a issue with OzLabs instance and not Patchwork
+ itself, and the fix included actually ended up corrupting subjects for
+ everyone. It has now been reverted.
+
+ __ https://github.com/getpatchwork/patchwork/issues/197
diff --git a/releasenotes/notes/issue-277-5bfda7ad1f72f267.yaml b/releasenotes/notes/issue-277-5bfda7ad1f72f267.yaml
new file mode 100644
index 000000000..da9460d60
--- /dev/null
+++ b/releasenotes/notes/issue-277-5bfda7ad1f72f267.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ The ``pwclientrc`` samples generated by Patchwork were previously not valid
+ INI files. This issue is resolved. (`#277
+ `__)
diff --git a/releasenotes/notes/issue-60-9d4fc111242f7db6.yaml b/releasenotes/notes/issue-60-9d4fc111242f7db6.yaml
new file mode 100644
index 000000000..798865927
--- /dev/null
+++ b/releasenotes/notes/issue-60-9d4fc111242f7db6.yaml
@@ -0,0 +1,8 @@
+---
+fixes:
+ - |
+ In the past, Patchwork used to support filtering patches that weren't
+ delegated to anyone. This feature was removed in v1.1.0, as part of a patch
+ designed to support delegation to anyone. However, that feature didn't scale
+ and was later removed. The ability to delegate to anyone is now itself
+ re-introduced.
diff --git a/releasenotes/notes/issue-78-accd1f9db45a2b71.yaml b/releasenotes/notes/issue-78-accd1f9db45a2b71.yaml
new file mode 100644
index 000000000..ba805a338
--- /dev/null
+++ b/releasenotes/notes/issue-78-accd1f9db45a2b71.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ The delegate and submitter fields will remain populated when moving
+ between different pages or changing filters.
+ (`#78 `__)
diff --git a/releasenotes/notes/sql-fix-table-lists-77667621052b2f72.yaml b/releasenotes/notes/sql-fix-table-lists-77667621052b2f72.yaml
new file mode 100644
index 000000000..8eaa9f48b
--- /dev/null
+++ b/releasenotes/notes/sql-fix-table-lists-77667621052b2f72.yaml
@@ -0,0 +1,4 @@
+---
+fixes:
+ - |
+ An sql error was fixed in `lib/sql/grant-all.postgres.sql`.
diff --git a/tox.ini b/tox.ini
index a64d95875..ed9e77f96 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,9 +10,11 @@ deps =
django19: django>=1.9,<1.10
django110: django>=1.10,<1.11
django111: django>=1.11,<2.0
- django{18,19,110}: djangorestframework>=3.4,<3.7
- django111: djangorestframework>=3.6,<3.7
- django{18,19,110,111}: django-filter>=1.0,<1.1
+ django{18,19}: djangorestframework>=3.4,<3.7
+ django110: djangorestframework>=3.4,<3.9
+ django111: djangorestframework>=3.6,<3.10
+ django18: django-filter>=1.0,<1.1
+ django{19,110,111}: django-filter>=1.0,<1.2
setenv =
DJANGO_SETTINGS_MODULE = patchwork.settings.dev
PYTHONDONTWRITEBYTECODE = 1
@@ -39,7 +41,12 @@ deps = flake8
commands = flake8 {posargs} patchwork patchwork/bin/pwclient
[flake8]
-ignore = E129, F405
+# Some rules are ignored as their use makes the code more difficult to read:
+#
+# E129 visually indented line with same indent as next logical line
+# F405 'name' may be undefined, or defined from star imports: 'module'
+# W504 line break after binary operator
+ignore = E129, F405, W504
exclude = ./patchwork/migrations
[testenv:docs]