Merge lp:~cjwatson/launchpad/relax-personal-git-mp-restrictions into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18726
Proposed branch: lp:~cjwatson/launchpad/relax-personal-git-mp-restrictions
Merge into: lp:launchpad
Diff against target: 585 lines (+320/-60)
7 files modified
lib/lp/app/widgets/suggestion.py (+39/-24)
lib/lp/app/widgets/tests/test_suggestion.py (+58/-20)
lib/lp/code/interfaces/gitnamespace.py (+7/-3)
lib/lp/code/model/gitnamespace.py (+18/-8)
lib/lp/code/model/gitrepository.py (+2/-2)
lib/lp/code/model/tests/test_gitnamespace.py (+183/-1)
lib/lp/code/model/tests/test_gitref.py (+13/-2)
To merge this branch: bzr merge lp:~cjwatson/launchpad/relax-personal-git-mp-restrictions
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+349253@code.launchpad.net

Commit message

Allow proposing merges between different branches of the same personal Git repository.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

Why is the suggestion widget now sometimes called on a GitRef rather than a GitRepository?

review: Approve (code)
Revision history for this message
Colin Watson (cjwatson) wrote :

> Why is the suggestion widget now sometimes called on a GitRef rather than a
> GitRepository?

It was in fact always that way: it's essentially just because this widget is used on GitRef:+register-merge. It happened to work by chance because GitRef has lots of properties that pass through to the repository. I only noticed when I tried to set `target_repositories = [repository]` in the personal namespace case and found that the UI rendered it as something like "~owner/+git/repo:branch" rather than just "~owner/+git/repo".

