← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~stevenk/launchpad/local-codeimports-bad into lp:launchpad

 

Steve Kowalik has proposed merging lp:~stevenk/launchpad/local-codeimports-bad into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #820069 in Launchpad itself: "BadUrlLaunchpad raised mirroring a branch"
  https://bugs.launchpad.net/launchpad/+bug/820069

For more details, see:
https://code.launchpad.net/~stevenk/launchpad/local-codeimports-bad/+merge/133183

Deny creation of a code import if it for a launchpad.net URL. I have refactored the portion of the validation method for both ProductSeries:+setbranch and CodeImport:+new-import (and CodeImport:+edit-import) that check the URL passed it.

I have also forced this branch to net-neutral by performing a bunch of whitespace cleanup as well as defining a new code enum, much like the same thing we did with PUBLIC_INFORMATION_TYPES and friends while on Disclosure.

This branch does not completely close the bug, since it can still occur after this lands. The next step is to clean up the production data.
-- 
https://code.launchpad.net/~stevenk/launchpad/local-codeimports-bad/+merge/133183
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stevenk/launchpad/local-codeimports-bad into lp:launchpad.
=== modified file 'lib/lp/code/browser/codeimport.py'
--- lib/lp/code/browser/codeimport.py	2012-10-09 01:07:52 +0000
+++ lib/lp/code/browser/codeimport.py	2012-11-07 05:18:19 +0000
@@ -12,8 +12,10 @@
     'CodeImportSetBreadcrumb',
     'CodeImportSetNavigation',
     'CodeImportSetView',
+    'validate_import_url',
     ]
 
+from urlparse import urlparse
 
 from BeautifulSoup import BeautifulSoup
 from lazr.restful.interface import (
@@ -49,6 +51,7 @@
     BranchSubscriptionNotificationLevel,
     CodeImportReviewStatus,
     CodeReviewNotificationLevel,
+    NON_CVS_RCS_TYPES,
     RevisionControlSystems,
     )
 from lp.code.errors import BranchExists
@@ -167,10 +170,7 @@
 
     def setSecondaryFieldError(self, field, error):
         """Set the field error only if there isn't an error already."""
-        if self.getFieldError(field):
-            # Leave this one as it is often required or a validator error.
-            pass
-        else:
+        if not self.getFieldError(field):
             self.setFieldError(field, error)
 
     def _validateCVS(self, cvs_root, cvs_module, existing_import=None):
@@ -201,16 +201,9 @@
             self.setSecondaryFieldError(
                 field_name, 'Enter the URL of a foreign VCS branch.')
         else:
-            code_import = getUtility(ICodeImportSet).getByURL(url)
-            if (code_import is not None and
-                code_import != existing_import):
-                self.setFieldError(
-                    field_name,
-                    structured("""
-                    This foreign branch URL is already specified for
-                    the imported branch <a href="%s">%s</a>.""",
-                    canonical_url(code_import.branch),
-                    code_import.branch.unique_name))
+            reason = validate_import_url(url, existing_import)
+            if reason:
+                self.setFieldError(field_name, reason)
 
 
 class NewCodeImportForm(Interface):
@@ -540,12 +533,8 @@
         # fields, and vice versa.
         if self.code_import.rcs_type == RevisionControlSystems.CVS:
             self.form_fields = self.form_fields.omit('url')
-        elif self.code_import.rcs_type in (RevisionControlSystems.SVN,
-                                           RevisionControlSystems.BZR_SVN,
-                                           RevisionControlSystems.GIT,
-                                           RevisionControlSystems.BZR):
-            self.form_fields = self.form_fields.omit(
-                'cvs_root', 'cvs_module')
+        elif self.code_import.rcs_type in NON_CVS_RCS_TYPES:
+            self.form_fields = self.form_fields.omit('cvs_root', 'cvs_module')
         else:
             raise AssertionError('Unknown rcs_type for code import.')
 
@@ -575,10 +564,7 @@
             self._validateCVS(
                 data.get('cvs_root'), data.get('cvs_module'),
                 self.code_import)
