~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-10-10 08:52:29 UTC
  • mfrom: (2903.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20071010085229-7x5al1tirr29mq0l
Fix 149019 by using a proper line number when reporting errors

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
# TODO: At some point, handle upgrades by just passing the whole request
18
18
# across to run on the server.
19
19
 
20
 
import bz2
21
20
from cStringIO import StringIO
22
21
 
23
22
from bzrlib import (
24
23
    branch,
25
 
    debug,
26
24
    errors,
27
 
    graph,
28
25
    lockdir,
29
26
    repository,
30
 
    revision,
31
 
    symbol_versioning,
32
27
)
33
 
from bzrlib.branch import BranchReferenceFormat
 
28
from bzrlib.branch import Branch, BranchReferenceFormat
34
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
35
30
from bzrlib.config import BranchConfig, TreeConfig
36
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
37
 
from bzrlib.errors import (
38
 
    NoSuchRevision,
39
 
    SmartProtocolError,
40
 
    )
 
32
from bzrlib.errors import NoSuchRevision
41
33
from bzrlib.lockable_files import LockableFiles
42
 
from bzrlib.pack import ContainerPushParser
 
34
from bzrlib.revision import NULL_REVISION
43
35
from bzrlib.smart import client, vfs
44
36
from bzrlib.symbol_versioning import (
45
 
    deprecated_in,
46
37
    deprecated_method,
 
38
    zero_ninetyone,
47
39
    )
48
 
from bzrlib.revision import ensure_null, NULL_REVISION
49
 
from bzrlib.trace import mutter, note, warning
 
40
from bzrlib.trace import note
50
41
 
51
42
# Note: RemoteBzrDirFormat is in bzrdir.py
52
43
 
65
56
        self._real_bzrdir = None
66
57
 
67
58
        if _client is None:
68
 
            medium = transport.get_smart_medium()
69
 
            self._client = client._SmartClient(medium)
 
59
            self._shared_medium = transport.get_shared_medium()
 
60
            self._client = client._SmartClient(self._shared_medium)
70
61
        else:
71
62
            self._client = _client
 
63
            self._shared_medium = None
72
64
            return
73
65
 
74
66
        path = self._path_for_remote_call(self._client)
92
84
        self._real_bzrdir.create_repository(shared=shared)
93
85
        return self.open_repository()
94
86
 
95
 
    def destroy_repository(self):
96
 
        """See BzrDir.destroy_repository"""
97
 
        self._ensure_real()
98
 
        self._real_bzrdir.destroy_repository()
99
 
 
100
87
    def create_branch(self):
101
88
        self._ensure_real()
102
89
        real_branch = self._real_bzrdir.create_branch()
107
94
        self._ensure_real()
108
95
        self._real_bzrdir.destroy_branch()
109
96
 
110
 
    def create_workingtree(self, revision_id=None, from_branch=None):
 
97
    def create_workingtree(self, revision_id=None):
111
98
        raise errors.NotLocalUrl(self.transport.base)
112
99
 
113
100
    def find_branch_format(self):
121
108
    def get_branch_reference(self):
122
109
        """See BzrDir.get_branch_reference()."""
123
110
        path = self._path_for_remote_call(self._client)
124
 
        try:
125
 
            response = self._client.call('BzrDir.open_branch', path)
126
 
        except errors.ErrorFromSmartServer, err:
127
 
            if err.error_tuple == ('nobranch',):
128
 
                raise errors.NotBranchError(path=self.root_transport.base)
129
 
            raise
 
111
        response = self._client.call('BzrDir.open_branch', path)
130
112
        if response[0] == 'ok':
131
113
            if response[1] == '':
132
114
                # branch at this location.
134
116
            else:
135
117
                # a branch reference, use the existing BranchReference logic.
136
118
                return response[1]
 
119
        elif response == ('nobranch',):
 
120
            raise errors.NotBranchError(path=self.root_transport.base)
137
121
        else:
138
122
            raise errors.UnexpectedSmartServerResponse(response)
139
123
 
140
 
    def _get_tree_branch(self):
141
 
        """See BzrDir._get_tree_branch()."""
142
 
        return None, self.open_branch()
143
 
 
144
124
    def open_branch(self, _unsupported=False):
145
 
        if _unsupported:
146
 
            raise NotImplementedError('unsupported flag support not implemented yet.')
 
125
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
147
126
        reference_url = self.get_branch_reference()
148
127
        if reference_url is None:
149
128
            # branch at this location.
155
134
                
156
135
    def open_repository(self):
157
136
        path = self._path_for_remote_call(self._client)
158
 
        verb = 'BzrDir.find_repositoryV2'
159
 
        try:
160
 
            try:
161
 
                response = self._client.call(verb, path)
162
 
            except errors.UnknownSmartMethod:
163
 
                verb = 'BzrDir.find_repository'
164
 
                response = self._client.call(verb, path)
165
 
        except errors.ErrorFromSmartServer, err:
166
 
            if err.error_verb == 'norepository':
167
 
                raise errors.NoRepositoryPresent(self)
168
 
            raise
169
 
        if response[0] != 'ok':
170
 
            raise errors.UnexpectedSmartServerResponse(response)
171
 
        if verb == 'BzrDir.find_repository':
172
 
            # servers that don't support the V2 method don't support external
173
 
            # references either.
174
 
            response = response + ('no', )
175
 
        if not (len(response) == 5):
176
 
            raise SmartProtocolError('incorrect response length %s' % (response,))
 
137
        response = self._client.call('BzrDir.find_repository', path)
 
138
        assert response[0] in ('ok', 'norepository'), \
 
139
            'unexpected response code %s' % (response,)
 
140
        if response[0] == 'norepository':
 
141
            raise errors.NoRepositoryPresent(self)
 
142
        assert len(response) == 4, 'incorrect response length %s' % (response,)
177
143
        if response[1] == '':
178
144
            format = RemoteRepositoryFormat()
179
145
            format.rich_root_data = (response[2] == 'yes')
180
146
            format.supports_tree_reference = (response[3] == 'yes')
181
 
            # No wire format to check this yet.
182
 
            format.supports_external_lookups = (response[4] == 'yes')
183
147
            return RemoteRepository(self, format)
184
148
        else:
185
149
            raise errors.NoRepositoryPresent(self)
227
191
    Instances of this repository are represented by RemoteRepository
228
192
    instances.
229
193
 
230
 
    The RemoteRepositoryFormat is parameterized during construction
 
194
    The RemoteRepositoryFormat is parameterised during construction
231
195
    to reflect the capabilities of the real, remote format. Specifically
232
196
    the attributes rich_root_data and supports_tree_reference are set
233
197
    on a per instance basis, and are not set (and should not be) at
237
201
    _matchingbzrdir = RemoteBzrDirFormat
238
202
 
239
203
    def initialize(self, a_bzrdir, shared=False):
240
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
241
 
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
 
204
        assert isinstance(a_bzrdir, RemoteBzrDir), \
 
205
            '%r is not a RemoteBzrDir' % (a_bzrdir,)
242
206
        return a_bzrdir.create_repository(shared=shared)
243
207
    
244
208
    def open(self, a_bzrdir):
245
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
246
 
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
 
209
        assert isinstance(a_bzrdir, RemoteBzrDir)
247
210
        return a_bzrdir.open_repository()
248
211
 
249
212
    def get_format_description(self):
286
249
            self._real_repository = None
287
250
        self.bzrdir = remote_bzrdir
288
251
        if _client is None:
289
 
            self._client = remote_bzrdir._client
 
252
            self._client = client._SmartClient(self.bzrdir._shared_medium)
290
253
        else:
291
254
            self._client = _client
292
255
        self._format = format
294
257
        self._lock_token = None
295
258
        self._lock_count = 0
296
259
        self._leave_lock = False
297
 
        # A cache of looked up revision parent data; reset at unlock time.
298
 
        self._parents_map = None
299
 
        if 'hpss' in debug.debug_flags:
300
 
            self._requested_parents = None
301
 
        # For tests:
302
 
        # These depend on the actual remote format, so force them off for
303
 
        # maximum compatibility. XXX: In future these should depend on the
304
 
        # remote repository instance, but this is irrelevant until we perform
305
 
        # reconcile via an RPC call.
306
 
        self._reconcile_does_inventory_gc = False
307
 
        self._reconcile_fixes_text_parents = False
308
 
        self._reconcile_backsup_inventory = False
 
260
        # for tests
 
261
        self._reconcile_does_inventory_gc = True
309
262
        self.base = self.bzrdir.transport.base
310
263
 
311
264
    def __str__(self):
345
298
            #self._real_repository = self.bzrdir._real_bzrdir.open_repository()
346
299
            self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
347
300
 
348
 
    def find_text_key_references(self):
349
 
        """Find the text key references within the repository.
350
 
 
351
 
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
352
 
        revision_ids. Each altered file-ids has the exact revision_ids that
353
 
        altered it listed explicitly.
354
 
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
355
 
            to whether they were referred to by the inventory of the
356
 
            revision_id that they contain. The inventory texts from all present
357
 
            revision ids are assessed to generate this report.
358
 
        """
359
 
        self._ensure_real()
360
 
        return self._real_repository.find_text_key_references()
361
 
 
362
 
    def _generate_text_key_index(self):
363
 
        """Generate a new text key index for the repository.
364
 
 
365
 
        This is an expensive function that will take considerable time to run.
366
 
 
367
 
        :return: A dict mapping (file_id, revision_id) tuples to a list of
368
 
            parents, also (file_id, revision_id) tuples.
369
 
        """
370
 
        self._ensure_real()
371
 
        return self._real_repository._generate_text_key_index()
372
 
 
373
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_four)
374
301
    def get_revision_graph(self, revision_id=None):
375
302
        """See Repository.get_revision_graph()."""
376
 
        return self._get_revision_graph(revision_id)
377
 
 
378
 
    def _get_revision_graph(self, revision_id):
379
 
        """Private method for using with old (< 1.2) servers to fallback."""
380
303
        if revision_id is None:
381
304
            revision_id = ''
382
 
        elif revision.is_null(revision_id):
 
305
        elif revision_id == NULL_REVISION:
383
306
            return {}
384
307
 
385
308
        path = self.bzrdir._path_for_remote_call(self._client)
386
 
        try:
387
 
            response = self._client.call_expecting_body(
388
 
                'Repository.get_revision_graph', path, revision_id)
389
 
        except errors.ErrorFromSmartServer, err:
390
 
            if err.error_verb == 'nosuchrevision':
391
 
                raise NoSuchRevision(self, revision_id)
392
 
            raise
393
 
        response_tuple, response_handler = response
394
 
        if response_tuple[0] != 'ok':
395
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
396
 
        coded = response_handler.read_body_bytes()
397
 
        if coded == '':
398
 
            # no revisions in this repository!
399
 
            return {}
400
 
        lines = coded.split('\n')
401
 
        revision_graph = {}
402
 
        for line in lines:
403
 
            d = tuple(line.split())
404
 
            revision_graph[d[0]] = d[1:]
405
 
            
406
 
        return revision_graph
 
309
        assert type(revision_id) is str
 
310
        response = self._client.call_expecting_body(
 
311
            'Repository.get_revision_graph', path, revision_id)
 
312
        if response[0][0] not in ['ok', 'nosuchrevision']:
 
313
            raise errors.UnexpectedSmartServerResponse(response[0])
 
314
        if response[0][0] == 'ok':
 
315
            coded = response[1].read_body_bytes()
 
316
            if coded == '':
 
317
                # no revisions in this repository!
 
318
                return {}
 
319
            lines = coded.split('\n')
 
320
            revision_graph = {}
 
321
            for line in lines:
 
322
                d = tuple(line.split())
 
323
                revision_graph[d[0]] = d[1:]
 
324
                
 
325
            return revision_graph
 
326
        else:
 
327
            response_body = response[1].read_body_bytes()
 
328
            assert response_body == ''
 
329
            raise NoSuchRevision(self, revision_id)
407
330
 
408
331
    def has_revision(self, revision_id):
409
332
        """See Repository.has_revision()."""
410
 
        if revision_id == NULL_REVISION:
 
333
        if revision_id is None:
411
334
            # The null revision is always present.
412
335
            return True
413
336
        path = self.bzrdir._path_for_remote_call(self._client)
414
 
        response = self._client.call(
415
 
            'Repository.has_revision', path, revision_id)
416
 
        if response[0] not in ('yes', 'no'):
417
 
            raise errors.UnexpectedSmartServerResponse(response)
 
337
        response = self._client.call('Repository.has_revision', path, revision_id)
 
338
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
418
339
        return response[0] == 'yes'
419
340
 
420
 
    def has_revisions(self, revision_ids):
421
 
        """See Repository.has_revisions()."""
422
 
        result = set()
423
 
        for revision_id in revision_ids:
424
 
            if self.has_revision(revision_id):
425
 
                result.add(revision_id)
426
 
        return result
427
 
 
428
341
    def has_same_location(self, other):
429
342
        return (self.__class__ == other.__class__ and
430
343
                self.bzrdir.transport.base == other.bzrdir.transport.base)
431
344
        
432
345
    def get_graph(self, other_repository=None):
433
346
        """Return the graph for this repository format"""
434
 
        parents_provider = self
435
 
        if (other_repository is not None and
436
 
            other_repository.bzrdir.transport.base !=
437
 
            self.bzrdir.transport.base):
438
 
            parents_provider = graph._StackedParentsProvider(
439
 
                [parents_provider, other_repository._make_parents_provider()])
440
 
        return graph.Graph(parents_provider)
 
347
        return self._real_repository.get_graph(other_repository)
441
348
 
442
349
    def gather_stats(self, revid=None, committers=None):
443
350
        """See Repository.gather_stats()."""
