~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/bzrdir.py

Major code cleanup.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2010 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Server-side bzrdir related request implmentations."""
 
18
 
 
19
 
 
20
from bzrlib import branch, errors, repository, urlutils
 
21
from bzrlib.bzrdir import (
 
22
    BzrDir,
 
23
    BzrDirFormat,
 
24
    BzrDirMetaFormat1,
 
25
    BzrProber,
 
26
    )
 
27
from bzrlib.controldir import (
 
28
    network_format_registry,
 
29
    )
 
30
from bzrlib.smart.request import (
 
31
    FailedSmartServerResponse,
 
32
    SmartServerRequest,
 
33
    SuccessfulSmartServerResponse,
 
34
    )
 
35
 
 
36
 
 
37
class SmartServerRequestOpenBzrDir(SmartServerRequest):
 
38
 
 
39
    def do(self, path):
 
40
        try:
 
41
            t = self.transport_from_client_path(path)
 
42
        except errors.PathNotChild:
 
43
            # The client is trying to ask about a path that they have no access
 
44
            # to.
 
45
            # Ideally we'd return a FailedSmartServerResponse here rather than
 
46
            # a "successful" negative, but we want to be compatibile with
 
47
            # clients that don't anticipate errors from this method.
 
48
            answer = 'no'
 
49
        else:
 
50
            bzr_prober = BzrProber()
 
51
            try:
 
52
                bzr_prober.probe_transport(t)
 
53
            except (errors.NotBranchError, errors.UnknownFormatError):
 
54
                answer = 'no'
 
55
            else:
 
56
                answer = 'yes'
 
57
        return SuccessfulSmartServerResponse((answer,))
 
58
 
 
59
 
 
60
class SmartServerRequestOpenBzrDir_2_1(SmartServerRequest):
 
61
 
 
62
    def do(self, path):
 
63
        """Is there a BzrDir present, and if so does it have a working tree?
 
64
 
 
65
        New in 2.1.
 
66
        """
 
67
        try:
 
68
            t = self.transport_from_client_path(path)
 
69
        except errors.PathNotChild:
 
70
            # The client is trying to ask about a path that they have no access
 
71
            # to.
 
72
            return SuccessfulSmartServerResponse(('no',))
 
73
        try:
 
74
            bd = BzrDir.open_from_transport(t)
 
75
        except errors.NotBranchError:
 
76
            answer = ('no',)
 
77
        else:
 
78
            answer = ('yes',)
 
79
            if bd.has_workingtree():
 
80
                answer += ('yes',)
 
81
            else:
 
82
                answer += ('no',)
 
83
        return SuccessfulSmartServerResponse(answer)
 
84
 
 
85
 
 
86
class SmartServerRequestBzrDir(SmartServerRequest):
 
87
 
 
88
    def do(self, path, *args):
 
89
        """Open a BzrDir at path, and return `self.do_bzrdir_request(*args)`."""
 
90
        try:
 
91
            self._bzrdir = BzrDir.open_from_transport(
 
92
                self.transport_from_client_path(path))
 
93
        except errors.NotBranchError, e:
 
94
            return FailedSmartServerResponse(('nobranch',))
 
95
        return self.do_bzrdir_request(*args)
 
96
 
 
97
    def _boolean_to_yes_no(self, a_boolean):
 
98
        if a_boolean:
 
99
            return 'yes'
 
100
        else:
 
101
            return 'no'
 
102
 
 
103
    def _format_to_capabilities(self, repo_format):
 
104
        rich_root = self._boolean_to_yes_no(repo_format.rich_root_data)
 
105
        tree_ref = self._boolean_to_yes_no(
 
106
            repo_format.supports_tree_reference)
 
107
        external_lookup = self._boolean_to_yes_no(
 
108
            repo_format.supports_external_lookups)
 
109
        return rich_root, tree_ref, external_lookup
 
110
 
 
111
    def _repo_relpath(self, current_transport, repository):
 
112
        """Get the relative path for repository from current_transport."""
 
113
        # the relpath of the bzrdir in the found repository gives us the
 
114
        # path segments to pop-out.
 
115
        relpath = repository.user_transport.relpath(
 
116
            current_transport.base)
 
117
        if len(relpath):
 
118
            segments = ['..'] * len(relpath.split('/'))
 
119
        else:
 
120
            segments = []
 
121
        return '/'.join(segments)
 
122
 
 
123
 
 
124
class SmartServerBzrDirRequestCloningMetaDir(SmartServerRequestBzrDir):
 
125
 
 
126
    def do_bzrdir_request(self, require_stacking):
 
127
        """Get the format that should be used when cloning from this dir.
 
