...
 
Commits (5)
  • Andrei Vagin's avatar
    permissions: add a fast path for non-authenticated users · 142e8fab
    Andrei Vagin authored
    patchwork returns a following back-trace without this patch:
    
    Internal Server Error: /patch/858/
    Traceback (most recent call last):
      File "/usr/lib/python2.7/site-packages/django/core/handlers/base.py", line 132, in get_response
        response = wrapped_callback(request, *callback_args, **callback_kwargs)
      File "/srv/patchwork/lib/apache2/../../patchwork/views/patch.py", line 34, in patch
        editable = Can(request.user).edit(patch)
      File "/srv/patchwork/lib/apache2/../../patchwork/permissions.py", line 38, in edit
        can.edit(patch.project)))
      File "/srv/patchwork/lib/apache2/../../patchwork/permissions.py", line 32, in edit
        project in self.user.profile.maintainer_projects.all())
      File "/usr/lib/python2.7/site-packages/django/utils/functional.py", line 226, in inner
        return func(self._wrapped, *args)
    AttributeError: 'AnonymousUser' object has no attribute 'profile'
    
    Fixes: e876b068 ("permissions: Introduce central point for defining permissions")
    142e8fab
  • Arkadiusz Hiler's avatar
    PEP8: Massage the code into conformance · 2a4a395a
    Arkadiusz Hiler authored
    A lot of minor changes.
    
    W504 is getting ignored, as we break the line after the binary operator
    throughout the code base (alternative would be W503 - break before the
    operator).
    Signed-off-by: Arkadiusz Hiler's avatarArkadiusz Hiler <arkadiusz.hiler@intel.com>
    2a4a395a
  • Arkadiusz Hiler's avatar
    api: Make /newrevision/ copy a revision and mark it as rerun · a159dcdc
    Arkadiusz Hiler authored
    Up until this patch, rerunning was just creating a 'newrevision' event
    for the existing revision, effectively queuing it again. Any incoming
    results would overwrite the existing ones.
    
    Now, whenever rerun is hit, the last revision will be duplicated (no
    results overwriting!), bumping "Last Update" time for the series. We
    also mark such series as a rerun so we will be able to display it for
    the users / expose it in the API.
    Signed-off-by: Arkadiusz Hiler's avatarArkadiusz Hiler <arkadiusz.hiler@intel.com>
    a159dcdc
  • Arkadiusz Hiler's avatar
    a7535e38
  • Arkadiusz Hiler's avatar
    Present series state to the user · 9943ab2c
    Arkadiusz Hiler authored
    Include information that series is not complete (i.e. we have received
    less patches than expected, basing on the [xx/yy] tag) and that series
    looks "strange" (i.e. ordering may is off or we have got too many
    patches).
    
    Changes in logic:
    If we get more patches than expected, *do not* reset status to
    incomplete series, we've got the event for queued already anyway.
    Just consider it "strange".
    
    Also, let's include information which revision we are rerunning on the
    rerun button.
    Signed-off-by: Arkadiusz Hiler's avatarArkadiusz Hiler <arkadiusz.hiler@intel.com>
    9943ab2c
