Commit ad1dd2d4 authored by eric@webkit.org's avatar eric@webkit.org
Browse files

2010-10-26 Eric Seidel <eric@webkit.org>

        Reviewed by Ojan Vafai.

        Remove rietveld code now that it's unused
        https://bugs.webkit.org/show_bug.cgi?id=48359

        Was cool that we added this support, but now that it's
        not used it makes little sense to keep it around.
        We can always restore this code from SVN if we need it.

        * Scripts/webkitpy/common/config/__init__.py:
        * Scripts/webkitpy/common/net/bugzilla.py:
        * Scripts/webkitpy/common/net/bugzilla_unittest.py:
        * Scripts/webkitpy/common/net/rietveld.py: Removed.
        * Scripts/webkitpy/common/net/rietveld_unittest.py: Removed.
        * Scripts/webkitpy/thirdparty/__init__.py:
        * Scripts/webkitpy/tool/commands/download.py:
        * Scripts/webkitpy/tool/commands/download_unittest.py:
        * Scripts/webkitpy/tool/commands/queues.py:
        * Scripts/webkitpy/tool/commands/queues_unittest.py:
        * Scripts/webkitpy/tool/main.py:
        * Scripts/webkitpy/tool/mocktool.py:
        * Scripts/webkitpy/tool/steps/__init__.py:
        * Scripts/webkitpy/tool/steps/postcodereview.py: Removed.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@70570 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 00a87554
2010-10-26 Eric Seidel <eric@webkit.org>
Reviewed by Ojan Vafai.
Remove rietveld code now that it's unused
https://bugs.webkit.org/show_bug.cgi?id=48359
Was cool that we added this support, but now that it's
not used it makes little sense to keep it around.
We can always restore this code from SVN if we need it.
* Scripts/webkitpy/common/config/__init__.py:
* Scripts/webkitpy/common/net/bugzilla.py:
* Scripts/webkitpy/common/net/bugzilla_unittest.py:
* Scripts/webkitpy/common/net/rietveld.py: Removed.
* Scripts/webkitpy/common/net/rietveld_unittest.py: Removed.
* Scripts/webkitpy/thirdparty/__init__.py:
* Scripts/webkitpy/tool/commands/download.py:
* Scripts/webkitpy/tool/commands/download_unittest.py:
* Scripts/webkitpy/tool/commands/queues.py:
* Scripts/webkitpy/tool/commands/queues_unittest.py:
* Scripts/webkitpy/tool/main.py:
* Scripts/webkitpy/tool/mocktool.py:
* Scripts/webkitpy/tool/steps/__init__.py:
* Scripts/webkitpy/tool/steps/postcodereview.py: Removed.
2010-10-26 Anders Carlsson <andersca@apple.com>
 
