/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Robert Collins
  • Date: 2008-02-03 22:55:08 UTC
  • mto: This revision was merged to the branch mainline in revision 3216.
  • Revision ID: robertc@robertcollins.net-20080203225508-0rogbg0ggonuqfhp
Change the smart server get_parents method to take a graph search to exclude already recieved parents from. This prevents history shortcuts causing huge numbers of duplicates.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
# TODO: At some point, handle upgrades by just passing the whole request
 
18
# across to run on the server.
 
19
 
 
20
from cStringIO import StringIO
 
21
 
 
22
from bzrlib import (
 
23
    branch,
 
24
    debug,
 
25
    errors,
 
26
    graph,
 
27
    lockdir,
 
28
    repository,
 
29
    revision,
 
30
)
 
31
from bzrlib.branch import BranchReferenceFormat
 
32
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
 
33
from bzrlib.config import BranchConfig, TreeConfig
 
34
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
35
from bzrlib.errors import NoSuchRevision
 
36
from bzrlib.lockable_files import LockableFiles
 
37
from bzrlib.pack import ContainerPushParser
 
38
from bzrlib.smart import client, vfs
 
39
from bzrlib.symbol_versioning import (
 
40
    deprecated_method,
 
41
    zero_ninetyone,
 
42
    )
 
43
from bzrlib.revision import NULL_REVISION
 
44
from bzrlib.trace import mutter, note
 
45
from bzrlib.tuned_gzip import GzipFile
 
46
 
 
47
# Note: RemoteBzrDirFormat is in bzrdir.py
 
48
 
 
49
class RemoteBzrDir(BzrDir):
 
50
    """Control directory on a remote server, accessed via bzr:// or similar."""
 
51
 
 
52
    def __init__(self, transport, _client=None):
 
53
        """Construct a RemoteBzrDir.
 
54
 
 
55
        :param _client: Private parameter for testing. Disables probing and the
 
56
            use of a real bzrdir.
 
57
        """
 
58
        BzrDir.__init__(self, transport, RemoteBzrDirFormat())
 
59
        # this object holds a delegated bzrdir that uses file-level operations
 
60
        # to talk to the other side
 
61
        self._real_bzrdir = None
 
62
 
 
63
        if _client is None:
 
64
            self._shared_medium = transport.get_shared_medium()
 
65
            self._client = client._SmartClient(self._shared_medium)
 
66
        else:
 
67
            self._client = _client
 
68
            self._shared_medium = None
 
69
            return
 
70
 
 
71
        path = self._path_for_remote_call(self._client)
 
72
        response = self._client.call('BzrDir.open', path)
 
73
        if response not in [('yes',), ('no',)]:
 
74
            raise errors.UnexpectedSmartServerResponse(response)
 
75
        if response == ('no',):
 
76
            raise errors.NotBranchError(path=transport.base)
 
77
 
 
78
    def _ensure_real(self):
 
79
        """Ensure that there is a _real_bzrdir set.
 
80
 
 
81
        Used before calls to self._real_bzrdir.
 
82
        """
 
83
        if not self._real_bzrdir:
 
84
            self._real_bzrdir = BzrDir.open_from_transport(
 
85
                self.root_transport, _server_formats=False)
 
86
 
 
87
    def create_repository(self, shared=False):
 
88
        self._ensure_real()
 
89
        self._real_bzrdir.create_repository(shared=shared)
 
90
        return self.open_repository()
 
91
 
 
92
    def destroy_repository(self):
 
93
        """See BzrDir.destroy_repository"""
 
94
        self._ensure_real()
 
95
        self._real_bzrdir.destroy_repository()
 
96
 
 
97
    def create_branch(self):
 
98
        self._ensure_real()
 
99
        real_branch = self._real_bzrdir.create_branch()
 
100
        return RemoteBranch(self, self.find_repository(), real_branch)
 
101
 
 
102
    def destroy_branch(self):
 
103
        """See BzrDir.destroy_branch"""
 
104
        self._ensure_real()
 
105
        self._real_bzrdir.destroy_branch()
 
106
 
 
107
    def create_workingtree(self, revision_id=None, from_branch=None):
 
108
        raise errors.NotLocalUrl(self.transport.base)
 
109
 
 
110
    def find_branch_format(self):
 
111
        """Find the branch 'format' for this bzrdir.
 
112
 
 
113
        This might be a synthetic object for e.g. RemoteBranch and SVN.
 
114
        """
 
115
        b = self.open_branch()
 
116
        return b._format
 
117
 
 
118
    def get_branch_reference(self):
 
119
        """See BzrDir.get_branch_reference()."""
 
120
        path = self._path_for_remote_call(self._client)
 
121
        response = self._client.call('BzrDir.open_branch', path)
 
122
        if response[0] == 'ok':
 
123
            if response[1] == '':
 
124
                # branch at this location.
 
125
                return None
 
126
            else:
 
127
                # a branch reference, use the existing BranchReference logic.
 
128
                return response[1]
 
129
        elif response == ('nobranch',):
 
130
            raise errors.NotBranchError(path=self.root_transport.base)
 
131
        else:
 
132
            raise errors.UnexpectedSmartServerResponse(response)
 
133
 
 
134
    def open_branch(self, _unsupported=False):
 
135
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
 
136
        reference_url = self.get_branch_reference()
 
137
        if reference_url is None:
 
138
            # branch at this location.
 
139
            return RemoteBranch(self, self.find_repository())
 
140
        else:
 
141
            # a branch reference, use the existing BranchReference logic.
 
142
            format = BranchReferenceFormat()
 
143
            return format.open(self, _found=True, location=reference_url)
 
144
                
 
145
    def open_repository(self):
 
146
        path = self._path_for_remote_call(self._client)
 
147
        response = self._client.call('BzrDir.find_repository', path)
 
148
        assert response[0] in ('ok', 'norepository'), \
 
149
            'unexpected response code %s' % (response,)
 
150
        if response[0] == 'norepository':
 
151
            raise errors.NoRepositoryPresent(self)
 
152
        assert len(response) == 4, 'incorrect response length %s' % (response,)
 
153
        if response[1] == '':
 
154
            format = RemoteRepositoryFormat()
 
155
            format.rich_root_data = (response[2] == 'yes')
 
156
            format.supports_tree_reference = (response[3] == 'yes')
 
157
            return RemoteRepository(self, format)
 
158
        else:
 
159
            raise errors.NoRepositoryPresent(self)
 
160
 
 
161
    def open_workingtree(self, recommend_upgrade=True):
 
162
        self._ensure_real()
 
163
        if self._real_bzrdir.has_workingtree():
 
164
            raise errors.NotLocalUrl(self.root_transport)
 
165
        else:
 
166
            raise errors.NoWorkingTree(self.root_transport.base)
 
167
 
 
168
    def _path_for_remote_call(self, client):
 
169
        """Return the path to be used for this bzrdir in a remote call."""
 
170
        return client.remote_path_from_transport(self.root_transport)
 
171
 
 
172
    def get_branch_transport(self, branch_format):
 
173
        self._ensure_real()
 
174
        return self._real_bzrdir.get_branch_transport(branch_format)
 
175
 
 
176
    def get_repository_transport(self, repository_format):
 
177
        self._ensure_real()
 
178
        return self._real_bzrdir.get_repository_transport(repository_format)
 
179
 
 
180
    def get_workingtree_transport(self, workingtree_format):
 
181
        self._ensure_real()
 
182
        return self._real_bzrdir.get_workingtree_transport(workingtree_format)
 
183
 
 
184
    def can_convert_format(self):
 
185
        """Upgrading of remote bzrdirs is not supported yet."""
 
186
        return False
 
187
 
 
188
    def needs_format_conversion(self, format=None):
 
189
        """Upgrading of remote bzrdirs is not supported yet."""
 
190
        return False
 
191
 
 
192
    def clone(self, url, revision_id=None, force_new_repo=False):
 
193
        self._ensure_real()
 
194
        return self._real_bzrdir.clone(url, revision_id=revision_id,
 
195
            force_new_repo=force_new_repo)
 