I think it's reasonably worth handling both even though we only use the GitRef case today, since we often want to expose similar operations on GitRef and GitRepository: for example, once we have a better ref picker I can well imagine wanting to add GitRepository:+register-merge.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/app/widgets/suggestion.py'
--- lib/lp/app/widgets/suggestion.py 2016-01-12 12:28:09 +0000
+++ lib/lp/app/widgets/suggestion.py 2018-07-11 08:48:06 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the1# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Widgets related to IBranch."""4"""Widgets related to IBranch."""
@@ -34,7 +34,9 @@
3434
35from lp.app.widgets.itemswidgets import LaunchpadRadioWidget35from lp.app.widgets.itemswidgets import LaunchpadRadioWidget
36from lp.code.interfaces.gitcollection import IGitCollection36from lp.code.interfaces.gitcollection import IGitCollection
37from lp.code.interfaces.gitref import IGitRef
37from lp.code.interfaces.gitrepository import IGitRepositorySet38from lp.code.interfaces.gitrepository import IGitRepositorySet
39from lp.registry.interfaces.person import IPerson
38from lp.services.webapp import canonical_url40from lp.services.webapp import canonical_url
39from lp.services.webapp.escaping import (41from lp.services.webapp.escaping import (
40 html_escape,42 html_escape,
@@ -295,31 +297,42 @@
295 """297 """
296298
297 @staticmethod299 @staticmethod
298 def _generateSuggestionVocab(repository, full_vocabulary):300 def _generateSuggestionVocab(context, full_vocabulary):
299 """Generate the vocabulary for the radio buttons.301 """Generate the vocabulary for the radio buttons.
300302
301 The generated vocabulary contains the default repository for the303 The generated vocabulary contains the default repository for the
302 target if there is one, and also any other repositories that the304 target if there is one, and also any other repositories that the
303 user has specified recently as a target for a proposed merge.305 user has specified recently as a target for a proposed merge.
304 """306 """
305 default_target = getUtility(IGitRepositorySet).getDefaultRepository(307 if IGitRef.providedBy(context):
306 repository.target)308 repository = context.repository
307 logged_in_user = getUtility(ILaunchBag).user309 else:
308 since = datetime.now(utc) - timedelta(days=90)310 repository = context
309 collection = IGitCollection(repository.target).targetedBy(311
310 logged_in_user, since)312 if IPerson.providedBy(repository.target):
311 collection = collection.visibleByUser(logged_in_user)313 # If the source is a personal repository, then the only valid
312 # May actually need some eager loading, but the API isn't fine grained314 # target is that same repository.
313 # yet.315 target_repositories = [repository]
314 repositories = collection.getRepositories(eager_load=False).config(316 else:
315 distinct=True)317 repository_set = getUtility(IGitRepositorySet)
316 target_repositories = list(repositories.config(limit=5))318 default_target = repository_set.getDefaultRepository(
317 # If there is a default repository, make sure it is always shown,319 repository.target)
318 # and as the first item.320 logged_in_user = getUtility(ILaunchBag).user
319 if default_target is not None:321 since = datetime.now(utc) - timedelta(days=90)
320 if default_target in target_repositories:322 collection = IGitCollection(repository.target).targetedBy(
321 target_repositories.remove(default_target)323 logged_in_user, since)
322 target_repositories.insert(0, default_target)324 collection = collection.visibleByUser(logged_in_user)
325 # May actually need some eager loading, but the API isn't fine
326 # grained yet.
327 repositories = collection.getRepositories(eager_load=False).config(
328 distinct=True)
329 target_repositories = list(repositories.config(limit=5))
330 # If there is a default repository, make sure it is always
331 # shown, and as the first item.
332 if default_target is not None:
333 if default_target in target_repositories:
334 target_repositories.remove(default_target)
335 target_repositories.insert(0, default_target)
323336
324 terms = []337 terms = []
325 for repository in target_repositories:338 for repository in target_repositories:
@@ -335,10 +348,12 @@
335 # instead to have a separate link to the repository details.348 # instead to have a separate link to the repository details.
336 text = u'%s (<a href="%s">repository details</a>)'349 text = u'%s (<a href="%s">repository details</a>)'
337 # If the repository is the default for the target, say so.350 # If the repository is the default for the target, say so.
338 default_target = getUtility(IGitRepositorySet).getDefaultRepository(351 if not IPerson.providedBy(repository.target):
339 repository.target)352 repository_set = getUtility(IGitRepositorySet)
340 if repository == default_target:353 default_target = repository_set.getDefaultRepository(
341 text += u"&ndash; <em>default repository</em>"354 repository.target)
355 if repository == default_target:
356 text += u"&ndash; <em>default repository</em>"
342 label = (357 label = (
343 u'<label for="%s" style="font-weight: normal">' + text +358 u'<label for="%s" style="font-weight: normal">' + text +
344 u'</label>')359 u'</label>')
345360
=== modified file 'lib/lp/app/widgets/tests/test_suggestion.py'
--- lib/lp/app/widgets/tests/test_suggestion.py 2015-07-08 16:05:11 +0000
+++ lib/lp/app/widgets/tests/test_suggestion.py 2018-07-11 08:48:06 +0000
@@ -1,4 +1,4 @@
1# Copyright 2011-2015 Canonical Ltd. This software is licensed under the1# Copyright 2011-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -199,11 +199,11 @@
199 DocTestMatches(expected, self.doctest_opts))199 DocTestMatches(expected, self.doctest_opts))
200200
201201
202def make_target_git_repository_widget(repository):202def make_target_git_repository_widget(context):
203 """Given a Git repository, return a widget for selecting where to land203 """Given a Git repository or reference, return a widget for selecting
204 it."""204 where to land it."""
205 choice = Choice(__name__='test_field', vocabulary='GitRepository')205 choice = Choice(__name__='test_field', vocabulary='GitRepository')
206 choice = choice.bind(repository)206 choice = choice.bind(context)
207 request = LaunchpadTestRequest()207 request = LaunchpadTestRequest()
208 return TargetGitRepositoryWidget(choice, None, request)208 return TargetGitRepositoryWidget(choice, None, request)
209209
@@ -216,41 +216,79 @@
216 doctest.NORMALIZE_WHITESPACE | doctest.REPORT_NDIFF |216 doctest.NORMALIZE_WHITESPACE | doctest.REPORT_NDIFF |
217 doctest.ELLIPSIS)217 doctest.ELLIPSIS)
218218
219 def makeRepositoryAndOldMergeProposal(self, timedelta):219 def makeRefAndOldMergeProposal(self, timedelta):
220 """Make an old merge proposal and a repository with the same target."""220 """Make an old merge proposal and a ref with the same target."""
221 bmp = self.factory.makeBranchMergeProposalForGit(221 bmp = self.factory.makeBranchMergeProposalForGit(
222 date_created=datetime.now(utc) - timedelta)222 date_created=datetime.now(utc) - timedelta)
223 login_person(bmp.registrant)223 login_person(bmp.registrant)
224 target = bmp.target_git_repository224 target = bmp.merge_target
225 return target, self.factory.makeGitRepository(target=target.target)225 return target, self.factory.makeGitRefs(target=target.target)[0]
226226
227 def test_recent_target(self):227 def test_recent_target(self):
228 """Targets for proposals newer than 90 days are included."""228 """Targets for proposals newer than 90 days are included."""
229 target, source = self.makeRepositoryAndOldMergeProposal(229 target, source = self.makeRefAndOldMergeProposal(timedelta(days=89))
230 timedelta(days=89))
231 widget = make_target_git_repository_widget(source)230 widget = make_target_git_repository_widget(source)
232 self.assertIn(target, widget.suggestion_vocab)231 self.assertIn(target.repository, widget.suggestion_vocab)
233232
234 def test_stale_target(self):233 def test_stale_target(self):
235 """Targets for proposals older than 90 days are not considered."""234 """Targets for proposals older than 90 days are not considered."""
236 target, source = self.makeRepositoryAndOldMergeProposal(235 target, source = self.makeRefAndOldMergeProposal(timedelta(days=91))
237 timedelta(days=91))236 widget = make_target_git_repository_widget(source)
238 widget = make_target_git_repository_widget(source)237 self.assertNotIn(target.repository, widget.suggestion_vocab)
239 self.assertNotIn(target, widget.suggestion_vocab)238
239 def test_personal_repository(self):
240 """Proposals for personal repositories only suggest that repository."""
241 owner = self.factory.makePerson()
242 this_source, this_target = self.factory.makeGitRefs(
243 owner=owner, target=owner,
244 paths=[u"refs/heads/source", u"refs/heads/target"])
245 bmp = self.factory.makeBranchMergeProposalForGit(
246 source_ref=this_source, target_ref=this_target,
247 date_created=datetime.now(utc) - timedelta(days=1))
248 other_source, other_target = self.factory.makeGitRefs(
249 owner=owner, target=owner,
250 paths=[u"refs/heads/source", u"refs/heads/target"])
251 self.factory.makeBranchMergeProposalForGit(
252 source_ref=other_source, target_ref=other_target,
253 date_created=datetime.now(utc) - timedelta(days=1))
254 login_person(bmp.registrant)
255 [source] = self.factory.makeGitRefs(repository=this_target.repository)
256 widget = make_target_git_repository_widget(source)
257 self.assertContentEqual(
258 [this_target.repository],
259 [term.value for term in widget.suggestion_vocab])
240260
241 def test__renderSuggestionLabel(self):261 def test__renderSuggestionLabel(self):
242 """Git repositories have a reasonable suggestion label."""262 """Git repositories have a reasonable suggestion label."""
243 target, source = self.makeRepositoryAndOldMergeProposal(263 target, source = self.makeRefAndOldMergeProposal(timedelta(days=1))
244 timedelta(days=1))
245 login_person(target.target.owner)264 login_person(target.target.owner)
246 getUtility(IGitRepositorySet).setDefaultRepository(265 getUtility(IGitRepositorySet).setDefaultRepository(
247 target.target, target)266 target.target, target.repository)
248 widget = make_target_git_repository_widget(source)267 widget = make_target_git_repository_widget(source)
249 expected = (268 expected = (
250 """<label for="field.test_field.2"269 """<label for="field.test_field.2"
251 ...>... (<a href="...">repository details</a>)&ndash;270 ...>... (<a href="...">repository details</a>)&ndash;
252 <em>default repository</em></label>""")271 <em>default repository</em></label>""")
253 structured_string = widget._renderSuggestionLabel(target, 2)272 structured_string = widget._renderSuggestionLabel(target.repository, 2)
273 self.assertThat(
274 structured_string.escapedtext,
275 DocTestMatches(expected, self.doctest_opts))
276
277 def test__renderSuggestionLabel_personal(self):
278 """Personal Git repositories have a reasonable suggestion label."""
279 owner = self.factory.makePerson()
280 source, target = self.factory.makeGitRefs(
281 owner=owner, target=owner,
282 paths=[u"refs/heads/source", u"refs/heads/target"])
283 bmp = self.factory.makeBranchMergeProposalForGit(
284 source_ref=source, target_ref=target,
285 date_created=datetime.now(utc) - timedelta(days=1))
286 login_person(bmp.registrant)
287 widget = make_target_git_repository_widget(source)
288 expected = (
289 """<label for="field.test_field.2"
290 ...>... (<a href="...">repository details</a>)</label>""")
291 structured_string = widget._renderSuggestionLabel(target.repository, 2)
254 self.assertThat(292 self.assertThat(
255 structured_string.escapedtext,293 structured_string.escapedtext,
256 DocTestMatches(expected, self.doctest_opts))294 DocTestMatches(expected, self.doctest_opts))
257295
=== modified file 'lib/lp/code/interfaces/gitnamespace.py'
--- lib/lp/code/interfaces/gitnamespace.py 2016-10-14 17:25:51 +0000
+++ lib/lp/code/interfaces/gitnamespace.py 2018-07-11 08:48:06 +0000
@@ -1,4 +1,4 @@
1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the1# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Interface for a Git repository namespace."""4"""Interface for a Git repository namespace."""
@@ -178,8 +178,12 @@
178 already exists in the namespace.178 already exists in the namespace.
179 """179 """
180180
181 def areRepositoriesMergeable(other_namespace):181 def areRepositoriesMergeable(this, other):
182 """Are repositories from other_namespace mergeable into this one?"""182 """Is `other` mergeable into `this`?
183
184 :param this: An `IGitRepository` in this namespace.
185 :param other: An `IGitRepository` in either this or another namespace.
186 """
183187
184 collection = Attribute("An `IGitCollection` for this namespace.")188 collection = Attribute("An `IGitCollection` for this namespace.")
185189
186190
=== modified file 'lib/lp/code/model/gitnamespace.py'
--- lib/lp/code/model/gitnamespace.py 2018-05-14 08:01:56 +0000
+++ lib/lp/code/model/gitnamespace.py 2018-07-11 08:48:06 +0000
@@ -1,4 +1,4 @@
1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the1# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Implementations of `IGitNamespace`."""4"""Implementations of `IGitNamespace`."""
@@ -250,7 +250,7 @@
250250
251 has_defaults = False251 has_defaults = False
252 allow_push_to_set_default = False252 allow_push_to_set_default = False
253 supports_merge_proposals = False253 supports_merge_proposals = True
254 supports_code_imports = False254 supports_code_imports = False
255 allow_recipe_name_from_target = False255 allow_recipe_name_from_target = False
256256
@@ -303,15 +303,17 @@
303 else:303 else:
304 return InformationType.PUBLIC304 return InformationType.PUBLIC
305305
306 def areRepositoriesMergeable(self, other_namespace):306 def areRepositoriesMergeable(self, this, other):
307 """See `IGitNamespacePolicy`."""307 """See `IGitNamespacePolicy`."""
308 return False308 if this.namespace != self:
309 raise AssertionError(
310 "Namespace of %s is not %s." % (this.unique_name, self.name))
311 return this == other
309312
310 @property313 @property
311 def collection(self):314 def collection(self):
312 """See `IGitNamespacePolicy`."""315 """See `IGitNamespacePolicy`."""
313 return getUtility(IAllGitRepositories).ownedBy(316 return getUtility(IAllGitRepositories).ownedBy(self.owner).isPersonal()
314 self.person).isPersonal()
315317
316 def assignKarma(self, person, action_name, date_created=None):318 def assignKarma(self, person, action_name, date_created=None):
317 """See `IGitNamespacePolicy`."""319 """See `IGitNamespacePolicy`."""
@@ -383,12 +385,16 @@
383 return None385 return None
384 return default_type386 return default_type
385387
386 def areRepositoriesMergeable(self, other_namespace):388 def areRepositoriesMergeable(self, this, other):
387 """See `IGitNamespacePolicy`."""389 """See `IGitNamespacePolicy`."""
388 # Repositories are mergeable into a project repository if the390 # Repositories are mergeable into a project repository if the
389 # project is the same.391 # project is the same.
390 # XXX cjwatson 2015-04-18: Allow merging from a package repository392 # XXX cjwatson 2015-04-18: Allow merging from a package repository
391 # if any (active?) series is linked to this project.393 # if any (active?) series is linked to this project.
394 if this.namespace != self:
395 raise AssertionError(
396 "Namespace of %s is not %s." % (this.unique_name, self.name))
397 other_namespace = other.namespace
392 if zope_isinstance(other_namespace, ProjectGitNamespace):398 if zope_isinstance(other_namespace, ProjectGitNamespace):
393 return self.target == other_namespace.target399 return self.target == other_namespace.target
394 else:400 else:
@@ -457,12 +463,16 @@
457 """See `IGitNamespace`."""463 """See `IGitNamespace`."""
458 return InformationType.PUBLIC464 return InformationType.PUBLIC
459465
460 def areRepositoriesMergeable(self, other_namespace):466 def areRepositoriesMergeable(self, this, other):
461 """See `IGitNamespacePolicy`."""467 """See `IGitNamespacePolicy`."""
462 # Repositories are mergeable into a package repository if the468 # Repositories are mergeable into a package repository if the
463 # package is the same.469 # package is the same.
464 # XXX cjwatson 2015-04-18: Allow merging from a project repository470 # XXX cjwatson 2015-04-18: Allow merging from a project repository
465 # if any (active?) series links this package to that project.471 # if any (active?) series links this package to that project.
472 if this.namespace != self:
473 raise AssertionError(
474 "Namespace of %s is not %s." % (this.unique_name, self.name))
475 other_namespace = other.namespace
466 if zope_isinstance(other_namespace, PackageGitNamespace):476 if zope_isinstance(other_namespace, PackageGitNamespace):
467 return self.target == other_namespace.target477 return self.target == other_namespace.target
468 else:478 else:
469479
=== modified file 'lib/lp/code/model/gitrepository.py'
--- lib/lp/code/model/gitrepository.py 2017-11-24 17:22:34 +0000
+++ lib/lp/code/model/gitrepository.py 2018-07-11 08:48:06 +0000
@@ -1,4 +1,4 @@
1# Copyright 2015-2017 Canonical Ltd. This software is licensed under the1# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -946,7 +946,7 @@
946946
947 def isRepositoryMergeable(self, other):947 def isRepositoryMergeable(self, other):
948 """See `IGitRepository`."""948 """See `IGitRepository`."""
949 return self.namespace.areRepositoriesMergeable(other.namespace)949 return self.namespace.areRepositoriesMergeable(self, other)
950950
951 @property951 @property
952 def pending_updates(self):952 def pending_updates(self):
953953
=== modified file 'lib/lp/code/model/tests/test_gitnamespace.py'
--- lib/lp/code/model/tests/test_gitnamespace.py 2017-10-04 01:29:35 +0000
+++ lib/lp/code/model/tests/test_gitnamespace.py 2018-07-11 08:48:06 +0000
@@ -1,4 +1,4 @@
1# Copyright 2015-2017 Canonical Ltd. This software is licensed under the1# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for `IGitNamespace` implementations."""4"""Tests for `IGitNamespace` implementations."""
@@ -278,6 +278,70 @@
278 namespace = PersonalGitNamespace(person)278 namespace = PersonalGitNamespace(person)
279 self.assertEqual(person, namespace.target)279 self.assertEqual(person, namespace.target)
280280
281 def test_supports_merge_proposals(self):
282 # Personal namespaces support merge proposals.
283 self.assertTrue(self.getNamespace().supports_merge_proposals)
284
285 def test_areRepositoriesMergeable_same_repository(self):
286 # A personal repository is mergeable into itself.
287 owner = self.factory.makePerson()
288 repository = self.factory.makeGitRepository(owner=owner, target=owner)
289 self.assertTrue(
290 repository.namespace.areRepositoriesMergeable(
291 repository, repository))
292
293 def test_areRepositoriesMergeable_same_namespace(self):
294 # A personal repository is not mergeable into another personal
295 # repository, even if they are in the same namespace.
296 owner = self.factory.makePerson()
297 this = self.factory.makeGitRepository(owner=owner, target=owner)
298 other = self.factory.makeGitRepository(owner=owner, target=owner)
299 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
300
301 def test_areRepositoriesMergeable_different_namespace(self):
302 # A personal repository is not mergeable into another personal
303 # repository with a different namespace.
304 this_owner = self.factory.makePerson()
305 this = self.factory.makeGitRepository(
306 owner=this_owner, target=this_owner)
307 other_owner = self.factory.makePerson()
308 other = self.factory.makeGitRepository(
309 owner=other_owner, target=other_owner)
310 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
311
312 def test_areRepositoriesMergeable_project(self):
313 # Project repositories are not mergeable into personal repositories.
314 owner = self.factory.makePerson()
315 this = self.factory.makeGitRepository(owner=owner, target=owner)
316 project = self.factory.makeProduct()
317 other = self.factory.makeGitRepository(owner=owner, target=project)
318 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
319
320 def test_areRepositoriesMergeable_package(self):
321 # Package repositories are not mergeable into personal repositories.
322 owner = self.factory.makePerson()
323 this = self.factory.makeGitRepository(owner=owner, target=owner)
324 dsp = self.factory.makeDistributionSourcePackage()
325 other = self.factory.makeGitRepository(owner=owner, target=dsp)
326 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
327
328 def test_collection(self):
329 # A personal namespace's collection is of personal repositories with
330 # the same owner.
331 owner = self.factory.makePerson()
332 repositories = [
333 self.factory.makeGitRepository(owner=owner, target=owner)
334 for _ in range(3)]
335 other_owner = self.factory.makePerson()
336 self.factory.makeGitRepository(owner=other_owner, target=other_owner)
337 self.factory.makeGitRepository(
338 owner=owner, target=self.factory.makeProduct())
339 self.factory.makeGitRepository(
340 owner=owner, target=self.factory.makeDistributionSourcePackage())
341 self.assertContentEqual(
342 repositories,
343 repositories[0].namespace.collection.getRepositories())
344
281345
282class TestProjectGitNamespace(TestCaseWithFactory, NamespaceMixin):346class TestProjectGitNamespace(TestCaseWithFactory, NamespaceMixin):
283 """Tests for `ProjectGitNamespace`."""347 """Tests for `ProjectGitNamespace`."""
@@ -312,6 +376,65 @@
312 namespace = ProjectGitNamespace(person, project)376 namespace = ProjectGitNamespace(person, project)
313 self.assertEqual(project, namespace.target)377 self.assertEqual(project, namespace.target)
314378
379 def test_supports_merge_proposals(self):
380 # Project namespaces support merge proposals.
381 self.assertTrue(self.getNamespace().supports_merge_proposals)
382
383 def test_areRepositoriesMergeable_same_repository(self):
384 # A project repository is mergeable into itself.
385 project = self.factory.makeProduct()
386 repository = self.factory.makeGitRepository(target=project)
387 self.assertTrue(
388 repository.namespace.areRepositoriesMergeable(
389 repository, repository))
390
391 def test_areRepositoriesMergeable_same_namespace(self):
392 # Repositories of the same project are mergeable.
393 project = self.factory.makeProduct()
394 this = self.factory.makeGitRepository(target=project)
395 other = self.factory.makeGitRepository(target=project)
396 self.assertTrue(this.namespace.areRepositoriesMergeable(this, other))
397
398 def test_areRepositoriesMergeable_different_namespace(self):
399 # Repositories of a different project are not mergeable.
400 this_project = self.factory.makeProduct()
401 this = self.factory.makeGitRepository(target=this_project)
402 other_project = self.factory.makeProduct()
403 other = self.factory.makeGitRepository(target=other_project)
404 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
405
406 def test_areRepositoriesMergeable_personal(self):
407 # Personal repositories are not mergeable into project repositories.
408 owner = self.factory.makePerson()
409 project = self.factory.makeProduct()
410 this = self.factory.makeGitRepository(owner=owner, target=project)
411 other = self.factory.makeGitRepository(owner=owner, target=owner)
412 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
413
414 def test_areRepositoriesMergeable_package(self):
415 # Package repositories are not mergeable into project repositories.
416 owner = self.factory.makePerson()
417 project = self.factory.makeProduct()
418 this = self.factory.makeGitRepository(owner=owner, target=project)
419 dsp = self.factory.makeDistributionSourcePackage()
420 other = self.factory.makeGitRepository(owner=owner, target=dsp)
421 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
422
423 def test_collection(self):
424 # A project namespace's collection is of repositories for the same
425 # project.
426 project = self.factory.makeProduct()
427 repositories = [
428 self.factory.makeGitRepository(target=project) for _ in range(3)]
429 self.factory.makeGitRepository(target=self.factory.makeProduct())
430 self.factory.makeGitRepository(
431 owner=repositories[0].owner, target=repositories[0].owner)
432 self.factory.makeGitRepository(
433 target=self.factory.makeDistributionSourcePackage())
434 self.assertContentEqual(
435 repositories,
436 repositories[0].namespace.collection.getRepositories())
437
315438
316class TestProjectGitNamespacePrivacyWithInformationType(TestCaseWithFactory):439class TestProjectGitNamespacePrivacyWithInformationType(TestCaseWithFactory):
317 """Tests for the privacy aspects of `ProjectGitNamespace`.440 """Tests for the privacy aspects of `ProjectGitNamespace`.
@@ -521,6 +644,65 @@
521 namespace = PackageGitNamespace(person, dsp)644 namespace = PackageGitNamespace(person, dsp)
522 self.assertEqual(dsp, namespace.target)645 self.assertEqual(dsp, namespace.target)
523646
647 def test_supports_merge_proposals(self):
648 # Package namespaces support merge proposals.
649 self.assertTrue(self.getNamespace().supports_merge_proposals)
650
651 def test_areRepositoriesMergeable_same_repository(self):
652 # A package repository is mergeable into itself.
653 dsp = self.factory.makeDistributionSourcePackage()
654 repository = self.factory.makeGitRepository(target=dsp)
655 self.assertTrue(
656 repository.namespace.areRepositoriesMergeable(
657 repository, repository))
658
659 def test_areRepositoriesMergeable_same_namespace(self):
660 # Repositories of the same package are mergeable.
661 dsp = self.factory.makeDistributionSourcePackage()
662 this = self.factory.makeGitRepository(target=dsp)
663 other = self.factory.makeGitRepository(target=dsp)
664 self.assertTrue(this.namespace.areRepositoriesMergeable(this, other))
665
666 def test_areRepositoriesMergeable_different_namespace(self):
667 # Repositories of a different package are not mergeable.
668 this_dsp = self.factory.makeDistributionSourcePackage()
669 this = self.factory.makeGitRepository(target=this_dsp)
670 other_dsp = self.factory.makeDistributionSourcePackage()
671 other = self.factory.makeGitRepository(target=other_dsp)
672 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
673
674 def test_areRepositoriesMergeable_personal(self):
675 # Personal repositories are not mergeable into package repositories.
676 owner = self.factory.makePerson()
677 dsp = self.factory.makeProduct()
678 this = self.factory.makeGitRepository(owner=owner, target=dsp)
679 other = self.factory.makeGitRepository(owner=owner, target=owner)
680 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
681
682 def test_areRepositoriesMergeable_project(self):
683 # Project repositories are not mergeable into package repositories.
684 owner = self.factory.makePerson()
685 dsp = self.factory.makeDistributionSourcePackage()
686 this = self.factory.makeGitRepository(owner=owner, target=dsp)
687 project = self.factory.makeProduct()
688 other = self.factory.makeGitRepository(owner=owner, target=project)
689 self.assertFalse(this.namespace.areRepositoriesMergeable(this, other))
690
691 def test_collection(self):
692 # A package namespace's collection is of repositories for the same
693 # package.
694 dsp = self.factory.makeDistributionSourcePackage()
695 repositories = [
696 self.factory.makeGitRepository(target=dsp) for _ in range(3)]
697 self.factory.makeGitRepository(
698 target=self.factory.makeDistributionSourcePackage())
699 self.factory.makeGitRepository(target=self.factory.makeProduct())
700 self.factory.makeGitRepository(
701 owner=repositories[0].owner, target=repositories[0].owner)
702 self.assertContentEqual(
703 repositories,
704 repositories[0].namespace.collection.getRepositories())
705
524706
525class BaseCanCreateRepositoriesMixin:707class BaseCanCreateRepositoriesMixin:
526 """Common tests for all namespaces."""708 """Common tests for all namespaces."""
527709
=== modified file 'lib/lp/code/model/tests/test_gitref.py'
--- lib/lp/code/model/tests/test_gitref.py 2017-10-04 01:29:35 +0000
+++ lib/lp/code/model/tests/test_gitref.py 2018-07-11 08:48:06 +0000
@@ -301,14 +301,25 @@
301 else:301 else:
302 self.assertEqual(review_type, vote.review_type)302 self.assertEqual(review_type, vote.review_type)
303303
304 def test_personal_source(self):304 def test_personal_source_project_target(self):
305 """Personal repositories cannot be used as a source for MPs."""305 """Personal repositories cannot be used as a source for MPs to
306 project repositories.
307 """
306 self.source.repository.setTarget(308 self.source.repository.setTarget(
307 target=self.source.owner, user=self.source.owner)309 target=self.source.owner, user=self.source.owner)
308 self.assertRaises(310 self.assertRaises(
309 InvalidBranchMergeProposal, self.source.addLandingTarget,311 InvalidBranchMergeProposal, self.source.addLandingTarget,
310 self.user, self.target)312 self.user, self.target)
311313
314 def test_personal_source_personal_target(self):
315 """A branch in a personal repository can be used as a source for MPs
316 to another branch in the same personal repository.
317 """
318 self.target.repository.setTarget(
319 target=self.target.owner, user=self.target.owner)
320 [source] = self.factory.makeGitRefs(repository=self.target.repository)
321 source.addLandingTarget(self.user, self.target)
322
312 def test_target_repository_same_target(self):323 def test_target_repository_same_target(self):
313 """The target repository's target must match that of the source."""324 """The target repository's target must match that of the source."""
314 self.target.repository.setTarget(325 self.target.repository.setTarget(