~launchpad-pqm/launchpad/devel

8687.15.17 by Karl Fogel
Add the copyright header block to the rest of the files under lib/lp/.
1
# Copyright 2009 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
6789.5.1 by Michael Hudson
136 lines of joy to add a 'branchdetails' endpoint to the internal xml-rpc
3
6789.8.7 by Michael Hudson
rename branchdetails endpoint to puller_api.
4
"""Implementations of the XML-RPC APIs for codehosting."""
6789.5.1 by Michael Hudson
136 lines of joy to add a 'branchdetails' endpoint to the internal xml-rpc
5
6
__metaclass__ = type
7
__all__ = [
9590.1.59 by Michael Hudson
rename ICodehosting -> ICodehostingAPI, Codehosting -> CodehostingAPI
8
    'CodehostingAPI',
7055.4.4 by Jonathan Lange
Move the fakes out of the test module.
9
    'datetime_from_tuple',
6789.5.1 by Michael Hudson
136 lines of joy to add a 'branchdetails' endpoint to the internal xml-rpc
10
    ]
11
6789.6.19 by Michael Hudson
copy and make slightly less general run_as_requester into xmlrpc code, together
12
6789.6.10 by Jonathan Lange
Make recordSuccess tests pass.
13
import datetime
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
14
15
from bzrlib.urlutils import (
16
    escape,
17
    unescape,
18
    )
19
import pytz
11121.5.38 by Tim Penhey
Make sure that the branch doesn't exist after a link failure.
20
import transaction
6789.6.2 by Michael Hudson
copy-paste tests, adapt and make pass the startMirroring tests
21
from zope.component import getUtility
22
from zope.interface import implements
6789.6.26 by Jonathan Lange
getBranchInformation tests, interface and implementation.
23
from zope.security.interfaces import Unauthorized
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
24
from zope.security.management import endInteraction
6789.6.2 by Michael Hudson
copy-paste tests, adapt and make pass the startMirroring tests
25
from zope.security.proxy import removeSecurityProxy
26
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
27
from lp.app.errors import (
28
    NameLookupFailed,
29
    NotFoundError,
30
    )
12442.2.9 by j.c.sackett
Ran import reformatter per review.
31
from lp.app.validators import LaunchpadValidationError
11270.2.12 by Tim Penhey
Fix and reorder imports.
32
from lp.code.enums import BranchType
11121.5.21 by Tim Penhey
Merge and resolve conflicts.
33
from lp.code.errors import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
34
    BranchCreationException,
11474.5.16 by Tim Penhey
Test invalid product name.
35
    CannotHaveLinkedBranch,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
36
    InvalidNamespace,
37
    NoLinkedBranch,
38
    UnknownBranchTypeError,
39
    )
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
40
from lp.code.interfaces import branchpuller
13084.5.3 by Aaron Bentley
extract get_db_branch_info from CodehostingAPI.branchChanged.
41
from lp.code.interfaces.branch import get_db_branch_info
11121.5.24 by Tim Penhey
slow progress
42
from lp.code.interfaces.branchlookup import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
43
    IBranchLookup,
44
    ILinkedBranchTraverser,
45
    )
8138.1.2 by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing.
46
from lp.code.interfaces.branchnamespace import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
47
    lookup_branch_namespace,
48
    split_unique_name,
49
    )
11121.5.27 by Tim Penhey
Can create and link a branch based on the alias.
50
from lp.code.interfaces.branchtarget import IBranchTarget
8138.1.2 by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing.
51
from lp.code.interfaces.codehosting import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
52
    BRANCH_ALIAS_PREFIX,
12707.7.6 by Tim Penhey
Created a function to do the basic alias assembly.
53
    branch_id_alias,
12707.4.6 by Tim Penhey
db version of translatePath seems to work, now for the inmemory copy.
54
    BRANCH_ID_ALIAS_PREFIX,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
55
    BRANCH_TRANSPORT,
56
    CONTROL_TRANSPORT,
57
    ICodehostingAPI,
58
    LAUNCHPAD_ANONYMOUS,
59
    LAUNCHPAD_SERVICES,
60
    )
11121.5.27 by Tim Penhey
Can create and link a branch based on the alias.
61
from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
13662.5.3 by Aaron Bentley
Handle invalid source package names.
62
from lp.registry.errors import (
63
    InvalidName,
64
    NoSuchSourcePackageName,
65
    )
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
66
from lp.registry.interfaces.person import (
67
    IPersonSet,
68
    NoSuchPerson,
69
    )
11474.5.16 by Tim Penhey
Test invalid product name.
70
from lp.registry.interfaces.product import (
71
    InvalidProductName,
72
    NoSuchProduct,
73
    )
13662.5.1 by Aaron Bentley
Pushing to non-existant sourcepackagename creates.
74
from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
8356.1.1 by Leonard Richardson
Partial move.
75
from lp.services.scripts.interfaces.scriptactivity import IScriptActivitySet
9719.2.1 by Michael Hudson
move iter_split somewhere generic
76
from lp.services.utils import iter_split
14612.2.1 by William Grant
format-imports on lib/. So many imports.
77
from lp.services.webapp import LaunchpadXMLRPCView
78
from lp.services.webapp.authorization import check_permission
79
from lp.services.webapp.interaction import setupInteractionForPerson
80
from lp.xmlrpc import faults
81
from lp.xmlrpc.helpers import return_fault
6789.5.1 by Michael Hudson
136 lines of joy to add a 'branchdetails' endpoint to the internal xml-rpc
82
83
6789.6.10 by Jonathan Lange
Make recordSuccess tests pass.
84
UTC = pytz.timezone('UTC')
85
86
9590.1.48 by Michael Hudson
a start at combining the puller and filesystem endpoints
87
def datetime_from_tuple(time_tuple):
88
    """Create a datetime from a sequence that quacks like time.struct_time.
89
90
    The tm_isdst is (index 8) is ignored. The created datetime uses
91
    tzinfo=UTC.
92
    """
93
    [year, month, day, hour, minute, second, unused, unused, unused] = (
94
        time_tuple)
95
    return datetime.datetime(
96
        year, month, day, hour, minute, second, tzinfo=UTC)
97
98
99
def run_with_login(login_id, function, *args, **kwargs):
100
    """Run 'function' logged in with 'login_id'.
101
102
    The first argument passed to 'function' will be the Launchpad
103
    `Person` object corresponding to 'login_id'.
104
105
    The exception is when the requesting login ID is `LAUNCHPAD_SERVICES`. In
106
    that case, we'll pass through the `LAUNCHPAD_SERVICES` variable and the
107
    method will do whatever security proxy hackery is required to provide read
108
    privileges to the Launchpad services.
109
    """
110
    if login_id == LAUNCHPAD_SERVICES or login_id == LAUNCHPAD_ANONYMOUS:
111
        # Don't pass in an actual user. Instead pass in LAUNCHPAD_SERVICES
112
        # and expect `function` to use `removeSecurityProxy` or similar.
113
        return function(login_id, *args, **kwargs)
114
    if isinstance(login_id, basestring):
115
        requester = getUtility(IPersonSet).getByName(login_id)
116
    else:
117
        requester = getUtility(IPersonSet).get(login_id)
118
    if requester is None:
119
        raise NotFoundError("No person with id %s." % login_id)
9590.1.51 by Michael Hudson
merge trunk via previous threads, fixing conflicts
120
    setupInteractionForPerson(requester)
9590.1.48 by Michael Hudson
a start at combining the puller and filesystem endpoints
121
    try:
122
        return function(requester, *args, **kwargs)
123
    finally:
9590.1.51 by Michael Hudson
merge trunk via previous threads, fixing conflicts
124
        endInteraction()
9590.1.48 by Michael Hudson
a start at combining the puller and filesystem endpoints
125
126
9590.1.59 by Michael Hudson
rename ICodehosting -> ICodehostingAPI, Codehosting -> CodehostingAPI
127
class CodehostingAPI(LaunchpadXMLRPCView):
128
    """See `ICodehostingAPI`."""
9590.1.48 by Michael Hudson
a start at combining the puller and filesystem endpoints
129
9590.1.59 by Michael Hudson
rename ICodehosting -> ICodehostingAPI, Codehosting -> CodehostingAPI
130
    implements(ICodehostingAPI)
6789.5.1 by Michael Hudson
136 lines of joy to add a 'branchdetails' endpoint to the internal xml-rpc
131
10379.2.5 by Michael Hudson
integration test and implied fixes
132
    def acquireBranchToPull(self, branch_type_names):
9590.1.59 by Michael Hudson
rename ICodehosting -> ICodehostingAPI, Codehosting -> CodehostingAPI
133
        """See `ICodehostingAPI`."""
10379.2.3 by Michael Hudson
acquireBranchToPull changes
134
        branch_types = []
135
        for branch_type_name in branch_type_names:
136
            try:
137
                branch_types.append(BranchType.items[branch_type_name])
138
            except KeyError:
139
                raise UnknownBranchTypeError(
140
                    'Unknown branch type: %r' % (branch_type_name,))
9590.1.53 by Michael Hudson
fix couple more mis-merges and an earlier mistake
141
        branch = getUtility(branchpuller.IBranchPuller).acquireBranchToPull(
10379.2.3 by Michael Hudson
acquireBranchToPull changes
142
            *branch_types)
8312.3.5 by Michael Hudson
mostly copy and pasted tests for the xml-rpc endpoint
143
        if branch is not None:
8811.1.1 by Michael Hudson
assorted tests and tweaks
144
            branch = removeSecurityProxy(branch)
8312.3.8 by Michael Hudson
return the branch type from the xml-rpc method too
145
            default_branch = branch.target.default_stacked_on_branch
8721.2.3 by Michael Hudson
bit less broken
146
            if default_branch is None:
147
                default_branch_name = ''
148
            elif (branch.branch_type == BranchType.MIRRORED
149
                  and default_branch.private):
150
                default_branch_name = ''
8312.3.8 by Michael Hudson
return the branch type from the xml-rpc method too
151
            else:
8721.2.3 by Michael Hudson
bit less broken
152
                default_branch_name = '/' + default_branch.unique_name
8312.3.8 by Michael Hudson
return the branch type from the xml-rpc method too
153
            return (branch.id, branch.getPullURL(), branch.unique_name,
154
                    default_branch_name, branch.branch_type.name)
8312.3.5 by Michael Hudson
mostly copy and pasted tests for the xml-rpc endpoint
155
        else:
156
            return ()
157
6789.6.11 by Jonathan Lange
Make the XML-RPC parameters coding standard compliant.
158
    def mirrorFailed(self, branch_id, reason):
9590.1.59 by Michael Hudson
rename ICodehosting -> ICodehostingAPI, Codehosting -> CodehostingAPI
159
        """See `ICodehostingAPI`."""
7940.2.5 by Jonathan Lange
Move branch lookup methods to IBranchLookup
160
        branch = getUtility(IBranchLookup).get(branch_id)
6789.6.5 by Jonathan Lange
Make mirrorFailed tests pass.
161
        if branch is None:
6125.37.7 by Jonathan Lange
Make the rest of the methods return NoBranchWithID if there's no branch
162
            return faults.NoBranchWithID(branch_id)
9590.1.76 by Michael Hudson
remove startMirroring from the endpoint too
163
        # The puller runs as no user and may pull private branches. We need to
164
        # bypass Zope's security proxy to set the mirroring information.
6789.6.5 by Jonathan Lange
Make mirrorFailed tests pass.
165
        removeSecurityProxy(branch).mirrorFailed(reason)
166
        return True
167
6789.6.10 by Jonathan Lange
Make recordSuccess tests pass.
168
    def recordSuccess(self, name, hostname, started_tuple, completed_tuple):
