launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #21050
[Merge] lp:~cjwatson/launchpad/codeimport-git-model into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/codeimport-git-model into lp:launchpad with lp:~cjwatson/launchpad/git-repository-type as a prerequisite.
Commit message:
Add basic model for code imports that target Git.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1469459 in Launchpad itself: "import external code into a LP git repo (natively)"
https://bugs.launchpad.net/launchpad/+bug/1469459
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/codeimport-git-model/+merge/307466
Add basic model for code imports that target Git.
This is overlong, but since I had to introduce the concept of a different target revision control system type here, it was tough to make it any shorter. ICodeImport.git_repository is exported read-only on the webservice, but that should be pretty harmless; nothing else here should be user-visible yet.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/codeimport-git-model into lp:launchpad.
=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml 2016-06-20 20:32:36 +0000
+++ lib/lp/code/configure.zcml 2016-10-03 17:03:17 +0000
@@ -633,6 +633,8 @@
<allow attributes="id
date_created
branch
+ git_repository
+ target
registrant
owner
assignee
=== modified file 'lib/lp/code/doc/codeimport.txt'
--- lib/lp/code/doc/codeimport.txt 2016-10-03 17:03:12 +0000
+++ lib/lp/code/doc/codeimport.txt 2016-10-03 17:03:17 +0000
@@ -52,8 +52,12 @@
The rcs_type field, which indicates whether the import is from CVS or
Subversion, takes values from the 'RevisionControlSystems' vocabulary.
+Similarly, target_rcs_type takes values from 'TargetRevisionControlSystems'.
- >>> from lp.code.enums import RevisionControlSystems
+ >>> from lp.code.enums import (
+ ... RevisionControlSystems,
+ ... TargetRevisionControlSystems,
+ ... )
>>> for item in RevisionControlSystems:
... print item.title
Concurrent Versions System
@@ -62,6 +66,10 @@
Git
Mercurial
Bazaar
+ >>> for item in TargetRevisionControlSystems:
+ ... print item.title
+ Bazaar
+ Git
Import from CVS
@@ -71,12 +79,14 @@
in the repository, known as the "module".
>>> cvs = RevisionControlSystems.CVS
+ >>> target_bzr = TargetRevisionControlSystems.BZR
>>> cvs_root = ':pserver:anonymous@xxxxxxxxxxxxxxx:/cvsroot'
>>> cvs_module = 'hello'
>>> context = factory.makeProduct(name='widget')
>>> cvs_import = code_import_set.new(
... registrant=nopriv, context=context, branch_name='trunk-cvs',
- ... rcs_type=cvs, cvs_root=cvs_root, cvs_module=cvs_module)
+ ... rcs_type=cvs, cvs_root=cvs_root, cvs_module=cvs_module,
+ ... target_rcs_type=target_bzr)
>>> verifyObject(ICodeImport, removeSecurityProxy(cvs_import))
True
@@ -136,7 +146,7 @@
>>> svn_url = 'svn://svn.example.com/trunk'
>>> svn_import = code_import_set.new(
... registrant=nopriv, context=context, branch_name='trunk-svn',
- ... rcs_type=svn, url=svn_url)
+ ... rcs_type=svn, url=svn_url, target_rcs_type=target_bzr)
>>> verifyObject(ICodeImport, removeSecurityProxy(svn_import))
True
@@ -164,7 +174,7 @@
>>> git_url = 'git://git.example.com/hello.git'
>>> git_import = code_import_set.new(
... registrant=nopriv, context=context, branch_name='trunk-git',
- ... rcs_type=git, url=git_url)
+ ... rcs_type=git, url=git_url, target_rcs_type=target_bzr)
>>> verifyObject(ICodeImport, removeSecurityProxy(git_import))
True
=== modified file 'lib/lp/code/emailtemplates/code-import-status-updated.txt'
--- lib/lp/code/emailtemplates/code-import-status-updated.txt 2011-12-18 22:31:46 +0000
+++ lib/lp/code/emailtemplates/code-import-status-updated.txt 2016-10-03 17:03:17 +0000
@@ -3,5 +3,5 @@
%(body)s
--
-%(branch)s
+%(target)s
%(rationale)s%(unsubscribe)s
=== modified file 'lib/lp/code/emailtemplates/new-code-import.txt'
--- lib/lp/code/emailtemplates/new-code-import.txt 2011-12-18 22:31:46 +0000
+++ lib/lp/code/emailtemplates/new-code-import.txt 2016-10-03 17:03:17 +0000
@@ -1,5 +1,5 @@
A new %(rcs_type)s code import has been requested by %(person)s:
- %(branch)s
+ %(target)s
from
%(location)s
=== modified file 'lib/lp/code/enums.py'
--- lib/lp/code/enums.py 2016-10-03 17:03:12 +0000
+++ lib/lp/code/enums.py 2016-10-03 17:03:17 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Enumerations used in the lp/code modules."""
@@ -24,6 +24,7 @@
'GitRepositoryType',
'NON_CVS_RCS_TYPES',
'RevisionControlSystems',
+ 'TargetRevisionControlSystems',
]
from lazr.enum import (
@@ -400,6 +401,25 @@
""")
+class TargetRevisionControlSystems(EnumeratedType):
+ """Target Revision Control Systems
+
+ Revision control systems that can be the target of a code import.
+ """
+
+ BZR = Item("""
+ Bazaar
+
+ Import to Bazaar.
+ """)
+
+ GIT = Item("""
+ Git
+
+ Import to Git.
+ """)
+
+
class CodeImportReviewStatus(DBEnumeratedType):
"""CodeImport review status.
=== modified file 'lib/lp/code/interfaces/codeimport.py'
--- lib/lp/code/interfaces/codeimport.py 2016-09-30 13:27:27 +0000
+++ lib/lp/code/interfaces/codeimport.py 2016-10-03 17:03:17 +0000
@@ -43,6 +43,7 @@
RevisionControlSystems,
)
from lp.code.interfaces.branch import IBranch
+from lp.code.interfaces.gitrepository import IGitRepository
from lp.services.fields import (
PublicPersonChoice,
URIField,
@@ -86,10 +87,18 @@
branch = exported(
ReferenceChoice(
- title=_('Branch'), required=True, readonly=True,
+ title=_('Branch'), required=False, readonly=True,
vocabulary='Branch', schema=IBranch,
description=_("The Bazaar branch produced by the "
"import system.")))
+ git_repository = exported(
+ ReferenceChoice(
+ title=_('Git repository'), required=False, readonly=True,
+ vocabulary='GitRepository', schema=IGitRepository,
+ description=_(
+ "The Git repository produced by the import system.")))
+ target = Attribute(
+ "The branch/repository produced by the import system (VCS-agnostic).")
registrant = PublicPersonChoice(
title=_('Registrant'), required=True, readonly=True,
@@ -220,8 +229,8 @@
class ICodeImportSet(Interface):
"""Interface representing the set of code imports."""
- def new(registrant, context, branch_name, rcs_type, url=None,
- cvs_root=None, cvs_module=None, review_status=None,
+ def new(registrant, context, branch_name, rcs_type, target_rcs_type,
+ url=None, cvs_root=None, cvs_module=None, review_status=None,
owner=None):
"""Create a new CodeImport.
@@ -238,7 +247,10 @@
"""
def getByBranch(branch):
- """Get the CodeImport, if any, associated to a Branch."""
+ """Get the CodeImport, if any, associated with a Branch."""
+
+ def getByGitRepository(repository):
+ """Get the CodeImport, if any, associated with a GitRepository."""
def getByCVSDetails(cvs_root, cvs_module):
"""Get the CodeImport with the specified CVS details."""
=== modified file 'lib/lp/code/interfaces/gitnamespace.py'
--- lib/lp/code/interfaces/gitnamespace.py 2016-10-03 17:03:12 +0000
+++ lib/lp/code/interfaces/gitnamespace.py 2016-10-03 17:03:17 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Interface for a Git repository namespace."""
@@ -22,6 +22,7 @@
from lp.registry.interfaces.distributionsourcepackage import (
IDistributionSourcePackage,
)
+from lp.registry.interfaces.person import IPerson
from lp.registry.interfaces.product import IProduct
@@ -96,6 +97,9 @@
supports_merge_proposals = Attribute(
"Does this namespace support merge proposals at all?")
+ supports_code_imports = Attribute(
+ "Does this namespace support code imports at all?")
+
allow_recipe_name_from_target = Attribute(
"Can recipe names reasonably be generated from the target name "
"rather than the branch name?")
@@ -188,8 +192,10 @@
return getUtility(IGitNamespaceSet).get(
owner, distribution=target.distribution,
sourcepackagename=target.sourcepackagename)
+ elif IPerson.providedBy(target):
+ return getUtility(IGitNamespaceSet).get(owner)
else:
- return getUtility(IGitNamespaceSet).get(owner)
+ raise AssertionError("No Git namespace defined for %s" % target)
# Marker for references to Git URL layouts: ##GITNAMESPACE##
=== modified file 'lib/lp/code/mail/codeimport.py'
--- lib/lp/code/mail/codeimport.py 2016-10-03 17:03:12 +0000
+++ lib/lp/code/mail/codeimport.py 2016-10-03 17:03:17 +0000
@@ -39,7 +39,7 @@
# test.
return
user = IPerson(event.user)
- subject = 'New code import: %s' % code_import.branch.unique_name
+ subject = 'New code import: %s' % code_import.target.unique_name
if code_import.rcs_type == RevisionControlSystems.CVS:
location = '%s, %s' % (code_import.cvs_root, code_import.cvs_module)
else:
@@ -52,7 +52,7 @@
}
body = get_email_template('new-code-import.txt', app='code') % {
'person': code_import.registrant.displayname,
- 'branch': canonical_url(code_import.branch),
+ 'target': canonical_url(code_import.target),
'rcs_type': rcs_type_map[code_import.rcs_type],
'location': location,
}
@@ -61,7 +61,7 @@
user.displayname, user.preferredemail.email)
vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports
- headers = {'X-Launchpad-Branch': code_import.branch.unique_name,
+ headers = {'X-Launchpad-Branch': code_import.target.unique_name,
'X-Launchpad-Message-Rationale':
'Operator @%s' % vcs_imports.name,
'X-Launchpad-Message-For': vcs_imports.name,
@@ -103,7 +103,7 @@
raise AssertionError('Unexpected review status for code import.')
details_change_prefix = '\n'.join(textwrap.wrap(
- "%s is now being imported from:" % code_import.branch.unique_name))
+ "%s is now being imported from:" % code_import.target.unique_name))
if code_import.rcs_type == RevisionControlSystems.CVS:
if (CodeImportEventDataType.OLD_CVS_ROOT in event_data or
CodeImportEventDataType.OLD_CVS_MODULE in event_data):
@@ -142,26 +142,26 @@
def code_import_updated(code_import, event, new_whiteboard, person):
- """Email the branch subscribers, and the vcs-imports team with new status.
+ """Email the target subscribers, and the vcs-imports team with new status.
"""
- branch = code_import.branch
- recipients = branch.getNotificationRecipients()
+ target = code_import.target
+ recipients = target.getNotificationRecipients()
# Add in the vcs-imports user.
vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports
herder_rationale = 'Operator @%s' % vcs_imports.name
recipients.add(vcs_imports, None, herder_rationale)
- headers = {'X-Launchpad-Branch': branch.unique_name}
+ headers = {'X-Launchpad-Branch': target.unique_name}
subject = 'Code import %s status: %s' % (
- code_import.branch.unique_name, code_import.review_status.title)
+ code_import.target.unique_name, code_import.review_status.title)
email_template = get_email_template(
'code-import-status-updated.txt', app='code')
template_params = {
'body': make_email_body_for_code_import_update(
code_import, event, new_whiteboard),
- 'branch': canonical_url(code_import.branch)}
+ 'target': canonical_url(code_import.target)}
if person:
from_address = format_address(
@@ -194,7 +194,7 @@
# Give the users a link to unsubscribe.
template_params['unsubscribe'] = (
"\nTo unsubscribe from this branch go to "
- "%s/+edit-subscription." % canonical_url(branch))
+ "%s/+edit-subscription." % canonical_url(target))
else:
template_params['unsubscribe'] = ''
for_person = subscription.person
=== modified file 'lib/lp/code/mail/tests/test_codeimport.py'
--- lib/lp/code/mail/tests/test_codeimport.py 2014-06-10 16:13:03 +0000
+++ lib/lp/code/mail/tests/test_codeimport.py 2016-10-03 17:03:17 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2014 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for code import related mailings"""
@@ -7,7 +7,10 @@
import transaction
-from lp.code.enums import RevisionControlSystems
+from lp.code.enums import (
+ RevisionControlSystems,
+ TargetRevisionControlSystems,
+ )
from lp.services.mail import stub
from lp.testing import (
login_person,
@@ -21,8 +24,8 @@
layer = DatabaseFunctionalLayer
- def test_cvs_import(self):
- # Test the email for a new CVS import.
+ def test_cvs_to_bzr_import(self):
+ # Test the email for a new CVS-to-Bazaar import.
eric = self.factory.makePerson(name='eric')
fooix = self.factory.makeProduct(name='fooix')
# Eric needs to be logged in for the mail to be sent.
@@ -44,8 +47,8 @@
'-- \nYou are getting this email because you are a member of the '
'vcs-imports team.\n', msg.get_payload(decode=True))
- def test_svn_import(self):
- # Test the email for a new subversion import.
+ def test_svn_to_bzr_import(self):
+ # Test the email for a new Subversion-to-Bazaar import.
eric = self.factory.makePerson(name='eric')
fooix = self.factory.makeProduct(name='fooix')
# Eric needs to be logged in for the mail to be sent.
@@ -67,8 +70,8 @@
'-- \nYou are getting this email because you are a member of the '
'vcs-imports team.\n', msg.get_payload(decode=True))
- def test_git_import(self):
- # Test the email for a new git import.
+ def test_git_to_bzr_import(self):
+ # Test the email for a new git-to-Bazaar import.
eric = self.factory.makePerson(name='eric')
fooix = self.factory.makeProduct(name='fooix')
# Eric needs to be logged in for the mail to be sent.
@@ -90,6 +93,30 @@
'-- \nYou are getting this email because you are a member of the '
'vcs-imports team.\n', msg.get_payload(decode=True))
+ def test_git_to_git_import(self):
+ # Test the email for a new git-to-git import.
+ eric = self.factory.makePerson(name='eric')
+ fooix = self.factory.makeProduct(name='fooix')
+ # Eric needs to be logged in for the mail to be sent.
+ login_person(eric)
+ self.factory.makeProductCodeImport(
+ git_repo_url='git://git.example.com/fooix.git',
+ branch_name=u'master', product=fooix, registrant=eric,
+ target_rcs_type=TargetRevisionControlSystems.GIT)
+ transaction.commit()
+ msg = message_from_string(stub.test_emails[0][2])
+ self.assertEqual('code-import', msg['X-Launchpad-Notification-Type'])
+ self.assertEqual('~eric/fooix/+git/master', msg['X-Launchpad-Branch'])
+ self.assertEqual(
+ 'A new git code import has been requested '
+ 'by Eric:\n'
+ ' http://code.launchpad.dev/~eric/fooix/+git/master\n'
+ 'from\n'
+ ' git://git.example.com/fooix.git\n'
+ '\n'
+ '-- \nYou are getting this email because you are a member of the '
+ 'vcs-imports team.\n', msg.get_payload(decode=True))
+
def test_new_source_package_import(self):
# Test the email for a new sourcepackage import.
eric = self.factory.makePerson(name='eric')
=== modified file 'lib/lp/code/model/codeimport.py'
--- lib/lp/code/model/codeimport.py 2016-09-30 13:27:27 +0000
+++ lib/lp/code/model/codeimport.py 2016-10-03 17:03:17 +0000
@@ -26,7 +26,10 @@
Func,
Select,
)
-from storm.locals import Store
+from storm.locals import (
+ Int,
+ Store,
+ )
from storm.references import Reference
from zope.component import getUtility
from zope.event import notify
@@ -38,14 +41,17 @@
CodeImportJobState,
CodeImportResultStatus,
CodeImportReviewStatus,
+ GitRepositoryType,
NON_CVS_RCS_TYPES,
RevisionControlSystems,
+ TargetRevisionControlSystems,
)
from lp.code.errors import (
CodeImportAlreadyRequested,
CodeImportAlreadyRunning,
CodeImportNotInReviewedState,
)
+from lp.code.interfaces.branch import IBranch
from lp.code.interfaces.branchtarget import IBranchTarget
from lp.code.interfaces.codeimport import (
ICodeImport,
@@ -53,6 +59,8 @@
)
from lp.code.interfaces.codeimportevent import ICodeImportEventSet
from lp.code.interfaces.codeimportjob import ICodeImportJobWorkflow
+from lp.code.interfaces.gitnamespace import get_git_namespace
+from lp.code.interfaces.gitrepository import IGitRepository
from lp.code.mail.codeimport import code_import_updated
from lp.code.model.codeimportjob import CodeImportJobWorkflow
from lp.code.model.codeimportresult import CodeImportResult
@@ -71,8 +79,31 @@
_table = 'CodeImport'
_defaultOrder = ['id']
+ def __init__(self, target=None, *args, **kwargs):
+ if target is not None:
+ assert 'branch' not in kwargs
+ assert 'repository' not in kwargs
+ if IBranch.providedBy(target):
+ kwargs['branch'] = target
+ elif IGitRepository.providedBy(target):
+ kwargs['git_repository'] = target
+ else:
+ raise AssertionError("Unknown code import target %s" % target)
+ super(CodeImport, self).__init__(*args, **kwargs)
+
date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
- branch = ForeignKey(dbName='branch', foreignKey='Branch', notNull=True)
+ branch = ForeignKey(dbName='branch', foreignKey='Branch', notNull=False)
+ git_repositoryID = Int(name='git_repository', allow_none=True)
+ git_repository = Reference(git_repositoryID, 'GitRepository.id')
+
+ @property
+ def target(self):
+ if self.branch is not None:
+ return self.branch
+ else:
+ assert self.git_repository is not None
+ return self.git_repository
+
registrant = ForeignKey(
dbName='registrant', foreignKey='Person',
storm_validator=validate_public_person, notNull=True)
@@ -175,12 +206,14 @@
new_whiteboard = None
if 'whiteboard' in data:
whiteboard = data.pop('whiteboard')
- if whiteboard != self.branch.whiteboard:
- if whiteboard is None:
- new_whiteboard = ''
- else:
- new_whiteboard = whiteboard
- self.branch.whiteboard = whiteboard
+ # XXX cjwatson 2016-10-03: Do we need something similar for Git?
+ if self.branch is not None:
+ if whiteboard != self.branch.whiteboard:
+ if whiteboard is None:
+ new_whiteboard = ''
+ else:
+ new_whiteboard = whiteboard
+ self.branch.whiteboard = whiteboard
token = event_set.beginModify(self)
for name, value in data.items():
setattr(self, name, value)
@@ -196,7 +229,7 @@
return event
def __repr__(self):
- return "<CodeImport for %s>" % self.branch.unique_name
+ return "<CodeImport for %s>" % self.target.unique_name
def tryFailingImportAgain(self, user):
"""See `ICodeImport`."""
@@ -233,7 +266,7 @@
class CodeImportSet:
"""See `ICodeImportSet`."""
- def new(self, registrant, context, branch_name, rcs_type,
+ def new(self, registrant, context, branch_name, rcs_type, target_rcs_type,
url=None, cvs_root=None, cvs_module=None, review_status=None,
owner=None):
"""See `ICodeImportSet`."""
@@ -247,22 +280,37 @@
raise AssertionError(
"Don't know how to sanity check source details for unknown "
"rcs_type %s" % rcs_type)
- target = IBranchTarget(context)
+ if owner is None:
+ owner = registrant
+ if target_rcs_type == TargetRevisionControlSystems.BZR:
+ target = IBranchTarget(context)
+ namespace = target.getNamespace(owner)
+ elif target_rcs_type == TargetRevisionControlSystems.GIT:
+ if rcs_type != RevisionControlSystems.GIT:
+ raise AssertionError(
+ "Can't import rcs_type %s into a Git repository" %
+ rcs_type)
+ target = namespace = get_git_namespace(context, owner)
+ else:
+ raise AssertionError(
+ "Can't import to target_rcs_type %s" % target_rcs_type)
if review_status is None:
# Auto approve imports.
review_status = CodeImportReviewStatus.REVIEWED
if not target.supports_code_imports:
raise AssertionError("%r doesn't support code imports" % target)
- if owner is None:
- owner = registrant
# Create the branch for the CodeImport.
- namespace = target.getNamespace(owner)
- import_branch = namespace.createBranch(
- branch_type=BranchType.IMPORTED, name=branch_name,
- registrant=registrant)
+ if target_rcs_type == TargetRevisionControlSystems.BZR:
+ import_target = namespace.createBranch(
+ branch_type=BranchType.IMPORTED, name=branch_name,
+ registrant=registrant)
+ else:
+ import_target = namespace.createRepository(
+ repository_type=GitRepositoryType.IMPORTED, name=branch_name,
+ registrant=registrant)
code_import = CodeImport(
- registrant=registrant, owner=owner, branch=import_branch,
+ registrant=registrant, owner=owner, target=import_target,
rcs_type=rcs_type, url=url,
cvs_root=cvs_root, cvs_module=cvs_module,
review_status=review_status)
@@ -303,6 +351,9 @@
"""See `ICodeImportSet`."""
return CodeImport.selectOneBy(branch=branch)
+ def getByGitRepository(self, repository):
+ return CodeImport.selectOneBy(git_repository=repository)
+
def search(self, review_status=None, rcs_type=None):
"""See `ICodeImportSet`."""
clauses = []
=== modified file 'lib/lp/code/model/codeimportjob.py'
--- lib/lp/code/model/codeimportjob.py 2015-07-08 16:05:11 +0000
+++ lib/lp/code/model/codeimportjob.py 2016-10-03 17:03:17 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Database classes for the CodeImportJob table."""
@@ -154,10 +154,10 @@
"""See `ICodeImportJobWorkflow`."""
assert code_import.review_status == CodeImportReviewStatus.REVIEWED, (
"Review status of %s is not REVIEWED: %s" % (
- code_import.branch.unique_name, code_import.review_status.name))
+ code_import.target.unique_name, code_import.review_status.name))
assert code_import.import_job is None, (
"Already associated to a CodeImportJob: %s" % (
- code_import.branch.unique_name))
+ code_import.target.unique_name))
if interval is None:
interval = code_import.effective_update_interval
@@ -182,13 +182,13 @@
"""See `ICodeImportJobWorkflow`."""
assert code_import.review_status != CodeImportReviewStatus.REVIEWED, (
"The review status of %s is %s." % (
- code_import.branch.unique_name, code_import.review_status.name))
+ code_import.target.unique_name, code_import.review_status.name))
assert code_import.import_job is not None, (
"Not associated to a CodeImportJob: %s" % (
- code_import.branch.unique_name,))
+ code_import.target.unique_name,))
assert code_import.import_job.state == CodeImportJobState.PENDING, (
"The CodeImportJob associated to %s is %s." % (
- code_import.branch.unique_name,
+ code_import.target.unique_name,
code_import.import_job.state.name))
# CodeImportJobWorkflow is the only class that is allowed to delete
# CodeImportJob rows, so destroySelf is not exposed in ICodeImportJob.
@@ -198,12 +198,12 @@
"""See `ICodeImportJobWorkflow`."""
assert import_job.state == CodeImportJobState.PENDING, (
"The CodeImportJob associated with %s is %s."
- % (import_job.code_import.branch.unique_name,
+ % (import_job.code_import.target.unique_name,
import_job.state.name))
assert import_job.requesting_user is None, (
"The CodeImportJob associated with %s "
"was already requested by %s."
- % (import_job.code_import.branch.unique_name,
+ % (import_job.code_import.target.unique_name,
import_job.requesting_user.name))
# CodeImportJobWorkflow is the only class that is allowed to set the
# date_due and requesting_user attributes of CodeImportJob, they are
@@ -219,7 +219,7 @@
"""See `ICodeImportJobWorkflow`."""
assert import_job.state == CodeImportJobState.PENDING, (
"The CodeImportJob associated with %s is %s."
- % (import_job.code_import.branch.unique_name,
+ % (import_job.code_import.target.unique_name,
import_job.state.name))
assert machine.state == CodeImportMachineState.ONLINE, (
"The machine %s is %s."
@@ -241,7 +241,7 @@
"""See `ICodeImportJobWorkflow`."""
assert import_job.state == CodeImportJobState.RUNNING, (
"The CodeImportJob associated with %s is %s."
- % (import_job.code_import.branch.unique_name,
+ % (import_job.code_import.target.unique_name,
import_job.state.name))
# CodeImportJobWorkflow is the only class that is allowed to
# set the heartbeat and logtail attributes of CodeImportJob,
@@ -281,7 +281,7 @@
"""See `ICodeImportJobWorkflow`."""
assert import_job.state == CodeImportJobState.RUNNING, (
"The CodeImportJob associated with %s is %s."
- % (import_job.code_import.branch.unique_name,
+ % (import_job.code_import.target.unique_name,
import_job.state.name))
code_import = import_job.code_import
machine = import_job.machine
@@ -311,7 +311,8 @@
naked_import.date_last_successful = result.date_created
# If the status was successful and revisions were imported, arrange
# for the branch to be mirrored.
- if status == CodeImportResultStatus.SUCCESS:
+ if (status == CodeImportResultStatus.SUCCESS and
+ code_import.branch is not None):
code_import.branch.requestMirror()
getUtility(ICodeImportEventSet).newFinish(
code_import, machine)
@@ -320,7 +321,7 @@
"""See `ICodeImportJobWorkflow`."""
assert import_job.state == CodeImportJobState.RUNNING, (
"The CodeImportJob associated with %s is %s."
- % (import_job.code_import.branch.unique_name,
+ % (import_job.code_import.target.unique_name,
import_job.state.name))
# Cribbing from codeimport-job.txt, this method does four things:
# 1) deletes the passed in job,
=== modified file 'lib/lp/code/model/gitnamespace.py'
--- lib/lp/code/model/gitnamespace.py 2016-10-03 17:03:12 +0000
+++ lib/lp/code/model/gitnamespace.py 2016-10-03 17:03:17 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Implementations of `IGitNamespace`."""
@@ -238,6 +238,7 @@
has_defaults = False
allow_push_to_set_default = False
supports_merge_proposals = False
+ supports_code_imports = False
allow_recipe_name_from_target = False
def __init__(self, person):
@@ -316,6 +317,7 @@
has_defaults = True
allow_push_to_set_default = True
supports_merge_proposals = True
+ supports_code_imports = True
allow_recipe_name_from_target = True
def __init__(self, person, project):
@@ -401,6 +403,7 @@
has_defaults = True
allow_push_to_set_default = False
supports_merge_proposals = True
+ supports_code_imports = True
allow_recipe_name_from_target = True
def __init__(self, person, distro_source_package):
=== modified file 'lib/lp/code/model/hasbranches.py'
--- lib/lp/code/model/hasbranches.py 2016-10-03 10:54:48 +0000
+++ lib/lp/code/model/hasbranches.py 2016-10-03 17:03:17 +0000
@@ -16,7 +16,10 @@
from zope.component import getUtility
from zope.security.proxy import removeSecurityProxy
-from lp.code.enums import BranchMergeProposalStatus
+from lp.code.enums import (
+ BranchMergeProposalStatus,
+ TargetRevisionControlSystems,
+ )
from lp.code.interfaces.branch import DEFAULT_BRANCH_STATUS_IN_LISTING
from lp.code.interfaces.branchcollection import (
IAllBranches,
@@ -130,5 +133,6 @@
owner=None):
"""See `IHasCodeImports`."""
return getUtility(ICodeImportSet).new(
- registrant, self, branch_name, rcs_type, url=url,
+ registrant, self, branch_name, rcs_type,
+ TargetRevisionControlSystems.BZR, url=url,
cvs_root=cvs_root, cvs_module=cvs_module, owner=owner)
=== modified file 'lib/lp/code/model/tests/test_codeimport.py'
--- lib/lp/code/model/tests/test_codeimport.py 2016-09-30 13:27:27 +0000
+++ lib/lp/code/model/tests/test_codeimport.py 2016-10-03 17:03:17 +0000
@@ -7,10 +7,15 @@
datetime,
timedelta,
)
+from functools import partial
import pytz
from sqlobject import SQLObjectNotFound
from storm.store import Store
+from testscenarios import (
+ load_tests_apply_scenarios,
+ WithScenarios,
+ )
from zope.component import getUtility
from zope.security.proxy import removeSecurityProxy
@@ -19,12 +24,14 @@
CodeImportResultStatus,
CodeImportReviewStatus,
RevisionControlSystems,
+ TargetRevisionControlSystems,
)
from lp.code.errors import (
BranchCreatorNotMemberOfOwnerTeam,
CodeImportAlreadyRequested,
CodeImportAlreadyRunning,
CodeImportNotInReviewedState,
+ GitRepositoryCreatorNotMemberOfOwnerTeam,
)
from lp.code.interfaces.branchtarget import IBranchTarget
from lp.code.interfaces.codeimportjob import ICodeImportJobWorkflow
@@ -51,63 +58,100 @@
)
-class TestCodeImportCreation(TestCaseWithFactory):
+class TestCodeImportBase(WithScenarios, TestCaseWithFactory):
+
+ scenarios = [
+ ("Branch", {
+ "target_rcs_type": TargetRevisionControlSystems.BZR,
+ "supports_source_cvs": True,
+ "supports_source_svn": True,
+ "supports_source_bzr": True,
+ }),
+ ("GitRepository", {
+ "target_rcs_type": TargetRevisionControlSystems.GIT,
+ "supports_source_cvs": False,
+ "supports_source_svn": False,
+ "supports_source_bzr": False,
+ }),
+ ]
+
+
+class TestCodeImportCreation(TestCodeImportBase):
"""Test the creation of CodeImports."""
layer = DatabaseFunctionalLayer
def test_new_svn_import_svn_scheme(self):
"""A subversion import can use the svn:// scheme."""
- code_import = CodeImportSet().new(
+ create_func = partial(
+ CodeImportSet().new,
registrant=self.factory.makePerson(),
context=self.factory.makeProduct(),
- branch_name='imported',
+ branch_name=u'imported',
rcs_type=RevisionControlSystems.BZR_SVN,
+ target_rcs_type=self.target_rcs_type,
url=self.factory.getUniqueURL(scheme="svn"))
- self.assertEqual(
- CodeImportReviewStatus.REVIEWED,
- code_import.review_status)
- # No job is created for the import.
- self.assertIsNot(None, code_import.import_job)
+ if self.supports_source_svn:
+ code_import = create_func()
+ self.assertEqual(
+ CodeImportReviewStatus.REVIEWED,
+ code_import.review_status)
+ # No job is created for the import.
+ self.assertIsNot(None, code_import.import_job)
+ else:
+ self.assertRaises(AssertionError, create_func)
def test_reviewed_svn_import(self):
"""A specific review status can be set for a new import."""
- code_import = CodeImportSet().new(
+ create_func = partial(
+ CodeImportSet().new,
registrant=self.factory.makePerson(),
context=self.factory.makeProduct(),
- branch_name='imported',
+ branch_name=u'imported',
rcs_type=RevisionControlSystems.BZR_SVN,
+ target_rcs_type=self.target_rcs_type,
url=self.factory.getUniqueURL(),
review_status=None)
- self.assertEqual(
- CodeImportReviewStatus.REVIEWED,
- code_import.review_status)
- # A job is created for the import.
- self.assertIsNot(None, code_import.import_job)
+ if self.supports_source_svn:
+ code_import = create_func()
+ self.assertEqual(
+ CodeImportReviewStatus.REVIEWED,
+ code_import.review_status)
+ # A job is created for the import.
+ self.assertIsNot(None, code_import.import_job)
+ else:
+ self.assertRaises(AssertionError, create_func)
def test_cvs_import_reviewed(self):
"""A new CVS code import should have REVIEWED status."""
- code_import = CodeImportSet().new(
+ create_func = partial(
+ CodeImportSet().new,
registrant=self.factory.makePerson(),
context=self.factory.makeProduct(),
- branch_name='imported',
+ branch_name=u'imported',
rcs_type=RevisionControlSystems.CVS,
+ target_rcs_type=self.target_rcs_type,
cvs_root=self.factory.getUniqueURL(),
- cvs_module='module',
+ cvs_module=u'module',
review_status=None)
- self.assertEqual(
- CodeImportReviewStatus.REVIEWED,
- code_import.review_status)
- # A job is created for the import.
- self.assertIsNot(None, code_import.import_job)
+ if self.supports_source_cvs:
+ code_import = create_func()
+ self.assertEqual(
+ CodeImportReviewStatus.REVIEWED,
+ code_import.review_status)
+ # A job is created for the import.
+ self.assertIsNot(None, code_import.import_job)
+ else:
+ self.assertRaises(AssertionError, create_func)
def test_git_import_git_scheme(self):
"""A git import can have a git:// style URL."""
code_import = CodeImportSet().new(
registrant=self.factory.makePerson(),
context=self.factory.makeProduct(),
- branch_name='imported',
+ branch_name=u'imported',
rcs_type=RevisionControlSystems.GIT,
+ target_rcs_type=self.target_rcs_type,
url=self.factory.getUniqueURL(scheme="git"),
review_status=None)
self.assertEqual(
@@ -121,8 +165,9 @@
code_import = CodeImportSet().new(
registrant=self.factory.makePerson(),
context=self.factory.makeProduct(),
- branch_name='imported',
+ branch_name=u'imported',
rcs_type=RevisionControlSystems.GIT,
+ target_rcs_type=self.target_rcs_type,
url=self.factory.getUniqueURL(),
review_status=None)
self.assertEqual(
@@ -133,18 +178,24 @@
def test_bzr_import_reviewed(self):
"""A new bzr import is always reviewed by default."""
- code_import = CodeImportSet().new(
+ create_func = partial(
+ CodeImportSet().new,
registrant=self.factory.makePerson(),
context=self.factory.makeProduct(),
- branch_name='mirrored',
+ branch_name=u'mirrored',
rcs_type=RevisionControlSystems.BZR,
+ target_rcs_type=self.target_rcs_type,
url=self.factory.getUniqueURL(),
review_status=None)
- self.assertEqual(
- CodeImportReviewStatus.REVIEWED,
- code_import.review_status)
- # A job is created for the import.
- self.assertIsNot(None, code_import.import_job)
+ if self.supports_source_bzr:
+ code_import = create_func()
+ self.assertEqual(
+ CodeImportReviewStatus.REVIEWED,
+ code_import.review_status)
+ # A job is created for the import.
+ self.assertIsNot(None, code_import.import_job)
+ else:
+ self.assertRaises(AssertionError, create_func)
def test_junk_code_import_rejected(self):
"""You are not allowed to create code imports targetting +junk."""
@@ -152,8 +203,9 @@
self.assertRaises(AssertionError, CodeImportSet().new,
registrant=registrant,
context=registrant,
- branch_name='imported',
+ branch_name=u'imported',
rcs_type=RevisionControlSystems.GIT,
+ target_rcs_type=self.target_rcs_type,
url=self.factory.getUniqueURL(),
review_status=None)
@@ -161,19 +213,27 @@
"""Test that we can create an import targetting a source package."""
registrant = self.factory.makePerson()
source_package = self.factory.makeSourcePackage()
+ if self.target_rcs_type == TargetRevisionControlSystems.BZR:
+ context = source_package
+ else:
+ context = source_package.distribution_sourcepackage
code_import = CodeImportSet().new(
registrant=registrant,
- context=source_package,
- branch_name='imported',
+ context=context,
+ branch_name=u'imported',
rcs_type=RevisionControlSystems.GIT,
+ target_rcs_type=self.target_rcs_type,
url=self.factory.getUniqueURL(),
review_status=None)
code_import = removeSecurityProxy(code_import)
self.assertEqual(registrant, code_import.registrant)
- self.assertEqual(registrant, code_import.branch.owner)
- self.assertEqual(
- IBranchTarget(source_package), code_import.branch.target)
- self.assertEqual(source_package, code_import.branch.sourcepackage)
+ if self.target_rcs_type == TargetRevisionControlSystems.BZR:
+ self.assertEqual(registrant, code_import.branch.owner)
+ self.assertEqual(IBranchTarget(context), code_import.branch.target)
+ self.assertEqual(source_package, code_import.branch.sourcepackage)
+ else:
+ self.assertEqual(registrant, code_import.git_repository.owner)
+ self.assertEqual(context, code_import.git_repository.target)
# And a job is still created
self.assertIsNot(None, code_import.import_job)
@@ -183,20 +243,29 @@
owner = self.factory.makeTeam()
removeSecurityProxy(registrant).join(owner)
source_package = self.factory.makeSourcePackage()
+ if self.target_rcs_type == TargetRevisionControlSystems.BZR:
+ context = source_package
+ else:
+ context = source_package.distribution_sourcepackage
code_import = CodeImportSet().new(
registrant=registrant,
- context=source_package,
- branch_name='imported',
+ context=context,
+ branch_name=u'imported',
rcs_type=RevisionControlSystems.GIT,
+ target_rcs_type=self.target_rcs_type,
url=self.factory.getUniqueURL(),
review_status=None, owner=owner)
code_import = removeSecurityProxy(code_import)
self.assertEqual(registrant, code_import.registrant)
- self.assertEqual(owner, code_import.branch.owner)
- self.assertEqual(registrant, code_import.branch.registrant)
- self.assertEqual(
- IBranchTarget(source_package), code_import.branch.target)
- self.assertEqual(source_package, code_import.branch.sourcepackage)
+ if self.target_rcs_type == TargetRevisionControlSystems.BZR:
+ self.assertEqual(owner, code_import.branch.owner)
+ self.assertEqual(registrant, code_import.branch.registrant)
+ self.assertEqual(IBranchTarget(context), code_import.branch.target)
+ self.assertEqual(source_package, code_import.branch.sourcepackage)
+ else:
+ self.assertEqual(owner, code_import.git_repository.owner)
+ self.assertEqual(registrant, code_import.git_repository.registrant)
+ self.assertEqual(context, code_import.git_repository.target)
# And a job is still created
self.assertIsNot(None, code_import.import_job)
@@ -205,30 +274,39 @@
registrant = self.factory.makePerson()
owner = self.factory.makeTeam()
source_package = self.factory.makeSourcePackage()
+ if self.target_rcs_type == TargetRevisionControlSystems.BZR:
+ context = source_package
+ expected_exception = BranchCreatorNotMemberOfOwnerTeam
+ else:
+ context = source_package.distribution_sourcepackage
+ expected_exception = GitRepositoryCreatorNotMemberOfOwnerTeam
self.assertRaises(
- BranchCreatorNotMemberOfOwnerTeam,
+ expected_exception,
CodeImportSet().new,
registrant=registrant,
- context=source_package,
- branch_name='imported',
+ context=context,
+ branch_name=u'imported',
rcs_type=RevisionControlSystems.GIT,
+ target_rcs_type=self.target_rcs_type,
url=self.factory.getUniqueURL(),
review_status=None, owner=owner)
-class TestCodeImportDeletion(TestCaseWithFactory):
+class TestCodeImportDeletion(TestCodeImportBase):
"""Test the deletion of CodeImports."""
layer = LaunchpadFunctionalLayer
def test_delete(self):
"""Ensure CodeImport objects can be deleted via CodeImportSet."""
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
CodeImportSet().delete(code_import)
def test_deleteIncludesJob(self):
"""Ensure deleting CodeImport objects deletes associated jobs."""
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
login_person(getUtility(ILaunchpadCelebrities).vcs_imports.teamowner)
job_id = code_import.import_job.id
CodeImportJobSet().getById(job_id)
@@ -240,7 +318,10 @@
def test_deleteIncludesEvent(self):
"""Ensure deleting CodeImport objects deletes associated events."""
- code_import_event = self.factory.makeCodeImportEvent()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
+ code_import_event = self.factory.makeCodeImportEvent(
+ code_import=code_import)
code_import_event_id = code_import_event.id
CodeImportSet().delete(code_import_event.code_import)
# CodeImportEvent.get should not raise anything.
@@ -251,7 +332,10 @@
def test_deleteIncludesResult(self):
"""Ensure deleting CodeImport objects deletes associated results."""
- code_import_result = self.factory.makeCodeImportResult()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
+ code_import_result = self.factory.makeCodeImportResult(
+ code_import=code_import)
code_import_result_id = code_import_result.id
CodeImportSet().delete(code_import_result.code_import)
# CodeImportResult.get should not raise anything.
@@ -261,7 +345,7 @@
SQLObjectNotFound, CodeImportResult.get, code_import_result_id)
-class TestCodeImportStatusUpdate(TestCaseWithFactory):
+class TestCodeImportStatusUpdate(TestCodeImportBase):
"""Test the status updates of CodeImports."""
layer = DatabaseFunctionalLayer
@@ -276,7 +360,8 @@
job.destroySelf()
def makeApprovedImportWithPendingJob(self):
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
code_import.updateFromData(
{'review_status': CodeImportReviewStatus.REVIEWED},
self.import_operator)
@@ -290,7 +375,8 @@
def test_approve(self):
# Approving a code import will create a job for it.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
code_import.updateFromData(
{'review_status': CodeImportReviewStatus.REVIEWED},
self.import_operator)
@@ -300,7 +386,8 @@
def test_suspend_no_job(self):
# Suspending a new import has no impact on jobs.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
code_import.updateFromData(
{'review_status': CodeImportReviewStatus.SUSPENDED},
self.import_operator)
@@ -330,7 +417,8 @@
def test_invalidate_no_job(self):
# Invalidating a new import has no impact on jobs.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
code_import.updateFromData(
{'review_status': CodeImportReviewStatus.INVALID},
self.import_operator)
@@ -360,7 +448,8 @@
def test_markFailing_no_job(self):
# Marking a new import as failing has no impact on jobs.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
code_import.updateFromData(
{'review_status': CodeImportReviewStatus.FAILING},
self.import_operator)
@@ -389,14 +478,15 @@
CodeImportReviewStatus.FAILING, code_import.review_status)
-class TestCodeImportResultsAttribute(TestCaseWithFactory):
+class TestCodeImportResultsAttribute(TestCodeImportBase):
"""Test the results attribute of a CodeImport."""
layer = LaunchpadFunctionalLayer
def setUp(self):
TestCaseWithFactory.setUp(self)
- self.code_import = self.factory.makeCodeImport()
+ self.code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
def tearDown(self):
super(TestCodeImportResultsAttribute, self).tearDown()
@@ -454,7 +544,7 @@
self.assertEqual(third, results[2])
-class TestConsecutiveFailureCount(TestCaseWithFactory):
+class TestConsecutiveFailureCount(TestCodeImportBase):
"""Tests for `ICodeImport.consecutive_failure_count`."""
layer = LaunchpadZopelessLayer
@@ -495,27 +585,31 @@
def test_consecutive_failure_count_zero_initially(self):
# A new code import has a consecutive_failure_count of 0.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
self.assertEqual(0, code_import.consecutive_failure_count)
def test_consecutive_failure_count_succeed(self):
# A code import that has succeeded once has a
# consecutive_failure_count of 0.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
self.succeedImport(code_import)
self.assertEqual(0, code_import.consecutive_failure_count)
def test_consecutive_failure_count_fail(self):
# A code import that has failed once has a consecutive_failure_count
# of 1.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
self.failImport(code_import)
self.assertEqual(1, code_import.consecutive_failure_count)
def test_consecutive_failure_count_succeed_succeed_no_changes(self):
# A code import that has succeeded then succeeded with no changes has
# a consecutive_failure_count of 0.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
self.succeedImport(code_import)
self.succeedImport(
code_import, CodeImportResultStatus.SUCCESS_NOCHANGE)
@@ -524,7 +618,8 @@
def test_consecutive_failure_count_succeed_succeed_partial(self):
# A code import that has succeeded then succeeded with no changes has
# a consecutive_failure_count of 0.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
self.succeedImport(code_import)
self.succeedImport(
code_import, CodeImportResultStatus.SUCCESS_NOCHANGE)
@@ -533,7 +628,8 @@
def test_consecutive_failure_count_fail_fail(self):
# A code import that has failed twice has a consecutive_failure_count
# of 2.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
self.failImport(code_import)
self.failImport(code_import)
self.assertEqual(2, code_import.consecutive_failure_count)
@@ -541,7 +637,8 @@
def test_consecutive_failure_count_fail_fail_succeed(self):
# A code import that has failed twice then succeeded has a
# consecutive_failure_count of 0.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
self.failImport(code_import)
self.failImport(code_import)
self.succeedImport(code_import)
@@ -550,7 +647,8 @@
def test_consecutive_failure_count_fail_succeed_fail(self):
# A code import that has failed then succeeded then failed again has a
# consecutive_failure_count of 1.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
self.failImport(code_import)
self.succeedImport(code_import)
self.failImport(code_import)
@@ -559,7 +657,8 @@
def test_consecutive_failure_count_succeed_fail_succeed(self):
# A code import that has succeeded then failed then succeeded again
# has a consecutive_failure_count of 0.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
self.succeedImport(code_import)
self.failImport(code_import)
self.succeedImport(code_import)
@@ -568,8 +667,10 @@
def test_consecutive_failure_count_other_import_non_interference(self):
# The failure or success of other code imports does not affect
# consecutive_failure_count.
- code_import = self.factory.makeCodeImport()
- other_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
+ other_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
self.failImport(code_import)
self.assertEqual(1, code_import.consecutive_failure_count)
self.failImport(other_import)
@@ -584,7 +685,7 @@
self.assertEqual(1, code_import.consecutive_failure_count)
-class TestTryFailingImportAgain(TestCaseWithFactory):
+class TestTryFailingImportAgain(TestCodeImportBase):
"""Tests for `ICodeImport.tryFailingImportAgain`."""
layer = DatabaseFunctionalLayer
@@ -599,6 +700,7 @@
outcomes = {}
for status in CodeImportReviewStatus.items:
code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type,
review_status=CodeImportReviewStatus.NEW)
code_import.updateFromData(
{'review_status': status}, self.factory.makePerson())
@@ -619,7 +721,8 @@
def test_resetsStatus(self):
# tryFailingImportAgain sets the review_status of the import back to
# REVIEWED.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
code_import.updateFromData(
{'review_status': CodeImportReviewStatus.FAILING},
self.factory.makePerson())
@@ -630,7 +733,8 @@
def test_requestsImport(self):
# tryFailingImportAgain requests an import.
- code_import = self.factory.makeCodeImport()
+ code_import = self.factory.makeCodeImport(
+ target_rcs_type=self.target_rcs_type)
code_import.updateFromData(
{'review_status': CodeImportReviewStatus.FAILING},
self.factory.makePerson())
@@ -640,7 +744,7 @@
requester, code_import.import_job.requesting_user)
-class TestRequestImport(TestCaseWithFactory):
+class TestRequestImport(TestCodeImportBase):
"""Tests for `ICodeImport.requestImport`."""
layer = DatabaseFunctionalLayer
@@ -651,7 +755,8 @@
def test_requestsJob(self):
code_import = self.factory.makeCodeImport(
- git_repo_url=self.factory.getUniqueURL())
+ git_repo_url=self.factory.getUniqueURL(),
+ target_rcs_type=self.target_rcs_type)
requester = self.factory.makePerson()
old_date = code_import.import_job.date_due
code_import.requestImport(requester)
@@ -660,7 +765,8 @@
def test_noop_if_already_requested(self):
code_import = self.factory.makeCodeImport(
- git_repo_url=self.factory.getUniqueURL())
+ git_repo_url=self.factory.getUniqueURL(),
+ target_rcs_type=self.target_rcs_type)
requester = self.factory.makePerson()
code_import.requestImport(requester)
old_date = code_import.import_job.date_due
@@ -672,7 +778,8 @@
def test_optional_error_if_already_requested(self):
code_import = self.factory.makeCodeImport(
- git_repo_url=self.factory.getUniqueURL())
+ git_repo_url=self.factory.getUniqueURL(),
+ target_rcs_type=self.target_rcs_type)
requester = self.factory.makePerson()
code_import.requestImport(requester)
e = self.assertRaises(
@@ -681,10 +788,14 @@
self.assertEqual(requester, e.requesting_user)
def test_exception_on_disabled(self):
- # get an SVN request which is suspended
+ # get an SVN/Git (as appropriate) request which is suspended
+ if self.supports_source_svn:
+ kwargs = {"svn_branch_url": self.factory.getUniqueURL()}
+ else:
+ kwargs = {"git_repo_url": self.factory.getUniqueURL()}
code_import = self.factory.makeCodeImport(
- svn_branch_url=self.factory.getUniqueURL(),
- review_status=CodeImportReviewStatus.SUSPENDED)
+ target_rcs_type=self.target_rcs_type,
+ review_status=CodeImportReviewStatus.SUSPENDED, **kwargs)
requester = self.factory.makePerson()
# which leads to an exception if we try and ask for an import
self.assertRaises(
@@ -693,10 +804,14 @@
def test_exception_if_already_running(self):
code_import = self.factory.makeCodeImport(
- git_repo_url=self.factory.getUniqueURL())
+ git_repo_url=self.factory.getUniqueURL(),
+ target_rcs_type=self.target_rcs_type)
code_import = make_running_import(factory=self.factory,
code_import=code_import)
requester = self.factory.makePerson()
self.assertRaises(
CodeImportAlreadyRunning, code_import.requestImport,
requester)
+
+
+load_tests = load_tests_apply_scenarios
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2016-09-30 13:27:27 +0000
+++ lib/lp/registry/browser/product.py 2016-10-03 17:03:17 +0000
@@ -145,7 +145,10 @@
from lp.code.browser.codeimport import validate_import_url
from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin
from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
-from lp.code.enums import RevisionControlSystems
+from lp.code.enums import (
+ RevisionControlSystems,
+ TargetRevisionControlSystems,
+ )
from lp.code.errors import BranchExists
from lp.code.interfaces.branch import IBranch
from lp.code.interfaces.branchjob import IRosettaUploadJobSource
@@ -1988,6 +1991,7 @@
context=self.context,
branch_name=branch_name,
rcs_type=rcs_item,
+ target_rcs_type=TargetRevisionControlSystems.BZR,
url=url,
cvs_root=cvs_root,
cvs_module=cvs_module)
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2016-10-03 17:03:12 +0000
+++ lib/lp/testing/factory.py 2016-10-03 17:03:17 +0000
@@ -114,6 +114,7 @@
GitObjectType,
GitRepositoryType,
RevisionControlSystems,
+ TargetRevisionControlSystems,
)
from lp.code.errors import UnknownBranchTypeError
from lp.code.interfaces.branch import IBranch
@@ -2374,21 +2375,28 @@
cvs_module=None, context=None, branch_name=None,
git_repo_url=None,
bzr_branch_url=None, registrant=None,
- rcs_type=None, review_status=None):
+ rcs_type=None, target_rcs_type=None,
+ review_status=None):
"""Create and return a new, arbitrary code import.
The type of code import will be inferred from the source details
- passed in, but defaults to a Subversion import from an arbitrary
- unique URL.
+ passed in, but defaults to a Subversion->Bazaar import from an
+ arbitrary unique URL. (If the target type is specified as Git, then
+ the source type instead defaults to Git.)
"""
+ if target_rcs_type is None:
+ target_rcs_type = TargetRevisionControlSystems.BZR
if (svn_branch_url is cvs_root is cvs_module is git_repo_url is
bzr_branch_url is None):
- svn_branch_url = self.getUniqueURL()
+ if target_rcs_type == TargetRevisionControlSystems.BZR:
+ svn_branch_url = self.getUniqueURL()
+ else:
+ git_repo_url = self.getUniqueURL()
if context is None:
context = self.makeProduct()
if branch_name is None:
- branch_name = self.getUniqueString('name')
+ branch_name = self.getUniqueUnicode('name')
if registrant is None:
registrant = self.makePerson()
@@ -2398,23 +2406,27 @@
return code_import_set.new(
registrant, context, branch_name,
rcs_type=RevisionControlSystems.BZR_SVN,
+ target_rcs_type=target_rcs_type,
url=svn_branch_url, review_status=review_status)
elif git_repo_url is not None:
assert rcs_type in (None, RevisionControlSystems.GIT)
return code_import_set.new(
registrant, context, branch_name,
rcs_type=RevisionControlSystems.GIT,
+ target_rcs_type=target_rcs_type,
url=git_repo_url, review_status=review_status)
elif bzr_branch_url is not None:
return code_import_set.new(
registrant, context, branch_name,
rcs_type=RevisionControlSystems.BZR,
+ target_rcs_type=target_rcs_type,
url=bzr_branch_url, review_status=review_status)
else:
assert rcs_type in (None, RevisionControlSystems.CVS)
return code_import_set.new(
registrant, context, branch_name,
rcs_type=RevisionControlSystems.CVS,
+ target_rcs_type=target_rcs_type,
cvs_root=cvs_root, cvs_module=cvs_module,
review_status=review_status)
@@ -2440,9 +2452,10 @@
changelog += entry
return self.makeLibraryFileAlias(content=changelog.encode("utf-8"))
- def makeCodeImportEvent(self):
+ def makeCodeImportEvent(self, code_import=None):
"""Create and return a CodeImportEvent."""
- code_import = self.makeCodeImport()
+ if code_import is None:
+ code_import = self.makeCodeImport()
person = self.makePerson()
code_import_event_set = getUtility(ICodeImportEventSet)
return code_import_event_set.newCreate(code_import, person)
Follow ups