196
 
 
197
 
 
198
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
199
    """Format for repositories accessed over a _SmartClient.
 
200
 
 
201
    Instances of this repository are represented by RemoteRepository
 
202
    instances.
 
203
 
 
204
    The RemoteRepositoryFormat is parameterized during construction
 
205
    to reflect the capabilities of the real, remote format. Specifically
 
206
    the attributes rich_root_data and supports_tree_reference are set
 
207
    on a per instance basis, and are not set (and should not be) at
 
208
    the class level.
 
209
    """
 
210
 
 
211
    _matchingbzrdir = RemoteBzrDirFormat
 
212
 
 
213
    def initialize(self, a_bzrdir, shared=False):
 
214
        assert isinstance(a_bzrdir, RemoteBzrDir), \
 
215
            '%r is not a RemoteBzrDir' % (a_bzrdir,)
 
216
        return a_bzrdir.create_repository(shared=shared)
 
217
    
 
218
    def open(self, a_bzrdir):
 
219
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
220
        return a_bzrdir.open_repository()
 
221
 
 
222
    def get_format_description(self):
 
223
        return 'bzr remote repository'
 
224
 
 
225
    def __eq__(self, other):
 
226
        return self.__class__ == other.__class__
 
227
 
 
228
    def check_conversion_target(self, target_format):
 
229
        if self.rich_root_data and not target_format.rich_root_data:
 
230
            raise errors.BadConversionTarget(
 
231
                'Does not support rich root data.', target_format)
 
232
        if (self.supports_tree_reference and
 
233
            not getattr(target_format, 'supports_tree_reference', False)):
 
234
            raise errors.BadConversionTarget(
 
235
                'Does not support nested trees', target_format)
 
236
 
 
237
 
 
238
class RemoteRepository(object):
 
239
    """Repository accessed over rpc.
 
240
 
 
241
    For the moment most operations are performed using local transport-backed
 
242
    Repository objects.
 
243
    """
 
244
 
 
245
    def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
 
246
        """Create a RemoteRepository instance.
 
247
        
 
248
        :param remote_bzrdir: The bzrdir hosting this repository.
 
249
        :param format: The RemoteFormat object to use.
 
250
        :param real_repository: If not None, a local implementation of the
 
251
            repository logic for the repository, usually accessing the data
 
252
            via the VFS.
 
253
        :param _client: Private testing parameter - override the smart client
 
254
            to be used by the repository.
 
255
        """
 
256
        if real_repository:
 
257
            self._real_repository = real_repository
 
258
        else:
 
259
            self._real_repository = None
 
260
        self.bzrdir = remote_bzrdir
 
261
        if _client is None:
 
262
            self._client = client._SmartClient(self.bzrdir._shared_medium)
 
263
        else:
 
264
            self._client = _client
 
265
        self._format = format
 
266
        self._lock_mode = None
 
267
        self._lock_token = None
 
268
        self._lock_count = 0
 
269
        self._leave_lock = False
 
270
        # A cache of looked up revision parent data; reset at unlock time.
 
271
        self._parents_map = None
 
272
        if 'hpss' in debug.debug_flags:
 
273
            self._requested_parents = None
 
274
        # For tests:
 
275
        # These depend on the actual remote format, so force them off for
 
276
        # maximum compatibility. XXX: In future these should depend on the
 
277
        # remote repository instance, but this is irrelevant until we perform
 
278
        # reconcile via an RPC call.
 
279
        self._reconcile_does_inventory_gc = False
 
280
        self._reconcile_fixes_text_parents = False
 
281
        self._reconcile_backsup_inventory = False
 
282
        self.base = self.bzrdir.transport.base
 
283
 
 
284
    def __str__(self):
 
285
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
286
 
 
287
    __repr__ = __str__
 
288
 
 
289
    def abort_write_group(self):
 
290
        """Complete a write group on the decorated repository.
 
291
        
 
292
        Smart methods peform operations in a single step so this api
 
293
        is not really applicable except as a compatibility thunk
 
294
        for older plugins that don't use e.g. the CommitBuilder
 
295
        facility.
 
296
        """
 
297
        self._ensure_real()
 
298
        return self._real_repository.abort_write_group()
 
299
 
 
300
    def commit_write_group(self):
 
301
        """Complete a write group on the decorated repository.
 
302
        
 
303
        Smart methods peform operations in a single step so this api
 
304
        is not really applicable except as a compatibility thunk
 
305
        for older plugins that don't use e.g. the CommitBuilder
 
306
        facility.
 
307
        """
 
308
        self._ensure_real()
 
309
        return self._real_repository.commit_write_group()
 
310
 
 
311
    def _ensure_real(self):
 
312
        """Ensure that there is a _real_repository set.
 
313
 
 
314
        Used before calls to self._real_repository.
 
315
        """
 
316
        if not self._real_repository:
 
317
            self.bzrdir._ensure_real()
 
318
            #self._real_repository = self.bzrdir._real_bzrdir.open_repository()
 
319
            self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
 
320
 
 
321
    def find_text_key_references(self):
 
322
        """Find the text key references within the repository.
 
323
 
 
324
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
 
325
        revision_ids. Each altered file-ids has the exact revision_ids that
 
326
        altered it listed explicitly.
 
327
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
 
328
            to whether they were referred to by the inventory of the
 
329
            revision_id that they contain. The inventory texts from all present
 
330
            revision ids are assessed to generate this report.
 
331
        """
 
332
        self._ensure_real()
 
333
        return self._real_repository.find_text_key_references()
 
334
 
 
335
    def _generate_text_key_index(self):
 
336
        """Generate a new text key index for the repository.
 
337
 
 
338
        This is an expensive function that will take considerable time to run.
 
339
 
 
340
        :return: A dict mapping (file_id, revision_id) tuples to a list of
 
341
            parents, also (file_id, revision_id) tuples.
 
342
        """
 
343
        self._ensure_real()
 
344
        return self._real_repository._generate_text_key_index()
 
345
 
 
346
    def get_revision_graph(self, revision_id=None):
 
347
        """See Repository.get_revision_graph()."""
 
348
        if revision_id is None:
 
349
            revision_id = ''
 
350
        elif revision.is_null(revision_id):
 
351
            return {}
 
352
 
 
353
        path = self.bzrdir._path_for_remote_call(self._client)
 
354
        assert type(revision_id) is str
 
355
        response = self._client.call_expecting_body(
 
356
            'Repository.get_revision_graph', path, revision_id)
 
357
        if response[0][0] not in ['ok', 'nosuchrevision']:
 
358
            raise errors.UnexpectedSmartServerResponse(response[0])
 
359
        if response[0][0] == 'ok':
 
360
            coded = response[1].read_body_bytes()
 
361
            if coded == '':
 
362
                # no revisions in this repository!
 
363
                return {}
 
364
            lines = coded.split('\n')
 
365
            revision_graph = {}
 
366
            for line in lines:
 
367
                d = tuple(line.split())
 
368
                revision_graph[d[0]] = d[1:]
 
369
                
 
370
            return revision_graph
 
371
        else:
 
372
            response_body = response[1].read_body_bytes()
 
373
            assert response_body == ''
 
374
            raise NoSuchRevision(self, revision_id)
 
375
 
 
376
    def has_revision(self, revision_id):
 
377
        """See Repository.has_revision()."""
 
378
        if revision_id == NULL_REVISION:
 
379
            # The null revision is always present.
 
380
            return True
 
381
        path = self.bzrdir._path_for_remote_call(self._client)
 
382
        response = self._client.call('Repository.has_revision', path, revision_id)
 
383
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
384
        return response[0] == 'yes'
 
385
 
 
386
    def has_revisions(self, revision_ids):
 
387
        """See Repository.has_revisions()."""
 
388
        result = set()
 
389
        for revision_id in revision_ids:
 
390
            if self.has_revision(revision_id):
 
391
                result.add(revision_id)
 
392
        return result
 
393
 
 
394
    def has_same_location(self, other):
 
395
        return (self.__class__ == other.__class__ and
 
396
                self.bzrdir.transport.base == other.bzrdir.transport.base)
 
