launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #18404
[Merge] lp:~cjwatson/launchpad/git-fix-default-flags into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/git-fix-default-flags into lp:launchpad.
Commit message:
When moving a repository, check that the target_default and owner_default flags fit the target namespace.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/git-fix-default-flags/+merge/257708
When moving a repository, check that the target_default and owner_default flags fit the target namespace. That is, we should refuse if you try to move the default repository for a target to a different target which already has a default, or a person's default repository for a target to a different person/target combination which already has a default. I thought about unsetting the existing default automatically, but I think the less magical behaviour is better, at least to start with.
I added a few GitNamespace tests to go with this, which I didn't add initially due to lack of sufficient infrastructure. We could flesh those out now, but that doesn't have to be done in this branch.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/git-fix-default-flags into lp:launchpad.
=== modified file 'lib/lp/code/errors.py'
--- lib/lp/code/errors.py 2015-04-19 12:56:32 +0000
+++ lib/lp/code/errors.py 2015-04-28 23:42:22 +0000
@@ -404,15 +404,15 @@
params = {
"unique_name": existing_repository.unique_name,
"target": target.displayname,
- "owner": owner.displayname,
}
if owner is None:
message = (
"The default repository for '%(target)s' is already set to "
"%(unique_name)s." % params)
else:
+ params["owner"] = owner.displayname
message = (
- "%(owner)'s default repository for '%(target)s' is already "
+ "%(owner)s's default repository for '%(target)s' is already "
"set to %(unique_name)s." % params)
self.existing_repository = existing_repository
self.target = target
=== modified file 'lib/lp/code/interfaces/gitnamespace.py'
--- lib/lp/code/interfaces/gitnamespace.py 2015-04-22 16:11:40 +0000
+++ lib/lp/code/interfaces/gitnamespace.py 2015-04-28 23:42:22 +0000
@@ -134,6 +134,16 @@
validation constraints on IGitRepository.name.
"""
+ def validateDefaultFlags(repository):
+ """Check that any default flags on 'repository' fit this namespace.
+
+ :param repository: An `IGitRepository` to check.
+ :raises GitDefaultConflict: If the repository has the target_default
+ flag set but this namespace already has a target default, or if
+ the repository has the owner_default flag set but this namespace
+ already has an owner-target default.
+ """
+
def validateMove(repository, mover, name=None):
"""Check that 'mover' can move 'repository' into this namespace.
=== modified file 'lib/lp/code/model/gitnamespace.py'
--- lib/lp/code/model/gitnamespace.py 2015-04-22 16:11:40 +0000
+++ lib/lp/code/model/gitnamespace.py 2015-04-28 23:42:22 +0000
@@ -34,6 +34,7 @@
CodeReviewNotificationLevel,
)
from lp.code.errors import (
+ GitDefaultConflict,
GitRepositoryCreationForbidden,
GitRepositoryCreatorNotMemberOfOwnerTeam,
GitRepositoryCreatorNotOwner,
@@ -47,6 +48,7 @@
)
from lp.code.interfaces.gitrepository import (
IGitRepository,
+ IGitRepositorySet,
user_has_special_git_repository_access,
)
from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
@@ -146,12 +148,29 @@
# "gitrepository" violates check constraint "valid_name"...'.
IGitRepository['name'].validate(unicode(name))
+ def validateDefaultFlags(self, repository):
+ """See `IGitNamespace`."""
+ repository_set = getUtility(IGitRepositorySet)
+ if repository.target_default and self.target != repository.target:
+ existing = repository_set.getDefaultRepository(self.target)
+ if existing is not None:
+ raise GitDefaultConflict(existing, self.target)
+ if (repository.owner_default and
+ (self.owner != repository.owner or
+ self.target != repository.target)):
+ existing = repository_set.getDefaultRepositoryForOwner(
+ self.owner, self.target)
+ if existing is not None:
+ raise GitDefaultConflict(
+ existing, self.target, owner=self.owner)
+
def validateMove(self, repository, mover, name=None):
"""See `IGitNamespace`."""
if name is None:
name = repository.name
self.validateRepositoryName(name)
self.validateRegistrant(mover)
+ self.validateDefaultFlags(repository)
def moveRepository(self, repository, mover, new_name=None,
rename_if_necessary=False):
=== added file 'lib/lp/code/model/tests/test_gitnamespace.py'
--- lib/lp/code/model/tests/test_gitnamespace.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/model/tests/test_gitnamespace.py 2015-04-28 23:42:22 +0000
@@ -0,0 +1,145 @@
+# Copyright 2015 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for `IGitNamespace` implementations."""
+
+from zope.component import getUtility
+
+from lp.app.interfaces.launchpad import ILaunchpadCelebrities
+from lp.code.errors import (
+ GitDefaultConflict,
+ GitRepositoryExists,
+ )
+from lp.code.interfaces.gitrepository import (
+ GIT_FEATURE_FLAG,
+ IGitRepositorySet,
+ )
+from lp.code.model.gitnamespace import ProjectGitNamespace
+from lp.services.features.testing import FeatureFixture
+from lp.testing import TestCaseWithFactory
+from lp.testing.layers import DatabaseFunctionalLayer
+
+
+class TestGitNamespaceMoveRepository(TestCaseWithFactory):
+ """Test the IGitNamespace.moveRepository method."""
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestGitNamespaceMoveRepository, self).setUp()
+ self.useFixture(FeatureFixture({GIT_FEATURE_FLAG: u"on"}))
+
+ def assertNamespacesEqual(self, expected, result):
+ """Assert that the namespaces refer to the same thing.
+
+ The name of the namespace contains the user name and the context
+ parts, so is the easiest thing to check.
+ """
+ self.assertEqual(expected.name, result.name)
+
+ def test_move_to_same_namespace(self):
+ # Moving to the same namespace is effectively a no-op. No
+ # exceptions about matching repository names should be raised.
+ repository = self.factory.makeGitRepository()
+ namespace = repository.namespace
+ namespace.moveRepository(repository, repository.owner)
+ self.assertNamespacesEqual(namespace, repository.namespace)
+
+ def test_name_clash_raises(self):
+ # A name clash will raise an exception.
+ repository = self.factory.makeGitRepository(name=u"test")
+ another = self.factory.makeGitRepository(
+ owner=repository.owner, name=u"test")
+ namespace = another.namespace
+ self.assertRaises(
+ GitRepositoryExists, namespace.moveRepository,
+ repository, repository.owner)
+
+ def test_move_with_rename(self):
+ # A name clash with 'rename_if_necessary' set to True will cause the
+ # repository to be renamed instead of raising an error.
+ repository = self.factory.makeGitRepository(name=u"test")
+ another = self.factory.makeGitRepository(
+ owner=repository.owner, name=u"test")
+ namespace = another.namespace
+ namespace.moveRepository(
+ repository, repository.owner, rename_if_necessary=True)
+ self.assertEqual("test-1", repository.name)
+ self.assertNamespacesEqual(namespace, repository.namespace)
+
+ def test_move_with_new_name(self):
+ # A new name for the repository can be specified as part of the move.
+ repository = self.factory.makeGitRepository(name=u"test")
+ another = self.factory.makeGitRepository(
+ owner=repository.owner, name=u"test")
+ namespace = another.namespace
+ namespace.moveRepository(repository, repository.owner, new_name=u"foo")
+ self.assertEqual("foo", repository.name)
+ self.assertNamespacesEqual(namespace, repository.namespace)
+
+ def test_sets_repository_owner(self):
+ # Moving to a new namespace may change the owner of the repository
+ # if the owner of the namespace is different.
+ repository = self.factory.makeGitRepository(name=u"test")
+ team = self.factory.makeTeam(repository.owner)
+ project = self.factory.makeProduct()
+ namespace = ProjectGitNamespace(team, project)
+ namespace.moveRepository(repository, repository.owner)
+ self.assertEqual(team, repository.owner)
+ # And for paranoia.
+ self.assertNamespacesEqual(namespace, repository.namespace)
+
+ def test_target_default_clash_raises(self):
+ # A clash between target_default repositories will raise an exception.
+ repository = self.factory.makeGitRepository()
+ repository.setTargetDefault(True)
+ another = self.factory.makeGitRepository()
+ another.setTargetDefault(True)
+ self.assertRaisesWithContent(
+ GitDefaultConflict,
+ "The default repository for '%s' is already set to %s." % (
+ another.target.displayname, another.unique_name),
+ another.namespace.moveRepository,
+ repository, getUtility(ILaunchpadCelebrities).admin.teamowner)
+
+ def test_owner_default_clash_raises(self):
+ # A clash between owner_default repositories will raise an exception.
+ repository = self.factory.makeGitRepository()
+ repository.setOwnerDefault(True)
+ another = self.factory.makeGitRepository()
+ another.setOwnerDefault(True)
+ self.assertRaisesWithContent(
+ GitDefaultConflict,
+ "%s's default repository for '%s' is already set to %s." % (
+ another.owner.displayname, another.target.displayname,
+ another.unique_name),
+ another.namespace.moveRepository,
+ repository, getUtility(ILaunchpadCelebrities).admin.teamowner)
+
+ def test_preserves_target_default(self):
+ # If there is no clash, target_default is preserved.
+ repository = self.factory.makeGitRepository()
+ repository.setTargetDefault(True)
+ another = self.factory.makeGitRepository()
+ namespace = another.namespace
+ namespace.moveRepository(
+ repository, getUtility(ILaunchpadCelebrities).admin.teamowner)
+ self.assertNamespacesEqual(namespace, repository.namespace)
+ repository_set = getUtility(IGitRepositorySet)
+ self.assertEqual(
+ repository, repository_set.getDefaultRepository(another.target))
+
+ def test_preserves_owner_default(self):
+ # If there is no clash, owner_default is preserved.
+ repository = self.factory.makeGitRepository()
+ repository.setOwnerDefault(True)
+ another = self.factory.makeGitRepository()
+ namespace = another.namespace
+ namespace.moveRepository(
+ repository, getUtility(ILaunchpadCelebrities).admin.teamowner)
+ self.assertNamespacesEqual(namespace, repository.namespace)
+ repository_set = getUtility(IGitRepositorySet)
+ self.assertEqual(
+ repository,
+ repository_set.getDefaultRepositoryForOwner(
+ another.owner, another.target))
Follow ups