128
 
 
129
        New in 1.13.
 
130
        
 
131
        :return: on success, a 3-tuple of network names for (control,
 
132
            repository, branch) directories, where '' signifies "not present".
 
133
            If this BzrDir contains a branch reference then this will fail with
 
134
            BranchReference; clients should resolve branch references before
 
135
            calling this RPC.
 
136
        """
 
137
        try:
 
138
            branch_ref = self._bzrdir.get_branch_reference()
 
139
        except errors.NotBranchError:
 
140
            branch_ref = None
 
141
        if branch_ref is not None:
 
142
            # The server shouldn't try to resolve references, and it quite
 
143
            # possibly can't reach them anyway.  The client needs to resolve
 
144
            # the branch reference to determine the cloning_metadir.
 
145
            return FailedSmartServerResponse(('BranchReference',))
 
146
        if require_stacking == "True":
 
147
            require_stacking = True
 
148
        else:
 
149
            require_stacking = False
 
150
        control_format = self._bzrdir.cloning_metadir(
 
151
            require_stacking=require_stacking)
 
152
        control_name = control_format.network_name()
 
153
        # XXX: There should be a method that tells us that the format does/does
 
154
        # not have subformats.
 
155
        if isinstance(control_format, BzrDirMetaFormat1):
 
156
            branch_name = ('branch',
 
157
                control_format.get_branch_format().network_name())
 
158
            repository_name = control_format.repository_format.network_name()
 
159
        else:
 
160
            # Only MetaDir has delegated formats today.
 
161
            branch_name = ('branch', '')
 
162
            repository_name = ''
 
163
        return SuccessfulSmartServerResponse((control_name, repository_name,
 
164
            branch_name))
 
165
 
 
166
 
 
167
class SmartServerRequestCreateBranch(SmartServerRequestBzrDir):
 
168
 
 
169
    def do(self, path, network_name):
 
170
        """Create a branch in the bzr dir at path.
 
171
 
 
172
        This operates precisely like 'bzrdir.create_branch'.
 
173
 
 
174
        If a bzrdir is not present, an exception is propogated
 
175
        rather than 'no branch' because these are different conditions (and
 
176
        this method should only be called after establishing that a bzr dir
 
177
        exists anyway).
 
178
 
 
179
        This is the initial version of this method introduced to the smart
 
180
        server for 1.13.
 
181
 
 
182
        :param path: The path to the bzrdir.
 
183
        :param network_name: The network name of the branch type to create.
 
184
        :return: ('ok', branch_format, repo_path, rich_root, tree_ref,
 
185
            external_lookup, repo_format)
 
186
        """
 
187
        bzrdir = BzrDir.open_from_transport(
 
188
            self.transport_from_client_path(path))
 
189
        format = branch.network_format_registry.get(network_name)
 
190
        bzrdir.branch_format = format
 
191
        result = format.initialize(bzrdir)
 
192
        rich_root, tree_ref, external_lookup = self._format_to_capabilities(
 
193
            result.repository._format)
 
194
        branch_format = result._format.network_name()
 
195
        repo_format = result.repository._format.network_name()
 
196
        repo_path = self._repo_relpath(bzrdir.root_transport,
 
197
            result.repository)
 
198
        # branch format, repo relpath, rich_root, tree_ref, external_lookup,
 
199
        # repo_network_name
 
200
        return SuccessfulSmartServerResponse(('ok', branch_format, repo_path,
 
201
            rich_root, tree_ref, external_lookup, repo_format))
 
202
 
 
203
 
 
204
class SmartServerRequestCreateRepository(SmartServerRequestBzrDir):
 
205
 
 
206
    def do(self, path, network_name, shared):
 
207
        """Create a repository in the bzr dir at path.
 
208
 
 
209
        This operates precisely like 'bzrdir.create_repository'.
 
210
 
 
211
        If a bzrdir is not present, an exception is propagated
 
212
        rather than 'no branch' because these are different conditions (and
 
213
        this method should only be called after establishing that a bzr dir
 
214
        exists anyway).
 
215
 
 
216
        This is the initial version of this method introduced to the smart
 
217
        server for 1.13.
 
218
 
 
219
        :param path: The path to the bzrdir.
 
220
        :param network_name: The network name of the repository type to create.
 
221
        :param shared: The value to pass create_repository for the shared
 
222
            parameter.
 
223
        :return: (ok, rich_root, tree_ref, external_lookup, network_name)
 