397
        
 
398
    def get_graph(self, other_repository=None):
 
399
        """Return the graph for this repository format"""
 
400
        parents_provider = self
 
401
        if (other_repository is not None and
 
402
            other_repository.bzrdir.transport.base !=
 
403
            self.bzrdir.transport.base):
 
404
            parents_provider = graph._StackedParentsProvider(
 
405
                [parents_provider, other_repository._make_parents_provider()])
 
406
        return graph.Graph(parents_provider)
 
407
 
 
408
    def gather_stats(self, revid=None, committers=None):
 
409
        """See Repository.gather_stats()."""
 
410
        path = self.bzrdir._path_for_remote_call(self._client)
 
411
        # revid can be None to indicate no revisions, not just NULL_REVISION
 
412
        if revid is None or revision.is_null(revid):
 
413
            fmt_revid = ''
 
414
        else:
 
415
            fmt_revid = revid
 
416
        if committers is None or not committers:
 
417
            fmt_committers = 'no'
 
418
        else:
 
419
            fmt_committers = 'yes'
 
420
        response = self._client.call_expecting_body(
 
421
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
 
422
        assert response[0][0] == 'ok', \
 
423
            'unexpected response code %s' % (response[0],)
 
424
 
 
425
        body = response[1].read_body_bytes()
 
426
        result = {}
 
427
        for line in body.split('\n'):
 
428
            if not line:
 
429
                continue
 
430
            key, val_text = line.split(':')
 
431
            if key in ('revisions', 'size', 'committers'):
 
432
                result[key] = int(val_text)
 
433
            elif key in ('firstrev', 'latestrev'):
 
434
                values = val_text.split(' ')[1:]
 
435
                result[key] = (float(values[0]), long(values[1]))
 
436
 
 
437
        return result
 
438
 
 
439
    def find_branches(self, using=False):
 
440
        """See Repository.find_branches()."""
 
441
        # should be an API call to the server.
 
442
        self._ensure_real()
 
443
        return self._real_repository.find_branches(using=using)
 
444
 
 
445
    def get_physical_lock_status(self):
 
446
        """See Repository.get_physical_lock_status()."""
 
447
        # should be an API call to the server.
 
448
        self._ensure_real()
 
449
        return self._real_repository.get_physical_lock_status()
 
450
 
 
451
    def is_in_write_group(self):
 
452
        """Return True if there is an open write group.
 
453
 
 
454
        write groups are only applicable locally for the smart server..
 
455
        """
 
456
        if self._real_repository:
 
457
            return self._real_repository.is_in_write_group()
 
458
 
 
459
    def is_locked(self):
 
460
        return self._lock_count >= 1
 
461
 
 
462
    def is_shared(self):
 
463
        """See Repository.is_shared()."""
 
464
        path = self.bzrdir._path_for_remote_call(self._client)
 
465
        response = self._client.call('Repository.is_shared', path)
 
466
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
467
        return response[0] == 'yes'
 
468
 
 
469
    def is_write_locked(self):
 
470
        return self._lock_mode == 'w'
 
471
 
 
472
    def lock_read(self):
 
473
        # wrong eventually - want a local lock cache context
 
474
        if not self._lock_mode:
 
475
            self._lock_mode = 'r'
 
476
            self._lock_count = 1
 
477
            self._parents_map = {}
 
478
            if 'hpss' in debug.debug_flags:
 
479
                self._requested_parents = set()
 
480
            if self._real_repository is not None:
 
481
                self._real_repository.lock_read()
 
482
        else:
 
483
            self._lock_count += 1
 
484
 
 
485
    def _remote_lock_write(self, token):
 
486
        path = self.bzrdir._path_for_remote_call(self._client)
 
487
        if token is None:
 
488
            token = ''
 
489
        response = self._client.call('Repository.lock_write', path, token)
 
490
        if response[0] == 'ok':
 
491
            ok, token = response
 
492
            return token
 
493
        elif response[0] == 'LockContention':
 
494
            raise errors.LockContention('(remote lock)')
 
495
        elif response[0] == 'UnlockableTransport':
 
496
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
497
        elif response[0] == 'LockFailed':
 
498
            raise errors.LockFailed(response[1], response[2])
 
499
        else:
 
500
            raise errors.UnexpectedSmartServerResponse(response)
 
501
 
 
502
    def lock_write(self, token=None):
 
503
        if not self._lock_mode:
 
504
            self._lock_token = self._remote_lock_write(token)
 
505
            # if self._lock_token is None, then this is something like packs or
 
506
            # svn where we don't get to lock the repo, or a weave style repository
 
507
            # where we cannot lock it over the wire and attempts to do so will
 
508
            # fail.
 
509
            if self._real_repository is not None:
 
510
                self._real_repository.lock_write(token=self._lock_token)
 
511
            if token is not None:
 
512
                self._leave_lock = True
 
513
            else:
 
514
                self._leave_lock = False
 
515
            self._lock_mode = 'w'
 
516
            self._lock_count = 1
 
517
            self._parents_map = {}
 
518
            if 'hpss' in debug.debug_flags:
 
519
                self._requested_parents = set()
 
520
        elif self._lock_mode == 'r':
 
521
            raise errors.ReadOnlyError(self)
 
522
        else:
 
523
            self._lock_count += 1
 
524
        return self._lock_token or None
 
525
 
 
526
    def leave_lock_in_place(self):
 
527
        if not self._lock_token:
 
528
            raise NotImplementedError(self.leave_lock_in_place)
 
529
        self._leave_lock = True
 
530
 
 
531
    def dont_leave_lock_in_place(self):
 
532
        if not self._lock_token:
 
533
            raise NotImplementedError(self.dont_leave_lock_in_place)
 
534
        self._leave_lock = False
 
535
 
 
536
    def _set_real_repository(self, repository):
 
537
        """Set the _real_repository for this repository.
 
538
 
 
539
        :param repository: The repository to fallback to for non-hpss
 
540
            implemented operations.
 
541
        """
 
542
        assert not isinstance(repository, RemoteRepository)
 
543
        self._real_repository = repository
 
544
        if self._lock_mode == 'w':
 
545
            # if we are already locked, the real repository must be able to
 
546
            # acquire the lock with our token.
 
547
            self._real_repository.lock_write(self._lock_token)
 
548
        elif self._lock_mode == 'r':
 
549
            self._real_repository.lock_read()
 
550
 
 
551
    def start_write_group(self):
 
552
        """Start a write group on the decorated repository.
 
553
        
 
554
        Smart methods peform operations in a single step so this api
 
555
        is not really applicable except as a compatibility thunk
 
556
        for older plugins that don't use e.g. the CommitBuilder
 
557
        facility.
 
558
        """
 
559
        self._ensure_real()
 
560
        return self._real_repository.start_write_group()
 
561
 
 
562
    def _unlock(self, token):
 
563
        path = self.bzrdir._path_for_remote_call(self._client)
 
564
        if not token:
 
565
            # with no token the remote repository is not persistently locked.
 
566
            return
 
567
        response = self._client.call('Repository.unlock', path, token)
 
568
        if response == ('ok',):
 
569
            return
 
570
        elif response[0] == 'TokenMismatch':
 
571
            raise errors.TokenMismatch(token, '(remote token)')
 
572
        else:
 
573
            raise errors.UnexpectedSmartServerResponse(response)
 
574
 
 
575
    def unlock(self):
 
576
        self._lock_count -= 1
 
577
        if self._lock_count > 0:
 
578
            return
 
579
        self._parents_map = None
 
580
        if 'hpss' in debug.debug_flags:
 
581
            self._requested_parents = None
 
582
        old_mode = self._lock_mode
 
583
        self._lock_mode = None
 
584
        try:
 
585
            # The real repository is responsible at present for raising an
 
586
            # exception if it's in an unfinished write group.  However, it
 
587
            # normally will *not* actually remove the lock from disk - that's
 
588
            # done by the server on receiving the Repository.unlock call.
 
589
            # This is just to let the _real_repository stay up to date.
 
590
            if self._real_repository is not None:
 
591
                self._real_repository.unlock()
 
592
        finally:
 
593
            # The rpc-level lock should be released even if there was a
 
594
            # problem releasing the vfs-based lock.
 
595
            if old_mode == 'w':
 
596
                # Only write-locked repositories need to make a remote method
 
597
                # call to perfom the unlock.
 
598
                old_token = self._lock_token
 
599
                self._lock_token = None
 
600
                if not self._leave_lock:
 
601
                    self._unlock(old_token)
 
602
 
 
603
    def break_lock(self):
 
604
        # should hand off to the network
 
605
        self._ensure_real()
 
606
        return self._real_repository.break_lock()
 
607
 
 
608
    def _get_tarball(self, compression):
 
609
        """Return a TemporaryFile containing a repository tarball.
 
610
        
 
611
        Returns None if the server does not support sending tarballs.
 
612
        """
 
613
        import tempfile
 
614
        path = self.bzrdir._path_for_remote_call(self._client)
 
615
        response, protocol = self._client.call_expecting_body(
 
616
            'Repository.tarball', path, compression)
 
617
        if response[0] == 'ok':
 
618
            # Extract the tarball and return it
 
619
            t = tempfile.NamedTemporaryFile()
 
620
            # TODO: rpc layer should read directly into it...
 
621
            t.write(protocol.read_body_bytes())
 
622
            t.seek(0)
 
623
            return t
 
624
        if (response == ('error', "Generic bzr smart protocol error: "
 
625
                "bad request 'Repository.tarball'") or
 
626
              response == ('error', "Generic bzr smart protocol error: "
 
627
                "bad request u'Repository.tarball'")):
 
628
            protocol.cancel_read_body()
 
629
            return None
 
630
        raise errors.UnexpectedSmartServerResponse(response)
 
631
 
 
632
    def sprout(self, to_bzrdir, revision_id=None):
 
633
        # TODO: Option to control what format is created?
 
634
        self._ensure_real()
 
635
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
 
636
                                                             shared=False)
 