Reviewed by Sam Weinig.
......
# Required for Python to search this directory for module files
import re
codereview_server_host = "wkrietveld.appspot.com"
codereview_server_url = "https://%s/" % codereview_server_host
......@@ -113,9 +113,6 @@ class Attachment(object):
def commit_queue(self):
return self._attachment_dictionary.get("commit-queue")
def in_rietveld(self):
return self._attachment_dictionary.get("in-rietveld")
def url(self):
# FIXME: This should just return
# self._bugzilla().attachment_url_for_id(self.id()). scm_unittest.py
......@@ -221,9 +218,6 @@ class Bug(object):
# a valid committer.
return filter(lambda patch: patch.committer(), patches)
def in_rietveld_queue_patches(self):
return [patch for patch in self.patches() if patch.in_rietveld() == None]
# A container for all of the logic for making and parsing buzilla queries.
class BugzillaQueries(object):
......@@ -287,16 +281,6 @@ class BugzillaQueries(object):
return sum([self._fetch_bug(bug_id).commit_queued_patches()
for bug_id in self.fetch_bug_ids_from_commit_queue()], [])
def fetch_first_patch_from_rietveld_queue(self):
# rietveld-queue processes all patches that don't have in-rietveld set.
query_url = "buglist.cgi?query_format=advanced&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&field0-0-0=flagtypes.name&type0-0-0=notsubstring&value0-0-0=in-rietveld&field0-1-0=attachments.ispatch&type0-1-0=equals&value0-1-0=1&order=Last+Changed&field0-2-0=attachments.isobsolete&type0-2-0=equals&value0-2-0=0"
bugs = self._fetch_bug_ids_advanced_query(query_url)
if not len(bugs):
return None
patches = self._fetch_bug(bugs[0]).in_rietveld_queue_patches()
return patches[0] if len(patches) else None
def _fetch_bug_ids_from_review_queue(self):
review_queue_url = "buglist.cgi?query_format=advanced&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&field0-0-0=flagtypes.name&type0-0-0=equals&value0-0-0=review?"
return self._fetch_bug_ids_advanced_query(review_queue_url)
......@@ -501,8 +485,6 @@ class Bugzilla(object):
attachment['type'] = self._string_contents(element.find('type'))
self._parse_attachment_flag(
element, 'review', attachment, 'reviewer_email')
self._parse_attachment_flag(
element, 'in-rietveld', attachment, 'rietveld_uploader_email')
self._parse_attachment_flag(
element, 'commit-queue', attachment, 'committer_email')
return attachment
......@@ -767,8 +749,6 @@ class Bugzilla(object):
return self.browser.find_control(type='select', nr=0)
elif flag_name == "commit-queue":
return self.browser.find_control(type='select', nr=1)
elif flag_name == "in-rietveld":
return self.browser.find_control(type='select', nr=2)
raise Exception("Don't know how to find flag named \"%s\"" % flag_name)
def clear_attachment_flags(self,
......
......@@ -83,11 +83,6 @@ class BugzillaTest(unittest.TestCase):
status="+"
setter="two@test.com"
/>
<flag name="in-rietveld"
id="17933"
status="+"
setter="three@test.com"
/>
</attachment>
'''
_expected_example_attachment_parsing = {
......@@ -103,8 +98,6 @@ class BugzillaTest(unittest.TestCase):
'reviewer_email' : 'one@test.com',
'commit-queue' : '+',
'committer_email' : 'two@test.com',
'in-rietveld': '+',
'rietveld_uploader_email': 'three@test.com',
'attacher_email' : 'christian.plesner.hansen@gmail.com',
}
......
# Copyright (c) 2010 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import logging
import os
import re
import stat
import webkitpy.common.config as config
from webkitpy.common.system.deprecated_logging import log
from webkitpy.common.system.executive import ScriptError
import webkitpy.thirdparty.autoinstalled.rietveld.upload as upload
class Rietveld(object):
def __init__(self, executive, dryrun=False):
self.dryrun = dryrun
self._executive = executive
def url_for_issue(self, codereview_issue):
if not codereview_issue:
return None
return "%s%s" % (config.codereview_server_url, codereview_issue)
def post(self, diff, patch_id, codereview_issue, message=None, cc=None):
if not message:
raise ScriptError("Rietveld requires a message.")
# Rietveld has a 100 character limit on message length.
if len(message) > 100:
message = message[:100]
args = [
# First argument is empty string to mimic sys.argv.
"",
"--assume_yes",
"--server=%s" % config.codereview_server_host,
"--message=%s" % message,
"--webkit_patch_id=%s" % patch_id,
]
if codereview_issue:
args.append("--issue=%s" % codereview_issue)
if cc:
args.append("--cc=%s" % cc)
if self.dryrun:
log("Would have run %s" % args)
return
# Use RealMain instead of calling upload from the commandline so that
# we can pass in the diff ourselves. Otherwise, upload will just use
# git diff for git checkouts, which doesn't respect --git-commit.
issue, patchset = upload.RealMain(args, data=diff)
return issue
# Copyright (c) 2010 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest
from webkitpy.common.net.rietveld import Rietveld
from webkitpy.thirdparty.mock import Mock
class RietveldTest(unittest.TestCase):
def test_url_for_issue(self):
rietveld = Rietveld(Mock())
self.assertEqual(rietveld.url_for_issue(34223),
"https://wkrietveld.appspot.com/34223")
......@@ -77,12 +77,6 @@ installer.install(url="http://pypi.python.org/packages/source/p/pep8/pep8-0.5.0.
installer.install(url="http://www.adambarth.com/webkit/eliza",
target_name="eliza.py")
rietveld_dir = os.path.join(autoinstalled_dir, "rietveld")
installer = AutoInstaller(target_dir=rietveld_dir)
installer.install(url="http://webkit-rietveld.googlecode.com/svn/trunk/upload_v26/upload.py",
target_name="upload.py")
# Since irclib and ircbot are two top-level packages, we need to import
# them separately. We group them into an irc package for better
# organization purposes.
......
......@@ -221,18 +221,6 @@ class BuildAndTestAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsM
]
class PostAttachmentToRietveld(AbstractPatchSequencingCommand, ProcessAttachmentsMixin):
name = "post-attachment-to-rietveld"
help_text = "Uploads a bugzilla attachment to rietveld"
arguments_names = "ATTACHMENTID"
main_steps = [
steps.CleanWorkingDirectory,
steps.Update,
steps.ApplyPatch,
steps.PostCodeReview,
]
class AbstractPatchApplyingCommand(AbstractPatchSequencingCommand):
prepare_steps = [
steps.EnsureLocalCommitIfNeeded,
......
......@@ -118,10 +118,6 @@ class DownloadCommandsTest(CommandsTest):
expected_stderr = "Processing 1 patch from 1 bug.\nUpdating working directory\nProcessing patch 197 from bug 42.\nBuilding WebKit\n"
self.assert_execute_outputs(BuildAttachment(), [197], options=self._default_options(), expected_stderr=expected_stderr)
def test_post_attachment_to_rietveld(self):
expected_stderr = "Processing 1 patch from 1 bug.\nUpdating working directory\nProcessing patch 197 from bug 42.\nMOCK: Uploading patch to rietveld\nMOCK setting flag 'in-rietveld' to '+' on attachment '197' with comment 'None' and additional comment 'None'\n"
self.assert_execute_outputs(PostAttachmentToRietveld(), [197], options=self._default_options(), expected_stderr=expected_stderr)
def test_land_attachment(self):
# FIXME: This expected result is imperfect, notice how it's seeing the same patch as still there after it thought it would have cleared the flags.
expected_stderr = """Processing 1 patch from 1 bug.
......
......@@ -348,52 +348,6 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler, CommitQueueTaskD
raise TryAgain()
# FIXME: All the Rietveld code is no longer used and should be deleted.
class RietveldUploadQueue(AbstractPatchQueue, StepSequenceErrorHandler):
name = "rietveld-upload-queue"
def __init__(self):
AbstractPatchQueue.__init__(self)
# AbstractPatchQueue methods
def next_work_item(self):
patch_id = self._tool.bugs.queries.fetch_first_patch_from_rietveld_queue()
if patch_id:
return patch_id
self._update_status("Empty queue")
def should_proceed_with_work_item(self, patch):
self._update_status("Uploading patch", patch)
return True
def process_work_item(self, patch):
try:
self.run_webkit_patch(["post-attachment-to-rietveld", "--force-clean", "--non-interactive", "--parent-command=rietveld-upload-queue", patch.id()])
self._did_pass(patch)
return True
except ScriptError, e:
if e.exit_code != QueueEngine.handled_error_code:
self._did_fail(patch)
raise e
@classmethod
def _reject_patch(cls, tool, patch_id):
tool.bugs.set_flag_on_attachment(patch_id, "in-rietveld", "-")
def handle_unexpected_error(self, patch, message):
log(message)
self._reject_patch(self._tool, patch.id())
# StepSequenceErrorHandler methods
@classmethod
def handle_script_error(cls, tool, state, script_error):
log(script_error.message_with_output())
cls._update_status_for_script_error(tool, state, script_error)
cls._reject_patch(tool, state["patch"].id())
class AbstractReviewQueue(AbstractPatchQueue, StepSequenceErrorHandler):
"""This is the base-class for the EWS queues and the style-queue."""
def __init__(self, options=None):
......
......@@ -361,17 +361,6 @@ Please file bugs against the tests. These tests were authored by abarth@webkit.
queue._read_file_contents = lambda path: ""
self.assertEquals(queue.layout_test_results(), None)
class RietveldUploadQueueTest(QueuesTest):
def test_rietveld_upload_queue(self):
expected_stderr = {
"begin_work_queue": self._default_begin_work_queue_stderr("rietveld-upload-queue", MockSCM.fake_checkout_root),
"should_proceed_with_work_item": "MOCK: update_status: rietveld-upload-queue Uploading patch\n",
"process_work_item": "MOCK: update_status: rietveld-upload-queue Pass\nMOCK: release_work_item: rietveld-upload-queue 197\n",
"handle_unexpected_error": "Mock error message\nMOCK setting flag 'in-rietveld' to '-' on attachment '197' with comment 'None' and additional comment 'None'\n",
"handle_script_error": "ScriptError error message\nMOCK: update_status: rietveld-upload-queue ScriptError error message\nMOCK setting flag 'in-rietveld' to '-' on attachment '197' with comment 'None' and additional comment 'None'\n",
}
self.assert_queue_outputs(RietveldUploadQueue(), expected_stderr=expected_stderr)
class StyleQueueTest(QueuesTest):
def test_style_queue(self):
......
......@@ -37,7 +37,6 @@ from webkitpy.common.checkout.scm import default_scm
from webkitpy.common.config.ports import WebKitPort
from webkitpy.common.net.bugzilla import Bugzilla
from webkitpy.common.net.buildbot import BuildBot
from webkitpy.common.net.rietveld import Rietveld
from webkitpy.common.net.irc.ircproxy import IRCProxy
from webkitpy.common.system.executive import Executive
from webkitpy.common.system.user import User
......@@ -79,7 +78,6 @@ class WebKitPatch(MultiCommandTool):
self._scm = None
self._checkout = None
self.status_server = StatusServer()
self.codereview = Rietveld(self.executive)
self.port_factory = port.factory
def scm(self):
......@@ -126,7 +124,6 @@ class WebKitPatch(MultiCommandTool):
if options.dry_run:
self.scm().dryrun = True
self.bugs.dryrun = True
self.codereview.dryrun = True
if options.status_host:
self.status_server.set_host(options.status_host)
if options.bot_id:
......
......@@ -33,7 +33,6 @@ from webkitpy.common.config.committers import CommitterList, Reviewer
from webkitpy.common.checkout.commitinfo import CommitInfo
from webkitpy.common.checkout.scm import CommitMessage
from webkitpy.common.net.bugzilla import Bug, Attachment
from webkitpy.common.net.rietveld import Rietveld
from webkitpy.thirdparty.mock import Mock
from webkitpy.common.system.deprecated_logging import log
......@@ -86,7 +85,6 @@ _patch3 = {
"name": "Patch3",
"is_obsolete": False,
"is_patch": True,
"in-rietveld": "?",
"review": "?",
"attacher_email": "eric@webkit.org",
}
......@@ -113,7 +111,6 @@ _patch5 = {
"name": "Patch5",
"is_obsolete": False,
"is_patch": True,
"in-rietveld": "?",
"review": "+",
"reviewer_email": "foo@bar.com",
"attacher_email": "eric@webkit.org",
......@@ -127,7 +124,6 @@ _patch6 = { # Valid committer, but no reviewer.
"name": "ROLLOUT of r3489",
"is_obsolete": False,
"is_patch": True,
"in-rietveld": "-",
"commit-queue": "+",
"committer_email": "foo@bar.com",
"attacher_email": "eric@webkit.org",
......@@ -141,7 +137,6 @@ _patch7 = { # Valid review, patch is marked obsolete.
"name": "Patch7",
"is_obsolete": True,
"is_patch": True,
"in-rietveld": "+",
"review": "+",
"reviewer_email": "foo@bar.com",
"attacher_email": "eric@webkit.org",
......@@ -230,13 +225,6 @@ class MockBugzillaQueries(Mock):
def fetch_patches_from_pending_commit_list(self):
return sum([bug.reviewed_patches() for bug in self._all_bugs()], [])
def fetch_first_patch_from_rietveld_queue(self):
for bug in self._all_bugs():
patches = bug.in_rietveld_queue_patches()
if len(patches):
return patches[0]
raise Exception('No patches in the rietveld queue')
_mock_reviewer = Reviewer("Foo Bar", "foo@bar.com")
......@@ -615,15 +603,6 @@ class MockOptions(object):
self.__dict__[key] = value
class MockRietveld(object):
def __init__(self, executive, dryrun=False):
pass
def post(self, diff, patch_id, codereview_issue, message=None, cc=None):
log("MOCK: Uploading patch to rietveld")
class MockTestPort1(object):
def skips_layout_test(self, test_name):
......@@ -655,7 +634,6 @@ class MockTool(object):
self._checkout = MockCheckout()
self.status_server = MockStatusServer()
self.irc_password = "MOCK irc password"
self.codereview = MockRietveld(self.executive)
self.port_factory = MockPortFactory()
def scm(self):
......
......@@ -44,7 +44,6 @@ from webkitpy.tool.steps.ensurebuildersaregreen import EnsureBuildersAreGreen
from webkitpy.tool.steps.ensurelocalcommitifneeded import EnsureLocalCommitIfNeeded
from webkitpy.tool.steps.obsoletepatches import ObsoletePatches
from webkitpy.tool.steps.options import Options
from webkitpy.tool.steps.postcodereview import PostCodeReview
from webkitpy.tool.steps.postdiff import PostDiff
from webkitpy.tool.steps.postdiffforcommit import PostDiffForCommit
from webkitpy.tool.steps.postdiffforrevert import PostDiffForRevert
......
# Copyright (C) 2010 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from webkitpy.tool.steps.abstractstep import AbstractStep
from webkitpy.tool.steps.options import Options
class PostCodeReview(AbstractStep):
@classmethod
def options(cls):
return AbstractStep.options() + [
Options.cc,
Options.description,
]
def run(self, state):
patch = state.get("patch")
bug_id = patch.bug_id()
title = patch.name()
# If the issue already exists, then the message becomes the label
# of the new patch. Otherwise, it becomes the title of the whole
# issue.
if title:
# This is the common case for the the first "upload" command.
message = title
elif bug_id:
# This is the common case for the "post" command and
# subsequent runs of the "upload" command.
message = "Code review for %s" % self._tool.bugs.bug_url_for_bug_id(bug_id)
else:
# Unreachable with our current commands, but we might hit
# this case if we support bug-less code reviews.
message = "Code review"
# Use the bug ID as the rietveld issue number. This means rietveld code reviews
# when there are multiple different patches on a bug will be a bit wonky, but
# webkit-patch assumes one-patch-per-bug.
created_issue = self._tool.codereview.post(diff=self.cached_lookup(state, "diff"),
message=message,
codereview_issue=bug_id,
cc=self._options.cc,
patch_id=patch.id())
self._tool.bugs.set_flag_on_attachment(patch.id(), 'in-rietveld', '+')
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment