Merge lp:~cjwatson/launchpad/export-git-repositories-new into lp:launchpad

Proposed by Colin Watson
Status: Rejected
Rejected by: Colin Watson
Proposed branch: lp:~cjwatson/launchpad/export-git-repositories-new
Merge into: lp:launchpad
Diff against target: 496 lines (+185/-80)
9 files modified
lib/lp/code/errors.py (+5/-1)
lib/lp/code/interfaces/gitnamespace.py (+6/-2)
lib/lp/code/interfaces/gitrepository.py (+15/-2)
lib/lp/code/model/githosting.py (+2/-2)
lib/lp/code/model/gitnamespace.py (+42/-2)
lib/lp/code/model/gitrepository.py (+21/-6)
lib/lp/code/model/tests/test_gitrepository.py (+66/-0)
lib/lp/code/xmlrpc/git.py (+27/-64)
lib/lp/code/xmlrpc/tests/test_git.py (+1/-1)
To merge this branch: bzr merge lp:~cjwatson/launchpad/export-git-repositories-new
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+373267@code.launchpad.net

Commit message

Export IGitRepositorySet.new on the webservice.

Description of the change

For example, this allows snapcraft to create a temporary repository, issue an access token to it, and push code to it, all without needing to configure an SSH key.

In order to make this possible, I had to do some preliminary refactoring to push on-disk repository creation down from the XML-RPC endpoint to the model.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

Unmerged revisions

19055. By Colin Watson

Fix GitRepositorySet.new, and export it on the webservice.

This was previously untested, and had been broken since r18222.

Now that namespace.createRepository can create the repository on the hosting
service, we can safely export this to create a bare repository via the API.
For example, this allows snapcraft to create a temporary repository, issue
an access token to it, and push code to it, all without needing to configure
an SSH key.

19054. By Colin Watson

Push Git hosting creation down from XML-RPC endpoint to model.

Having the creation of the actual repository on disk be done by the XML-RPC
endpoint makes it difficult to expose other methods of creating
repositories.

I rearranged how target and owner defaults are set. We have to create the
repository on the hosting service as the last step to avoid problems with
rolling back transactions, so pushing this part down to the model also
requires pushing down target/owner default handling, and the callback
mechanism previously in place wasn't very suited to that.