637
        dest_repo.fetch(self, revision_id=revision_id)
 
638
        return dest_repo
 
639
 
 
640
    ### These methods are just thin shims to the VFS object for now.
 
641
 
 
642
    def revision_tree(self, revision_id):
 
643
        self._ensure_real()
 
644
        return self._real_repository.revision_tree(revision_id)
 
645
 
 
646
    def get_serializer_format(self):
 
647
        self._ensure_real()
 
648
        return self._real_repository.get_serializer_format()
 
649
 
 
650
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
651
                           timezone=None, committer=None, revprops=None,
 
652
                           revision_id=None):
 
653
        # FIXME: It ought to be possible to call this without immediately
 
654
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
655
        self._ensure_real()
 
656
        builder = self._real_repository.get_commit_builder(branch, parents,
 
657
                config, timestamp=timestamp, timezone=timezone,
 
658
                committer=committer, revprops=revprops, revision_id=revision_id)
 
659
        return builder
 
660
 
 
661
    def add_inventory(self, revid, inv, parents):
 
662
        self._ensure_real()
 
663
        return self._real_repository.add_inventory(revid, inv, parents)
 
664
 
 
665
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
666
        self._ensure_real()
 
667
        return self._real_repository.add_revision(
 
668
            rev_id, rev, inv=inv, config=config)
 
669
 
 
670
    @needs_read_lock
 
671
    def get_inventory(self, revision_id):
 
672
        self._ensure_real()
 
673
        return self._real_repository.get_inventory(revision_id)
 
674
 
 
675
    def iter_inventories(self, revision_ids):
 
676
        self._ensure_real()
 
677
        return self._real_repository.iter_inventories(revision_ids)
 
678
 
 
679
    @needs_read_lock
 
680
    def get_revision(self, revision_id):
 
681
        self._ensure_real()
 
682
        return self._real_repository.get_revision(revision_id)
 
683
 
 
684
    @property
 
685
    def weave_store(self):
 
686
        self._ensure_real()
 
687
        return self._real_repository.weave_store
 
688
 
 
689
    def get_transaction(self):
 
690
        self._ensure_real()
 
691
        return self._real_repository.get_transaction()
 
692
 
 
693
    @needs_read_lock
 
694
    def clone(self, a_bzrdir, revision_id=None):
 
695
        self._ensure_real()
 
696
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
 
697
 
 
698
    def make_working_trees(self):
 
699
        """RemoteRepositories never create working trees by default."""
 
700
        return False
 
701
 
 
702
    def revision_ids_to_search_result(self, result_set):
 
703
        """Convert a set of revision ids to a graph SearchResult."""
 
704
        result_parents = set()
 
705
        for parents in self.get_graph().get_parent_map(
 
706
            result_set).itervalues():
 
707
            result_parents.update(parents)
 
708
        included_keys = result_set.intersection(result_parents)
 
709
        start_keys = result_set.difference(included_keys)
 
710
        exclude_keys = result_parents.difference(result_set)
 
711
        result = graph.SearchResult(start_keys, exclude_keys,
 
712
            len(result_set), result_set)
 
713
        return result
 
714
 
 
715
    @needs_read_lock
 
716
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
717
        """Return the revision ids that other has that this does not.
 
718
        
 
719
        These are returned in topological order.
 
720
 
 
721
        revision_id: only return revision ids included by revision_id.
 
722
        """
 