9590.1.59 by Michael Hudson
rename ICodehosting -> ICodehostingAPI, Codehosting -> CodehostingAPI
169
        """See `ICodehostingAPI`."""
6789.6.10 by Jonathan Lange
Make recordSuccess tests pass.
170
        date_started = datetime_from_tuple(started_tuple)
171
        date_completed = datetime_from_tuple(completed_tuple)
172
        getUtility(IScriptActivitySet).recordSuccess(
173
            name=name, date_started=date_started,
174
            date_completed=date_completed, hostname=hostname)
175
        return True
176
11121.5.13 by Tim Penhey
Function defn.
177
    def _getBranchNamespaceExtras(self, path, requester):
178
        """Get the branch namespace, branch name and callback for the path.
179
180
        If the path defines a full branch path including the owner and branch
181
        name, then the namespace that is returned is the namespace for the
182
        owner and the branch target specified.
183
184
        If the path uses an lp short name, then we only allow the requester to
185
        create a branch if they have permission to link the newly created
186
        branch to the short name target.  If there is an existing branch
187
        already linked, then BranchExists is raised.  The branch name that is
11121.5.27 by Tim Penhey
Can create and link a branch based on the alias.
188
        used is determined by the namespace as the first unused name starting
189
        with 'trunk'.
11121.5.13 by Tim Penhey
Function defn.
190
        """
11121.5.20 by Tim Penhey
Framework in place.
191
        if path.startswith(BRANCH_ALIAS_PREFIX + '/'):
192
            path = path[len(BRANCH_ALIAS_PREFIX) + 1:]
193
            if not path.startswith('~'):
11121.5.27 by Tim Penhey
Can create and link a branch based on the alias.
194
                context = getUtility(ILinkedBranchTraverser).traverse(path)
195
                target = IBranchTarget(context)
196
                namespace = target.getNamespace(requester)
197
                branch_name = namespace.findUnusedName('trunk')
13084.5.3 by Aaron Bentley
extract get_db_branch_info from CodehostingAPI.branchChanged.
198
11121.5.27 by Tim Penhey
Can create and link a branch based on the alias.
199
                def link_func(new_branch):
200
                    link = ICanHasLinkedBranch(context)
201
                    link.setBranch(new_branch, requester)
11121.5.28 by Tim Penhey
Add tests for product series, inmemory tests still failing.
202
                return namespace, branch_name, link_func, path
11121.5.20 by Tim Penhey
Framework in place.
203
        namespace_name, branch_name = split_unique_name(path)
204
        namespace = lookup_branch_namespace(namespace_name)
11121.5.28 by Tim Penhey
Add tests for product series, inmemory tests still failing.
205
        return namespace, branch_name, None, path
11121.5.20 by Tim Penhey
Framework in place.
206
7055.10.28 by Jonathan Lange
Have createBranch take a fragment and parse it, rather than parameters.
207
    def createBranch(self, login_id, branch_path):
9590.1.59 by Michael Hudson
rename ICodehosting -> ICodehostingAPI, Codehosting -> CodehostingAPI
208
        """See `ICodehostingAPI`."""
6789.9.1 by Michael Hudson
Turns out that Zope won't let you have decorators on XML-RPC methods. Or rather,
209
        def create_branch(requester):
7213.4.6 by Jonathan Lange
Require createBranch to take an absolute path, bringing it inline
210
            if not branch_path.startswith('/'):
211
                return faults.InvalidPath(branch_path)
9738.1.4 by Michael Hudson
a small collection of hacks to get creating a non-ascii branch name failing with a good message
212
            escaped_path = unescape(branch_path.strip('/'))
7213.4.4 by Jonathan Lange
createBranch now raises PermissionDenied when users try to create branches
213
            try:
11121.5.28 by Tim Penhey
Add tests for product series, inmemory tests still failing.
214
                namespace, branch_name, link_func, path = (
11121.5.27 by Tim Penhey
Can create and link a branch based on the alias.
215
                    self._getBranchNamespaceExtras(escaped_path, requester))