224
        """
 
225
        bzrdir = BzrDir.open_from_transport(
 
226
            self.transport_from_client_path(path))
 
227
        shared = shared == 'True'
 
228
        format = repository.network_format_registry.get(network_name)
 
229
        bzrdir.repository_format = format
 
230
        result = format.initialize(bzrdir, shared=shared)
 
231
        rich_root, tree_ref, external_lookup = self._format_to_capabilities(
 
232
            result._format)
 
233
        return SuccessfulSmartServerResponse(('ok', rich_root, tree_ref,
 
234
            external_lookup, result._format.network_name()))
 
235
 
 
236
 
 
237
class SmartServerRequestFindRepository(SmartServerRequestBzrDir):
 
238
 
 
239
    def _find(self, path):
 
240
        """try to find a repository from path upwards
 
241
 
 
242
        This operates precisely like 'bzrdir.find_repository'.
 
243
 
 
244
        :return: (relpath, rich_root, tree_ref, external_lookup, network_name).
 
245
            All are strings, relpath is a / prefixed path, the next three are
 
246
            either 'yes' or 'no', and the last is a repository format network
 
247
            name.
 
248
        :raises errors.NoRepositoryPresent: When there is no repository
 
249
            present.
 
250
        """
 
251
        bzrdir = BzrDir.open_from_transport(
 
252
            self.transport_from_client_path(path))
 
253
        repository = bzrdir.find_repository()
 
254
        path = self._repo_relpath(bzrdir.root_transport, repository)
 
255
        rich_root, tree_ref, external_lookup = self._format_to_capabilities(
 
256
            repository._format)
 
257
        network_name = repository._format.network_name()
 
258
        return path, rich_root, tree_ref, external_lookup, network_name
 
259
 
 
260
 
 
261
class SmartServerRequestFindRepositoryV1(SmartServerRequestFindRepository):
 
262
 
 
263
    def do(self, path):
 
264
        """try to find a repository from path upwards
 
265
 
 
266
        This operates precisely like 'bzrdir.find_repository'.
 
267
 
 
268
        If a bzrdir is not present, an exception is propagated
 
269
        rather than 'no branch' because these are different conditions.
 
270
 
 
271
        This is the initial version of this method introduced with the smart
 
272
        server. Modern clients will try the V2 method that adds support for the
 
273
        supports_external_lookups attribute.
 
274
 
 
275
        :return: norepository or ok, relpath.
 
276
        """
 
277
        try:
 
278
            path, rich_root, tree_ref, external_lookup, name = self._find(path)
 
279
            return SuccessfulSmartServerResponse(('ok', path, rich_root, tree_ref))
 
280
        except errors.NoRepositoryPresent:
 
281
            return FailedSmartServerResponse(('norepository', ))
 
282
 
 
283
 
 
284
class SmartServerRequestFindRepositoryV2(SmartServerRequestFindRepository):
 
285
 
 
286
    def do(self, path):
 
287
        """try to find a repository from path upwards
 
288
 
 
289
        This operates precisely like 'bzrdir.find_repository'.
 
290
 
 
291
        If a bzrdir is not present, an exception is propagated
 
292
        rather than 'no branch' because these are different conditions.
 
293
 
 
294
        This is the second edition of this method introduced in bzr 1.3, which
 
295
        returns information about the supports_external_lookups format
 
296
        attribute too.
 
297
 
 
298
        :return: norepository or ok, relpath, rich_root, tree_ref,
 
299
            external_lookup.
 
300
        """
 
301
        try:
 
302
            path, rich_root, tree_ref, external_lookup, name = self._find(path)
 
303
            return SuccessfulSmartServerResponse(
 
304
                ('ok', path, rich_root, tree_ref, external_lookup))
 
305
        except errors.NoRepositoryPresent:
 
306
            return FailedSmartServerResponse(('norepository', ))
 
307
 
 
308
 
 
309
class SmartServerRequestFindRepositoryV3(SmartServerRequestFindRepository):
 
310
 
 
311
    def do(self, path):
 
312
        """try to find a repository from path upwards
 
313
 
 
314
        This operates precisely like 'bzrdir.find_repository'.
 
315
 
 
316
        If a bzrdir is not present, an exception is propogated
 
317
        rather than 'no branch' because these are different conditions.
 
318
 
 
319
        This is the third edition of this method introduced in bzr 1.13, which
 
320
        returns information about the network name of the repository format.
 
321
 
 
322
        :return: norepository or ok, relpath, rich_root, tree_ref,
 
323
            external_lookup, network_name.
 