723
        return repository.InterRepository.get(
 
724
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
 
725
 
 
726
    def fetch(self, source, revision_id=None, pb=None):
 
727
        if self.has_same_location(source):
 
728
            # check that last_revision is in 'from' and then return a
 
729
            # no-operation.
 
730
            if (revision_id is not None and
 
731
                not revision.is_null(revision_id)):
 
732
                self.get_revision(revision_id)
 
733
            return 0, []
 
734
        self._ensure_real()
 
735
        return self._real_repository.fetch(
 
736
            source, revision_id=revision_id, pb=pb)
 
737
 
 
738
    def create_bundle(self, target, base, fileobj, format=None):
 
739
        self._ensure_real()
 
740
        self._real_repository.create_bundle(target, base, fileobj, format)
 
741
 
 
742
    @property
 
743
    def control_weaves(self):
 
744
        self._ensure_real()
 
745
        return self._real_repository.control_weaves
 
746
 
 
747
    @needs_read_lock
 
748
    def get_ancestry(self, revision_id, topo_sorted=True):
 
749
        self._ensure_real()
 
750
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
 
751
 
 
752
    @needs_read_lock
 
753
    def get_inventory_weave(self):
 
754
        self._ensure_real()
 
755
        return self._real_repository.get_inventory_weave()
 
756
 
 
757
    def fileids_altered_by_revision_ids(self, revision_ids):
 
758
        self._ensure_real()
 
759
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
 
760
 
 
761
    def _get_versioned_file_checker(self, revisions, revision_versions_cache):
 
762
        self._ensure_real()
 
763
        return self._real_repository._get_versioned_file_checker(
 
764
            revisions, revision_versions_cache)
 
765
        
 
766
    def iter_files_bytes(self, desired_files):
 
767
        """See Repository.iter_file_bytes.
 
768
        """
 
769
        self._ensure_real()
 
770
        return self._real_repository.iter_files_bytes(desired_files)
 
771
 
 
772
    def get_parent_map(self, keys):
 
773
        """See bzrlib.Graph.get_parent_map()."""
 
774
        # Hack to build up the caching logic.
 
775
        ancestry = self._parents_map
 
776
        missing_revisions = set(key for key in keys if key not in ancestry)
 
777
        if missing_revisions:
 
778
            parent_map = self._get_parent_map(missing_revisions)
 
779
            if 'hpss' in debug.debug_flags:
 
780
                mutter('retransmitted revisions: %d of %d',
 
781
                        len(set(self._parents_map).intersection(parent_map)),
 
782
                        len(parent_map))
 
783
            self._parents_map.update(parent_map)
 
784
        present_keys = [k for k in keys if k in ancestry]
 
785
        if 'hpss' in debug.debug_flags:
 
786
            self._requested_parents.update(present_keys)
 
787
            mutter('Current RemoteRepository graph hit rate: %d%%',
 
788
                100.0 * len(self._requested_parents) / len(self._parents_map))
 
789
        return dict((k, ancestry[k]) for k in present_keys)
 
790
 
 
791
    def _response_is_unknown_method(self, response, verb):
 
792
        """Return True if response is an unknonwn method response to verb.
 
793
        
 
794
        :param response: The response from a smart client call_expecting_body
 
795
            call.
 
796
        :param verb: The verb used in that call.
 
797
        :return: True if an unknown method was encountered.
 
798
        """
 
799
        # This might live better on
 
800
        # bzrlib.smart.protocol.SmartClientRequestProtocolOne
 
801
        if (response[0] == ('error', "Generic bzr smart protocol error: "
 
802
                "bad request '%s'" % verb) or
 
803
              response[0] == ('error', "Generic bzr smart protocol error: "
 
804
                "bad request u'%s'" % verb)):
 
805
           response[1].cancel_read_body()
 
806
           return True
 
807
        return False
 
808
 
 
809
    def _get_parent_map(self, keys):
 
810
        """Helper for get_parent_map that performs the RPC."""
 
811
        keys = set(keys)
 
812
        if NULL_REVISION in keys:
 
813
            keys.discard(NULL_REVISION)
 
814
            found_parents = {NULL_REVISION:()}
 
815
            if not keys:
 
816
                return found_parents
 
817
        else:
 
818
            found_parents = {}
 
819
        # TODO(Needs analysis): We could assume that the keys being requested
 
820
        # from get_parent_map are in a breadth first search, so typically they
 
821
        # will all be depth N from some common parent, and we don't have to
 
822
        # have the server iterate from the root parent, but rather from the
 
823
        # keys we're searching; and just tell the server the keyspace we
 
824
        # already have; but this may be more traffic again.
 
825
 
 
826
        # Transform self._parents_map into a search request recipe.
 
827
        # TODO: Manage this incrementally to avoid covering the same path
 
828
        # repeatedly. (The server will have to on each request, but the less
 
829
        # work done the better).
 
830
        start_set = set(self._parents_map)
 
831
        result_parents = set()
 
832
        for parents in self._parents_map.itervalues():
 
833
            result_parents.update(parents)
 
834
        stop_keys = result_parents.difference(start_set)
 
835
        included_keys = start_set.intersection(result_parents)
 
836
        start_set.difference_update(included_keys)
 
837
        recipe = (start_set, stop_keys, len(self._parents_map))
 
838
        body = self._serialise_search_recipe(recipe)
 
839
        path = self.bzrdir._path_for_remote_call(self._client)
 
840
        for key in keys:
 
841
            assert type(key) is str
 
842
        verb = 'Repository.get_parent_map'
 
843
        args = (path,) + tuple(keys)
 
844
        response = self._client.call_with_body_bytes_expecting_body(
 
845
            verb, args, self._serialise_search_recipe(recipe))
 
846
        if self._response_is_unknown_method(response, verb):
 
847
            # Server that does not support this method, get the whole graph.
 
848
            response = self._client.call_expecting_body(
 
849
                'Repository.get_revision_graph', path, '')
 
850
            if response[0][0] not in ['ok', 'nosuchrevision']:
 
851
                reponse[1].cancel_read_body()
 
852
                raise errors.UnexpectedSmartServerResponse(response[0])
 
853
        elif response[0][0] not in ['ok']:
 
854
            reponse[1].cancel_read_body()
 
855
            raise errors.UnexpectedSmartServerResponse(response[0])
 
856
        if response[0][0] == 'ok':
 
857
            coded = GzipFile(mode='rb',
 
858
                fileobj=StringIO(response[1].read_body_bytes())).read()
 
859
            if coded == '':
 
860
                # no revisions found
 
861
                return {}
 
862
            lines = coded.split('\n')
 
863
            revision_graph = {}
 
864
            for line in lines:
 
865
                d = tuple(line.split())
 
866
                if len(d) > 1:
 
867
                    revision_graph[d[0]] = d[1:]
 
868
                else:
 
869
                    # No parents - so give the Graph result (NULL_REVISION,).
 
870
                    revision_graph[d[0]] = (NULL_REVISION,)
 
871
            return revision_graph
 
872
 
 
873
    @needs_read_lock
 
874
    def get_signature_text(self, revision_id):
 
875
        self._ensure_real()
 
876
        return self._real_repository.get_signature_text(revision_id)
 
877
 
 
878
    @needs_read_lock
 
879
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
880
        self._ensure_real()
 
881
        return self._real_repository.get_revision_graph_with_ghosts(
 
882
            revision_ids=revision_ids)
 
883
 
 
884
    @needs_read_lock
 
885
    def get_inventory_xml(self, revision_id):
 
886
        self._ensure_real()
 
887
        return self._real_repository.get_inventory_xml(revision_id)
 
888
 
 
889
    def deserialise_inventory(self, revision_id, xml):
 
890
        self._ensure_real()
 
891
        return self._real_repository.deserialise_inventory(revision_id, xml)
 
892
 
 
893
    def reconcile(self, other=None, thorough=False):
 
894
        self._ensure_real()
 
895
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
896
        
 
897
    def all_revision_ids(self):
 
898
        self._ensure_real()
 
899
        return self._real_repository.all_revision_ids()
 
900
    
 
901
    @needs_read_lock
 
902
    def get_deltas_for_revisions(self, revisions):
 
903
        self._ensure_real()
 
904
        return self._real_repository.get_deltas_for_revisions(revisions)
 
905
 
 
906
    @needs_read_lock
 
907
    def get_revision_delta(self, revision_id):
 
908
        self._ensure_real()
 
909
        return self._real_repository.get_revision_delta(revision_id)
 
910
 
 
911
    @needs_read_lock
 
912
    def revision_trees(self, revision_ids):
 
913
        self._ensure_real()
 
914
        return self._real_repository.revision_trees(revision_ids)
 
915
 
 
916
    @needs_read_lock
 
917
    def get_revision_reconcile(self, revision_id):
 
918
        self._ensure_real()
 
919
        return self._real_repository.get_revision_reconcile(revision_id)
 
920
 
 
921
    @needs_read_lock
 
922
    def check(self, revision_ids=None):
 
923
        self._ensure_real()
 
924
        return self._real_repository.check(revision_ids=revision_ids)
 
925
 
 
926
    def copy_content_into(self, destination, revision_id=None):
 
927
        self._ensure_real()
 
928
        return self._real_repository.copy_content_into(
 
929
            destination, revision_id=revision_id)
 
930
 
 
931
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
 
932
        # get a tarball of the remote repository, and copy from that into the
 
933
        # destination
 
934
        from bzrlib import osutils
 
935
        import tarfile
 
936
        import tempfile
 
937
        # TODO: Maybe a progress bar while streaming the tarball?
 
938
        note("Copying repository content as tarball...")
 
939
        tar_file = self._get_tarball('bz2')
 
940
        if tar_file is None:
 
941
            return None
 
942
        destination = to_bzrdir.create_repository()
 
943
        try:
 
944
            tar = tarfile.open('repository', fileobj=tar_file,
 
945
                mode='r|bz2')
 
946
            tmpdir = tempfile.mkdtemp()
 
947
            try:
 
948
                _extract_tar(tar, tmpdir)
 
949
                tmp_bzrdir = BzrDir.open(tmpdir)
 
950
                tmp_repo = tmp_bzrdir.open_repository()
 
951
                tmp_repo.copy_content_into(destination, revision_id)
 
952
            finally:
 
953
                osutils.rmtree(tmpdir)
 
954
        finally:
 
955
            tar_file.close()
 
956
        return destination
 
957
        # TODO: Suggestion from john: using external tar is much faster than
 
958
        # python's tarfile library, but it may not work on windows.
 
959
 
 
960
    @needs_write_lock
 
961
    def pack(self):
 
962
        """Compress the data within the repository.
 
963
 
 
964
        This is not currently implemented within the smart server.
 
965
        """
 
966
        self._ensure_real()
 
967
        return self._real_repository.pack()
 
968
 
 
969
    def set_make_working_trees(self, new_value):
 
970
        raise NotImplementedError(self.set_make_working_trees)
 
971
 
 
972
    @needs_write_lock
 
973
    def sign_revision(self, revision_id, gpg_strategy):
 
974
        self._ensure_real()
 
975
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
 
976
 
 
977
    @needs_read_lock
 
978
    def get_revisions(self, revision_ids):
 
979
        self._ensure_real()
 
980
        return self._real_repository.get_revisions(revision_ids)
 
981
 
 
982
    def supports_rich_root(self):
 
983
        self._ensure_real()
 
984
        return self._real_repository.supports_rich_root()
 
985
 
 
986
    def iter_reverse_revision_history(self, revision_id):
 
987
        self._ensure_real()
 
988
        return self._real_repository.iter_reverse_revision_history(revision_id)
 
989
 
 
990
    @property
 
991
    def _serializer(self):
 
992
        self._ensure_real()
 
993
        return self._real_repository._serializer
 
994
 
 
995
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
996
        self._ensure_real()
 
997
        return self._real_repository.store_revision_signature(
 
998
            gpg_strategy, plaintext, revision_id)
 
999
 
 
1000
    def add_signature_text(self, revision_id, signature):
 
1001
        self._ensure_real()
 
1002
        return self._real_repository.add_signature_text(revision_id, signature)
 
1003
 
 
1004
    def has_signature_for_revision_id(self, revision_id):
 
1005
        self._ensure_real()
 
1006
        return self._real_repository.has_signature_for_revision_id(revision_id)
 
1007
 
 
1008
    def get_data_stream_for_search(self, search):
 
1009
        REQUEST_NAME = 'Repository.stream_revisions_chunked'
 
1010
        path = self.bzrdir._path_for_remote_call(self._client)
 
1011
        body = self._serialise_search_recipe(search.get_recipe())
 
1012
        response, protocol = self._client.call_with_body_bytes_expecting_body(
 
1013
            REQUEST_NAME, (path,), body)
 
1014
 
 
1015
        if response == ('ok',):
 
1016
            return self._deserialise_stream(protocol)
 
1017
        if response == ('NoSuchRevision', ):
 
1018
            # We cannot easily identify the revision that is missing in this
 
1019
            # situation without doing much more network IO. For now, bail.
 
1020
            raise NoSuchRevision(self, "unknown")
 
1021
        elif (response == ('error', "Generic bzr smart protocol error: "
 
1022
                "bad request '%s'" % REQUEST_NAME) or
 
1023
              response == ('error', "Generic bzr smart protocol error: "
 
1024
                "bad request u'%s'" % REQUEST_NAME)):
 
1025
            protocol.cancel_read_body()
 
1026
            self._ensure_real()
 
1027
            return self._real_repository.get_data_stream_for_search(search)
 
1028
        else:
 
1029
            raise errors.UnexpectedSmartServerResponse(response)
 
1030
 
 
1031
    def _deserialise_stream(self, protocol):
 
1032
        stream = protocol.read_streamed_body()
 
1033
        container_parser = ContainerPushParser()
 
1034
        for bytes in stream:
 
1035
            container_parser.accept_bytes(bytes)
 
1036
            records = container_parser.read_pending_records()
 
1037
            for record_names, record_bytes in records:
 
1038
                if len(record_names) != 1:
 
1039
                    # These records should have only one name, and that name
 
1040
                    # should be a one-element tuple.
 
1041
                    raise errors.SmartProtocolError(
 
1042
                        'Repository data stream had invalid record name %r'
 
1043
                        % (record_names,))
 
1044
                name_tuple = record_names[0]
 
1045
                yield name_tuple, record_bytes
 
1046
 
 
1047
    def insert_data_stream(self, stream):
 
1048
        self._ensure_real()
 
1049
        self._real_repository.insert_data_stream(stream)
 
1050
 
 
1051
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
 
1052
        self._ensure_real()
 
1053
        return self._real_repository.item_keys_introduced_by(revision_ids,
 
1054
            _files_pb=_files_pb)
 
1055
 
 
1056
    def revision_graph_can_have_wrong_parents(self):
 
1057
        # The answer depends on the remote repo format.
 
1058
        self._ensure_real()
 
1059
        return self._real_repository.revision_graph_can_have_wrong_parents()
 
1060
 
 
1061
    def _find_inconsistent_revision_parents(self):
 
1062
        self._ensure_real()
 
1063
        return self._real_repository._find_inconsistent_revision_parents()
 
1064
 
 
1065
    def _check_for_inconsistent_revision_parents(self):
 
1066
        self._ensure_real()
 
1067
        return self._real_repository._check_for_inconsistent_revision_parents()
 
1068
 
 
1069
    def _make_parents_provider(self):
 
1070
        return self
 
1071
 
 
1072
    def _serialise_search_recipe(self, recipe):
 
1073
        """Serialise a graph search recipe.
 
1074
 
 
1075
        :param recipe: A search recipe (start, stop, count).
 
1076
        :return: Serialised bytes.
 
1077
        """
 
1078
        start_keys = ' '.join(recipe[0])
 
1079
        stop_keys = ' '.join(recipe[1])
 
1080
        count = str(recipe[2])
 
1081
        return '\n'.join((start_keys, stop_keys, count))
 
1082
 
 
1083
 
 
1084
class RemoteBranchLockableFiles(LockableFiles):
 
1085
    """A 'LockableFiles' implementation that talks to a smart server.
 
1086
    
 
1087
    This is not a public interface class.
 
1088
    """
 
1089
 
 
1090
    def __init__(self, bzrdir, _client):
 
1091
        self.bzrdir = bzrdir
 
1092
        self._client = _client
 
1093
        self._need_find_modes = True
 
1094
        LockableFiles.__init__(
 
1095
            self, bzrdir.get_branch_transport(None),
 
1096
            'lock', lockdir.LockDir)
 
1097
 
 
1098
    def _find_modes(self):
 
1099
        # RemoteBranches don't let the client set the mode of control files.
 
1100
        self._dir_mode = None
 
1101
        self._file_mode = None
 
1102
 
 
1103
    def get(self, path):
 
1104
        """'get' a remote path as per the LockableFiles interface.
 
1105
 
 
1106
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
1107
             just retrieve a file, instead we ask the smart server to generate
 
1108
             a configuration for us - which is retrieved as an INI file.
 
1109
        """
 
1110
        if path == 'branch.conf':
 
1111
            path = self.bzrdir._path_for_remote_call(self._client)
 
1112
            response = self._client.call_expecting_body(
 
1113
                'Branch.get_config_file', path)
 
1114
            assert response[0][0] == 'ok', \
 
1115
                'unexpected response code %s' % (response[0],)
 
1116
            return StringIO(response[1].read_body_bytes())
 
1117
        else:
 
1118
            # VFS fallback.
 
1119
            return LockableFiles.get(self, path)
 
1120
 
 
1121
 
 
1122
class RemoteBranchFormat(branch.BranchFormat):
 
1123
 
 
1124
    def __eq__(self, other):
 
1125
        return (isinstance(other, RemoteBranchFormat) and 
 
1126
            self.__dict__ == other.__dict__)
 
1127
 
 
1128
    def get_format_description(self):
 
1129
        return 'Remote BZR Branch'
 
1130
 
 
1131
    def get_format_string(self):
 
1132
        return 'Remote BZR Branch'
 
1133
 
 
1134
    def open(self, a_bzrdir):
 
1135
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
1136
        return a_bzrdir.open_branch()
 
1137
 
 
1138
    def initialize(self, a_bzrdir):
 
1139
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
1140
        return a_bzrdir.create_branch()
 
1141
 
 
1142
    def supports_tags(self):
 
1143
        # Remote branches might support tags, but we won't know until we
 
1144
        # access the real remote branch.
 
1145
        return True
 
1146
 
 
1147
 
 
1148
class RemoteBranch(branch.Branch):
 
1149
    """Branch stored on a server accessed by HPSS RPC.
 
1150
 
 
1151
    At the moment most operations are mapped down to simple file operations.
 
1152
    """
 
1153
 
 
1154
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
1155
        _client=None):
 