-        elif self.code_import.rcs_type in (RevisionControlSystems.SVN,
-                                           RevisionControlSystems.BZR_SVN,
-                                           RevisionControlSystems.GIT,
-                                           RevisionControlSystems.BZR):
+        elif self.code_import.rcs_type in NON_CVS_RCS_TYPES:
             self._validateURL(data.get('url'), self.code_import)
         else:
             raise AssertionError('Unknown rcs_type for code import.')
@@ -593,3 +579,19 @@
     def machines(self):
         """Get the machines, sorted alphabetically by hostname."""
         return getUtility(ICodeImportMachineSet).getAll()
+
+
+def validate_import_url(url, existing_import=None):
+    """Validate the given import URL."""
+    if urlparse(url).netloc.endswith('launchpad.net'):
+        return (
+            "You can not create imports for branches that are hosted by "
+            "Launchpad.")
+    code_import = getUtility(ICodeImportSet).getByURL(url)
+    if code_import is not None:
+        if existing_import and code_import == existing_import:
+            return None
+        return structured(
+            "This foreign branch URL is already specified for the imported "
+            "branch <a href='%s'>%s</a>.", canonical_url(code_import.branch),
+            code_import.branch.unique_name)

=== modified file 'lib/lp/code/enums.py'
--- lib/lp/code/enums.py	2012-10-11 04:14:37 +0000
+++ lib/lp/code/enums.py	2012-11-07 05:18:19 +0000
@@ -20,6 +20,7 @@
     'CodeImportReviewStatus',
     'CodeReviewNotificationLevel',
     'CodeReviewVote',
+    'NON_CVS_RCS_TYPES',
     'RevisionControlSystems',
     'UICreatableBranchType',
     ]