324
        """
 
325
        try:
 
326
            path, rich_root, tree_ref, external_lookup, name = self._find(path)
 
327
            return SuccessfulSmartServerResponse(
 
328
                ('ok', path, rich_root, tree_ref, external_lookup, name))
 
329
        except errors.NoRepositoryPresent:
 
330
            return FailedSmartServerResponse(('norepository', ))
 
331
 
 
332
 
 
333
class SmartServerBzrDirRequestConfigFile(SmartServerRequestBzrDir):
 
334
 
 
335
    def do_bzrdir_request(self):
 
336
        """Get the configuration bytes for a config file in bzrdir.
 
337
        
 
338
        The body is not utf8 decoded - it is the literal bytestream from disk.
 
339
        """
 
340
        config = self._bzrdir._get_config()
 
341
        if config is None:
 
342
            content = ''
 
343
        else:
 
344
            content = config._get_config_file().read()
 
345
        return SuccessfulSmartServerResponse((), content)
 
346
 
 
347
 
 
348
class SmartServerRequestInitializeBzrDir(SmartServerRequest):
 
349
 
 
350
    def do(self, path):
 
351
        """Initialize a bzrdir at path.
 
352
 
 
353
        The default format of the server is used.
 
354
        :return: SmartServerResponse(('ok', ))
 
355
        """
 
356
        target_transport = self.transport_from_client_path(path)
 
357
        BzrDirFormat.get_default_format().initialize_on_transport(target_transport)
 
358
        return SuccessfulSmartServerResponse(('ok', ))
 
359
 
 
360
 
 
361
class SmartServerRequestBzrDirInitializeEx(SmartServerRequestBzrDir):
 
362
 
 
363
    def parse_NoneTrueFalse(self, arg):
 
364
        if not arg:
 
365
            return None
 
366
        if arg == 'False':
 
367
            return False
 
368
        if arg == 'True':
 
369
            return True
 
370
        raise AssertionError("invalid arg %r" % arg)
 
371
 
 
372
    def parse_NoneString(self, arg):
 
373
        return arg or None
 
374
 
 
375
    def _serialize_NoneTrueFalse(self, arg):
 
376
        if arg is False:
 
377
            return 'False'
 
378
        if not arg:
 
379
            return ''
 
380
        return 'True'
 
381
 
 
382
    def do(self, bzrdir_network_name, path, use_existing_dir, create_prefix,
 
383
        force_new_repo, stacked_on, stack_on_pwd, repo_format_name,
 
384
        make_working_trees, shared_repo):
 
385
        """Initialize a bzrdir at path as per
 
386
        BzrDirFormat.initialize_on_transport_ex.
 
387
 
 
388
        New in 1.16.  (Replaces BzrDirFormat.initialize_ex verb from 1.15).
 
389
 
 
390
        :return: return SuccessfulSmartServerResponse((repo_path, rich_root,
 
391
            tree_ref, external_lookup, repo_network_name,
 
392
            repo_bzrdir_network_name, bzrdir_format_network_name,
 
393
            NoneTrueFalse(stacking), final_stack, final_stack_pwd,
 
394
            repo_lock_token))
 