1156
        """Create a RemoteBranch instance.
 
1157
 
 
1158
        :param real_branch: An optional local implementation of the branch
 
1159
            format, usually accessing the data via the VFS.
 
1160
        :param _client: Private parameter for testing.
 
1161
        """
 
1162
        # We intentionally don't call the parent class's __init__, because it
 
1163
        # will try to assign to self.tags, which is a property in this subclass.
 
1164
        # And the parent's __init__ doesn't do much anyway.
 
1165
        self._revision_id_to_revno_cache = None
 
1166
        self._revision_history_cache = None
 
1167
        self.bzrdir = remote_bzrdir
 
1168
        if _client is not None:
 
1169
            self._client = _client
 
1170
        else:
 
1171
            self._client = client._SmartClient(self.bzrdir._shared_medium)
 
1172
        self.repository = remote_repository
 
1173
        if real_branch is not None:
 
1174
            self._real_branch = real_branch
 
1175
            # Give the remote repository the matching real repo.
 
1176
            real_repo = self._real_branch.repository
 
1177
            if isinstance(real_repo, RemoteRepository):
 
1178
                real_repo._ensure_real()
 
1179
                real_repo = real_repo._real_repository
 
1180
            self.repository._set_real_repository(real_repo)
 
1181
            # Give the branch the remote repository to let fast-pathing happen.
 