@@ -856,3 +857,7 @@
 
         The reviewer needs more information before making a decision.
         """)
+
+NON_CVS_RCS_TYPES = (
+    RevisionControlSystems.SVN, RevisionControlSystems.BZR_SVN,
+    RevisionControlSystems.GIT, RevisionControlSystems.BZR)

=== modified file 'lib/lp/code/model/codeimport.py'
--- lib/lp/code/model/codeimport.py	2012-10-10 22:18:41 +0000
+++ lib/lp/code/model/codeimport.py	2012-11-07 05:18:19 +0000
@@ -38,6 +38,7 @@
     CodeImportJobState,
     CodeImportResultStatus,
     CodeImportReviewStatus,
+    NON_CVS_RCS_TYPES,
     RevisionControlSystems,
     )
 from lp.code.errors import (
@@ -71,8 +72,7 @@
     _defaultOrder = ['id']
 
     date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
-    branch = ForeignKey(dbName='branch', foreignKey='Branch',
-                        notNull=True)
+    branch = ForeignKey(dbName='branch', foreignKey='Branch', notNull=True)
     registrant = ForeignKey(
         dbName='registrant', foreignKey='Person',
         storm_validator=validate_public_person, notNull=True)
@@ -243,10 +243,7 @@
         if rcs_type == RevisionControlSystems.CVS:
             assert cvs_root is not None and cvs_module is not None
             assert url is None
-        elif rcs_type in (RevisionControlSystems.SVN,
-                          RevisionControlSystems.BZR_SVN,
-                          RevisionControlSystems.GIT,
-                          RevisionControlSystems.BZR):
+        elif rcs_type in NON_CVS_RCS_TYPES:
             assert cvs_root is None and cvs_module is None
             assert url is not None
         else:

=== modified file 'lib/lp/code/stories/codeimport/xx-create-codeimport.txt'
--- lib/lp/code/stories/codeimport/xx-create-codeimport.txt	2012-10-09 01:07:52 +0000
+++ lib/lp/code/stories/codeimport/xx-create-codeimport.txt	2012-11-07 05:18:19 +0000
@@ -97,6 +97,19 @@
     at http://user:password@xxxxxxxxxxxxxxx/firefox/trunk.
     The next import is scheduled to run
     as soon as possible.
+    
+Specifing a Launchpad URL results in an error.
+
+    >>> browser.open("http://code.launchpad.dev/+code-imports/+new";)
+    >>> browser.getControl('Branch Name').value = "invalid"
+    >>> browser.getControl('Branch URL', index=0).value = (
+    ...     "http://bazaar.launchpad.net/firefox/trunk";)
+    >>> browser.getControl('Project').value = "firefox"
+    >>> browser.getControl('Request Import').click()
+    >>> for message in get_feedback_messages(browser.contents):
+    ...     print extract_text(message)
+    There is 1 error.
+    You can not create imports for branches that are hosted by Launchpad.
 
 Requesting a Subversion import
 ==============================

=== modified file 'lib/lp/registry/browser/productseries.py'
--- lib/lp/registry/browser/productseries.py	2012-10-16 15:12:09 +0000
+++ lib/lp/registry/browser/productseries.py	2012-11-07 05:18:19 +0000
@@ -86,6 +86,7 @@
 from lp.bugs.interfaces.bugtask import IBugTaskSet
 from lp.code.browser.branch import BranchNameValidationMixin
 from lp.code.browser.branchref import BranchRef
+from lp.code.browser.codeimport import validate_import_url
 from lp.code.enums import (
     BranchType,
     RevisionControlSystems,
@@ -660,10 +661,7 @@
         return 'Edit %s %s series' % (
             self.context.product.displayname, self.context.name)
 
-    @property
-    def page_title(self):
-        """The page title."""
-        return self.label
+    page_title = label
 
     def validate(self, data):
         """See `LaunchpadFormView`."""
@@ -697,10 +695,7 @@
         return 'Delete %s %s series' % (
             self.context.product.displayname, self.context.name)
 
-    @property
-    def page_title(self):
-        """The page title."""
-        return self.label
+    page_title = label
 
     @cachedproperty
     def milestones(self):
@@ -906,8 +901,7 @@
         """Validate data for link-lp-bzr case."""
         if 'branch_location' not in data:
             self.setFieldError(
-                'branch_location',
-                'The branch location must be set.')
+                'branch_location', 'The branch location must be set.')
 
     def _validateImportExternal(self, data):
         """Validate data for import external case."""
@@ -925,16 +919,9 @@
             self.setFieldError(
                 'repo_url', 'You must set the external repository URL.')
         else:
-            # Ensure this URL has not been imported before.
-            code_import = getUtility(ICodeImportSet).getByURL(repo_url)
-            if code_import is not None:
-                self.setFieldError(
-                    'repo_url',
-                    structured("""
-                    This foreign branch URL is already specified for
-                    the imported branch <a href="%s">%s</a>.""",
-                               canonical_url(code_import.branch),
-                               code_import.branch.unique_name))
+            reason = validate_import_url(repo_url)
+            if reason:
+                self.setFieldError('repo_url', reason)
 
         # RCS type is mandatory.
         # This condition should never happen since an initial value is set.
@@ -945,19 +932,15 @@
                 'You must specify the type of RCS for the remote host.')
         elif rcs_type == RevisionControlSystems.CVS:
             if 'cvs_module' not in data:
-                self.setFieldError(
-                    'cvs_module',
-                    'The CVS module must be set.')
+                self.setFieldError('cvs_module', 'The CVS module must be set.')
         self._validateBranch(data)
 
     def _validateBranch(self, data):
         """Validate that branch name and owner are set."""
         if 'branch_name' not in data:
-            self.setFieldError(
-                'branch_name', 'The branch name must be set.')
+            self.setFieldError('branch_name', 'The branch name must be set.')
         if 'branch_owner' not in data:
-            self.setFieldError(
-                'branch_owner', 'The branch owner must be set.')
+            self.setFieldError('branch_owner', 'The branch owner must be set.')
 
     def _setRequired(self, names, value):
         """Mark the widget field as optional."""
@@ -1030,25 +1013,19 @@
         branch_type = data.get('branch_type')
         if branch_type == LINK_LP_BZR:
             branch_location = data.get('branch_location')
+            self.context.branch = branch_location
             if branch_location != self.context.branch:
-                self.context.branch = branch_location
                 # Request an initial upload of translation files.
                 getUtility(IRosettaUploadJobSource).create(
                     self.context.branch, NULL_REVISION)
-            else:
-                self.context.branch = branch_location
             self.request.response.addInfoNotification(
                 'Series code location updated.')
         else:
             branch_name = data.get('branch_name')
             branch_owner = data.get('branch_owner')
 
-            # Import or mirror an external branch.
             if branch_type == IMPORT_EXTERNAL:
-                # Either create an externally hosted bzr branch
-                # (a.k.a. 'mirrored') or create a new code import.
                 rcs_type = data.get('rcs_type')
-                # We need to create an import request.
                 if rcs_type == RevisionControlSystems.CVS:
                     cvs_root = data.get('repo_url')
                     cvs_module = data.get('cvs_module')
@@ -1069,8 +1046,7 @@
                         cvs_root=cvs_root,
                         cvs_module=cvs_module)
                 except BranchExists as e:
-                    self._setBranchExists(
-                        e.existing_branch, 'branch_name')
+                    self._setBranchExists(e.existing_branch, 'branch_name')
                     self.errors_in_action = True
                     # Abort transaction. This is normally handled
                     # by LaunchpadFormView, but we are already in
@@ -1126,13 +1102,11 @@
     @action(_('Update'), name='update')
     def update_action(self, action, data):
         """Update the branch attribute."""
+        self.updateContextFromData(data)
         if data['branch'] != self.context.branch:
-            self.updateContextFromData(data)
             # Request an initial upload of translation files.
             getUtility(IRosettaUploadJobSource).create(
                 self.context.branch, NULL_REVISION)
-        else:
-            self.updateContextFromData(data)
         self.request.response.addInfoNotification(
             'Series code location updated.')
 
@@ -1177,8 +1151,7 @@
 class ProductSeriesRdfView(BaseRdfView):
     """A view that sets its mime-type to application/rdf+xml"""
 
-    template = ViewPageTemplateFile(
-        '../templates/productseries-rdf.pt')
+    template = ViewPageTemplateFile('../templates/productseries-rdf.pt')
 
     @property
     def filename(self):

=== modified file 'lib/lp/registry/browser/tests/productseries-setbranch-view.txt'
--- lib/lp/registry/browser/tests/productseries-setbranch-view.txt	2012-10-09 01:07:52 +0000
+++ lib/lp/registry/browser/tests/productseries-setbranch-view.txt	2012-11-07 05:18:19 +0000
@@ -138,7 +138,6 @@
     ('repo_url'...The URI scheme "bzr+ssh" is not allowed.  Only URIs with
     the following schemes may be used: bzr, http, https'))
 
-
 A correct URL is accepted.
 
     >>> form = {
@@ -161,6 +160,17 @@
     blazer-branch
     >>> series.branch.registrant.name == driver.name
     True
+    
+External imports can not use an Launchpad URL.
+
+    >>> form['field.repo_url'] = 'http://bazaar.launchpad.net/firefox/foo'
+    >>> form['field.branch_name'] = 'chevette-branch'
+    >>> view = create_initialized_view(
+    ...     series, name='+setbranch', principal=driver, form=form)
+    >>> transaction.commit()
+    >>> for error in view.errors:
+    ...     print error
+    You can not create imports for branches that are hosted by Launchpad.
 
 Git branches cannnot use svn.
 
@@ -303,7 +313,7 @@
     <BLANKLINE>
     This foreign branch URL is already specified for
     the imported branch
-    <a href="http://.../chevy/chevette-branch";>~.../chevy/chevette-branch</a>.
+    <a href='http://.../chevy/chevette-branch'>~.../chevy/chevette-branch</a>.
     >>> for notification in view.request.response.notifications:
     ...     print notification.message
 
@@ -348,7 +358,7 @@
     ...     print error
     <BLANKLINE>
     This foreign branch URL is already specified for the imported branch
-    <a href="http://.../blazer-branch";>...</a>.
+    <a href='http://.../blazer-branch'>...</a>.
     >>> print view.errors_in_action
     False
     >>> print view.next_url


Follow ups