I had to adjust the permission check in
GitRepositorySet.setDefaultRepository slightly, because pushing down
target/owner default handling is easier if the Unauthorized exception
message is more accurate. setDefaultRepositoryForOwner already raised
exceptions with accurate messages.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/code/errors.py'
--- lib/lp/code/errors.py 2018-08-17 11:46:36 +0000
+++ lib/lp/code/errors.py 2019-09-26 15:27:30 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2018 Canonical Ltd. This software is licensed under the1# Copyright 2009-2019 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"""Errors used in the lp/code modules."""4"""Errors used in the lp/code modules."""
@@ -432,6 +432,10 @@
432class GitRepositoryCreationFault(Exception):432class GitRepositoryCreationFault(Exception):
433 """Raised when there is a hosting fault creating a Git repository."""433 """Raised when there is a hosting fault creating a Git repository."""
434434
435 def __init__(self, message, path):
436 super(GitRepositoryCreationFault, self).__init__(message)
437 self.path = path
438
435439
436class GitRepositoryScanFault(Exception):440class GitRepositoryScanFault(Exception):
437 """Raised when there is a fault scanning a repository."""441 """Raised when there is a fault scanning a repository."""
438442
=== modified file 'lib/lp/code/interfaces/gitnamespace.py'
--- lib/lp/code/interfaces/gitnamespace.py 2018-07-10 11:26:57 +0000
+++ lib/lp/code/interfaces/gitnamespace.py 2019-09-26 15:27:30 +0000
@@ -1,4 +1,4 @@
1# Copyright 2015-2018 Canonical Ltd. This software is licensed under the1# Copyright 2015-2019 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."""
@@ -32,10 +32,14 @@
32 name = Attribute(32 name = Attribute(
33 "The name of the namespace. This is prepended to the repository name.")33 "The name of the namespace. This is prepended to the repository name.")
3434
35 owner = Attribute("The `IPerson` who owns this namespace.")
36
35 target = Attribute("The `IHasGitRepositories` for this namespace.")37 target = Attribute("The `IHasGitRepositories` for this namespace.")
3638
37 def createRepository(repository_type, registrant, name,39 def createRepository(repository_type, registrant, name,
38 information_type=None, date_created=None):40 information_type=None, date_created=None,
41 target_default=False, owner_default=False,
42 with_hosting=False):
39 """Create and return an `IGitRepository` in this namespace."""43 """Create and return an `IGitRepository` in this namespace."""
4044
41 def isNameUsed(name):45 def isNameUsed(name):
4246
=== modified file 'lib/lp/code/interfaces/gitrepository.py'
--- lib/lp/code/interfaces/gitrepository.py 2019-05-11 11:16:16 +0000
+++ lib/lp/code/interfaces/gitrepository.py 2019-09-26 15:27:30 +0000
@@ -25,6 +25,7 @@
25 export_as_webservice_collection,25 export_as_webservice_collection,
26 export_as_webservice_entry,26 export_as_webservice_entry,
27 export_destructor_operation,27 export_destructor_operation,
28 export_factory_operation,
28 export_operation_as,29 export_operation_as,
29 export_read_operation,30 export_read_operation,
30 export_write_operation,31 export_write_operation,
@@ -927,10 +928,21 @@
927928
928 export_as_webservice_collection(IGitRepository)929 export_as_webservice_collection(IGitRepository)
929930
930 def new(registrant, owner, target, name, information_type=None,931 @call_with(
931 date_created=None):932 repository_type=GitRepositoryType.HOSTED,
933 registrant=REQUEST_USER,
934 with_hosting=True)
935 @operation_parameters(
936 information_type=copy_field(
937 IGitRepositoryView["information_type"], required=False))
938 @export_factory_operation(IGitRepository, ["owner", "target", "name"])
939 @operation_for_version("devel")
940 def new(repository_type, registrant, owner, target, name,
941 information_type=None, date_created=None, with_hosting=False):
932 """Create a Git repository and return it.942 """Create a Git repository and return it.
933943
944 :param repository_type: The `GitRepositoryType` of the new
945 repository.
934 :param registrant: The `IPerson` who registered the new repository.946 :param registrant: The `IPerson` who registered the new repository.
935 :param owner: The `IPerson` who owns the new repository.947 :param owner: The `IPerson` who owns the new repository.
936 :param target: The `IProduct`, `IDistributionSourcePackage`, or948 :param target: The `IProduct`, `IDistributionSourcePackage`, or
@@ -939,6 +951,7 @@
939 :param information_type: Set the repository's information type to951 :param information_type: Set the repository's information type to
940 one different from the target's default. The type must conform952 one different from the target's default. The type must conform
941 to the target's code sharing policy. (optional)953 to the target's code sharing policy. (optional)
954 :param with_hosting: Create the repository on the hosting service.
942 """955 """
943956
944 # Marker for references to Git URL layouts: ##GITNAMESPACE##957 # Marker for references to Git URL layouts: ##GITNAMESPACE##
945958
=== modified file 'lib/lp/code/model/githosting.py'
--- lib/lp/code/model/githosting.py 2018-11-22 16:35:28 +0000
+++ lib/lp/code/model/githosting.py 2019-09-26 15:27:30 +0000
@@ -1,4 +1,4 @@
1# Copyright 2015-2018 Canonical Ltd. This software is licensed under the1# Copyright 2015-2019 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"""Communication with the Git hosting service."""4"""Communication with the Git hosting service."""
@@ -98,7 +98,7 @@
98 self._post("/repo", json=request)98 self._post("/repo", json=request)
99 except requests.RequestException as e:99 except requests.RequestException as e:
100 raise GitRepositoryCreationFault(100 raise GitRepositoryCreationFault(
101 "Failed to create Git repository: %s" % unicode(e))101 "Failed to create Git repository: %s" % unicode(e), path)
102102
103 def getProperties(self, path):103 def getProperties(self, path):
104 """See `IGitHostingClient`."""104 """See `IGitHostingClient`."""
105105
=== modified file 'lib/lp/code/model/gitnamespace.py'
--- lib/lp/code/model/gitnamespace.py 2018-07-10 16:36:54 +0000
+++ lib/lp/code/model/gitnamespace.py 2019-09-26 15:27:30 +0000
@@ -1,4 +1,4 @@
1# Copyright 2015-2018 Canonical Ltd. This software is licensed under the1# Copyright 2015-2019 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`."""
@@ -39,6 +39,7 @@
39 GitRepositoryCreatorNotMemberOfOwnerTeam,39 GitRepositoryCreatorNotMemberOfOwnerTeam,
40 GitRepositoryCreatorNotOwner,40 GitRepositoryCreatorNotOwner,
41 GitRepositoryExists,41 GitRepositoryExists,
42 GitTargetError,
42 )43 )
43from lp.code.interfaces.gitcollection import IAllGitRepositories44from lp.code.interfaces.gitcollection import IAllGitRepositories
44from lp.code.interfaces.gitnamespace import (45from lp.code.interfaces.gitnamespace import (
@@ -72,8 +73,11 @@
7273
73 def createRepository(self, repository_type, registrant, name,74 def createRepository(self, repository_type, registrant, name,
74 reviewer=None, information_type=None,75 reviewer=None, information_type=None,
75 date_created=DEFAULT, description=None):76 date_created=DEFAULT, description=None,
77 target_default=False, owner_default=False,
78 with_hosting=False):
76 """See `IGitNamespace`."""79 """See `IGitNamespace`."""
80 repository_set = getUtility(IGitRepositorySet)
7781
78 self.validateRegistrant(registrant)82 self.validateRegistrant(registrant)
79 self.validateRepositoryName(name)83 self.validateRepositoryName(name)
@@ -102,6 +106,42 @@
102106
103 notify(ObjectCreatedEvent(repository))107 notify(ObjectCreatedEvent(repository))
104108
109 if target_default:
110 repository_set.setDefaultRepository(self.target, repository)
111 if owner_default:
112 repository_set.setDefaultRepositoryForOwner(
113 self.owner, self.target, repository, registrant)
114
115 if with_hosting:
116 # Ask the hosting service to create the repository on disk. Do
117 # this as late as possible, since if this succeeds it can't
118 # easily be rolled back with the rest of the transaction.
119
120 # Flush to make sure that repository.id is populated.
121 IStore(repository).flush()
122 assert repository.id is not None
123
124 # If repository has target_default, clone from default.
125 clone_from_repository = None
126 try:
127 default = repository_set.getDefaultRepository(
128 repository.target)
129 if default is not None and default.visibleByUser(registrant):
130 clone_from_repository = default
131 else:
132 default = repository_set.getDefaultRepositoryForOwner(
133 repository.owner, repository.target)
134 if (default is not None and
135 default.visibleByUser(registrant)):
136 clone_from_repository = default
137 except GitTargetError:
138 pass # Ignore Personal repositories.
139 if clone_from_repository == repository:
140 clone_from_repository = None
141
142 repository._createOnHostingService(
143 clone_from_repository=clone_from_repository)
144
105 return repository145 return repository
106146
107 def isNameUsed(self, repository_name):147 def isNameUsed(self, repository_name):
108148
=== modified file 'lib/lp/code/model/gitrepository.py'
--- lib/lp/code/model/gitrepository.py 2019-09-16 09:05:01 +0000
+++ lib/lp/code/model/gitrepository.py 2019-09-26 15:27:30 +0000
@@ -197,7 +197,7 @@
197 cachedproperty,197 cachedproperty,
198 get_property_cache,198 get_property_cache,
199 )199 )
200from lp.services.webapp.authorization import available_with_permission200from lp.services.webapp.authorization import check_permission
201from lp.services.webapp.interfaces import ILaunchBag201from lp.services.webapp.interfaces import ILaunchBag
202from lp.services.webhooks.interfaces import IWebhookSet202from lp.services.webhooks.interfaces import IWebhookSet
203from lp.services.webhooks.model import WebhookTargetMixin203from lp.services.webhooks.model import WebhookTargetMixin
@@ -342,6 +342,16 @@
342 self.owner_default = False342 self.owner_default = False
343 self.target_default = False343 self.target_default = False
344344
345 def _createOnHostingService(self, clone_from_repository=None):
346 """Create this repository on the hosting service."""
347 hosting_path = self.getInternalPath()
348 if clone_from_repository is not None:
349 clone_from_path = clone_from_repository.getInternalPath()
350 else:
351 clone_from_path = None
352 getUtility(IGitHostingClient).create(
353 hosting_path, clone_from=clone_from_path)
354
345 @property355 @property
346 def valid_webhook_event_types(self):356 def valid_webhook_event_types(self):
347 return ["git:push:0.1", "merge-proposal:0.1"]357 return ["git:push:0.1", "merge-proposal:0.1"]
@@ -1648,13 +1658,15 @@
1648class GitRepositorySet:1658class GitRepositorySet:
1649 """See `IGitRepositorySet`."""1659 """See `IGitRepositorySet`."""
16501660
1651 def new(self, registrant, owner, target, name, information_type=None,1661 def new(self, repository_type, registrant, owner, target, name,
1652 date_created=DEFAULT, description=None):1662 information_type=None, date_created=DEFAULT, description=None,
1663 with_hosting=False):
1653 """See `IGitRepositorySet`."""1664 """See `IGitRepositorySet`."""
1654 namespace = get_git_namespace(target, owner)1665 namespace = get_git_namespace(target, owner)
1655 return namespace.createRepository(1666 return namespace.createRepository(
1656 registrant, name, information_type=information_type,1667 repository_type, registrant, name,
1657 date_created=date_created, description=description)1668 information_type=information_type, date_created=date_created,
1669 description=description, with_hosting=with_hosting)
16581670
1659 def getByPath(self, user, path):1671 def getByPath(self, user, path):
1660 """See `IGitRepositorySet`."""1672 """See `IGitRepositorySet`."""
@@ -1724,13 +1736,16 @@
1724 "Personal repositories cannot be defaults for any target.")1736 "Personal repositories cannot be defaults for any target.")
1725 return IStore(GitRepository).find(GitRepository, *clauses).one()1737 return IStore(GitRepository).find(GitRepository, *clauses).one()
17261738
1727 @available_with_permission('launchpad.Edit', 'target')
1728 def setDefaultRepository(self, target, repository):1739 def setDefaultRepository(self, target, repository):
1729 """See `IGitRepositorySet`."""1740 """See `IGitRepositorySet`."""
1730 if IPerson.providedBy(target):1741 if IPerson.providedBy(target):
1731 raise GitTargetError(1742 raise GitTargetError(
1732 "Cannot set a default Git repository for a person, only "1743 "Cannot set a default Git repository for a person, only "
1733 "for a project or a package.")1744 "for a project or a package.")
1745 if not check_permission("launchpad.Edit", target):
1746 raise Unauthorized(
1747 "You cannot set the default Git repository for %s." %
1748 target.display_name)
1734 if repository is not None and repository.target != target:1749 if repository is not None and repository.target != target:
1735 raise GitTargetError(1750 raise GitTargetError(
1736 "Cannot set default Git repository to one attached to "1751 "Cannot set default Git repository to one attached to "
17371752
=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
--- lib/lp/code/model/tests/test_gitrepository.py 2019-09-16 09:05:01 +0000
+++ lib/lp/code/model/tests/test_gitrepository.py 2019-09-26 15:27:30 +0000
@@ -26,6 +26,7 @@
26from storm.store import Store26from storm.store import Store
27from testtools.matchers import (27from testtools.matchers import (
28 AnyMatch,28 AnyMatch,
29 ContainsDict,
29 EndsWith,30 EndsWith,
30 Equals,31 Equals,
31 Is,32 Is,
@@ -2990,6 +2991,35 @@
2990 super(TestGitRepositorySet, self).setUp()2991 super(TestGitRepositorySet, self).setUp()
2991 self.repository_set = getUtility(IGitRepositorySet)2992 self.repository_set = getUtility(IGitRepositorySet)
29922993
2994 def test_new(self):
2995 # By default, GitRepositorySet.new creates a new repository in the
2996 # database but not on the hosting service.
2997 hosting_fixture = self.useFixture(GitHostingFixture())
2998 owner = self.factory.makePerson()
2999 target = self.factory.makeProduct()
3000 name = self.factory.getUniqueUnicode()
3001 repository = self.repository_set.new(
3002 GitRepositoryType.HOSTED, owner, owner, target, name)
3003 self.assertThat(repository, MatchesStructure.byEquality(
3004 registrant=owner, owner=owner, target=target, name=name))
3005 self.assertEqual(0, hosting_fixture.create.call_count)
3006
3007 def test_new_with_hosting(self):
3008 # GitRepositorySet.new(with_hosting=True) creates a new repository
3009 # in both the database and the hosting service.
3010 hosting_fixture = self.useFixture(GitHostingFixture())
3011 owner = self.factory.makePerson()
3012 target = self.factory.makeProduct()
3013 name = self.factory.getUniqueUnicode()
3014 repository = self.repository_set.new(
3015 GitRepositoryType.HOSTED, owner, owner, target, name,
3016 with_hosting=True)
3017 self.assertThat(repository, MatchesStructure.byEquality(
3018 registrant=owner, owner=owner, target=target, name=name))
3019 self.assertEqual(
3020 [((repository.getInternalPath(),), {"clone_from": None})],
3021 hosting_fixture.create.calls)
3022
2993 def test_provides_IGitRepositorySet(self):3023 def test_provides_IGitRepositorySet(self):
2994 # GitRepositorySet instances provide IGitRepositorySet.3024 # GitRepositorySet instances provide IGitRepositorySet.
2995 verifyObject(IGitRepositorySet, self.repository_set)3025 verifyObject(IGitRepositorySet, self.repository_set)
@@ -3370,6 +3400,42 @@
3370 "git+ssh://git.launchpad.test/~person/project/+git/repository",3400 "git+ssh://git.launchpad.test/~person/project/+git/repository",
3371 repository["git_ssh_url"])3401 repository["git_ssh_url"])
33723402
3403 def assertNewWorks(self, target_db):
3404 hosting_fixture = self.useFixture(GitHostingFixture())
3405 if IPerson.providedBy(target_db):
3406 owner_db = target_db
3407 else:
3408 owner_db = self.factory.makePerson()
3409 owner_url = api_url(owner_db)
3410 target_url = api_url(target_db)
3411 name = "repository"
3412 webservice = webservice_for_person(
3413 owner_db, permission=OAuthPermission.WRITE_PUBLIC)
3414 webservice.default_api_version = "devel"
3415 response = webservice.named_post(
3416 "/+git", "new", owner=owner_url, target=target_url, name=name)
3417 self.assertEqual(201, response.status)
3418 repository = webservice.get(response.getHeader("Location")).jsonBody()
3419 self.assertThat(repository, ContainsDict({
3420 "repository_type": Equals("Hosted"),
3421 "registrant_link": EndsWith(owner_url),
3422 "owner_link": EndsWith(owner_url),
3423 "target_link": EndsWith(target_url),
3424 "name": Equals(name),
3425 "owner_default": Is(False),
3426 "target_default": Is(False),
3427 }))
3428 self.assertEqual(1, hosting_fixture.create.call_count)
3429
3430 def test_new_project(self):
3431 self.assertNewWorks(self.factory.makeProduct())
3432
3433 def test_new_package(self):
3434 self.assertNewWorks(self.factory.makeDistributionSourcePackage())
3435
3436 def test_new_person(self):
3437 self.assertNewWorks(self.factory.makePerson())
3438
3373 def assertGetRepositoriesWorks(self, target_db):3439 def assertGetRepositoriesWorks(self, target_db):
3374 if IPerson.providedBy(target_db):3440 if IPerson.providedBy(target_db):
3375 owner_db = target_db3441 owner_db = target_db
33763442
=== modified file 'lib/lp/code/xmlrpc/git.py'
--- lib/lp/code/xmlrpc/git.py 2019-09-10 09:58:52 +0000
+++ lib/lp/code/xmlrpc/git.py 2019-09-26 15:27:30 +0000
@@ -13,7 +13,6 @@
13from pymacaroons import Macaroon13from pymacaroons import Macaroon
14import six14import six
15from six.moves import xmlrpc_client15from six.moves import xmlrpc_client
16from storm.store import Store
17import transaction16import transaction
18from zope.component import (17from zope.component import (
19 ComponentLookupError,18 ComponentLookupError,
@@ -36,7 +35,6 @@
36 GitRepositoryCreationFault,35 GitRepositoryCreationFault,
37 GitRepositoryCreationForbidden,36 GitRepositoryCreationForbidden,
38 GitRepositoryExists,37 GitRepositoryExists,
39 GitTargetError,
40 InvalidNamespace,38 InvalidNamespace,
41 )39 )
42from lp.code.interfaces.codehosting import (40from lp.code.interfaces.codehosting import (
@@ -44,7 +42,6 @@
44 LAUNCHPAD_SERVICES,42 LAUNCHPAD_SERVICES,
45 )43 )
46from lp.code.interfaces.gitapi import IGitAPI44from lp.code.interfaces.gitapi import IGitAPI
47from lp.code.interfaces.githosting import IGitHostingClient
48from lp.code.interfaces.gitjob import IGitRefScanJobSource45from lp.code.interfaces.gitjob import IGitRefScanJobSource
49from lp.code.interfaces.gitlookup import (46from lp.code.interfaces.gitlookup import (
50 IGitLookup,47 IGitLookup,
@@ -282,20 +279,15 @@
282 if repository_name is None and not namespace.has_defaults:279 if repository_name is None and not namespace.has_defaults:
283 raise InvalidNamespace(path)280 raise InvalidNamespace(path)
284 if repository_name is None:281 if repository_name is None:
285 def default_func(new_repository):
286 if owner is None:
287 self.repository_set.setDefaultRepository(
288 target, new_repository)
289 if (owner is not None or
290 self.repository_set.getDefaultRepositoryForOwner(
291 repository_owner, target) is None):
292 self.repository_set.setDefaultRepositoryForOwner(
293 repository_owner, target, new_repository, requester)
294
295 repository_name = namespace.findUnusedName(target.name)282 repository_name = namespace.findUnusedName(target.name)
296 return namespace, repository_name, default_func283 target_default = owner is None
284 owner_default = (
285 owner is None or
286 self.repository_set.getDefaultRepositoryForOwner(
287 repository_owner, target) is None)
288 return namespace, repository_name, target_default, owner_default
297 else:289 else:
298 return namespace, repository_name, None290 return namespace, repository_name, False, False
299291
300 def _reportError(self, path, exception, hosting_path=None):292 def _reportError(self, path, exception, hosting_path=None):
301 properties = [293 properties = [
@@ -310,7 +302,7 @@
310302
311 def _createRepository(self, requester, path, clone_from=None):303 def _createRepository(self, requester, path, clone_from=None):
312 try:304 try:
313 namespace, repository_name, default_func = (305 namespace, repository_name, target_default, owner_default = (
314 self._getGitNamespaceExtras(path, requester))306 self._getGitNamespaceExtras(path, requester))
315 except InvalidNamespace:307 except InvalidNamespace:
316 raise faults.PermissionDenied(308 raise faults.PermissionDenied(
@@ -331,56 +323,27 @@
331 raise faults.PermissionDenied(unicode(e))323 raise faults.PermissionDenied(unicode(e))
332324
333 try:325 try:
334 repository = namespace.createRepository(326 try:
335 GitRepositoryType.HOSTED, requester, repository_name)327 namespace.createRepository(
336 except LaunchpadValidationError as e:328 GitRepositoryType.HOSTED, requester, repository_name,
337 # Despite the fault name, this just passes through the exception329 target_default=target_default, owner_default=owner_default,
338 # text so there's no need for a new Git-specific fault.330 with_hosting=True)
339 raise faults.InvalidBranchName(e)331 except LaunchpadValidationError as e:
340 except GitRepositoryExists as e:332 # Despite the fault name, this just passes through the
341 # We should never get here, as we just tried to translate the333 # exception text so there's no need for a new Git-specific
342 # path and found nothing (not even an inaccessible private334 # fault.
343 # repository). Log an OOPS for investigation.335 raise faults.InvalidBranchName(e)
344 self._reportError(path, e)336 except GitRepositoryExists as e:
345 except GitRepositoryCreationException as e:337 # We should never get here, as we just tried to translate
346 raise faults.PermissionDenied(unicode(e))338 # the path and found nothing (not even an inaccessible
347339 # private repository). Log an OOPS for investigation.
348 try:340 self._reportError(path, e)
349 if default_func:341 except (GitRepositoryCreationException, Unauthorized) as e:
350 try:342 raise faults.PermissionDenied(unicode(e))
351 default_func(repository)
352 except Unauthorized:
353 raise faults.PermissionDenied(
354 "You cannot set the default Git repository for '%s'." %
355 path)
356
357 # Flush to make sure that repository.id is populated.
358 Store.of(repository).flush()
359 assert repository.id is not None
360
361 # If repository has target_default, clone from default.
362 target_path = None
363 try:
364 default = self.repository_set.getDefaultRepository(
365 repository.target)
366 if default is not None and default.visibleByUser(requester):
367 target_path = default.getInternalPath()
368 else:
369 default = self.repository_set.getDefaultRepositoryForOwner(
370 repository.owner, repository.target)
371 if (default is not None and
372 default.visibleByUser(requester)):
373 target_path = default.getInternalPath()
374 except GitTargetError:
375 pass # Ignore Personal repositories.
376
377 hosting_path = repository.getInternalPath()
378 try:
379 getUtility(IGitHostingClient).create(
380 hosting_path, clone_from=target_path)
381 except GitRepositoryCreationFault as e:343 except GitRepositoryCreationFault as e:
382 # The hosting service failed. Log an OOPS for investigation.344 # The hosting service failed. Log an OOPS for investigation.
383 self._reportError(path, e, hosting_path=hosting_path)345 self._reportError(path, e, hosting_path=e.path)
346 raise
384 except Exception:347 except Exception:
385 # We don't want to keep the repository we created.348 # We don't want to keep the repository we created.
386 transaction.abort()349 transaction.abort()
387350
=== modified file 'lib/lp/code/xmlrpc/tests/test_git.py'
--- lib/lp/code/xmlrpc/tests/test_git.py 2019-09-05 11:29:00 +0000
+++ lib/lp/code/xmlrpc/tests/test_git.py 2019-09-26 15:27:30 +0000
@@ -908,7 +908,7 @@
908 # If the hosting service is down, trying to create a repository908 # If the hosting service is down, trying to create a repository
909 # fails and doesn't leave junk around in the Launchpad database.909 # fails and doesn't leave junk around in the Launchpad database.
910 self.hosting_fixture.create.failure = GitRepositoryCreationFault(910 self.hosting_fixture.create.failure = GitRepositoryCreationFault(
911 "nothing here")911 "nothing here", path="123")
912 requester = self.factory.makePerson()912 requester = self.factory.makePerson()
913 initial_count = getUtility(IAllGitRepositories).count()913 initial_count = getUtility(IAllGitRepositories).count()
914 oops_id = self.assertOopsOccurred(914 oops_id = self.assertOopsOccurred(