395
        """
 
396
        target_transport = self.transport_from_client_path(path)
 
397
        format = network_format_registry.get(bzrdir_network_name)
 
398
        use_existing_dir = self.parse_NoneTrueFalse(use_existing_dir)
 
399
        create_prefix = self.parse_NoneTrueFalse(create_prefix)
 
400
        force_new_repo = self.parse_NoneTrueFalse(force_new_repo)
 
401
        stacked_on = self.parse_NoneString(stacked_on)
 
402
        stack_on_pwd = self.parse_NoneString(stack_on_pwd)
 
403
        make_working_trees = self.parse_NoneTrueFalse(make_working_trees)
 
404
        shared_repo = self.parse_NoneTrueFalse(shared_repo)
 
405
        if stack_on_pwd == '.':
 
406
            stack_on_pwd = target_transport.base
 
407
        repo_format_name = self.parse_NoneString(repo_format_name)
 
408
        repo, bzrdir, stacking, repository_policy = \
 
409
            format.initialize_on_transport_ex(target_transport,
 
410
            use_existing_dir=use_existing_dir, create_prefix=create_prefix,
 
411
            force_new_repo=force_new_repo, stacked_on=stacked_on,
 
412
            stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
 
413
            make_working_trees=make_working_trees, shared_repo=shared_repo)
 
414
        if repo is None:
 
415
            repo_path = ''
 
416
            repo_name = ''
 
417
            rich_root = tree_ref = external_lookup = ''
 
418
            repo_bzrdir_name = ''
 
419
            final_stack = None
 
420
            final_stack_pwd = None
 
421
            repo_lock_token = ''
 
422
        else:
 
423
            repo_path = self._repo_relpath(bzrdir.root_transport, repo)
 
424
            if repo_path == '':
 
425
                repo_path = '.'
 
426
            rich_root, tree_ref, external_lookup = self._format_to_capabilities(
 
427
                repo._format)
 
428
            repo_name = repo._format.network_name()
 
429
            repo_bzrdir_name = repo.bzrdir._format.network_name()
 
430
            final_stack = repository_policy._stack_on
 
431
            final_stack_pwd = repository_policy._stack_on_pwd
 
432
            # It is returned locked, but we need to do the lock to get the lock
 
433
            # token.
 
434
            repo.unlock()
 
435
            repo_lock_token = repo.lock_write().repository_token or ''
 
436
            if repo_lock_token:
 
437
                repo.leave_lock_in_place()
 
438
            repo.unlock()
 
439
        final_stack = final_stack or ''
 
440
        final_stack_pwd = final_stack_pwd or ''
 
441
 
 
442
        # We want this to be relative to the bzrdir.
 
443
        if final_stack_pwd:
 
444
            final_stack_pwd = urlutils.relative_url(
 
445
                target_transport.base, final_stack_pwd)
 
446
 
 
447
        # Can't meaningfully return a root path.
 
448
        if final_stack.startswith('/'):
 
449
            client_path = self._root_client_path + final_stack[1:]
 
450
            final_stack = urlutils.relative_url(
 
451
                self._root_client_path, client_path)
 
452
            final_stack_pwd = '.'
 
453
 
 
454
        return SuccessfulSmartServerResponse((repo_path, rich_root, tree_ref,
 
455
            external_lookup, repo_name, repo_bzrdir_name,
 
456
            bzrdir._format.network_name(),
 
457
            self._serialize_NoneTrueFalse(stacking), final_stack,
 
458
            final_stack_pwd, repo_lock_token))
 
459
 
 
460
 
 
461
class SmartServerRequestOpenBranch(SmartServerRequestBzrDir):
 
462
 
 
463
    def do_bzrdir_request(self):
 
464
        """open a branch at path and return the branch reference or branch."""
 
465
        try:
 
466
            reference_url = self._bzrdir.get_branch_reference()
 
467
            if reference_url is None:
 
468
                return SuccessfulSmartServerResponse(('ok', ''))
 
469
            else:
 
470
                return SuccessfulSmartServerResponse(('ok', reference_url))
 
471
        except errors.NotBranchError, e:
 
472
            return FailedSmartServerResponse(('nobranch',))
 
473
 
 
474
 
 
475
class SmartServerRequestOpenBranchV2(SmartServerRequestBzrDir):
 
476
 
 
477
    def do_bzrdir_request(self):
 
478
        """open a branch at path and return the reference or format."""
 
479
        try:
 
480
            reference_url = self._bzrdir.get_branch_reference()
 
481
            if reference_url is None:
 
482
                br = self._bzrdir.open_branch(ignore_fallbacks=True)
 
483
                format = br._format.network_name()
 
484
                return SuccessfulSmartServerResponse(('branch', format))
 
485
            else:
 
486
                return SuccessfulSmartServerResponse(('ref', reference_url))
 
487
        except errors.NotBranchError, e:
 
488
            return FailedSmartServerResponse(('nobranch',))
 
489
 
 
490
 
 
491
class SmartServerRequestOpenBranchV3(SmartServerRequestBzrDir):
 
492
 
 
493
    def do_bzrdir_request(self):
 
494
        """Open a branch at path and return the reference or format.
 
495
        
 
496
        This version introduced in 2.1.
 
497
 
 
498
        Differences to SmartServerRequestOpenBranchV2:
 
499
          * can return 2-element ('nobranch', extra), where 'extra' is a string
 
500
            with an explanation like 'location is a repository'.  Previously
 
501
            a 'nobranch' response would never have more than one element.
 
502
        """
 
503
        try:
 
504
            reference_url = self._bzrdir.get_branch_reference()
 
505
            if reference_url is None:
 
506
                br = self._bzrdir.open_branch(ignore_fallbacks=True)
 
507
                format = br._format.network_name()
 
508
                return SuccessfulSmartServerResponse(('branch', format))
 
509
            else:
 
510
                return SuccessfulSmartServerResponse(('ref', reference_url))
 
511
        except errors.NotBranchError, e:
 
512
            # Stringify the exception so that its .detail attribute will be
 
513
            # filled out.
 
514
            str(e)
 
515
            resp = ('nobranch',)
 
516
            detail = e.detail
 
517
            if detail:
 
518
                if detail.startswith(': '):
 
519
                    detail = detail[2:]
 
520
                resp += (detail,)
 
521
            return FailedSmartServerResponse(resp)
 
522