7213.4.4 by Jonathan Lange
createBranch now raises PermissionDenied when users try to create branches
216
            except ValueError:
7459.6.5 by Michael Hudson
kill PERMISSION_DENIED_FAULT_CODE, replace with faults.PermissionDenied
217
                return faults.PermissionDenied(
7213.4.4 by Jonathan Lange
createBranch now raises PermissionDenied when users try to create branches
218
                    "Cannot create branch at '%s'" % branch_path)
7362.9.11 by Jonathan Lange
create branches from the XMLRPC server using the namespace classes.
219
            except InvalidNamespace:
7459.6.5 by Michael Hudson
kill PERMISSION_DENIED_FAULT_CODE, replace with faults.PermissionDenied
220
                return faults.PermissionDenied(
7362.9.11 by Jonathan Lange
create branches from the XMLRPC server using the namespace classes.
221
                    "Cannot create branch at '%s'" % branch_path)
222
            except NoSuchPerson, e:
7459.6.7 by Michael Hudson
kill NOT_FOUND_FAULT_CODE, replace with faults.NotFound
223
                return faults.NotFound(
7362.9.11 by Jonathan Lange
create branches from the XMLRPC server using the namespace classes.
224
                    "User/team '%s' does not exist." % e.name)
225
            except NoSuchProduct, e:
7459.6.7 by Michael Hudson
kill NOT_FOUND_FAULT_CODE, replace with faults.NotFound
226
                return faults.NotFound(
7362.9.11 by Jonathan Lange
create branches from the XMLRPC server using the namespace classes.
227
                    "Project '%s' does not exist." % e.name)
13662.5.1 by Aaron Bentley
Pushing to non-existant sourcepackagename creates.
228
            except NoSuchSourcePackageName as e:
13662.5.3 by Aaron Bentley
Handle invalid source package names.
229
                try:
230
                    getUtility(ISourcePackageNameSet).new(e.name)
231
                except InvalidName:
232
                    return faults.InvalidSourcePackageName(e.name)
13662.5.1 by Aaron Bentley
Pushing to non-existant sourcepackagename creates.
233
                return self.createBranch(login_id, branch_path)
7362.10.12 by Jonathan Lange
Merge lookup into lp-path
234
            except NameLookupFailed, e:
7459.6.7 by Michael Hudson
kill NOT_FOUND_FAULT_CODE, replace with faults.NotFound
235
                return faults.NotFound(str(e))
7362.9.11 by Jonathan Lange
create branches from the XMLRPC server using the namespace classes.
236
            try:
237
                branch = namespace.createBranch(
238
                    BranchType.HOSTED, branch_name, requester)
9738.1.4 by Michael Hudson
a small collection of hacks to get creating a non-ascii branch name failing with a good message
239
            except LaunchpadValidationError, e:
240
                msg = e.args[0]
241
                if isinstance(msg, unicode):
242
                    msg = msg.encode('utf-8')
243
                return faults.PermissionDenied(msg)
244
            except BranchCreationException, e:
7459.6.5 by Michael Hudson
kill PERMISSION_DENIED_FAULT_CODE, replace with faults.PermissionDenied
245
                return faults.PermissionDenied(str(e))
11121.5.27 by Tim Penhey
Can create and link a branch based on the alias.
246
247
            if link_func:
248
                try:
249
                    link_func(branch)
250
                except Unauthorized:
11121.5.38 by Tim Penhey
Make sure that the branch doesn't exist after a link failure.
251
                    # We don't want to keep the branch we created.
252
                    transaction.abort()
11121.5.27 by Tim Penhey
Can create and link a branch based on the alias.
253
                    return faults.PermissionDenied(
11121.5.28 by Tim Penhey
Add tests for product series, inmemory tests still failing.
254
                        "Cannot create linked branch at '%s'." % path)
11121.5.27 by Tim Penhey
Can create and link a branch based on the alias.
255
256
            return branch.id
6789.9.1 by Michael Hudson
Turns out that Zope won't let you have decorators on XML-RPC methods. Or rather,
257
        return run_with_login(login_id, create_branch)