......@@ -761,7 +761,7 @@ class GitPatchwork(object):
try:
with open(ts_filename) as ts_file:
since = ts_file.read()
except IOError as e:
except IOError:
pass
if self.cmd.since:
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2018-11-27 20:31
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('patchwork', '0028_auto_20180220_2246'),
]
operations = [
migrations.AddField(
model_name='seriesrevision',
name='is_rerun',
field=models.BooleanField(default=False),
),
]
......@@ -296,7 +296,7 @@ class PatchManager(models.Manager):
def filename(name, ext):
fname_re = re.compile('[^-_A-Za-z0-9\.]+')
fname_re = re.compile(r'[^-_A-Za-z0-9\.]+')
str = fname_re.sub('-', name)
return str.strip('-') + ext
......@@ -611,6 +611,7 @@ class SeriesRevision(models.Model):
state_summary = jsonfield.JSONField(null=True)
test_state = models.SmallIntegerField(choices=TestState.STATE_CHOICES,
null=True, blank=True)
is_rerun = models.BooleanField(default=False)
class Meta:
unique_together = [('series', 'version')]
......@@ -629,10 +630,37 @@ class SeriesRevision(models.Model):
order=order)
sp.save()
revision_complete = self.patches.count() == self.n_patches
if revision_complete:
if self.patches_count == self.n_patches:
series_revision_complete.send(sender=self.__class__, revision=self)
@property
def patches_count(self):
return self.patches.count()
@property
def is_complete(self):
return self.patches_count >= self.n_patches
@property
def is_strange(self):
if self.patches_count > self.n_patches:
return True
names = [patch.name for patch in self.ordered_patches()]
# one patch "series", not much to break
if len(names) == 1:
return False
for index, name in enumerate(names):
match = re.search(r"\[(\d+)/(\d+)\]", name)
if not match: # we have a patch without proper numbering
return True
if (index + 1) != int(match.group(1)): # numbering is off
return True
return False
def duplicate_meta(self):
new = SeriesRevision.objects.get(pk=self.pk)
new.pk = None
......@@ -898,8 +926,7 @@ def _revision_update_state(revision):
} for s in summary]
# revision not yet complete
revision_complete = revision.patches.count() == revision.n_patches
if not revision_complete:
if not revision.is_complete:
revision.state = RevisionState.INCOMPLETE
# initial state
......
......@@ -29,8 +29,8 @@ import re
from django.utils.six.moves import map
_hunk_re = re.compile('^\@\@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? \@\@')
_filename_re = re.compile('^(---|\+\+\+) (\S+)')
_hunk_re = re.compile(r'^\@\@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? \@\@')
_filename_re = re.compile(r'^(---|\+\+\+) (\S+)')
def parse_patch(text):
......@@ -126,7 +126,7 @@ def parse_patch(text):
buf = ''
state = 2
elif hunk and line.startswith('\ No newline at end of file'):
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
......@@ -145,7 +145,7 @@ def parse_patch(text):
lc[0] -= 1
elif line.startswith('+'):
lc[1] -= 1
elif line.startswith('\ No newline at end of file'):
elif line.startswith('\\ No newline at end of file'):
# Special case: Not included as part of the hunk's line count
pass
else:
......
......@@ -25,6 +25,9 @@ class Can:
self.user = user
def edit(self, obj):
if not self.user.is_authenticated():
return False
can = self
if isinstance(obj, Project):
project = obj
......
......@@ -50,7 +50,7 @@ $(document).ready(function() {
{% block body %}
<h1>{{ series.name }}</h1>
=
<div class="core-info">
<span>Submitted by {{ series.submitter|personify:project }} on {{ series.submitted }}</span>
</div>
......@@ -97,10 +97,17 @@ $(document).ready(function() {
<div id="retest-latest-container" class="btn-group" style="float: right;"
data-disabled-title="You have to be logged in and be the submitter or the project maintainer.">
{% if series.last_revision %}
<button class="btn btn-info" id="retest-latest"
data-series="{{ series.id }}" data-revision="{{ series.last_revision.version }}">
test latest revision again
test revision {{ series.last_revision.version }} again
</button>
{% else %}
<button class="btn btn-danger disabled" id="retest-latest"
data-series="{{ series.id }}" data-revision="{{ series.last_revision.version }}">
no complete revisions to test!
</button>
{% endif %}
</div>
<ul class="nav nav-pills small-pills" data-tabs="tabs" role="tablist">
......@@ -116,6 +123,22 @@ $(document).ready(function() {
<div role="tabpanel" id="rev{{ revision.version }}"
class="tab-pane fade{% if forloop.last %} in active{% endif %}">
{% if revision.is_rerun %}
<div class="alert alert-info">
<strong>THIS SERIES REVISION IS A RERUN.</strong> Please don't overuse the "test again" button.
</div>
{% endif %}
{% if not revision.is_complete %}
<div class="alert alert-danger">
<strong>SERIES REVISION IS NOT COMPLETE.</strong> We've got {{ revision.patches_count }} out of {{ revision.n_patches }} expected patches.
</div>
{% elif revision.is_strange %}
<div class="alert alert-warning">
<strong>SERIES REVISION LOOKS STRANGE.</strong> Please double-check patch list and the ordering before proceeding.
</div>
{% endif %}
<h3>Patches <a href="{% url 'seriesrevision-mbox' series.pk revision.version %}" >download mbox</a></h3>
<div class="well transparent">
......
......@@ -36,23 +36,23 @@ def _compile(t):
_patch_span_res = list(map(_compile, [
('^(Index:?|diff|\-\-\-|\+\+\+|\*\*\*) .*$', 'p_header'),
('^\+.*$', 'p_add'),
('^-.*$', 'p_del'),
('^!.*$', 'p_mod'),
(r'^(Index:?|diff|\-\-\-|\+\+\+|\*\*\*) .*$', 'p_header'),
(r'^\+.*$', 'p_add'),
(r'^-.*$', 'p_del'),
(r'^!.*$', 'p_mod'),
]))
_patch_chunk_re = \
re.compile('^(@@ \-\d+(?:,\d+)? \+\d+(?:,\d+)? @@)(.*)$', re.M | re.I)
re.compile(r'^(@@ \-\d+(?:,\d+)? \+\d+(?:,\d+)? @@)(.*)$', re.M | re.I)
_comment_span_res = list(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'),
(r'^\s*Signed-off-by: .*$', 'signed-off-by'),
(r'^\s*Acked-by: .*$', 'acked-by'),
(r'^\s*Nacked-by: .*$', 'nacked-by'),
(r'^\s*Tested-by: .*$', 'tested-by'),
(r'^\s*Reviewed-by: .*$', 'reviewed-by'),
(r'^\s*From: .*$', 'from'),
(r'^\s*&gt;.*$', 'quote'),
]))
_span = '<span class="%s">%s</span>'
......
......@@ -91,7 +91,7 @@ class PatchOrderTest(TestCase):
patch.save()
def _extract_patch_ids(self, response):
id_re = re.compile('<tr id="patch_row:(\d+)"')
id_re = re.compile(r'<tr id="patch_row:(\d+)"')
ids = [int(m.group(1))
for m in id_re.finditer(response.content.decode())]
return ids
......
......@@ -258,9 +258,9 @@ class UserProfileOptoutFormTest(TestCase):
self.url = reverse('user')
self.optout_url = reverse('mail_optout')
self.optin_url = reverse('mail_optin')
self.form_re_template = ('<form\s+[^>]*action="%(url)s"[^>]*>'
'.*?<input\s+[^>]*value="%(email)s"[^>]*>.*?'
'</form>')
self.form_re_template = (r'<form\s+[^>]*action="%(url)s"[^>]*>'
r'.*?<input\s+[^>]*value="%(email)s"[^>]*>.*?'
r'</form>')
self.secondary_email = 'test2@example.com'
self.user = create_user()
......
......@@ -657,12 +657,13 @@ class NoNewlineAtEndOfFilePatchTest(MBoxPatchTest):
'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'))
'\\ 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'))
'\\ No newline at end of file'))
# Confirm we got both markers
self.assertEqual(2, patch.content.count('\ No newline at end of file'))
self.assertEqual(2,
patch.content.count('\\ No newline at end of file'))
class DelegateRequestTest(TestCase):
......
......@@ -395,6 +395,14 @@ class EventTest(APITestBase):
self.assertEqual(403, ret.status_code)
self.assertEqual(events_before['count'], events_after['count'])
def testRetestApiEndpointCreatesNewRerunRevision(self):
revisions_before = list(self.series.revisions())
self.post(NEW_REV_URL, user=self.maintainer)
revisions_after = list(self.series.revisions())
self.assertEqual(len(revisions_before) + 1, len(revisions_after))
self.assertTrue(revisions_after[-1].is_rerun)
def testPullRequestEvent(self):
submitter = Person()
submitter.save()
......
......@@ -110,6 +110,61 @@ class GeneratedSeriesTest(SeriesTest):
return (series, mails)
class RevisionStatus(GeneratedSeriesTest):
@property
def series(self):
return Series.objects.all()[0]
@property
def last_revision(self):
return list(self.series.revisions())[-1]
def setUp(self):
(self.test_series, self.mails) = self._create_series(3)
def testFreshSeriesWithoutAllMailsShouldNotBeComple(self):
self.test_series.insert(self.mails[:-2])
self.assertFalse(self.last_revision.is_complete)
self.assertFalse(self.last_revision.is_strange)
def testSeriesWithAllMailsShouldBeComplete(self):
self.test_series.insert(self.mails)
self.assertTrue(self.last_revision.is_complete)
self.assertFalse(self.last_revision.is_strange)
def testSeriesWithExtraEmailShouldBeCompleteAndStrange(self):
extra = self.test_series.create_patch(self.test_series.n_patches + 1,
in_reply_to=self.mails[0])
self.test_series.insert(self.mails)
self.test_series.insert([extra])
self.assertTrue(self.last_revision.is_complete)
self.assertTrue(self.last_revision.is_strange)
def testSeriesWithExtraEmailShouldNotCreateNewCompletionEvent(self):
extra = self.test_series.create_patch(self.test_series.n_patches + 1,
in_reply_to=self.mails[0])
self.test_series.insert(self.mails)
event_count = EventLog.objects.all().count()
self.test_series.insert([extra])
self.assertEquals(EventLog.objects.all().count(), event_count)
def testSeriesWithWrongOrderingShouldBeCompleteAndStrange(self):
# replace patch 1/3 with a patch that is numbered 2/3
extra = self.test_series.create_patch(2, self.mails[1])
self.test_series.insert(self.mails)
self.test_series.insert([extra])
self.assertTrue(self.last_revision.is_complete)
self.assertTrue(self.last_revision.is_strange)
class BasicGeneratedSeriesTests(GeneratedSeriesTest):
def testInsertion(self):
......
......@@ -27,7 +27,7 @@ from django.http import HttpResponse
from patchwork.tasks import send_reviewer_notification
from patchwork.models import (Project, Series, SeriesRevision, Patch, EventLog,
State, Test, TestResult, TestState, Person,
RevisionState, Event)
RevisionState)
from rest_framework import (views, viewsets, mixins, filters, permissions,
status)
from rest_framework.authentication import BasicAuthentication
......@@ -315,11 +315,9 @@ class RevisionViewSet(mixins.ListModelMixin, ListMixin,
raise PermissionDenied
# log event
new_revision = Event.objects.get(name='series-new-revision')
log = EventLog(event=new_revision, series=rev.series,
user=request.user,
parameters={'revision': rev.version})
log.save()
new_rev = rev.duplicate()
new_rev.is_rerun = True
new_rev.save()
return HttpResponse()
......
......@@ -23,7 +23,7 @@ deps = flake8
commands = flake8 {posargs:patchwork patchwork/bin/pwclient git-pw/git-pw}
[flake8]
ignore = E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E203,E241,E251,E402,H405,F405
ignore = E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E203,E241,E251,E402,H405,F405,W504
exclude = ./patchwork/migrations
[testenv:lint]
......