1182
            self._real_branch.repository = self.repository
 
1183
        else:
 
1184
            self._real_branch = None
 
1185
        # Fill out expected attributes of branch for bzrlib api users.
 
1186
        self._format = RemoteBranchFormat()
 
1187
        self.base = self.bzrdir.root_transport.base
 
1188
        self._control_files = None
 
1189
        self._lock_mode = None
 
1190
        self._lock_token = None
 
1191
        self._lock_count = 0
 
1192
        self._leave_lock = False
 
1193
 
 
1194
    def __str__(self):
 
1195
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
1196
 
 
1197
    __repr__ = __str__
 
1198
 
 
1199
    def _ensure_real(self):
 
1200
        """Ensure that there is a _real_branch set.
 
1201
 
 
1202
        Used before calls to self._real_branch.
 
1203
        """
 
1204
        if not self._real_branch:
 
1205
            assert vfs.vfs_enabled()
 
1206
            self.bzrdir._ensure_real()
 
1207
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
1208
            # Give the remote repository the matching real repo.
 
1209
            real_repo = self._real_branch.repository
 
1210
            if isinstance(real_repo, RemoteRepository):
 
1211
                real_repo._ensure_real()
 
1212
                real_repo = real_repo._real_repository
 
1213
            self.repository._set_real_repository(real_repo)
 
1214
            # Give the branch the remote repository to let fast-pathing happen.
 
1215
            self._real_branch.repository = self.repository
 
1216
            # XXX: deal with _lock_mode == 'w'
 
1217
            if self._lock_mode == 'r':
 
1218
                self._real_branch.lock_read()
 
1219
 
 
1220
    @property
 
1221
    def control_files(self):
 
1222
        # Defer actually creating RemoteBranchLockableFiles until its needed,
 
1223
        # because it triggers an _ensure_real that we otherwise might not need.
 
1224
        if self._control_files is None:
 
1225
            self._control_files = RemoteBranchLockableFiles(
 
1226
                self.bzrdir, self._client)
 
1227
        return self._control_files
 
1228
 
 
1229
    def _get_checkout_format(self):
 
1230
        self._ensure_real()
 
1231
        return self._real_branch._get_checkout_format()
 
1232
 
 
1233
    def get_physical_lock_status(self):
 
1234
        """See Branch.get_physical_lock_status()."""
 
1235
        # should be an API call to the server, as branches must be lockable.
 
1236
        self._ensure_real()
 
1237
        return self._real_branch.get_physical_lock_status()
 
1238
 
 
1239
    def lock_read(self):
 
1240
        if not self._lock_mode:
 
1241
            self._lock_mode = 'r'
 
1242
            self._lock_count = 1
 
1243
            if self._real_branch is not None:
 
1244
                self._real_branch.lock_read()
 
1245
        else:
 
1246
            self._lock_count += 1
 
1247
 
 
1248
    def _remote_lock_write(self, token):
 
1249
        if token is None:
 
1250
            branch_token = repo_token = ''
 
1251
        else:
 
1252
            branch_token = token
 
1253
            repo_token = self.repository.lock_write()
 
1254
            self.repository.unlock()
 
1255
        path = self.bzrdir._path_for_remote_call(self._client)
 
1256
        response = self._client.call('Branch.lock_write', path, branch_token,
 
1257
                                     repo_token or '')
 
1258
        if response[0] == 'ok':
 
1259
            ok, branch_token, repo_token = response
 
1260
            return branch_token, repo_token
 
1261
        elif response[0] == 'LockContention':
 
1262
            raise errors.LockContention('(remote lock)')
 
1263
        elif response[0] == 'TokenMismatch':
 
1264
            raise errors.TokenMismatch(token, '(remote token)')
 
1265
        elif response[0] == 'UnlockableTransport':
 
1266
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
1267
        elif response[0] == 'ReadOnlyError':
 
1268
            raise errors.ReadOnlyError(self)
 
1269
        elif response[0] == 'LockFailed':
 
1270
            raise errors.LockFailed(response[1], response[2])
 
1271
        else:
 
1272
            raise errors.UnexpectedSmartServerResponse(response)
 
1273
            
 
1274
    def lock_write(self, token=None):
 
1275
        if not self._lock_mode:
 
1276
            remote_tokens = self._remote_lock_write(token)
 
1277
            self._lock_token, self._repo_lock_token = remote_tokens
 
1278
            assert self._lock_token, 'Remote server did not return a token!'
 
1279
            # TODO: We really, really, really don't want to call _ensure_real
 
1280
            # here, but it's the easiest way to ensure coherency between the
 
1281
            # state of the RemoteBranch and RemoteRepository objects and the
 
1282
            # physical locks.  If we don't materialise the real objects here,
 
1283
            # then getting everything in the right state later is complex, so
 
1284
            # for now we just do it the lazy way.
 
1285
            #   -- Andrew Bennetts, 2007-02-22.
 
1286
            self._ensure_real()
 
1287
            if self._real_branch is not None:
 
1288
                self._real_branch.repository.lock_write(
 
1289
                    token=self._repo_lock_token)
 
1290
                try:
 
1291
                    self._real_branch.lock_write(token=self._lock_token)
 
1292
                finally:
 
1293
                    self._real_branch.repository.unlock()
 
1294
            if token is not None:
 
1295
                self._leave_lock = True
 
1296
            else:
 
1297
                # XXX: this case seems to be unreachable; token cannot be None.
 
1298
                self._leave_lock = False
 
1299
            self._lock_mode = 'w'
 
1300
            self._lock_count = 1
 
1301
        elif self._lock_mode == 'r':
 
1302
            raise errors.ReadOnlyTransaction
 
1303
        else:
 
1304
            if token is not None:
 
1305
                # A token was given to lock_write, and we're relocking, so check
 
1306
                # that the given token actually matches the one we already have.
 
1307
                if token != self._lock_token:
 
1308
                    raise errors.TokenMismatch(token, self._lock_token)
 
1309
            self._lock_count += 1
 
1310
        return self._lock_token or None
 
1311
 
 
1312
    def _unlock(self, branch_token, repo_token):
 
1313
        path = self.bzrdir._path_for_remote_call(self._client)
 
1314
        response = self._client.call('Branch.unlock', path, branch_token,
 
1315
                                     repo_token or '')
 
1316
        if response == ('ok',):
 
1317
            return
 
1318
        elif response[0] == 'TokenMismatch':
 
1319
            raise errors.TokenMismatch(
 
1320
                str((branch_token, repo_token)), '(remote tokens)')
 