6789.6.26 by Jonathan Lange
getBranchInformation tests, interface and implementation.
258
259
    def _canWriteToBranch(self, requester, branch):
260
        """Can `requester` write to `branch`?"""
261
        if requester == LAUNCHPAD_SERVICES:
262
            return False
263
        return (branch.branch_type == BranchType.HOSTED
6789.9.6 by Michael Hudson
respond to most of the review
264
                and check_permission('launchpad.Edit', branch))
6789.6.26 by Jonathan Lange
getBranchInformation tests, interface and implementation.
265
6789.9.1 by Michael Hudson
Turns out that Zope won't let you have decorators on XML-RPC methods. Or rather,
266
    def requestMirror(self, login_id, branchID):
9590.1.59 by Michael Hudson
rename ICodehosting -> ICodehostingAPI, Codehosting -> CodehostingAPI
267
        """See `ICodehostingAPI`."""
6789.9.1 by Michael Hudson
Turns out that Zope won't let you have decorators on XML-RPC methods. Or rather,
268
        def request_mirror(requester):
7940.2.5 by Jonathan Lange
Move branch lookup methods to IBranchLookup
269
            branch = getUtility(IBranchLookup).get(branchID)
6789.9.1 by Michael Hudson
Turns out that Zope won't let you have decorators on XML-RPC methods. Or rather,
270
            # We don't really care who requests a mirror of a branch.
271
            branch.requestMirror()
272
            return True
273
        return run_with_login(login_id, request_mirror)
7055.10.1 by Jonathan Lange
Start work on translatePath.
274
9590.4.8 by Michael Hudson
don't use removeSecurityProxy to freely
275
    def branchChanged(self, login_id, branch_id, stacked_on_location,
276
                      last_revision_id, control_string, branch_string,
277
                      repository_string):
9590.1.59 by Michael Hudson
rename ICodehosting -> ICodehostingAPI, Codehosting -> CodehostingAPI
278
        """See `ICodehostingAPI`."""
9590.1.73 by Michael Hudson
tweaks to get the puller acceptance tests running
279
        def branch_changed(requester):
9590.4.8 by Michael Hudson
don't use removeSecurityProxy to freely
280
            branch_set = getUtility(IBranchLookup)
281
            branch = branch_set.get(branch_id)
282
            if branch is None:
283
                return faults.NoBranchWithID(branch_id)
284
9590.1.73 by Michael Hudson
tweaks to get the puller acceptance tests running
285
            if requester == LAUNCHPAD_SERVICES:
286
                branch = removeSecurityProxy(branch)
287
13084.5.3 by Aaron Bentley
extract get_db_branch_info from CodehostingAPI.branchChanged.
288
            info = get_db_branch_info(
289
                stacked_on_location, last_revision_id, control_string,
290
                branch_string, repository_string)
291
            branch.branchChanged(**info)
9590.4.8 by Michael Hudson
don't use removeSecurityProxy to freely
292
293
            return True
294
295
        return run_with_login(login_id, branch_changed)
9590.1.8 by Michael Hudson
typing
296
12707.4.6 by Tim Penhey
db version of translatePath seems to work, now for the inmemory copy.
297
    def _serializeBranch(self, requester, branch, trailing_path,
298
                         force_readonly=False):
7055.10.12 by Jonathan Lange
Add a failing test for CONTROL_PATH and slightly refactor translatePath
299
        if requester == LAUNCHPAD_SERVICES:
300
            branch = removeSecurityProxy(branch)
301
        try:
302
            branch_id = branch.id
303
        except Unauthorized:
7459.6.11 by Michael Hudson
define and use (once) a return_fault decorator
304
            raise faults.PermissionDenied()
7055.10.12 by Jonathan Lange
Add a failing test for CONTROL_PATH and slightly refactor translatePath
305
        if branch.branch_type == BranchType.REMOTE:
306
            return None
12707.4.6 by Tim Penhey
db version of translatePath seems to work, now for the inmemory copy.
307
        if force_readonly:
308
            writable = False
309
        else:
310
            writable = self._canWriteToBranch(requester, branch)
7055.10.12 by Jonathan Lange
Add a failing test for CONTROL_PATH and slightly refactor translatePath
311
        return (
312
            BRANCH_TRANSPORT,
12707.4.6 by Tim Penhey
db version of translatePath seems to work, now for the inmemory copy.
313
            {'id': branch_id, 'writable': writable},
7055.10.12 by Jonathan Lange
Add a failing test for CONTROL_PATH and slightly refactor translatePath
314
            trailing_path)
315
7213.4.8 by Jonathan Lange
_getProduct -> _serializeControlDirectory.
316
    def _serializeControlDirectory(self, requester, product_path,
317
                                   trailing_path):
7055.10.16 by Jonathan Lange
Make the first control directory test pass in a fairly crappy way.
318
        try:
9738.1.4 by Michael Hudson
a small collection of hacks to get creating a non-ascii branch name failing with a good message
319
            namespace = lookup_branch_namespace(product_path)
8031.4.4 by Jonathan Lange
Use namespaces to get the control directory.
320
        except (InvalidNamespace, NotFoundError):
321
            return
322
        if not ('.bzr' == trailing_path or trailing_path.startswith('.bzr/')):
8031.4.18 by Jonathan Lange
Comment logic
323
            # '.bzr' is OK, '.bzr/foo' is OK, '.bzrfoo' is not.
8031.4.4 by Jonathan Lange
Use namespaces to get the control directory.
324
            return
325
        default_branch = namespace.target.default_stacked_on_branch
7055.10.22 by Jonathan Lange
Handle the case where there is no default stacked-on branch set.
326
        if default_branch is None:
327
            return
7055.10.24 by Jonathan Lange
Return a NotFound for product control directories where the stacked-on branch
328
        try:
12707.7.6 by Tim Penhey
Created a function to do the basic alias assembly.
329
            path = branch_id_alias(default_branch)
7055.10.24 by Jonathan Lange
Return a NotFound for product control directories where the stacked-on branch
330
        except Unauthorized:
331
            return
332
        return (
7213.4.11 by Jonathan Lange
Return escaped URLs for the stacked-on branch.
333
            CONTROL_TRANSPORT,
12707.7.1 by Tim Penhey
Make the offered stacked location to be based on id.
334
            {'default_stack_on': escape(path)},
8031.4.4 by Jonathan Lange
Use namespaces to get the control directory.
335
            trailing_path)
7055.10.16 by Jonathan Lange
Make the first control directory test pass in a fairly crappy way.
336
12707.4.6 by Tim Penhey
db version of translatePath seems to work, now for the inmemory copy.
337
    def _translateBranchIdAlias(self, requester, path):
338
        # If the path isn't a branch id alias, nothing more to do.
339
        stripped_path = unescape(path.strip('/'))
340
        if not stripped_path.startswith(BRANCH_ID_ALIAS_PREFIX + '/'):
341
            return None
342
        try:
343
            parts = stripped_path.split('/', 2)
344
            branch_id = int(parts[1])
345
        except (ValueError, IndexError):
346
            raise faults.PathTranslationError(path)
347
        branch = getUtility(IBranchLookup).get(branch_id)
348
        if branch is None:
349
            raise faults.PathTranslationError(path)
350
        try:
351
            trailing = parts[2]
352
        except IndexError:
353
            trailing = ''
354
        return self._serializeBranch(requester, branch, trailing, True)
355
7055.10.1 by Jonathan Lange
Start work on translatePath.
356
    def translatePath(self, requester_id, path):
9590.1.59 by Michael Hudson
rename ICodehosting -> ICodehostingAPI, Codehosting -> CodehostingAPI
357
        """See `ICodehostingAPI`."""
7459.6.11 by Michael Hudson
define and use (once) a return_fault decorator
358
        @return_fault
7055.10.9 by Jonathan Lange
Start handling private branches.
359
        def translate_path(requester):
