Merge lp:~cjwatson/launchpad/relax-personal-git-mp-restrictions into lp:launchpad
- relax-personal-git-mp-restrictions
- Merge into devel
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 |
Related bugs: |
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.
Description of the change
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:
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:
Preview Diff
1 | === modified file 'lib/lp/app/widgets/suggestion.py' |
2 | --- lib/lp/app/widgets/suggestion.py 2016-01-12 12:28:09 +0000 |
3 | +++ lib/lp/app/widgets/suggestion.py 2018-07-11 08:48:06 +0000 |
4 | @@ -1,4 +1,4 @@ |
5 | -# Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
6 | +# Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
7 | # GNU Affero General Public License version 3 (see the file LICENSE). |
8 | |
9 | """Widgets related to IBranch.""" |
10 | @@ -34,7 +34,9 @@ |
11 | |
12 | from lp.app.widgets.itemswidgets import LaunchpadRadioWidget |
13 | from lp.code.interfaces.gitcollection import IGitCollection |
14 | +from lp.code.interfaces.gitref import IGitRef |
15 | from lp.code.interfaces.gitrepository import IGitRepositorySet |
16 | +from lp.registry.interfaces.person import IPerson |
17 | from lp.services.webapp import canonical_url |
18 | from lp.services.webapp.escaping import ( |
19 | html_escape, |
20 | @@ -295,31 +297,42 @@ |
21 | """ |
22 | |
23 | @staticmethod |
24 | - def _generateSuggestionVocab(repository, full_vocabulary): |
25 | + def _generateSuggestionVocab(context, full_vocabulary): |
26 | """Generate the vocabulary for the radio buttons. |
27 | |
28 | The generated vocabulary contains the default repository for the |
29 | target if there is one, and also any other repositories that the |
30 | user has specified recently as a target for a proposed merge. |
31 | """ |
32 | - default_target = getUtility(IGitRepositorySet).getDefaultRepository( |
33 | - repository.target) |
34 | - logged_in_user = getUtility(ILaunchBag).user |
35 | - since = datetime.now(utc) - timedelta(days=90) |
36 | - collection = IGitCollection(repository.target).targetedBy( |
37 | - logged_in_user, since) |
38 | - collection = collection.visibleByUser(logged_in_user) |
39 | - # May actually need some eager loading, but the API isn't fine grained |
40 | - # yet. |
41 | - repositories = collection.getRepositories(eager_load=False).config( |
42 | - distinct=True) |
43 | - target_repositories = list(repositories.config(limit=5)) |
44 | - # If there is a default repository, make sure it is always shown, |
45 | - # and as the first item. |
46 | - if default_target is not None: |
47 | - if default_target in target_repositories: |
48 | - target_repositories.remove(default_target) |
49 | - target_repositories.insert(0, default_target) |
50 | + if IGitRef.providedBy(context): |
51 | + repository = context.repository |
52 | + else: |
53 | + repository = context |
54 | + |
55 | + if IPerson.providedBy(repository.target): |
56 | + # If the source is a personal repository, then the only valid |
57 | + # target is that same repository. |
58 | + target_repositories = [repository] |
59 | + else: |
60 | + repository_set = getUtility(IGitRepositorySet) |
61 | + default_target = repository_set.getDefaultRepository( |
62 | + repository.target) |
63 | + logged_in_user = getUtility(ILaunchBag).user |
64 | + since = datetime.now(utc) - timedelta(days=90) |
65 | + collection = IGitCollection(repository.target).targetedBy( |
66 | + logged_in_user, since) |
67 | + collection = collection.visibleByUser(logged_in_user) |
68 | + # May actually need some eager loading, but the API isn't fine |
69 | + # grained yet. |
70 | + repositories = collection.getRepositories(eager_load=False).config( |
71 | + distinct=True) |
72 | + target_repositories = list(repositories.config(limit=5)) |
73 | + # If there is a default repository, make sure it is always |
74 | + # shown, and as the first item. |
75 | + if default_target is not None: |
76 | + if default_target in target_repositories: |
77 | + target_repositories.remove(default_target) |
78 | + target_repositories.insert(0, default_target) |
79 | |
80 | terms = [] |
81 | for repository in target_repositories: |
82 | @@ -335,10 +348,12 @@ |
83 | # instead to have a separate link to the repository details. |
84 | text = u'%s (<a href="%s">repository details</a>)' |
85 | # If the repository is the default for the target, say so. |
86 | - default_target = getUtility(IGitRepositorySet).getDefaultRepository( |
87 | - repository.target) |
88 | - if repository == default_target: |
89 | - text += u"– <em>default repository</em>" |
90 | + if not IPerson.providedBy(repository.target): |
91 | + repository_set = getUtility(IGitRepositorySet) |
92 | + default_target = repository_set.getDefaultRepository( |
93 | + repository.target) |
94 | + if repository == default_target: |
95 | + text += u"– <em>default repository</em>" |
96 | label = ( |
97 | u'<label for="%s" style="font-weight: normal">' + text + |
98 | u'</label>') |
99 | |
100 | === modified file 'lib/lp/app/widgets/tests/test_suggestion.py' |
101 | --- lib/lp/app/widgets/tests/test_suggestion.py 2015-07-08 16:05:11 +0000 |
102 | +++ lib/lp/app/widgets/tests/test_suggestion.py 2018-07-11 08:48:06 +0000 |
103 | @@ -1,4 +1,4 @@ |
104 | -# Copyright 2011-2015 Canonical Ltd. This software is licensed under the |
105 | +# Copyright 2011-2018 Canonical Ltd. This software is licensed under the |
106 | # GNU Affero General Public License version 3 (see the file LICENSE). |
107 | |
108 | __metaclass__ = type |
109 | @@ -199,11 +199,11 @@ |
110 | DocTestMatches(expected, self.doctest_opts)) |
111 | |
112 | |
113 | -def make_target_git_repository_widget(repository): |
114 | - """Given a Git repository, return a widget for selecting where to land |
115 | - it.""" |
116 | +def make_target_git_repository_widget(context): |
117 | + """Given a Git repository or reference, return a widget for selecting |
118 | + where to land it.""" |
119 | choice = Choice(__name__='test_field', vocabulary='GitRepository') |
120 | - choice = choice.bind(repository) |
121 | + choice = choice.bind(context) |
122 | request = LaunchpadTestRequest() |
123 | return TargetGitRepositoryWidget(choice, None, request) |
124 | |
125 | @@ -216,41 +216,79 @@ |
126 | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_NDIFF | |
127 | doctest.ELLIPSIS) |
128 | |
129 | - def makeRepositoryAndOldMergeProposal(self, timedelta): |
130 | - """Make an old merge proposal and a repository with the same target.""" |
131 | + def makeRefAndOldMergeProposal(self, timedelta): |
132 | + """Make an old merge proposal and a ref with the same target.""" |
133 | bmp = self.factory.makeBranchMergeProposalForGit( |
134 | date_created=datetime.now(utc) - timedelta) |
135 | login_person(bmp.registrant) |
136 | - target = bmp.target_git_repository |
137 | - return target, self.factory.makeGitRepository(target=target.target) |
138 | + target = bmp.merge_target |
139 | + return target, self.factory.makeGitRefs(target=target.target)[0] |
140 | |
141 | def test_recent_target(self): |
142 | """Targets for proposals newer than 90 days are included.""" |
143 | - target, source = self.makeRepositoryAndOldMergeProposal( |
144 | - timedelta(days=89)) |
145 | + target, source = self.makeRefAndOldMergeProposal(timedelta(days=89)) |
146 | widget = make_target_git_repository_widget(source) |
147 | - self.assertIn(target, widget.suggestion_vocab) |
148 | + self.assertIn(target.repository, widget.suggestion_vocab) |
149 | |
150 | def test_stale_target(self): |
151 | """Targets for proposals older than 90 days are not considered.""" |
152 | - target, source = self.makeRepositoryAndOldMergeProposal( |
153 | - timedelta(days=91)) |
154 | - widget = make_target_git_repository_widget(source) |
155 | - self.assertNotIn(target, widget.suggestion_vocab) |
156 | + target, source = self.makeRefAndOldMergeProposal(timedelta(days=91)) |
157 | + widget = make_target_git_repository_widget(source) |
158 | + self.assertNotIn(target.repository, widget.suggestion_vocab) |
159 | + |
160 | + def test_personal_repository(self): |
161 | + """Proposals for personal repositories only suggest that repository.""" |
162 | + owner = self.factory.makePerson() |
163 | + this_source, this_target = self.factory.makeGitRefs( |
164 | + owner=owner, target=owner, |
165 | + paths=[u"refs/heads/source", u"refs/heads/target"]) |
166 | + bmp = self.factory.makeBranchMergeProposalForGit( |
167 | + source_ref=this_source, target_ref=this_target, |
168 | + date_created=datetime.now(utc) - timedelta(days=1)) |
169 | + other_source, other_target = self.factory.makeGitRefs( |
170 | + owner=owner, target=owner, |
171 | + paths=[u"refs/heads/source", u"refs/heads/target"]) |
172 | + self.factory.makeBranchMergeProposalForGit( |
173 | + source_ref=other_source, target_ref=other_target, |
174 | + date_created=datetime.now(utc) - timedelta(days=1)) |
175 | + login_person(bmp.registrant) |
176 | + [source] = self.factory.makeGitRefs(repository=this_target.repository) |
177 | + widget = make_target_git_repository_widget(source) |
178 | + self.assertContentEqual( |
179 | + [this_target.repository], |
180 | + [term.value for term in widget.suggestion_vocab]) |
181 | |
182 | def test__renderSuggestionLabel(self): |
183 | """Git repositories have a reasonable suggestion label.""" |
184 | - target, source = self.makeRepositoryAndOldMergeProposal( |
185 | - timedelta(days=1)) |
186 | + target, source = self.makeRefAndOldMergeProposal(timedelta(days=1)) |
187 | login_person(target.target.owner) |
188 | getUtility(IGitRepositorySet).setDefaultRepository( |
189 | - target.target, target) |
190 | + target.target, target.repository) |
191 | widget = make_target_git_repository_widget(source) |
192 | expected = ( |
193 | """<label for="field.test_field.2" |
194 | ...>... (<a href="...">repository details</a>)– |
195 | <em>default repository</em></label>""") |
196 | - structured_string = widget._renderSuggestionLabel(target, 2) |
197 | + structured_string = widget._renderSuggestionLabel(target.repository, 2) |
198 | + self.assertThat( |
199 | + structured_string.escapedtext, |
200 | + DocTestMatches(expected, self.doctest_opts)) |
201 | + |
202 | + def test__renderSuggestionLabel_personal(self): |
203 | + """Personal Git repositories have a reasonable suggestion label.""" |
204 | + owner = self.factory.makePerson() |
205 | + source, target = self.factory.makeGitRefs( |
206 | + owner=owner, target=owner, |
207 | + paths=[u"refs/heads/source", u"refs/heads/target"]) |
208 | + bmp = self.factory.makeBranchMergeProposalForGit( |
209 | + source_ref=source, target_ref=target, |
210 | + date_created=datetime.now(utc) - timedelta(days=1)) |
211 | + login_person(bmp.registrant) |
212 | + widget = make_target_git_repository_widget(source) |
213 | + expected = ( |
214 | + """<label for="field.test_field.2" |
215 | + ...>... (<a href="...">repository details</a>)</label>""") |
216 | + structured_string = widget._renderSuggestionLabel(target.repository, 2) |
217 | self.assertThat( |
218 | structured_string.escapedtext, |
219 | DocTestMatches(expected, self.doctest_opts)) |
220 | |
221 | === modified file 'lib/lp/code/interfaces/gitnamespace.py' |
222 | --- lib/lp/code/interfaces/gitnamespace.py 2016-10-14 17:25:51 +0000 |
223 | +++ lib/lp/code/interfaces/gitnamespace.py 2018-07-11 08:48:06 +0000 |
224 | @@ -1,4 +1,4 @@ |
225 | -# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
226 | +# Copyright 2015-2018 Canonical Ltd. This software is licensed under the |
227 | # GNU Affero General Public License version 3 (see the file LICENSE). |
228 | |
229 | """Interface for a Git repository namespace.""" |
230 | @@ -178,8 +178,12 @@ |
231 | already exists in the namespace. |
232 | """ |
233 | |
234 | - def areRepositoriesMergeable(other_namespace): |
235 | - """Are repositories from other_namespace mergeable into this one?""" |
236 | + def areRepositoriesMergeable(this, other): |
237 | + """Is `other` mergeable into `this`? |
238 | + |
239 | + :param this: An `IGitRepository` in this namespace. |
240 | + :param other: An `IGitRepository` in either this or another namespace. |
241 | + """ |
242 | |
243 | collection = Attribute("An `IGitCollection` for this namespace.") |
244 | |
245 | |
246 | === modified file 'lib/lp/code/model/gitnamespace.py' |
247 | --- lib/lp/code/model/gitnamespace.py 2018-05-14 08:01:56 +0000 |
248 | +++ lib/lp/code/model/gitnamespace.py 2018-07-11 08:48:06 +0000 |
249 | @@ -1,4 +1,4 @@ |
250 | -# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
251 | +# Copyright 2015-2018 Canonical Ltd. This software is licensed under the |
252 | # GNU Affero General Public License version 3 (see the file LICENSE). |
253 | |
254 | """Implementations of `IGitNamespace`.""" |
255 | @@ -250,7 +250,7 @@ |
256 | |
257 | has_defaults = False |
258 | allow_push_to_set_default = False |
259 | - supports_merge_proposals = False |
260 | + supports_merge_proposals = True |
261 | supports_code_imports = False |
262 | allow_recipe_name_from_target = False |
263 | |
264 | @@ -303,15 +303,17 @@ |
265 | else: |
266 | return InformationType.PUBLIC |
267 | |
268 | - def areRepositoriesMergeable(self, other_namespace): |
269 | + def areRepositoriesMergeable(self, this, other): |
270 | """See `IGitNamespacePolicy`.""" |
271 | - return False |
272 | + if this.namespace != self: |
273 | + raise AssertionError( |
274 | + "Namespace of %s is not %s." % (this.unique_name, self.name)) |
275 | + return this == other |
276 | |
277 | @property |
278 | def collection(self): |
279 | """See `IGitNamespacePolicy`.""" |
280 | - return getUtility(IAllGitRepositories).ownedBy( |
281 | - self.person).isPersonal() |
282 | + return getUtility(IAllGitRepositories).ownedBy(self.owner).isPersonal() |
283 | |
284 | def assignKarma(self, person, action_name, date_created=None): |
285 | """See `IGitNamespacePolicy`.""" |
286 | @@ -383,12 +385,16 @@ |
287 | return None |
288 | return default_type |
289 | |
290 | - def areRepositoriesMergeable(self, other_namespace): |
291 | + def areRepositoriesMergeable(self, this, other): |
292 | """See `IGitNamespacePolicy`.""" |
293 | # Repositories are mergeable into a project repository if the |
294 | # project is the same. |
295 | # XXX cjwatson 2015-04-18: Allow merging from a package repository |
296 | # if any (active?) series is linked to this project. |
297 | + if this.namespace != self: |
298 | + raise AssertionError( |
299 | + "Namespace of %s is not %s." % (this.unique_name, self.name)) |
300 | + other_namespace = other.namespace |
301 | if zope_isinstance(other_namespace, ProjectGitNamespace): |
302 | return self.target == other_namespace.target |
303 | else: |
304 | @@ -457,12 +463,16 @@ |
305 | """See `IGitNamespace`.""" |
306 | return InformationType.PUBLIC |
307 | |
308 | - def areRepositoriesMergeable(self, other_namespace): |
309 | + def areRepositoriesMergeable(self, this, other): |
310 | """See `IGitNamespacePolicy`.""" |
311 | # Repositories are mergeable into a package repository if the |
312 | # package is the same. |
313 | # XXX cjwatson 2015-04-18: Allow merging from a project repository |
314 | # if any (active?) series links this package to that project. |
315 | + if this.namespace != self: |
316 | + raise AssertionError( |
317 | + "Namespace of %s is not %s." % (this.unique_name, self.name)) |
318 | + other_namespace = other.namespace |
319 | if zope_isinstance(other_namespace, PackageGitNamespace): |
320 | return self.target == other_namespace.target |
321 | else: |
322 | |
323 | === modified file 'lib/lp/code/model/gitrepository.py' |
324 | --- lib/lp/code/model/gitrepository.py 2017-11-24 17:22:34 +0000 |
325 | +++ lib/lp/code/model/gitrepository.py 2018-07-11 08:48:06 +0000 |
326 | @@ -1,4 +1,4 @@ |
327 | -# Copyright 2015-2017 Canonical Ltd. This software is licensed under the |
328 | +# Copyright 2015-2018 Canonical Ltd. This software is licensed under the |
329 | # GNU Affero General Public License version 3 (see the file LICENSE). |
330 | |
331 | __metaclass__ = type |
332 | @@ -946,7 +946,7 @@ |
333 | |
334 | def isRepositoryMergeable(self, other): |
335 | """See `IGitRepository`.""" |
336 | - return self.namespace.areRepositoriesMergeable(other.namespace) |
337 | + return self.namespace.areRepositoriesMergeable(self, other) |
338 | |
339 | @property |
340 | def pending_updates(self): |
341 | |
342 | === modified file 'lib/lp/code/model/tests/test_gitnamespace.py' |
343 | --- lib/lp/code/model/tests/test_gitnamespace.py 2017-10-04 01:29:35 +0000 |
344 | +++ lib/lp/code/model/tests/test_gitnamespace.py 2018-07-11 08:48:06 +0000 |
345 | @@ -1,4 +1,4 @@ |
346 | -# Copyright 2015-2017 Canonical Ltd. This software is licensed under the |
347 | +# Copyright 2015-2018 Canonical Ltd. This software is licensed under the |
348 | # GNU Affero General Public License version 3 (see the file LICENSE). |
349 | |
350 | """Tests for `IGitNamespace` implementations.""" |
351 | @@ -278,6 +278,70 @@ |
352 | namespace = PersonalGitNamespace(person) |
353 | self.assertEqual(person, namespace.target) |
354 | |
355 | + def test_supports_merge_proposals(self): |
356 | + # Personal namespaces support merge proposals. |
357 | + self.assertTrue(self.getNamespace().supports_merge_proposals) |
358 | + |
359 | + def test_areRepositoriesMergeable_same_repository(self): |
360 | + # A personal repository is mergeable into itself. |
361 | + owner = self.factory.makePerson() |
362 | + repository = self.factory.makeGitRepository(owner=owner, target=owner) |
363 | + self.assertTrue( |
364 | + repository.namespace.areRepositoriesMergeable( |
365 | + repository, repository)) |
366 | + |
367 | + def test_areRepositoriesMergeable_same_namespace(self): |
368 | + # A personal repository is not mergeable into another personal |
369 | + # repository, even if they are in the same namespace. |
370 | + owner = self.factory.makePerson() |
371 | + this = self.factory.makeGitRepository(owner=owner, target=owner) |
372 | + other = self.factory.makeGitRepository(owner=owner, target=owner) |
373 | + self.assertFalse(this.namespace.areRepositoriesMergeable(this, other)) |
374 | + |
375 | + def test_areRepositoriesMergeable_different_namespace(self): |
376 | + # A personal repository is not mergeable into another personal |
377 | + # repository with a different namespace. |
378 | + this_owner = self.factory.makePerson() |
379 | + this = self.factory.makeGitRepository( |
380 | + owner=this_owner, target=this_owner) |
381 | + other_owner = self.factory.makePerson() |
382 | + other = self.factory.makeGitRepository( |
383 | + owner=other_owner, target=other_owner) |
384 | + self.assertFalse(this.namespace.areRepositoriesMergeable(this, other)) |
385 | + |
386 | + def test_areRepositoriesMergeable_project(self): |
387 | + # Project repositories are not mergeable into personal repositories. |
388 | + owner = self.factory.makePerson() |
389 | + this = self.factory.makeGitRepository(owner=owner, target=owner) |
390 | + project = self.factory.makeProduct() |
391 | + other = self.factory.makeGitRepository(owner=owner, target=project) |
392 | + self.assertFalse(this.namespace.areRepositoriesMergeable(this, other)) |
393 | + |
394 | + def test_areRepositoriesMergeable_package(self): |
395 | + # Package repositories are not mergeable into personal repositories. |
396 | + owner = self.factory.makePerson() |
397 | + this = self.factory.makeGitRepository(owner=owner, target=owner) |
398 | + dsp = self.factory.makeDistributionSourcePackage() |
399 | + other = self.factory.makeGitRepository(owner=owner, target=dsp) |
400 | + self.assertFalse(this.namespace.areRepositoriesMergeable(this, other)) |
401 | + |
402 | + def test_collection(self): |
403 | + # A personal namespace's collection is of personal repositories with |
404 | + # the same owner. |
405 | + owner = self.factory.makePerson() |
406 | + repositories = [ |
407 | + self.factory.makeGitRepository(owner=owner, target=owner) |
408 | + for _ in range(3)] |
409 | + other_owner = self.factory.makePerson() |
410 | + self.factory.makeGitRepository(owner=other_owner, target=other_owner) |
411 | + self.factory.makeGitRepository( |
412 | + owner=owner, target=self.factory.makeProduct()) |
413 | + self.factory.makeGitRepository( |
414 | + owner=owner, target=self.factory.makeDistributionSourcePackage()) |
415 | + self.assertContentEqual( |
416 | + repositories, |
417 | + repositories[0].namespace.collection.getRepositories()) |
418 | + |
419 | |
420 | class TestProjectGitNamespace(TestCaseWithFactory, NamespaceMixin): |
421 | """Tests for `ProjectGitNamespace`.""" |
422 | @@ -312,6 +376,65 @@ |
423 | namespace = ProjectGitNamespace(person, project) |
424 | self.assertEqual(project, namespace.target) |
425 | |
426 | + def test_supports_merge_proposals(self): |
427 | + # Project namespaces support merge proposals. |
428 | + self.assertTrue(self.getNamespace().supports_merge_proposals) |
429 | + |
430 | + def test_areRepositoriesMergeable_same_repository(self): |
431 | + # A project repository is mergeable into itself. |
432 | + project = self.factory.makeProduct() |
433 | + repository = self.factory.makeGitRepository(target=project) |
434 | + self.assertTrue( |
435 | + repository.namespace.areRepositoriesMergeable( |
436 | + repository, repository)) |
437 | + |
438 | + def test_areRepositoriesMergeable_same_namespace(self): |
439 | + # Repositories of the same project are mergeable. |
440 | + project = self.factory.makeProduct() |
441 | + this = self.factory.makeGitRepository(target=project) |
442 | + other = self.factory.makeGitRepository(target=project) |
443 | + self.assertTrue(this.namespace.areRepositoriesMergeable(this, other)) |
444 | + |
445 | + def test_areRepositoriesMergeable_different_namespace(self): |
446 | + # Repositories of a different project are not mergeable. |
447 | + this_project = self.factory.makeProduct() |
448 | + this = self.factory.makeGitRepository(target=this_project) |
449 | + other_project = self.factory.makeProduct() |
450 | + other = self.factory.makeGitRepository(target=other_project) |
451 | + self.assertFalse(this.namespace.areRepositoriesMergeable(this, other)) |
452 | + |
453 | + def test_areRepositoriesMergeable_personal(self): |
454 | + # Personal repositories are not mergeable into project repositories. |
455 | + owner = self.factory.makePerson() |
456 | + project = self.factory.makeProduct() |
457 | + this = self.factory.makeGitRepository(owner=owner, target=project) |
458 | + other = self.factory.makeGitRepository(owner=owner, target=owner) |
459 | + self.assertFalse(this.namespace.areRepositoriesMergeable(this, other)) |
460 | + |
461 | + def test_areRepositoriesMergeable_package(self): |
462 | + # Package repositories are not mergeable into project repositories. |
463 | + owner = self.factory.makePerson() |
464 | + project = self.factory.makeProduct() |
465 | + this = self.factory.makeGitRepository(owner=owner, target=project) |
466 | + dsp = self.factory.makeDistributionSourcePackage() |
467 | + other = self.factory.makeGitRepository(owner=owner, target=dsp) |
468 | + self.assertFalse(this.namespace.areRepositoriesMergeable(this, other)) |
469 | + |
470 | + def test_collection(self): |
471 | + # A project namespace's collection is of repositories for the same |
472 | + # project. |
473 | + project = self.factory.makeProduct() |
474 | + repositories = [ |
475 | + self.factory.makeGitRepository(target=project) for _ in range(3)] |
476 | + self.factory.makeGitRepository(target=self.factory.makeProduct()) |
477 | + self.factory.makeGitRepository( |
478 | + owner=repositories[0].owner, target=repositories[0].owner) |
479 | + self.factory.makeGitRepository( |
480 | + target=self.factory.makeDistributionSourcePackage()) |
481 | + self.assertContentEqual( |
482 | + repositories, |
483 | + repositories[0].namespace.collection.getRepositories()) |
484 | + |
485 | |
486 | class TestProjectGitNamespacePrivacyWithInformationType(TestCaseWithFactory): |
487 | """Tests for the privacy aspects of `ProjectGitNamespace`. |
488 | @@ -521,6 +644,65 @@ |
489 | namespace = PackageGitNamespace(person, dsp) |
490 | self.assertEqual(dsp, namespace.target) |
491 | |
492 | + def test_supports_merge_proposals(self): |
493 | + # Package namespaces support merge proposals. |
494 | + self.assertTrue(self.getNamespace().supports_merge_proposals) |
495 | + |
496 | + def test_areRepositoriesMergeable_same_repository(self): |
497 | + # A package repository is mergeable into itself. |
498 | + dsp = self.factory.makeDistributionSourcePackage() |
499 | + repository = self.factory.makeGitRepository(target=dsp) |
500 | + self.assertTrue( |
501 | + repository.namespace.areRepositoriesMergeable( |
502 | + repository, repository)) |
503 | + |
504 | + def test_areRepositoriesMergeable_same_namespace(self): |
505 | + # Repositories of the same package are mergeable. |
506 | + dsp = self.factory.makeDistributionSourcePackage() |
507 | + this = self.factory.makeGitRepository(target=dsp) |
508 | + other = self.factory.makeGitRepository(target=dsp) |
509 | + self.assertTrue(this.namespace.areRepositoriesMergeable(this, other)) |
510 | + |
511 | + def test_areRepositoriesMergeable_different_namespace(self): |
512 | + # Repositories of a different package are not mergeable. |
513 | + this_dsp = self.factory.makeDistributionSourcePackage() |
514 | + this = self.factory.makeGitRepository(target=this_dsp) |
515 | + other_dsp = self.factory.makeDistributionSourcePackage() |
516 | + other = self.factory.makeGitRepository(target=other_dsp) |
517 | + self.assertFalse(this.namespace.areRepositoriesMergeable(this, other)) |
518 | + |
519 | + def test_areRepositoriesMergeable_personal(self): |
520 | + # Personal repositories are not mergeable into package repositories. |
521 | + owner = self.factory.makePerson() |
522 | + dsp = self.factory.makeProduct() |
523 | + this = self.factory.makeGitRepository(owner=owner, target=dsp) |
524 | + other = self.factory.makeGitRepository(owner=owner, target=owner) |
525 | + self.assertFalse(this.namespace.areRepositoriesMergeable(this, other)) |
526 | + |
527 | + def test_areRepositoriesMergeable_project(self): |
528 | + # Project repositories are not mergeable into package repositories. |
529 | + owner = self.factory.makePerson() |
530 | + dsp = self.factory.makeDistributionSourcePackage() |
531 | + this = self.factory.makeGitRepository(owner=owner, target=dsp) |
532 | + project = self.factory.makeProduct() |
533 | + other = self.factory.makeGitRepository(owner=owner, target=project) |
534 | + self.assertFalse(this.namespace.areRepositoriesMergeable(this, other)) |
535 | + |
536 | + def test_collection(self): |
537 | + # A package namespace's collection is of repositories for the same |
538 | + # package. |
539 | + dsp = self.factory.makeDistributionSourcePackage() |
540 | + repositories = [ |
541 | + self.factory.makeGitRepository(target=dsp) for _ in range(3)] |
542 | + self.factory.makeGitRepository( |
543 | + target=self.factory.makeDistributionSourcePackage()) |
544 | + self.factory.makeGitRepository(target=self.factory.makeProduct()) |
545 | + self.factory.makeGitRepository( |
546 | + owner=repositories[0].owner, target=repositories[0].owner) |
547 | + self.assertContentEqual( |
548 | + repositories, |
549 | + repositories[0].namespace.collection.getRepositories()) |
550 | + |
551 | |
552 | class BaseCanCreateRepositoriesMixin: |
553 | """Common tests for all namespaces.""" |
554 | |
555 | === modified file 'lib/lp/code/model/tests/test_gitref.py' |
556 | --- lib/lp/code/model/tests/test_gitref.py 2017-10-04 01:29:35 +0000 |
557 | +++ lib/lp/code/model/tests/test_gitref.py 2018-07-11 08:48:06 +0000 |
558 | @@ -301,14 +301,25 @@ |
559 | else: |
560 | self.assertEqual(review_type, vote.review_type) |
561 | |
562 | - def test_personal_source(self): |
563 | - """Personal repositories cannot be used as a source for MPs.""" |
564 | + def test_personal_source_project_target(self): |
565 | + """Personal repositories cannot be used as a source for MPs to |
566 | + project repositories. |
567 | + """ |
568 | self.source.repository.setTarget( |
569 | target=self.source.owner, user=self.source.owner) |
570 | self.assertRaises( |
571 | InvalidBranchMergeProposal, self.source.addLandingTarget, |
572 | self.user, self.target) |
573 | |
574 | + def test_personal_source_personal_target(self): |
575 | + """A branch in a personal repository can be used as a source for MPs |
576 | + to another branch in the same personal repository. |
577 | + """ |
578 | + self.target.repository.setTarget( |
579 | + target=self.target.owner, user=self.target.owner) |
580 | + [source] = self.factory.makeGitRefs(repository=self.target.repository) |
581 | + source.addLandingTarget(self.user, self.target) |
582 | + |
583 | def test_target_repository_same_target(self): |
584 | """The target repository's target must match that of the source.""" |
585 | self.target.repository.setTarget( |
Why is the suggestion widget now sometimes called on a GitRef rather than a GitRepository?