1321
        else:
 
1322
            raise errors.UnexpectedSmartServerResponse(response)
 
1323
 
 
1324
    def unlock(self):
 
1325
        self._lock_count -= 1
 
1326
        if not self._lock_count:
 
1327
            self._clear_cached_state()
 
1328
            mode = self._lock_mode
 
1329
            self._lock_mode = None
 
1330
            if self._real_branch is not None:
 
1331
                if (not self._leave_lock and mode == 'w' and
 
1332
                    self._repo_lock_token):
 
1333
                    # If this RemoteBranch will remove the physical lock for the
 
1334
                    # repository, make sure the _real_branch doesn't do it
 
1335
                    # first.  (Because the _real_branch's repository is set to
 
1336
                    # be the RemoteRepository.)
 
1337
                    self._real_branch.repository.leave_lock_in_place()
 
1338
                self._real_branch.unlock()
 
1339
            if mode != 'w':
 
1340
                # Only write-locked branched need to make a remote method call
 
1341
                # to perfom the unlock.
 
1342
                return
 
1343
            assert self._lock_token, 'Locked, but no token!'
 
1344
            branch_token = self._lock_token
 
1345
            repo_token = self._repo_lock_token
 
1346
            self._lock_token = None
 
1347
            self._repo_lock_token = None
 
1348
            if not self._leave_lock:
 
1349
                self._unlock(branch_token, repo_token)
 
1350
 
 
1351
    def break_lock(self):
 
1352
        self._ensure_real()
 
1353
        return self._real_branch.break_lock()
 
1354
 
 
1355
    def leave_lock_in_place(self):
 
1356
        if not self._lock_token:
 
1357
            raise NotImplementedError(self.leave_lock_in_place)
 
1358
        self._leave_lock = True
 
1359
 
 
1360
    def dont_leave_lock_in_place(self):
 
1361
        if not self._lock_token:
 
1362
            raise NotImplementedError(self.dont_leave_lock_in_place)
 
1363
        self._leave_lock = False
 
1364
 
 
1365
    def last_revision_info(self):
 
1366
        """See Branch.last_revision_info()."""
 
1367
        path = self.bzrdir._path_for_remote_call(self._client)
 
1368
        response = self._client.call('Branch.last_revision_info', path)
 
1369
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
1370
        revno = int(response[1])
 
1371
        last_revision = response[2]
 
1372
        return (revno, last_revision)
 
1373
 
 
1374
    def _gen_revision_history(self):
 
1375
        """See Branch._gen_revision_history()."""
 
1376
        path = self.bzrdir._path_for_remote_call(self._client)
 
1377
        response = self._client.call_expecting_body(
 
1378
            'Branch.revision_history', path)
 
1379
        assert response[0][0] == 'ok', ('unexpected response code %s'
 
1380
                                        % (response[0],))
 
1381
        result = response[1].read_body_bytes().split('\x00')
 
1382
        if result == ['']:
 
1383
            return []
 
1384
        return result
 
1385
 
 
1386
    @needs_write_lock
 
1387
    def set_revision_history(self, rev_history):
 
1388
        # Send just the tip revision of the history; the server will generate
 
1389
        # the full history from that.  If the revision doesn't exist in this
 
1390
        # branch, NoSuchRevision will be raised.
 
1391
        path = self.bzrdir._path_for_remote_call(self._client)
 
1392
        if rev_history == []:
 
1393
            rev_id = 'null:'
 
1394
        else:
 
1395
            rev_id = rev_history[-1]
 
1396
        self._clear_cached_state()
 
1397
        response = self._client.call('Branch.set_last_revision',
 
1398
            path, self._lock_token, self._repo_lock_token, rev_id)
 
1399
        if response[0] == 'NoSuchRevision':
 
1400
            raise NoSuchRevision(self, rev_id)
 
1401
        else:
 
1402
            assert response == ('ok',), (
 
1403
                'unexpected response code %r' % (response,))
 
1404
        self._cache_revision_history(rev_history)
 
1405
 
 
1406
    def get_parent(self):
 
1407
        self._ensure_real()
 
1408
        return self._real_branch.get_parent()
 
1409
        
 
1410
    def set_parent(self, url):
 
1411
        self._ensure_real()
 
1412
        return self._real_branch.set_parent(url)
 
1413
        
 
1414
    def get_config(self):
 
1415
        return RemoteBranchConfig(self)
 
1416
 
 
1417
    def sprout(self, to_bzrdir, revision_id=None):
 
1418
        # Like Branch.sprout, except that it sprouts a branch in the default
 
1419
        # format, because RemoteBranches can't be created at arbitrary URLs.
 
1420
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
 
1421
        # to_bzrdir.create_branch...
 
1422
        self._ensure_real()
 
1423
        result = self._real_branch._format.initialize(to_bzrdir)
 
1424
        self.copy_content_into(result, revision_id=revision_id)
 
1425
        result.set_parent(self.bzrdir.root_transport.base)
 
1426
        return result
 
1427
 
 
1428
    @needs_write_lock
 
1429
    def pull(self, source, overwrite=False, stop_revision=None,
 
1430
             **kwargs):
 
1431
        # FIXME: This asks the real branch to run the hooks, which means
 
1432
        # they're called with the wrong target branch parameter. 
 
1433
        # The test suite specifically allows this at present but it should be
 
1434
        # fixed.  It should get a _override_hook_target branch,
 
1435
        # as push does.  -- mbp 20070405
 
1436
        self._ensure_real()
 
1437
        self._real_branch.pull(
 
1438
            source, overwrite=overwrite, stop_revision=stop_revision,
 
1439
            **kwargs)
 
1440
 
 
1441
    @needs_read_lock
 
1442
    def push(self, target, overwrite=False, stop_revision=None):
 
1443
        self._ensure_real()
 
1444
        return self._real_branch.push(
 
1445
            target, overwrite=overwrite, stop_revision=stop_revision,
 
1446
            _override_hook_source_branch=self)
 
1447
 
 
1448
    def is_locked(self):
 
1449
        return self._lock_count >= 1
 
1450
 
 
1451
    def set_last_revision_info(self, revno, revision_id):
 
1452
        self._ensure_real()
 
1453
        self._clear_cached_state()
 
1454
        return self._real_branch.set_last_revision_info(revno, revision_id)
 
1455
 
 
1456
    def generate_revision_history(self, revision_id, last_rev=None,
 
1457
                                  other_branch=None):
 
1458
        self._ensure_real()
 
1459
        return self._real_branch.generate_revision_history(
 
1460
            revision_id, last_rev=last_rev, other_branch=other_branch)
 
1461
 
 
1462
    @property
 
1463
    def tags(self):
 
1464
        self._ensure_real()
 
1465
        return self._real_branch.tags
 
1466
 
 
1467
    def set_push_location(self, location):
 
1468
        self._ensure_real()
 
1469
        return self._real_branch.set_push_location(location)
 
1470
 
 
1471
    def update_revisions(self, other, stop_revision=None, overwrite=False):
 
1472
        self._ensure_real()
 
1473
        return self._real_branch.update_revisions(
 
1474
            other, stop_revision=stop_revision, overwrite=overwrite)
 
1475
 
 
1476
 
 
1477
class RemoteBranchConfig(BranchConfig):
 
1478
 
 
1479
    def username(self):
 
1480
        self.branch._ensure_real()
 
1481
        return self.branch._real_branch.get_config().username()
 
1482
 
 
1483
    def _get_branch_data_config(self):
 
1484
        self.branch._ensure_real()
 
1485
        if self._branch_data_config is None:
 
1486
            self._branch_data_config = TreeConfig(self.branch._real_branch)
 
1487
        return self._branch_data_config
 
1488
 
 
1489
 
 
1490
def _extract_tar(tar, to_dir):
 
1491
    """Extract all the contents of a tarfile object.
 
1492
 
 
1493
    A replacement for extractall, which is not present in python2.4
 
1494
    """
 
1495
    for tarinfo in tar:
 
1496
        tar.extract(tarinfo, to_dir)