360
            if not path.startswith('/'):
361
                return faults.InvalidPath(path)
12707.4.6 by Tim Penhey
db version of translatePath seems to work, now for the inmemory copy.
362
            branch = self._translateBranchIdAlias(requester, path)
363
            if branch is not None:
364
                return branch
7055.10.9 by Jonathan Lange
Start handling private branches.
365
            stripped_path = path.strip('/')
366
            for first, second in iter_split(stripped_path, '/'):
9738.1.4 by Michael Hudson
a small collection of hacks to get creating a non-ascii branch name failing with a good message
367
                first = unescape(first)
7055.10.16 by Jonathan Lange
Make the first control directory test pass in a fairly crappy way.
368
                # Is it a branch?
11121.5.2 by Jonathan Lange
Use a constant, not a literal.
369
                if first.startswith(BRANCH_ALIAS_PREFIX + '/'):
11121.5.5 by Jonathan Lange
Test for actual paths sent by the client, rather than our best guesses.
370
                    try:
11121.5.31 by Tim Penhey
More tests and tweaks to make the tests pass.
371
                        # translatePath('/+branch/.bzr') *must* return not
372
                        # found, otherwise bzr will look for it and we don't
373
                        # have a global bzr dir.
374
                        lp_path = first[len(BRANCH_ALIAS_PREFIX + '/'):]
11121.5.38 by Tim Penhey
Make sure that the branch doesn't exist after a link failure.
375
                        if lp_path == '.bzr' or lp_path.startswith('.bzr/'):
11121.5.31 by Tim Penhey
More tests and tweaks to make the tests pass.
376
                            raise faults.PathTranslationError(path)
377
                        branch, trailing = getUtility(
378
                            IBranchLookup).getByLPPath(lp_path)
11474.5.16 by Tim Penhey
Test invalid product name.
379
                    except (InvalidProductName, NoLinkedBranch,
380
                            CannotHaveLinkedBranch):
381
                        # If we get one of these errors, then there is no
382
                        # point walking back through the path parts.
383
                        break
384
                    except (NameLookupFailed, InvalidNamespace):
11121.5.33 by Tim Penhey
Comment tweaks.
385
                        # The reason we're doing it is that getByLPPath thinks
386
                        # that 'foo/.bzr' is a request for the '.bzr' series
387
                        # of a product.
11121.5.5 by Jonathan Lange
Test for actual paths sent by the client, rather than our best guesses.
388
                        continue
11121.5.31 by Tim Penhey
More tests and tweaks to make the tests pass.
389
                    if trailing is None:
390
                        trailing = ''
11121.5.5 by Jonathan Lange
Test for actual paths sent by the client, rather than our best guesses.
391
                    second = '/'.join([trailing, second]).strip('/')
11121.5.1 by Jonathan Lange
Begin supporting +branch over codehosting.
392
                else:
393
                    branch = getUtility(IBranchLookup).getByUniqueName(first)
7055.10.16 by Jonathan Lange
Make the first control directory test pass in a fairly crappy way.
394
                if branch is not None:
395
                    branch = self._serializeBranch(requester, branch, second)
7459.6.11 by Michael Hudson
define and use (once) a return_fault decorator
396
                    if branch is None:
7055.10.16 by Jonathan Lange
Make the first control directory test pass in a fairly crappy way.
397
                        break
398
                    return branch
7055.10.22 by Jonathan Lange
Handle the case where there is no default stacked-on branch set.
399
                # Is it a product control directory?
7213.4.8 by Jonathan Lange
_getProduct -> _serializeControlDirectory.
400
                product = self._serializeControlDirectory(
401
                    requester, first, second)
7055.10.16 by Jonathan Lange
Make the first control directory test pass in a fairly crappy way.
402
                if product is not None:
403
                    return product
7459.6.11 by Michael Hudson
define and use (once) a return_fault decorator
404
            raise faults.PathTranslationError(path)
7055.10.9 by Jonathan Lange
Start handling private branches.
405
        return run_with_login(requester_id, translate_path)