/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

Fix some bit of fetching.

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