444
351
        path = self.bzrdir._path_for_remote_call(self._client)
445
 
        # revid can be None to indicate no revisions, not just NULL_REVISION
446
 
        if revid is None or revision.is_null(revid):
 
352
        if revid in (None, NULL_REVISION):
447
353
            fmt_revid = ''
448
354
        else:
449
355
            fmt_revid = revid
451
357
            fmt_committers = 'no'
452
358
        else:
453
359
            fmt_committers = 'yes'
454
 
        response_tuple, response_handler = self._client.call_expecting_body(
 
360
        response = self._client.call_expecting_body(
455
361
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
456
 
        if response_tuple[0] != 'ok':
457
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
362
        assert response[0][0] == 'ok', \
 
363
            'unexpected response code %s' % (response[0],)
458
364
 
459
 
        body = response_handler.read_body_bytes()
 
365
        body = response[1].read_body_bytes()
460
366
        result = {}
461
367
        for line in body.split('\n'):
462
368
            if not line:
470
376
 
471
377
        return result
472
378
 
473
 
    def find_branches(self, using=False):
474
 
        """See Repository.find_branches()."""
475
 
        # should be an API call to the server.
476
 
        self._ensure_real()
477
 
        return self._real_repository.find_branches(using=using)
478
 
 
479
379
    def get_physical_lock_status(self):
480
380
        """See Repository.get_physical_lock_status()."""
481
 
        # should be an API call to the server.
482
 
        self._ensure_real()
483
 
        return self._real_repository.get_physical_lock_status()
 
381
        return False
484
382
 
485
383
    def is_in_write_group(self):
486
384
        """Return True if there is an open write group.
497
395
        """See Repository.is_shared()."""
498
396
        path = self.bzrdir._path_for_remote_call(self._client)
499
397
        response = self._client.call('Repository.is_shared', path)
500
 
        if response[0] not in ('yes', 'no'):
501
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
398
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
502
399
        return response[0] == 'yes'
503
400
 
504
 
    def is_write_locked(self):
505
 
        return self._lock_mode == 'w'
506
 
 
507
401
    def lock_read(self):
508
402
        # wrong eventually - want a local lock cache context
509
403
        if not self._lock_mode:
510
404
            self._lock_mode = 'r'
511
405
            self._lock_count = 1
512
 
            self._parents_map = {}
513
 
            if 'hpss' in debug.debug_flags:
514
 
                self._requested_parents = set()
515
406
            if self._real_repository is not None:
516
407
                self._real_repository.lock_read()
517
408
        else:
521
412
        path = self.bzrdir._path_for_remote_call(self._client)
522
413
        if token is None:
523
414
            token = ''
524
 
        try:
525
 
            response = self._client.call('Repository.lock_write', path, token)
526
 
        except errors.ErrorFromSmartServer, err:
527
 
            if err.error_verb == 'LockContention':
528
 
                raise errors.LockContention('(remote lock)')
529
 
            elif err.error_verb == 'UnlockableTransport':
530
 
                raise errors.UnlockableTransport(self.bzrdir.root_transport)
531
 
            elif err.error_verb == 'LockFailed':
532
 
                raise errors.LockFailed(err.error_args[0], err.error_args[1])
533
 
            raise
534
 
 
 
415
        response = self._client.call('Repository.lock_write', path, token)
535
416
        if response[0] == 'ok':
536
417
            ok, token = response
537
418
            return token
 
419
        elif response[0] == 'LockContention':
 
420
            raise errors.LockContention('(remote lock)')
 
421
        elif response[0] == 'UnlockableTransport':
 
422
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
423
        elif response[0] == 'LockFailed':
 
424
            raise errors.LockFailed(response[1], response[2])
538
425
        else:
539
426
            raise errors.UnexpectedSmartServerResponse(response)
540
427
 
541
428
    def lock_write(self, token=None):
542
429
        if not self._lock_mode:
543
430
            self._lock_token = self._remote_lock_write(token)
544
 
            # if self._lock_token is None, then this is something like packs or
545
 
            # svn where we don't get to lock the repo, or a weave style repository
546
 
            # where we cannot lock it over the wire and attempts to do so will
547
 
            # fail.
 
431
            assert self._lock_token, 'Remote server did not return a token!'
548
432
            if self._real_repository is not None:
549
433
                self._real_repository.lock_write(token=self._lock_token)
550
434
            if token is not None:
553
437
                self._leave_lock = False
554
438
            self._lock_mode = 'w'
555
439
            self._lock_count = 1
556
 
            self._parents_map = {}
557
 
            if 'hpss' in debug.debug_flags:
558
 
                self._requested_parents = set()
559
440
        elif self._lock_mode == 'r':
560
441
            raise errors.ReadOnlyError(self)
561
442
        else:
562
443
            self._lock_count += 1
563
 
        return self._lock_token or None
 
444
        return self._lock_token
564
445
 
565
446
    def leave_lock_in_place(self):
566
 
        if not self._lock_token:
567
 
            raise NotImplementedError(self.leave_lock_in_place)
568
447
        self._leave_lock = True
569
448
 
570
449
    def dont_leave_lock_in_place(self):
571
 
        if not self._lock_token:
572
 
            raise NotImplementedError(self.dont_leave_lock_in_place)
573
450
        self._leave_lock = False
574
451
 
575
452
    def _set_real_repository(self, repository):
578
455
        :param repository: The repository to fallback to for non-hpss
579
456
            implemented operations.
580
457
        """
581
 
        if isinstance(repository, RemoteRepository):
582
 
            raise AssertionError()
 
458
        assert not isinstance(repository, RemoteRepository)
583
459
        self._real_repository = repository
584
460
        if self._lock_mode == 'w':
585
461
            # if we are already locked, the real repository must be able to
601
477
 
602
478
    def _unlock(self, token):
603
479
        path = self.bzrdir._path_for_remote_call(self._client)
604
 
        if not token:
605
 
            # with no token the remote repository is not persistently locked.
606
 
            return
607
 
        try:
608
 
            response = self._client.call('Repository.unlock', path, token)
609
 
        except errors.ErrorFromSmartServer, err:
610
 
            if err.error_verb == 'TokenMismatch':
611
 
                raise errors.TokenMismatch(token, '(remote token)')
612
 
            raise
 
480
        response = self._client.call('Repository.unlock', path, token)
613
481
        if response == ('ok',):
614
482
            return
 
483
        elif response[0] == 'TokenMismatch':
 
484
            raise errors.TokenMismatch(token, '(remote token)')
615
485
        else:
616
486
            raise errors.UnexpectedSmartServerResponse(response)
617
487
 
618
488
    def unlock(self):
 
489
        if self._lock_count == 1 and self._lock_mode == 'w':
 
490
            # don't unlock if inside a write group.
 
491
            if self.is_in_write_group():
 
492
                raise errors.BzrError(
 
493
                    'Must end write groups before releasing write locks.')
619
494
        self._lock_count -= 1
620
 
        if self._lock_count > 0:
621
 
            return
622
 
        self._parents_map = None
623
 
        if 'hpss' in debug.debug_flags:
624
 
            self._requested_parents = None
625
 
        old_mode = self._lock_mode
626
 
        self._lock_mode = None
627
 
        try:
628
 
            # The real repository is responsible at present for raising an
629
 
            # exception if it's in an unfinished write group.  However, it
630
 
            # normally will *not* actually remove the lock from disk - that's
631
 
            # done by the server on receiving the Repository.unlock call.
632
 
            # This is just to let the _real_repository stay up to date.
 
495
        if not self._lock_count:
 
496
            mode = self._lock_mode
 
497
            self._lock_mode = None
633
498
            if self._real_repository is not None:
634
499
                self._real_repository.unlock()
635
 
        finally:
636
 
            # The rpc-level lock should be released even if there was a
637
 
            # problem releasing the vfs-based lock.
638
 
            if old_mode == 'w':
 
500
            if mode != 'w':
639
501
                # Only write-locked repositories need to make a remote method
640
502
                # call to perfom the unlock.
641
 
                old_token = self._lock_token
642
 
                self._lock_token = None
643
 
                if not self._leave_lock:
644
 
                    self._unlock(old_token)
 
503
                return
 
504
            assert self._lock_token, 'Locked, but no token!'
 
505
            token = self._lock_token
 
506
            self._lock_token = None
 
507
            if not self._leave_lock:
 
508
                self._unlock(token)
645
509
 
646
510
    def break_lock(self):
647
511
        # should hand off to the network
655
519
        """
656
520
        import tempfile
657
521
        path = self.bzrdir._path_for_remote_call(self._client)
658
 
        try:
659
 
            response, protocol = self._client.call_expecting_body(
660
 
                'Repository.tarball', path, compression)
661
 
        except errors.UnknownSmartMethod:
662
 
            protocol.cancel_read_body()
663
 
            return None
 
522
        response, protocol = self._client.call_expecting_body(
 
523
            'Repository.tarball', path, compression)
664
524
        if response[0] == 'ok':
665
525
            # Extract the tarball and return it
666
526
            t = tempfile.NamedTemporaryFile()
668
528
            t.write(protocol.read_body_bytes())
669
529
            t.seek(0)
670
530
            return t
 
531
        if (response == ('error', "Generic bzr smart protocol error: "
 
532
                "bad request 'Repository.tarball'") or
 
533
              response == ('error', "Generic bzr smart protocol error: "
 
534
                "bad request u'Repository.tarball'")):
 
535
            protocol.cancel_read_body()
 
536
            return None
671
537
        raise errors.UnexpectedSmartServerResponse(response)
672
538
 
673
539
    def sprout(self, to_bzrdir, revision_id=None):
674
540
        # TODO: Option to control what format is created?
675
 
        self._ensure_real()
676
 
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
677
 
                                                             shared=False)
678
 
        dest_repo.fetch(self, revision_id=revision_id)
679
 
        return dest_repo
 
541
        to_repo = self._copy_repository_tarball(to_bzrdir, revision_id)
 
542
        if to_repo is None:
 
543
            self._ensure_real()
 
544
            return self._real_repository.sprout(
 
545
                to_bzrdir, revision_id=revision_id)
 
546
        else:
 
547
            return to_repo
680
548
 
681
549
    ### These methods are just thin shims to the VFS object for now.
682
550
 
697
565
        builder = self._real_repository.get_commit_builder(branch, parents,
698
566
                config, timestamp=timestamp, timezone=timezone,
699
567
                committer=committer, revprops=revprops, revision_id=revision_id)
 
568
        # Make the builder use this RemoteRepository rather than the real one.
 
569
        builder.repository = self
700
570
        return builder
701
571
 
 
572
    @needs_write_lock
702
573
    def add_inventory(self, revid, inv, parents):
703
574
        self._ensure_real()
704
575
        return self._real_repository.add_inventory(revid, inv, parents)
705
576
 
 
577
    @needs_write_lock
706
578
    def add_revision(self, rev_id, rev, inv=None, config=None):
707
579
        self._ensure_real()
708
580
        return self._real_repository.add_revision(
713
585
        self._ensure_real()
714
586
        return self._real_repository.get_inventory(revision_id)
715
587
 
716
 
    def iter_inventories(self, revision_ids):
717
 
        self._ensure_real()
718
 
        return self._real_repository.iter_inventories(revision_ids)
719
 
 
720
588
    @needs_read_lock
721
589
    def get_revision(self, revision_id):
722
590
        self._ensure_real()
737
605
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
738
606
 
739
607
    def make_working_trees(self):
740
 
        """See Repository.make_working_trees"""
741
 
        self._ensure_real()
742
 
        return self._real_repository.make_working_trees()
743
 
 
744
 
    def revision_ids_to_search_result(self, result_set):
745
 
        """Convert a set of revision ids to a graph SearchResult."""
746
 
        result_parents = set()
747
 
        for parents in self.get_graph().get_parent_map(
748
 
            result_set).itervalues():
749
 
            result_parents.update(parents)
750
 
        included_keys = result_set.intersection(result_parents)
751
 
        start_keys = result_set.difference(included_keys)
752
 
        exclude_keys = result_parents.difference(result_set)
753
 
        result = graph.SearchResult(start_keys, exclude_keys,
754
 
            len(result_set), result_set)
755
 
        return result
756
 
 
757
 
    @needs_read_lock
758
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
759
 
        """Return the revision ids that other has that this does not.
760
 
        
761
 
        These are returned in topological order.
762
 
 
763
 
        revision_id: only return revision ids included by revision_id.
764
 
        """
765
 
        return repository.InterRepository.get(
766
 
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
 
608
        """RemoteRepositories never create working trees by default."""
 
609
        return False
767
610
 
768
611
    def fetch(self, source, revision_id=None, pb=None):
769
612
        if self.has_same_location(source):
770
613
            # check that last_revision is in 'from' and then return a
771
614
            # no-operation.
772
615
            if (revision_id is not None and
773
 
                not revision.is_null(revision_id)):
 
616
                not _mod_revision.is_null(revision_id)):
774
617
                self.get_revision(revision_id)
775
618
            return 0, []
776
619
        self._ensure_real()
800
643
        self._ensure_real()
801
644
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
802
645
 
803
 
    def _get_versioned_file_checker(self, revisions, revision_versions_cache):
804
 
        self._ensure_real()
805
 
        return self._real_repository._get_versioned_file_checker(
806
 
            revisions, revision_versions_cache)
807
 
        
808
646
    def iter_files_bytes(self, desired_files):
809
647
        """See Repository.iter_file_bytes.
810
648
        """
811
649
        self._ensure_real()
812
650
        return self._real_repository.iter_files_bytes(desired_files)
813
651
 
814
 
    def get_parent_map(self, keys):
815
 
        """See bzrlib.Graph.get_parent_map()."""
816
 
        # Hack to build up the caching logic.
817
 
        ancestry = self._parents_map
818
 
        if ancestry is None:
819
 
            # Repository is not locked, so there's no cache.
820
 
            missing_revisions = set(keys)
821
 
            ancestry = {}
822
 
        else:
823
 
            missing_revisions = set(key for key in keys if key not in ancestry)
824
 
        if missing_revisions:
825
 
            parent_map = self._get_parent_map(missing_revisions)
826
 
            if 'hpss' in debug.debug_flags:
827
 
                mutter('retransmitted revisions: %d of %d',
828
 
                        len(set(ancestry).intersection(parent_map)),
829
 
                        len(parent_map))
830
 
            ancestry.update(parent_map)
831
 
        present_keys = [k for k in keys if k in ancestry]
832
 
        if 'hpss' in debug.debug_flags:
833
 
            if self._requested_parents is not None and len(ancestry) != 0:
834
 
                self._requested_parents.update(present_keys)
835
 
                mutter('Current RemoteRepository graph hit rate: %d%%',
836
 
                    100.0 * len(self._requested_parents) / len(ancestry))
837
 
        return dict((k, ancestry[k]) for k in present_keys)
838
 
 
839
 
    def _get_parent_map(self, keys):
840
 
        """Helper for get_parent_map that performs the RPC."""
841
 
        medium = self._client._medium
842
 
        if not medium._remote_is_at_least_1_2:
843
 
            # We already found out that the server can't understand
844
 
            # Repository.get_parent_map requests, so just fetch the whole
845
 
            # graph.
846
 
            # XXX: Note that this will issue a deprecation warning. This is ok
847
 
            # :- its because we're working with a deprecated server anyway, and
848
 
            # the user will almost certainly have seen a warning about the
849
 
            # server version already.
850
 
            rg = self.get_revision_graph()
851
 
            # There is an api discrepency between get_parent_map and
852
 
            # get_revision_graph. Specifically, a "key:()" pair in
853
 
            # get_revision_graph just means a node has no parents. For
854
 
            # "get_parent_map" it means the node is a ghost. So fix up the
855
 
            # graph to correct this.
856
 
            #   https://bugs.launchpad.net/bzr/+bug/214894
857
 
            # There is one other "bug" which is that ghosts in
858
 
            # get_revision_graph() are not returned at all. But we won't worry
859
 
            # about that for now.
860
 
            for node_id, parent_ids in rg.iteritems():
861
 
                if parent_ids == ():
862
 
                    rg[node_id] = (NULL_REVISION,)
863
 
            rg[NULL_REVISION] = ()
864
 
            return rg
865
 
 
866
 
        keys = set(keys)
867
 
        if None in keys:
868
 
            raise ValueError('get_parent_map(None) is not valid')
869
 
        if NULL_REVISION in keys:
870
 
            keys.discard(NULL_REVISION)
871
 
            found_parents = {NULL_REVISION:()}
872
 
            if not keys:
873
 
                return found_parents
874
 
        else:
875
 
            found_parents = {}
876
 
        # TODO(Needs analysis): We could assume that the keys being requested
877
 
        # from get_parent_map are in a breadth first search, so typically they
878
 
        # will all be depth N from some common parent, and we don't have to
879
 
        # have the server iterate from the root parent, but rather from the
880
 
        # keys we're searching; and just tell the server the keyspace we
881
 
        # already have; but this may be more traffic again.
882
 
 
883
 
        # Transform self._parents_map into a search request recipe.
884
 
        # TODO: Manage this incrementally to avoid covering the same path
885
 
        # repeatedly. (The server will have to on each request, but the less
886
 
        # work done the better).
887
 
        parents_map = self._parents_map
888
 
        if parents_map is None:
889
 
            # Repository is not locked, so there's no cache.
890
 
            parents_map = {}
891
 
        start_set = set(parents_map)
892
 
        result_parents = set()
893
 
        for parents in parents_map.itervalues():
894
 
            result_parents.update(parents)
895
 
        stop_keys = result_parents.difference(start_set)
896
 
        included_keys = start_set.intersection(result_parents)
897
 
        start_set.difference_update(included_keys)
898
 
        recipe = (start_set, stop_keys, len(parents_map))
899
 
        body = self._serialise_search_recipe(recipe)
900
 
        path = self.bzrdir._path_for_remote_call(self._client)
901
 
        for key in keys:
902
 
            if type(key) is not str:
903
 
                raise ValueError(
904
 
                    "key %r not a plain string" % (key,))
905
 
        verb = 'Repository.get_parent_map'
906
 
        args = (path,) + tuple(keys)
907
 
        try:
908
 
            response = self._client.call_with_body_bytes_expecting_body(
909
 
                verb, args, self._serialise_search_recipe(recipe))
910
 
        except errors.UnknownSmartMethod:
911
 
            # Server does not support this method, so get the whole graph.
912
 
            # Worse, we have to force a disconnection, because the server now
913
 
            # doesn't realise it has a body on the wire to consume, so the
914
 
            # only way to recover is to abandon the connection.
915
 
            warning(
916
 
                'Server is too old for fast get_parent_map, reconnecting.  '
917
 
                '(Upgrade the server to Bazaar 1.2 to avoid this)')
918
 
            medium.disconnect()
919
 
            # To avoid having to disconnect repeatedly, we keep track of the
920
 
            # fact the server doesn't understand remote methods added in 1.2.
921
 
            medium._remote_is_at_least_1_2 = False
922
 
            return self.get_revision_graph(None)
923
 
        response_tuple, response_handler = response
924
 
        if response_tuple[0] not in ['ok']:
925
 
            response_handler.cancel_read_body()
926
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
927
 
        if response_tuple[0] == 'ok':
928
 
            coded = bz2.decompress(response_handler.read_body_bytes())
929
 
            if coded == '':
930
 
                # no revisions found
931
 
                return {}
932
 
            lines = coded.split('\n')
933
 
            revision_graph = {}
934
 
            for line in lines:
935
 
                d = tuple(line.split())
936
 
                if len(d) > 1:
937
 
                    revision_graph[d[0]] = d[1:]
938
 
                else:
939
 
                    # No parents - so give the Graph result (NULL_REVISION,).
940
 
                    revision_graph[d[0]] = (NULL_REVISION,)
941
 
            return revision_graph
942
 
 
943
652
    @needs_read_lock
944
653
    def get_signature_text(self, revision_id):
945
654
        self._ensure_real()
946
655
        return self._real_repository.get_signature_text(revision_id)
947
656
 
948
657
    @needs_read_lock
949
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_three)
950
658
    def get_revision_graph_with_ghosts(self, revision_ids=None):
951
659
        self._ensure_real()
952
660
        return self._real_repository.get_revision_graph_with_ghosts(
990
698
        return self._real_repository.get_revision_reconcile(revision_id)
991
699
 
992
700
    @needs_read_lock
993
 
    def check(self, revision_ids=None):
 
701
    def check(self, revision_ids):
994
702
        self._ensure_real()
995
 
        return self._real_repository.check(revision_ids=revision_ids)
 
703
        return self._real_repository.check(revision_ids)
996
704
 
997
705
    def copy_content_into(self, destination, revision_id=None):
998
706
        self._ensure_real()
1005
713
        from bzrlib import osutils
1006
714
        import tarfile
1007
715
        import tempfile
 
716
        from StringIO import StringIO
1008
717
        # TODO: Maybe a progress bar while streaming the tarball?
1009
718
        note("Copying repository content as tarball...")
1010
719
        tar_file = self._get_tarball('bz2')
1038
747
        return self._real_repository.pack()
1039
748
 
1040
749
    def set_make_working_trees(self, new_value):
1041
 
        self._ensure_real()
1042
 
        self._real_repository.set_make_working_trees(new_value)
 
750
        raise NotImplementedError(self.set_make_working_trees)
1043
751
 
1044
752
    @needs_write_lock
1045
753
    def sign_revision(self, revision_id, gpg_strategy):
1069
777
        return self._real_repository.store_revision_signature(
1070
778
            gpg_strategy, plaintext, revision_id)
1071
779
 
1072
 
    def add_signature_text(self, revision_id, signature):
1073
 
        self._ensure_real()
1074
 
        return self._real_repository.add_signature_text(revision_id, signature)
1075
 
 
1076
780
    def has_signature_for_revision_id(self, revision_id):
1077
781
        self._ensure_real()
1078
782
        return self._real_repository.has_signature_for_revision_id(revision_id)
1079
783
 
1080
 
    def get_data_stream_for_search(self, search):
1081
 
        medium = self._client._medium
1082
 
        if not medium._remote_is_at_least_1_2:
1083
 
            self._ensure_real()
1084
 
            return self._real_repository.get_data_stream_for_search(search)
1085
 
        REQUEST_NAME = 'Repository.stream_revisions_chunked'
1086
 
        path = self.bzrdir._path_for_remote_call(self._client)
1087
 
        body = self._serialise_search_recipe(search.get_recipe())
1088
 
        try:
1089
 
            result = self._client.call_with_body_bytes_expecting_body(
1090
 
                REQUEST_NAME, (path,), body)
1091
 
            response, protocol = result
1092
 
        except errors.UnknownSmartMethod:
1093
 
            # Server does not support this method, so fall back to VFS.
1094
 
            # Worse, we have to force a disconnection, because the server now
1095
 
            # doesn't realise it has a body on the wire to consume, so the
1096
 
            # only way to recover is to abandon the connection.
1097
 
            warning(
1098
 
                'Server is too old for streaming pull, reconnecting.  '
1099
 
                '(Upgrade the server to Bazaar 1.2 to avoid this)')
1100
 
            medium.disconnect()
1101
 
            # To avoid having to disconnect repeatedly, we keep track of the
1102
 
            # fact the server doesn't understand this remote method.
1103
 
            medium._remote_is_at_least_1_2 = False
1104
 
            self._ensure_real()
1105
 
            return self._real_repository.get_data_stream_for_search(search)
1106
 
 
1107
 
        if response == ('ok',):
1108
 
            return self._deserialise_stream(protocol)
1109
 
        if response == ('NoSuchRevision', ):
1110
 
            # We cannot easily identify the revision that is missing in this
1111
 
            # situation without doing much more network IO. For now, bail.
1112
 
            raise NoSuchRevision(self, "unknown")
1113
 
        else:
1114
 
            raise errors.UnexpectedSmartServerResponse(response)
1115
 
 
1116
 
    def _deserialise_stream(self, protocol):
1117
 
        stream = protocol.read_streamed_body()
1118
 
        container_parser = ContainerPushParser()
1119
 
        for bytes in stream:
1120
 
            container_parser.accept_bytes(bytes)
1121
 
            records = container_parser.read_pending_records()
1122
 
            for record_names, record_bytes in records:
1123
 
                if len(record_names) != 1:
1124
 
                    # These records should have only one name, and that name
1125
 
                    # should be a one-element tuple.
1126
 
                    raise errors.SmartProtocolError(
1127
 
                        'Repository data stream had invalid record name %r'
1128
 
                        % (record_names,))
1129
 
                name_tuple = record_names[0]
1130
 
                yield name_tuple, record_bytes
1131
 
 
1132
 
    def insert_data_stream(self, stream):
1133
 
        self._ensure_real()
1134
 
        self._real_repository.insert_data_stream(stream)
1135
 
 
1136
 
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1137
 
        self._ensure_real()
1138
 
        return self._real_repository.item_keys_introduced_by(revision_ids,
1139
 
            _files_pb=_files_pb)
1140
 
 
1141
 
    def revision_graph_can_have_wrong_parents(self):
1142
 
        # The answer depends on the remote repo format.
1143
 
        self._ensure_real()
1144
 
        return self._real_repository.revision_graph_can_have_wrong_parents()
1145
 
 
1146
 
    def _find_inconsistent_revision_parents(self):
1147
 
        self._ensure_real()
1148
 
        return self._real_repository._find_inconsistent_revision_parents()
1149
 
 
1150
 
    def _check_for_inconsistent_revision_parents(self):
1151
 
        self._ensure_real()
1152
 
        return self._real_repository._check_for_inconsistent_revision_parents()
1153
 
 
1154
 
    def _make_parents_provider(self):
1155
 
        return self
1156
 
 
1157
 
    def _serialise_search_recipe(self, recipe):
1158
 
        """Serialise a graph search recipe.
1159
 
 
1160
 
        :param recipe: A search recipe (start, stop, count).
1161
 
        :return: Serialised bytes.
1162
 
        """
1163
 
        start_keys = ' '.join(recipe[0])
1164
 
        stop_keys = ' '.join(recipe[1])
1165
 
        count = str(recipe[2])
1166
 
        return '\n'.join((start_keys, stop_keys, count))
1167
 
 
1168
784
 
1169
785
class RemoteBranchLockableFiles(LockableFiles):
1170
786
    """A 'LockableFiles' implementation that talks to a smart server.
1185
801
        self._dir_mode = None
1186
802
        self._file_mode = None
1187
803
 
 
804
    def get(self, path):
 
805
        """'get' a remote path as per the LockableFiles interface.
 
806
 
 
807
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
808
             just retrieve a file, instead we ask the smart server to generate
 
809
             a configuration for us - which is retrieved as an INI file.
 
810
        """
 
811
        if path == 'branch.conf':
 
812
            path = self.bzrdir._path_for_remote_call(self._client)
 
813
            response = self._client.call_expecting_body(
 
814
                'Branch.get_config_file', path)
 
815
            assert response[0][0] == 'ok', \
 
816
                'unexpected response code %s' % (response[0],)
 
817
            return StringIO(response[1].read_body_bytes())
 
818
        else:
 
819
            # VFS fallback.
 
820
            return LockableFiles.get(self, path)
 
821
 
1188
822
 
1189
823
class RemoteBranchFormat(branch.BranchFormat):
1190
824
 
1199
833
        return 'Remote BZR Branch'
1200
834
 
1201
835
    def open(self, a_bzrdir):
 
836
        assert isinstance(a_bzrdir, RemoteBzrDir)
1202
837
        return a_bzrdir.open_branch()
1203
838
 
1204
839
    def initialize(self, a_bzrdir):
 
840
        assert isinstance(a_bzrdir, RemoteBzrDir)
1205
841
        return a_bzrdir.create_branch()
1206
842
 
1207
843
    def supports_tags(self):
1227
863
        # We intentionally don't call the parent class's __init__, because it
1228
864
        # will try to assign to self.tags, which is a property in this subclass.
1229
865
        # And the parent's __init__ doesn't do much anyway.
1230
 
        self._revision_id_to_revno_cache = None
1231
866
        self._revision_history_cache = None
1232
867
        self.bzrdir = remote_bzrdir
1233
868
        if _client is not None:
1234
869
            self._client = _client
1235
870
        else:
1236
 
            self._client = remote_bzrdir._client
 
871
            self._client = client._SmartClient(self.bzrdir._shared_medium)
1237
872
        self.repository = remote_repository
1238
873
        if real_branch is not None:
1239
874
            self._real_branch = real_branch
1253
888
        self._control_files = None
1254
889
        self._lock_mode = None
1255
890
        self._lock_token = None
1256
 
        self._repo_lock_token = None
1257
891
        self._lock_count = 0
1258
892
        self._leave_lock = False
1259
893
 
1260
 
    def _ensure_real_transport(self):
1261
 
        # if we try vfs access, return the real branch's vfs transport
1262
 
        self._ensure_real()
1263
 
        return self._real_branch._transport
1264
 
 
1265
 
    _transport = property(_ensure_real_transport)
1266
 
 
1267
894
    def __str__(self):
1268
895
        return "%s(%s)" % (self.__class__.__name__, self.base)
1269
896
 
1274
901
 
1275
902
        Used before calls to self._real_branch.
1276
903
        """
1277
 
        if self._real_branch is None:
1278
 
            if not vfs.vfs_enabled():
1279
 
                raise AssertionError('smart server vfs must be enabled '
1280
 
                    'to use vfs implementation')
 
904
        if not self._real_branch:
 
905
            assert vfs.vfs_enabled()
1281
906
            self.bzrdir._ensure_real()
1282
907
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1283
908
            # Give the remote repository the matching real repo.
1328
953
            repo_token = self.repository.lock_write()
1329
954
            self.repository.unlock()
1330
955
        path = self.bzrdir._path_for_remote_call(self._client)
1331
 
        try:
1332
 
            response = self._client.call(
1333
 
                'Branch.lock_write', path, branch_token, repo_token or '')
1334
 
        except errors.ErrorFromSmartServer, err:
1335
 
            if err.error_verb == 'LockContention':
1336
 
                raise errors.LockContention('(remote lock)')
1337
 
            elif err.error_verb == 'TokenMismatch':
1338
 
                raise errors.TokenMismatch(token, '(remote token)')
1339
 
            elif err.error_verb == 'UnlockableTransport':
1340
 
                raise errors.UnlockableTransport(self.bzrdir.root_transport)
1341
 
            elif err.error_verb == 'ReadOnlyError':
1342
 
                raise errors.ReadOnlyError(self)
1343
 
            elif err.error_verb == 'LockFailed':
1344
 
                raise errors.LockFailed(err.error_args[0], err.error_args[1])
1345
 
            raise
1346
 
        if response[0] != 'ok':
 
956
        response = self._client.call('Branch.lock_write', path, branch_token,
 
957
                                     repo_token)
 
958
        if response[0] == 'ok':
 
959
            ok, branch_token, repo_token = response
 
960
            return branch_token, repo_token
 
961
        elif response[0] == 'LockContention':
 
962
            raise errors.LockContention('(remote lock)')
 
963
        elif response[0] == 'TokenMismatch':
 
964
            raise errors.TokenMismatch(token, '(remote token)')
 
965
        elif response[0] == 'UnlockableTransport':
 
966
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
967
        elif response[0] == 'ReadOnlyError':
 
968
            raise errors.ReadOnlyError(self)
 
969
        elif response[0] == 'LockFailed':
 
970
            raise errors.LockFailed(response[1], response[2])
 
971
        else:
1347
972
            raise errors.UnexpectedSmartServerResponse(response)
1348
 
        ok, branch_token, repo_token = response
1349
 
        return branch_token, repo_token
1350
973
            
1351
974
    def lock_write(self, token=None):
1352
975
        if not self._lock_mode:
1353
976
            remote_tokens = self._remote_lock_write(token)
1354
977
            self._lock_token, self._repo_lock_token = remote_tokens
1355
 
            if not self._lock_token:
1356
 
                raise SmartProtocolError('Remote server did not return a token!')
 
978
            assert self._lock_token, 'Remote server did not return a token!'
1357
979
            # TODO: We really, really, really don't want to call _ensure_real
1358
980
            # here, but it's the easiest way to ensure coherency between the
1359
981
            # state of the RemoteBranch and RemoteRepository objects and the
1385
1007
                if token != self._lock_token:
1386
1008
                    raise errors.TokenMismatch(token, self._lock_token)
1387
1009
            self._lock_count += 1
1388
 
        return self._lock_token or None
 
1010
        return self._lock_token
1389
1011
 
1390
1012
    def _unlock(self, branch_token, repo_token):
1391
1013
        path = self.bzrdir._path_for_remote_call(self._client)
1392
 
        try:
1393
 
            response = self._client.call('Branch.unlock', path, branch_token,
1394
 
                                         repo_token or '')
1395
 
        except errors.ErrorFromSmartServer, err:
1396
 
            if err.error_verb == 'TokenMismatch':
1397
 
                raise errors.TokenMismatch(
1398
 
                    str((branch_token, repo_token)), '(remote tokens)')
1399
 
            raise
 
1014
        response = self._client.call('Branch.unlock', path, branch_token,
 
1015
                                     repo_token)
1400
1016
        if response == ('ok',):
1401
1017
            return
1402
 
        raise errors.UnexpectedSmartServerResponse(response)
 
1018
        elif response[0] == 'TokenMismatch':
 
1019
            raise errors.TokenMismatch(
 
1020
                str((branch_token, repo_token)), '(remote tokens)')
 
1021
        else:
 
1022
            raise errors.UnexpectedSmartServerResponse(response)
1403
1023
 
1404
1024
    def unlock(self):
1405
1025
        self._lock_count -= 1
1408
1028
            mode = self._lock_mode
1409
1029
            self._lock_mode = None
1410
1030
            if self._real_branch is not None:
1411
 
                if (not self._leave_lock and mode == 'w' and
1412
 
                    self._repo_lock_token):
 
1031
                if not self._leave_lock:
1413
1032
                    # If this RemoteBranch will remove the physical lock for the
1414
1033
                    # repository, make sure the _real_branch doesn't do it
1415
1034
                    # first.  (Because the _real_branch's repository is set to
1420
1039
                # Only write-locked branched need to make a remote method call
1421
1040
                # to perfom the unlock.
1422
1041
                return
1423
 
            if not self._lock_token:
1424
 
                raise AssertionError('Locked, but no token!')
 
1042
            assert self._lock_token, 'Locked, but no token!'
1425
1043
            branch_token = self._lock_token
1426
1044
            repo_token = self._repo_lock_token
1427
1045
            self._lock_token = None
1434
1052
        return self._real_branch.break_lock()
1435
1053
 
1436
1054
    def leave_lock_in_place(self):
1437
 
        if not self._lock_token:
1438
 
            raise NotImplementedError(self.leave_lock_in_place)
1439
1055
        self._leave_lock = True
1440
1056
 
1441
1057
    def dont_leave_lock_in_place(self):
1442
 
        if not self._lock_token:
1443
 
            raise NotImplementedError(self.dont_leave_lock_in_place)
1444
1058
        self._leave_lock = False
1445
1059
 
1446
1060
    def last_revision_info(self):
1447
1061
        """See Branch.last_revision_info()."""
1448
1062
        path = self.bzrdir._path_for_remote_call(self._client)
1449
1063
        response = self._client.call('Branch.last_revision_info', path)
1450
 
        if response[0] != 'ok':
1451
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
1064
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1452
1065
        revno = int(response[1])
1453
1066
        last_revision = response[2]
1454
1067
        return (revno, last_revision)
1456
1069
    def _gen_revision_history(self):
1457
1070
        """See Branch._gen_revision_history()."""
1458
1071
        path = self.bzrdir._path_for_remote_call(self._client)
1459
 
        response_tuple, response_handler = self._client.call_expecting_body(
 
1072
        response = self._client.call_expecting_body(
1460
1073
            'Branch.revision_history', path)
1461
 
        if response_tuple[0] != 'ok':
1462
 
            raise UnexpectedSmartServerResponse(response_tuple)
1463
 
        result = response_handler.read_body_bytes().split('\x00')
 
1074
        assert response[0][0] == 'ok', ('unexpected response code %s'
 
1075
                                        % (response[0],))
 
1076
        result = response[1].read_body_bytes().split('\x00')
1464
1077
        if result == ['']:
1465
1078
            return []
1466
1079
        return result
1476
1089
        else:
1477
1090
            rev_id = rev_history[-1]
1478
1091
        self._clear_cached_state()
1479
 
        try:
1480
 
            response = self._client.call('Branch.set_last_revision',
1481
 
                path, self._lock_token, self._repo_lock_token, rev_id)
1482
 
        except errors.ErrorFromSmartServer, err:
1483
 
            if err.error_verb == 'NoSuchRevision':
1484
 
                raise NoSuchRevision(self, rev_id)
1485
 
            raise
1486
 
        if response != ('ok',):
1487
 
            raise errors.UnexpectedSmartServerResponse(response)
 
1092
        response = self._client.call('Branch.set_last_revision',
 
1093
            path, self._lock_token, self._repo_lock_token, rev_id)
 
1094
        if response[0] == 'NoSuchRevision':
 
1095
            raise NoSuchRevision(self, rev_id)
 
1096
        else:
 
1097
            assert response == ('ok',), (
 
1098
                'unexpected response code %r' % (response,))
1488
1099
        self._cache_revision_history(rev_history)
1489
1100
 
1490
1101
    def get_parent(self):
1495
1106
        self._ensure_real()
1496
1107
        return self._real_branch.set_parent(url)
1497
1108
        
 
1109
    def get_config(self):
 
1110
        return RemoteBranchConfig(self)
 
1111
 
1498
1112
    def sprout(self, to_bzrdir, revision_id=None):
1499
1113
        # Like Branch.sprout, except that it sprouts a branch in the default
1500
1114
        # format, because RemoteBranches can't be created at arbitrary URLs.
1501
1115
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1502
1116
        # to_bzrdir.create_branch...
1503
 
        self._ensure_real()
1504
 
        result = self._real_branch._format.initialize(to_bzrdir)
 
1117
        result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1505
1118
        self.copy_content_into(result, revision_id=revision_id)
1506
1119
        result.set_parent(self.bzrdir.root_transport.base)
1507
1120
        return result
1529
1142
    def is_locked(self):
1530
1143
        return self._lock_count >= 1
1531
1144
 
1532
 
    @needs_write_lock
1533
1145
    def set_last_revision_info(self, revno, revision_id):
1534
 
        revision_id = ensure_null(revision_id)
1535
 
        path = self.bzrdir._path_for_remote_call(self._client)
1536
 
        try:
1537
 
            response = self._client.call('Branch.set_last_revision_info',
1538
 
                path, self._lock_token, self._repo_lock_token, str(revno), revision_id)
1539
 
        except errors.UnknownSmartMethod:
1540
 
            self._ensure_real()
1541
 
            self._clear_cached_state()
1542
 
            return self._real_branch.set_last_revision_info(revno, revision_id)
1543
 
        except errors.ErrorFromSmartServer, err:
1544
 
            if err.error_verb == 'NoSuchRevision':
1545
 
                raise NoSuchRevision(self, err.error_args[0])
1546
 
            raise
1547
 
        if response == ('ok',):
1548
 
            self._clear_cached_state()
1549
 
        else:
1550
 
            raise errors.UnexpectedSmartServerResponse(response)
 
1146
        self._ensure_real()
 
1147
        self._clear_cached_state()
 
1148
        return self._real_branch.set_last_revision_info(revno, revision_id)
1551
1149
 
1552
1150
    def generate_revision_history(self, revision_id, last_rev=None,
1553
1151
                                  other_branch=None):
1564
1162
        self._ensure_real()
1565
1163
        return self._real_branch.set_push_location(location)
1566
1164
 
1567
 
    def update_revisions(self, other, stop_revision=None, overwrite=False):
 
1165
    def update_revisions(self, other, stop_revision=None):
1568
1166
        self._ensure_real()
1569
1167
        return self._real_branch.update_revisions(
1570
 
            other, stop_revision=stop_revision, overwrite=overwrite)
 
1168
            other, stop_revision=stop_revision)
 
1169
 
 
1170
 
 
1171
class RemoteBranchConfig(BranchConfig):
 
1172
 
 
1173
    def username(self):
 
1174
        self.branch._ensure_real()
 
1175
        return self.branch._real_branch.get_config().username()
 
1176
 
 
1177
    def _get_branch_data_config(self):
 
1178
        self.branch._ensure_real()
 
1179
        if self._branch_data_config is None:
 
1180
            self._branch_data_config = TreeConfig(self.branch._real_branch)
 
1181
        return self._branch_data_config
1571
1182
 
1572
1183
 
1573
1184
def _extract_tar(tar, to_dir):