/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: Alexander Belchenko
  • Date: 2007-08-10 17:18:01 UTC
  • mto: (2681.1.28 send-bundle)
  • mto: This revision was merged to the branch mainline in revision 2736.
  • Revision ID: bialix@ukr.net-20070810171801-hwfokeamjt88658g
win32: looking for full path of mail client executable in registry

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
    errors,
 
25
    lockdir,
 
26
    repository,
 
27
)
 
28
from bzrlib.branch import Branch, BranchReferenceFormat
 
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
 
30
from bzrlib.config import BranchConfig, TreeConfig
 
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
32
from bzrlib.errors import NoSuchRevision
 
33
from bzrlib.lockable_files import LockableFiles
 
34
from bzrlib.revision import NULL_REVISION
 
35
from bzrlib.smart import client, vfs
 
36
from bzrlib.trace import note
 
37
 
 
38
# Note: RemoteBzrDirFormat is in bzrdir.py
 
39
 
 
40
class RemoteBzrDir(BzrDir):
 
41
    """Control directory on a remote server, accessed via bzr:// or similar."""
 
42
 
 
43
    def __init__(self, transport, _client=None):
 
44
        """Construct a RemoteBzrDir.
 
45
 
 
46
        :param _client: Private parameter for testing. Disables probing and the
 
47
            use of a real bzrdir.
 
48
        """
 
49
        BzrDir.__init__(self, transport, RemoteBzrDirFormat())
 
50
        # this object holds a delegated bzrdir that uses file-level operations
 
51
        # to talk to the other side
 
52
        self._real_bzrdir = None
 
53
 
 
54
        if _client is None:
 
55
            self._shared_medium = transport.get_shared_medium()
 
56
            self._client = client._SmartClient(self._shared_medium)
 
57
        else:
 
58
            self._client = _client
 
59
            self._shared_medium = None
 
60
            return
 
61
 
 
62
        path = self._path_for_remote_call(self._client)
 
63
        response = self._client.call('BzrDir.open', path)
 
64
        if response not in [('yes',), ('no',)]:
 
65
            raise errors.UnexpectedSmartServerResponse(response)
 
66
        if response == ('no',):
 
67
            raise errors.NotBranchError(path=transport.base)
 
68
 
 
69
    def _ensure_real(self):
 
70
        """Ensure that there is a _real_bzrdir set.
 
71
 
 
72
        Used before calls to self._real_bzrdir.
 
73
        """
 
74
        if not self._real_bzrdir:
 
75
            self._real_bzrdir = BzrDir.open_from_transport(
 
76
                self.root_transport, _server_formats=False)
 
77
 
 
78
    def create_repository(self, shared=False):
 
79
        self._ensure_real()
 
80
        self._real_bzrdir.create_repository(shared=shared)
 
81
        return self.open_repository()
 
82
 
 
83
    def create_branch(self):
 
84
        self._ensure_real()
 
85
        real_branch = self._real_bzrdir.create_branch()
 
86
        return RemoteBranch(self, self.find_repository(), real_branch)
 
87
 
 
88
    def create_workingtree(self, revision_id=None):
 
89
        raise errors.NotLocalUrl(self.transport.base)
 
90
 
 
91
    def find_branch_format(self):
 
92
        """Find the branch 'format' for this bzrdir.
 
93
 
 
94
        This might be a synthetic object for e.g. RemoteBranch and SVN.
 
95
        """
 
96
        b = self.open_branch()
 
97
        return b._format
 
98
 
 
99
    def get_branch_reference(self):
 
100
        """See BzrDir.get_branch_reference()."""
 
101
        path = self._path_for_remote_call(self._client)
 
102
        response = self._client.call('BzrDir.open_branch', path)
 
103
        if response[0] == 'ok':
 
104
            if response[1] == '':
 
105
                # branch at this location.
 
106
                return None
 
107
            else:
 
108
                # a branch reference, use the existing BranchReference logic.
 
109
                return response[1]
 
110
        elif response == ('nobranch',):
 
111
            raise errors.NotBranchError(path=self.root_transport.base)
 
112
        else:
 
113
            raise errors.UnexpectedSmartServerResponse(response)
 
114
 
 
115
    def open_branch(self, _unsupported=False):
 
116
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
 
117
        reference_url = self.get_branch_reference()
 
118
        if reference_url is None:
 
119
            # branch at this location.
 
120
            return RemoteBranch(self, self.find_repository())
 
121
        else:
 
122
            # a branch reference, use the existing BranchReference logic.
 
123
            format = BranchReferenceFormat()
 
124
            return format.open(self, _found=True, location=reference_url)
 
125
                
 
126
    def open_repository(self):
 
127
        path = self._path_for_remote_call(self._client)
 
128
        response = self._client.call('BzrDir.find_repository', path)
 
129
        assert response[0] in ('ok', 'norepository'), \
 
130
            'unexpected response code %s' % (response,)
 
131
        if response[0] == 'norepository':
 
132
            raise errors.NoRepositoryPresent(self)
 
133
        assert len(response) == 4, 'incorrect response length %s' % (response,)
 
134
        if response[1] == '':
 
135
            format = RemoteRepositoryFormat()
 
136
            format.rich_root_data = (response[2] == 'yes')
 
137
            format.supports_tree_reference = (response[3] == 'yes')
 
138
            return RemoteRepository(self, format)
 
139
        else:
 
140
            raise errors.NoRepositoryPresent(self)
 
141
 
 
142
    def open_workingtree(self, recommend_upgrade=True):
 
143
        self._ensure_real()
 
144
        if self._real_bzrdir.has_workingtree():
 
145
            raise errors.NotLocalUrl(self.root_transport)
 
146
        else:
 
147
            raise errors.NoWorkingTree(self.root_transport.base)
 
148
 
 
149
    def _path_for_remote_call(self, client):
 
150
        """Return the path to be used for this bzrdir in a remote call."""
 
151
        return client.remote_path_from_transport(self.root_transport)
 
152
 
 
153
    def get_branch_transport(self, branch_format):
 
154
        self._ensure_real()
 
155
        return self._real_bzrdir.get_branch_transport(branch_format)
 
156
 
 
157
    def get_repository_transport(self, repository_format):
 
158
        self._ensure_real()
 
159
        return self._real_bzrdir.get_repository_transport(repository_format)
 
160
 
 
161
    def get_workingtree_transport(self, workingtree_format):
 
162
        self._ensure_real()
 
163
        return self._real_bzrdir.get_workingtree_transport(workingtree_format)
 
164
 
 
165
    def can_convert_format(self):
 
166
        """Upgrading of remote bzrdirs is not supported yet."""
 
167
        return False
 
168
 
 
169
    def needs_format_conversion(self, format=None):
 
170
        """Upgrading of remote bzrdirs is not supported yet."""
 
171
        return False
 
172
 
 
173
    def clone(self, url, revision_id=None, force_new_repo=False):
 
174
        self._ensure_real()
 
175
        return self._real_bzrdir.clone(url, revision_id=revision_id,
 
176
            force_new_repo=force_new_repo)
 
177
 
 
178
 
 
179
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
180
    """Format for repositories accessed over a _SmartClient.
 
181
 
 
182
    Instances of this repository are represented by RemoteRepository
 
183
    instances.
 
184
 
 
185
    The RemoteRepositoryFormat is parameterised during construction
 
186
    to reflect the capabilities of the real, remote format. Specifically
 
187
    the attributes rich_root_data and supports_tree_reference are set
 
188
    on a per instance basis, and are not set (and should not be) at
 
189
    the class level.
 
190
    """
 
191
 
 
192
    _matchingbzrdir = RemoteBzrDirFormat
 
193
 
 
194
    def initialize(self, a_bzrdir, shared=False):
 
195
        assert isinstance(a_bzrdir, RemoteBzrDir), \
 
196
            '%r is not a RemoteBzrDir' % (a_bzrdir,)
 
197
        return a_bzrdir.create_repository(shared=shared)
 
198
    
 
199
    def open(self, a_bzrdir):
 
200
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
201
        return a_bzrdir.open_repository()
 
202
 
 
203
    def get_format_description(self):
 
204
        return 'bzr remote repository'
 
205
 
 
206
    def __eq__(self, other):
 
207
        return self.__class__ == other.__class__
 
208
 
 
209
    def check_conversion_target(self, target_format):
 
210
        if self.rich_root_data and not target_format.rich_root_data:
 
211
            raise errors.BadConversionTarget(
 
212
                'Does not support rich root data.', target_format)
 
213
        if (self.supports_tree_reference and
 
214
            not getattr(target_format, 'supports_tree_reference', False)):
 
215
            raise errors.BadConversionTarget(
 
216
                'Does not support nested trees', target_format)
 
217
 
 
218
 
 
219
class RemoteRepository(object):
 
220
    """Repository accessed over rpc.
 
221
 
 
222
    For the moment most operations are performed using local transport-backed
 
223
    Repository objects.
 
224
    """
 
225
 
 
226
    def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
 
227
        """Create a RemoteRepository instance.
 
228
        
 
229
        :param remote_bzrdir: The bzrdir hosting this repository.
 
230
        :param format: The RemoteFormat object to use.
 
231
        :param real_repository: If not None, a local implementation of the
 
232
            repository logic for the repository, usually accessing the data
 
233
            via the VFS.
 
234
        :param _client: Private testing parameter - override the smart client
 
235
            to be used by the repository.
 
236
        """
 
237
        if real_repository:
 
238
            self._real_repository = real_repository
 
239
        else:
 
240
            self._real_repository = None
 
241
        self.bzrdir = remote_bzrdir
 
242
        if _client is None:
 
243
            self._client = client._SmartClient(self.bzrdir._shared_medium)
 
244
        else:
 
245
            self._client = _client
 
246
        self._format = format
 
247
        self._lock_mode = None
 
248
        self._lock_token = None
 
249
        self._lock_count = 0
 
250
        self._leave_lock = False
 
251
 
 
252
    def has_same_location(self, other):
 
253
        return (self.__class__ == other.__class__ and
 
254
                self.bzrdir.transport.base == other.bzrdir.transport.base)
 
255
        
 
256
    def _ensure_real(self):
 
257
        """Ensure that there is a _real_repository set.
 
258
 
 
259
        Used before calls to self._real_repository.
 
260
        """
 
261
        if not self._real_repository:
 
262
            self.bzrdir._ensure_real()
 
263
            #self._real_repository = self.bzrdir._real_bzrdir.open_repository()
 
264
            self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
 
265
 
 
266
    def get_revision_graph(self, revision_id=None):
 
267
        """See Repository.get_revision_graph()."""
 
268
        if revision_id is None:
 
269
            revision_id = ''
 
270
        elif revision_id == NULL_REVISION:
 
271
            return {}
 
272
 
 
273
        path = self.bzrdir._path_for_remote_call(self._client)
 
274
        assert type(revision_id) is str
 
275
        response = self._client.call_expecting_body(
 
276
            'Repository.get_revision_graph', path, revision_id)
 
277
        if response[0][0] not in ['ok', 'nosuchrevision']:
 
278
            raise errors.UnexpectedSmartServerResponse(response[0])
 
279
        if response[0][0] == 'ok':
 
280
            coded = response[1].read_body_bytes()
 
281
            if coded == '':
 
282
                # no revisions in this repository!
 
283
                return {}
 
284
            lines = coded.split('\n')
 
285
            revision_graph = {}
 
286
            for line in lines:
 
287
                d = tuple(line.split())
 
288
                revision_graph[d[0]] = d[1:]
 
289
                
 
290
            return revision_graph
 
291
        else:
 
292
            response_body = response[1].read_body_bytes()
 
293
            assert response_body == ''
 
294
            raise NoSuchRevision(self, revision_id)
 
295
 
 
296
    def has_revision(self, revision_id):
 
297
        """See Repository.has_revision()."""
 
298
        if revision_id is None:
 
299
            # The null revision is always present.
 
300
            return True
 
301
        path = self.bzrdir._path_for_remote_call(self._client)
 
302
        response = self._client.call('Repository.has_revision', path, revision_id)
 
303
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
304
        return response[0] == 'yes'
 
305
 
 
306
    def get_graph(self, other_repository=None):
 
307
        """Return the graph for this repository format"""
 
308
        return self._real_repository.get_graph(other_repository)
 
309
 
 
310
    def gather_stats(self, revid=None, committers=None):
 
311
        """See Repository.gather_stats()."""
 
312
        path = self.bzrdir._path_for_remote_call(self._client)
 
313
        if revid in (None, NULL_REVISION):
 
314
            fmt_revid = ''
 
315
        else:
 
316
            fmt_revid = revid
 
317
        if committers is None or not committers:
 
318
            fmt_committers = 'no'
 
319
        else:
 
320
            fmt_committers = 'yes'
 
321
        response = self._client.call_expecting_body(
 
322
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
 
323
        assert response[0][0] == 'ok', \
 
324
            'unexpected response code %s' % (response[0],)
 
325
 
 
326
        body = response[1].read_body_bytes()
 
327
        result = {}
 
328
        for line in body.split('\n'):
 
329
            if not line:
 
330
                continue
 
331
            key, val_text = line.split(':')
 
332
            if key in ('revisions', 'size', 'committers'):
 
333
                result[key] = int(val_text)
 
334
            elif key in ('firstrev', 'latestrev'):
 
335
                values = val_text.split(' ')[1:]
 
336
                result[key] = (float(values[0]), long(values[1]))
 
337
 
 
338
        return result
 
339
 
 
340
    def get_physical_lock_status(self):
 
341
        """See Repository.get_physical_lock_status()."""
 
342
        return False
 
343
 
 
344
    def is_shared(self):
 
345
        """See Repository.is_shared()."""
 
346
        path = self.bzrdir._path_for_remote_call(self._client)
 
347
        response = self._client.call('Repository.is_shared', path)
 
348
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
349
        return response[0] == 'yes'
 
350
 
 
351
    def lock_read(self):
 
352
        # wrong eventually - want a local lock cache context
 
353
        if not self._lock_mode:
 
354
            self._lock_mode = 'r'
 
355
            self._lock_count = 1
 
356
            if self._real_repository is not None:
 
357
                self._real_repository.lock_read()
 
358
        else:
 
359
            self._lock_count += 1
 
360
 
 
361
    def _remote_lock_write(self, token):
 
362
        path = self.bzrdir._path_for_remote_call(self._client)
 
363
        if token is None:
 
364
            token = ''
 
365
        response = self._client.call('Repository.lock_write', path, token)
 
366
        if response[0] == 'ok':
 
367
            ok, token = response
 
368
            return token
 
369
        elif response[0] == 'LockContention':
 
370
            raise errors.LockContention('(remote lock)')
 
371
        elif response[0] == 'UnlockableTransport':
 
372
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
373
        else:
 
374
            raise errors.UnexpectedSmartServerResponse(response)
 
375
 
 
376
    def lock_write(self, token=None):
 
377
        if not self._lock_mode:
 
378
            self._lock_token = self._remote_lock_write(token)
 
379
            assert self._lock_token, 'Remote server did not return a token!'
 
380
            if self._real_repository is not None:
 
381
                self._real_repository.lock_write(token=self._lock_token)
 
382
            if token is not None:
 
383
                self._leave_lock = True
 
384
            else:
 
385
                self._leave_lock = False
 
386
            self._lock_mode = 'w'
 
387
            self._lock_count = 1
 
388
        elif self._lock_mode == 'r':
 
389
            raise errors.ReadOnlyError(self)
 
390
        else:
 
391
            self._lock_count += 1
 
392
        return self._lock_token
 
393
 
 
394
    def leave_lock_in_place(self):
 
395
        self._leave_lock = True
 
396
 
 
397
    def dont_leave_lock_in_place(self):
 
398
        self._leave_lock = False
 
399
 
 
400
    def _set_real_repository(self, repository):
 
401
        """Set the _real_repository for this repository.
 
402
 
 
403
        :param repository: The repository to fallback to for non-hpss
 
404
            implemented operations.
 
405
        """
 
406
        assert not isinstance(repository, RemoteRepository)
 
407
        self._real_repository = repository
 
408
        if self._lock_mode == 'w':
 
409
            # if we are already locked, the real repository must be able to
 
410
            # acquire the lock with our token.
 
411
            self._real_repository.lock_write(self._lock_token)
 
412
        elif self._lock_mode == 'r':
 
413
            self._real_repository.lock_read()
 
414
 
 
415
    def _unlock(self, token):
 
416
        path = self.bzrdir._path_for_remote_call(self._client)
 
417
        response = self._client.call('Repository.unlock', path, token)
 
418
        if response == ('ok',):
 
419
            return
 
420
        elif response[0] == 'TokenMismatch':
 
421
            raise errors.TokenMismatch(token, '(remote token)')
 
422
        else:
 
423
            raise errors.UnexpectedSmartServerResponse(response)
 
424
 
 
425
    def unlock(self):
 
426
        self._lock_count -= 1
 
427
        if not self._lock_count:
 
428
            mode = self._lock_mode
 
429
            self._lock_mode = None
 
430
            if self._real_repository is not None:
 
431
                self._real_repository.unlock()
 
432
            if mode != 'w':
 
433
                # Only write-locked repositories need to make a remote method
 
434
                # call to perfom the unlock.
 
435
                return
 
436
            assert self._lock_token, 'Locked, but no token!'
 
437
            token = self._lock_token
 
438
            self._lock_token = None
 
439
            if not self._leave_lock:
 
440
                self._unlock(token)
 
441
 
 
442
    def break_lock(self):
 
443
        # should hand off to the network
 
444
        self._ensure_real()
 
445
        return self._real_repository.break_lock()
 
446
 
 
447
    def _get_tarball(self, compression):
 
448
        """Return a TemporaryFile containing a repository tarball"""
 
449
        import tempfile
 
450
        path = self.bzrdir._path_for_remote_call(self._client)
 
451
        response, protocol = self._client.call_expecting_body(
 
452
            'Repository.tarball', path, compression)
 
453
        assert response[0] in ('ok', 'failure'), \
 
454
            'unexpected response code %s' % (response,)
 
455
        if response[0] == 'ok':
 
456
            # Extract the tarball and return it
 
457
            t = tempfile.NamedTemporaryFile()
 
458
            # TODO: rpc layer should read directly into it...
 
459
            t.write(protocol.read_body_bytes())
 
460
            t.seek(0)
 
461
            return t
 
462
        else:
 
463
            raise errors.SmartServerError(error_code=response)
 
464
 
 
465
    def sprout(self, to_bzrdir, revision_id=None):
 
466
        # TODO: Option to control what format is created?
 
467
        to_repo = to_bzrdir.create_repository()
 
468
        self._copy_repository_tarball(to_repo, revision_id)
 
469
        return to_repo
 
470
 
 
471
    ### These methods are just thin shims to the VFS object for now.
 
472
 
 
473
    def revision_tree(self, revision_id):
 
474
        self._ensure_real()
 
475
        return self._real_repository.revision_tree(revision_id)
 
476
 
 
477
    def get_serializer_format(self):
 
478
        self._ensure_real()
 
479
        return self._real_repository.get_serializer_format()
 
480
 
 
481
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
482
                           timezone=None, committer=None, revprops=None,
 
483
                           revision_id=None):
 
484
        # FIXME: It ought to be possible to call this without immediately
 
485
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
486
        self._ensure_real()
 
487
        builder = self._real_repository.get_commit_builder(branch, parents,
 
488
                config, timestamp=timestamp, timezone=timezone,
 
489
                committer=committer, revprops=revprops, revision_id=revision_id)
 
490
        # Make the builder use this RemoteRepository rather than the real one.
 
491
        builder.repository = self
 
492
        return builder
 
493
 
 
494
    @needs_write_lock
 
495
    def add_inventory(self, revid, inv, parents):
 
496
        self._ensure_real()
 
497
        return self._real_repository.add_inventory(revid, inv, parents)
 
498
 
 
499
    @needs_write_lock
 
500
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
501
        self._ensure_real()
 
502
        return self._real_repository.add_revision(
 
503
            rev_id, rev, inv=inv, config=config)
 
504
 
 
505
    @needs_read_lock
 
506
    def get_inventory(self, revision_id):
 
507
        self._ensure_real()
 
508
        return self._real_repository.get_inventory(revision_id)
 
509
 
 
510
    @needs_read_lock
 
511
    def get_revision(self, revision_id):
 
512
        self._ensure_real()
 
513
        return self._real_repository.get_revision(revision_id)
 
514
 
 
515
    @property
 
516
    def weave_store(self):
 
517
        self._ensure_real()
 
518
        return self._real_repository.weave_store
 
519
 
 
520
    def get_transaction(self):
 
521
        self._ensure_real()
 
522
        return self._real_repository.get_transaction()
 
523
 
 
524
    @needs_read_lock
 
525
    def clone(self, a_bzrdir, revision_id=None):
 
526
        self._ensure_real()
 
527
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
 
528
 
 
529
    def make_working_trees(self):
 
530
        """RemoteRepositories never create working trees by default."""
 
531
        return False
 
532
 
 
533
    def fetch(self, source, revision_id=None, pb=None):
 
534
        self._ensure_real()
 
535
        return self._real_repository.fetch(
 
536
            source, revision_id=revision_id, pb=pb)
 
537
 
 
538
    def create_bundle(self, target, base, fileobj, format=None):
 
539
        self._ensure_real()
 
540
        self._real_repository.create_bundle(target, base, fileobj, format)
 
541
 
 
542
    @property
 
543
    def control_weaves(self):
 
544
        self._ensure_real()
 
545
        return self._real_repository.control_weaves
 
546
 
 
547
    @needs_read_lock
 
548
    def get_ancestry(self, revision_id, topo_sorted=True):
 
549
        self._ensure_real()
 
550
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
 
551
 
 
552
    @needs_read_lock
 
553
    def get_inventory_weave(self):
 
554
        self._ensure_real()
 
555
        return self._real_repository.get_inventory_weave()
 
556
 
 
557
    def fileids_altered_by_revision_ids(self, revision_ids):
 
558
        self._ensure_real()
 
559
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
 
560
 
 
561
    @needs_read_lock
 
562
    def get_signature_text(self, revision_id):
 
563
        self._ensure_real()
 
564
        return self._real_repository.get_signature_text(revision_id)
 
565
 
 
566
    @needs_read_lock
 
567
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
568
        self._ensure_real()
 
569
        return self._real_repository.get_revision_graph_with_ghosts(
 
570
            revision_ids=revision_ids)
 
571
 
 
572
    @needs_read_lock
 
573
    def get_inventory_xml(self, revision_id):
 
574
        self._ensure_real()
 
575
        return self._real_repository.get_inventory_xml(revision_id)
 
576
 
 
577
    def deserialise_inventory(self, revision_id, xml):
 
578
        self._ensure_real()
 
579
        return self._real_repository.deserialise_inventory(revision_id, xml)
 
580
 
 
581
    def reconcile(self, other=None, thorough=False):
 
582
        self._ensure_real()
 
583
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
584
        
 
585
    def all_revision_ids(self):
 
586
        self._ensure_real()
 
587
        return self._real_repository.all_revision_ids()
 
588
    
 
589
    @needs_read_lock
 
590
    def get_deltas_for_revisions(self, revisions):
 
591
        self._ensure_real()
 
592
        return self._real_repository.get_deltas_for_revisions(revisions)
 
593
 
 
594
    @needs_read_lock
 
595
    def get_revision_delta(self, revision_id):
 
596
        self._ensure_real()
 
597
        return self._real_repository.get_revision_delta(revision_id)
 
598
 
 
599
    @needs_read_lock
 
600
    def revision_trees(self, revision_ids):
 
601
        self._ensure_real()
 
602
        return self._real_repository.revision_trees(revision_ids)
 
603
 
 
604
    @needs_read_lock
 
605
    def get_revision_reconcile(self, revision_id):
 
606
        self._ensure_real()
 
607
        return self._real_repository.get_revision_reconcile(revision_id)
 
608
 
 
609
    @needs_read_lock
 
610
    def check(self, revision_ids):
 
611
        self._ensure_real()
 
612
        return self._real_repository.check(revision_ids)
 
613
 
 
614
    def copy_content_into(self, destination, revision_id=None):
 
615
        self._ensure_real()
 
616
        return self._real_repository.copy_content_into(
 
617
            destination, revision_id=revision_id)
 
618
 
 
619
    def _copy_repository_tarball(self, destination, revision_id=None):
 
620
        # get a tarball of the remote repository, and copy from that into the
 
621
        # destination
 
622
        from bzrlib import osutils
 
623
        import tarfile
 
624
        import tempfile
 
625
        from StringIO import StringIO
 
626
        # TODO: Maybe a progress bar while streaming the tarball?
 
627
        note("Copying repository content as tarball...")
 
628
        tar_file = self._get_tarball('bz2')
 
629
        try:
 
630
            tar = tarfile.open('repository', fileobj=tar_file,
 
631
                mode='r|bz2')
 
632
            tmpdir = tempfile.mkdtemp()
 
633
            try:
 
634
                _extract_tar(tar, tmpdir)
 
635
                tmp_bzrdir = BzrDir.open(tmpdir)
 
636
                tmp_repo = tmp_bzrdir.open_repository()
 
637
                tmp_repo.copy_content_into(destination, revision_id)
 
638
            finally:
 
639
                osutils.rmtree(tmpdir)
 
640
        finally:
 
641
            tar_file.close()
 
642
        # TODO: if the server doesn't support this operation, maybe do it the
 
643
        # slow way using the _real_repository?
 
644
        #
 
645
        # TODO: Suggestion from john: using external tar is much faster than
 
646
        # python's tarfile library, but it may not work on windows.
 
647
 
 
648
    @needs_write_lock
 
649
    def pack(self):
 
650
        """Compress the data within the repository.
 
651
 
 
652
        This is not currently implemented within the smart server.
 
653
        """
 
654
        self._ensure_real()
 
655
        return self._real_repository.pack()
 
656
 
 
657
    def set_make_working_trees(self, new_value):
 
658
        raise NotImplementedError(self.set_make_working_trees)
 
659
 
 
660
    @needs_write_lock
 
661
    def sign_revision(self, revision_id, gpg_strategy):
 
662
        self._ensure_real()
 
663
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
 
664
 
 
665
    @needs_read_lock
 
666
    def get_revisions(self, revision_ids):
 
667
        self._ensure_real()
 
668
        return self._real_repository.get_revisions(revision_ids)
 
669
 
 
670
    def supports_rich_root(self):
 
671
        self._ensure_real()
 
672
        return self._real_repository.supports_rich_root()
 
673
 
 
674
    def iter_reverse_revision_history(self, revision_id):
 
675
        self._ensure_real()
 
676
        return self._real_repository.iter_reverse_revision_history(revision_id)
 
677
 
 
678
    @property
 
679
    def _serializer(self):
 
680
        self._ensure_real()
 
681
        return self._real_repository._serializer
 
682
 
 
683
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
684
        self._ensure_real()
 
685
        return self._real_repository.store_revision_signature(
 
686
            gpg_strategy, plaintext, revision_id)
 
687
 
 
688
    def has_signature_for_revision_id(self, revision_id):
 
689
        self._ensure_real()
 
690
        return self._real_repository.has_signature_for_revision_id(revision_id)
 
691
 
 
692
 
 
693
class RemoteBranchLockableFiles(LockableFiles):
 
694
    """A 'LockableFiles' implementation that talks to a smart server.
 
695
    
 
696
    This is not a public interface class.
 
697
    """
 
698
 
 
699
    def __init__(self, bzrdir, _client):
 
700
        self.bzrdir = bzrdir
 
701
        self._client = _client
 
702
        self._need_find_modes = True
 
703
        LockableFiles.__init__(
 
704
            self, bzrdir.get_branch_transport(None),
 
705
            'lock', lockdir.LockDir)
 
706
 
 
707
    def _find_modes(self):
 
708
        # RemoteBranches don't let the client set the mode of control files.
 
709
        self._dir_mode = None
 
710
        self._file_mode = None
 
711
 
 
712
    def get(self, path):
 
713
        """'get' a remote path as per the LockableFiles interface.
 
714
 
 
715
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
716
             just retrieve a file, instead we ask the smart server to generate
 
717
             a configuration for us - which is retrieved as an INI file.
 
718
        """
 
719
        if path == 'branch.conf':
 
720
            path = self.bzrdir._path_for_remote_call(self._client)
 
721
            response = self._client.call_expecting_body(
 
722
                'Branch.get_config_file', path)
 
723
            assert response[0][0] == 'ok', \
 
724
                'unexpected response code %s' % (response[0],)
 
725
            return StringIO(response[1].read_body_bytes())
 
726
        else:
 
727
            # VFS fallback.
 
728
            return LockableFiles.get(self, path)
 
729
 
 
730
 
 
731
class RemoteBranchFormat(branch.BranchFormat):
 
732
 
 
733
    def __eq__(self, other):
 
734
        return (isinstance(other, RemoteBranchFormat) and 
 
735
            self.__dict__ == other.__dict__)
 
736
 
 
737
    def get_format_description(self):
 
738
        return 'Remote BZR Branch'
 
739
 
 
740
    def get_format_string(self):
 
741
        return 'Remote BZR Branch'
 
742
 
 
743
    def open(self, a_bzrdir):
 
744
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
745
        return a_bzrdir.open_branch()
 
746
 
 
747
    def initialize(self, a_bzrdir):
 
748
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
749
        return a_bzrdir.create_branch()
 
750
 
 
751
 
 
752
class RemoteBranch(branch.Branch):
 
753
    """Branch stored on a server accessed by HPSS RPC.
 
754
 
 
755
    At the moment most operations are mapped down to simple file operations.
 
756
    """
 
757
 
 
758
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
759
        _client=None):
 
760
        """Create a RemoteBranch instance.
 
761
 
 
762
        :param real_branch: An optional local implementation of the branch
 
763
            format, usually accessing the data via the VFS.
 
764
        :param _client: Private parameter for testing.
 
765
        """
 
766
        # We intentionally don't call the parent class's __init__, because it
 
767
        # will try to assign to self.tags, which is a property in this subclass.
 
768
        # And the parent's __init__ doesn't do much anyway.
 
769
        self._revision_history_cache = None
 
770
        self.bzrdir = remote_bzrdir
 
771
        if _client is not None:
 
772
            self._client = _client
 
773
        else:
 
774
            self._client = client._SmartClient(self.bzrdir._shared_medium)
 
775
        self.repository = remote_repository
 
776
        if real_branch is not None:
 
777
            self._real_branch = real_branch
 
778
            # Give the remote repository the matching real repo.
 
779
            real_repo = self._real_branch.repository
 
780
            if isinstance(real_repo, RemoteRepository):
 
781
                real_repo._ensure_real()
 
782
                real_repo = real_repo._real_repository
 
783
            self.repository._set_real_repository(real_repo)
 
784
            # Give the branch the remote repository to let fast-pathing happen.
 
785
            self._real_branch.repository = self.repository
 
786
        else:
 
787
            self._real_branch = None
 
788
        # Fill out expected attributes of branch for bzrlib api users.
 
789
        self._format = RemoteBranchFormat()
 
790
        self.base = self.bzrdir.root_transport.base
 
791
        self._control_files = None
 
792
        self._lock_mode = None
 
793
        self._lock_token = None
 
794
        self._lock_count = 0
 
795
        self._leave_lock = False
 
796
 
 
797
    def __str__(self):
 
798
        return "%s(%s)" % (self.__class__.__name__, self.base)
 
799
 
 
800
    __repr__ = __str__
 
801
 
 
802
    def _ensure_real(self):
 
803
        """Ensure that there is a _real_branch set.
 
804
 
 
805
        Used before calls to self._real_branch.
 
806
        """
 
807
        if not self._real_branch:
 
808
            assert vfs.vfs_enabled()
 
809
            self.bzrdir._ensure_real()
 
810
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
811
            # Give the remote repository the matching real repo.
 
812
            real_repo = self._real_branch.repository
 
813
            if isinstance(real_repo, RemoteRepository):
 
814
                real_repo._ensure_real()
 
815
                real_repo = real_repo._real_repository
 
816
            self.repository._set_real_repository(real_repo)
 
817
            # Give the branch the remote repository to let fast-pathing happen.
 
818
            self._real_branch.repository = self.repository
 
819
            # XXX: deal with _lock_mode == 'w'
 
820
            if self._lock_mode == 'r':
 
821
                self._real_branch.lock_read()
 
822
 
 
823
    @property
 
824
    def control_files(self):
 
825
        # Defer actually creating RemoteBranchLockableFiles until its needed,
 
826
        # because it triggers an _ensure_real that we otherwise might not need.
 
827
        if self._control_files is None:
 
828
            self._control_files = RemoteBranchLockableFiles(
 
829
                self.bzrdir, self._client)
 
830
        return self._control_files
 
831
 
 
832
    def _get_checkout_format(self):
 
833
        self._ensure_real()
 
834
        return self._real_branch._get_checkout_format()
 
835
 
 
836
    def get_physical_lock_status(self):
 
837
        """See Branch.get_physical_lock_status()."""
 
838
        # should be an API call to the server, as branches must be lockable.
 
839
        self._ensure_real()
 
840
        return self._real_branch.get_physical_lock_status()
 
841
 
 
842
    def lock_read(self):
 
843
        if not self._lock_mode:
 
844
            self._lock_mode = 'r'
 
845
            self._lock_count = 1
 
846
            if self._real_branch is not None:
 
847
                self._real_branch.lock_read()
 
848
        else:
 
849
            self._lock_count += 1
 
850
 
 
851
    def _remote_lock_write(self, token):
 
852
        if token is None:
 
853
            branch_token = repo_token = ''
 
854
        else:
 
855
            branch_token = token
 
856
            repo_token = self.repository.lock_write()
 
857
            self.repository.unlock()
 
858
        path = self.bzrdir._path_for_remote_call(self._client)
 
859
        response = self._client.call('Branch.lock_write', path, branch_token,
 
860
                                     repo_token)
 
861
        if response[0] == 'ok':
 
862
            ok, branch_token, repo_token = response
 
863
            return branch_token, repo_token
 
864
        elif response[0] == 'LockContention':
 
865
            raise errors.LockContention('(remote lock)')
 
866
        elif response[0] == 'TokenMismatch':
 
867
            raise errors.TokenMismatch(token, '(remote token)')
 
868
        elif response[0] == 'UnlockableTransport':
 
869
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
870
        elif response[0] == 'ReadOnlyError':
 
871
            raise errors.ReadOnlyError(self)
 
872
        else:
 
873
            raise errors.UnexpectedSmartServerResponse(response)
 
874
            
 
875
    def lock_write(self, token=None):
 
876
        if not self._lock_mode:
 
877
            remote_tokens = self._remote_lock_write(token)
 
878
            self._lock_token, self._repo_lock_token = remote_tokens
 
879
            assert self._lock_token, 'Remote server did not return a token!'
 
880
            # TODO: We really, really, really don't want to call _ensure_real
 
881
            # here, but it's the easiest way to ensure coherency between the
 
882
            # state of the RemoteBranch and RemoteRepository objects and the
 
883
            # physical locks.  If we don't materialise the real objects here,
 
884
            # then getting everything in the right state later is complex, so
 
885
            # for now we just do it the lazy way.
 
886
            #   -- Andrew Bennetts, 2007-02-22.
 
887
            self._ensure_real()
 
888
            if self._real_branch is not None:
 
889
                self._real_branch.repository.lock_write(
 
890
                    token=self._repo_lock_token)
 
891
                try:
 
892
                    self._real_branch.lock_write(token=self._lock_token)
 
893
                finally:
 
894
                    self._real_branch.repository.unlock()
 
895
            if token is not None:
 
896
                self._leave_lock = True
 
897
            else:
 
898
                # XXX: this case seems to be unreachable; token cannot be None.
 
899
                self._leave_lock = False
 
900
            self._lock_mode = 'w'
 
901
            self._lock_count = 1
 
902
        elif self._lock_mode == 'r':
 
903
            raise errors.ReadOnlyTransaction
 
904
        else:
 
905
            if token is not None:
 
906
                # A token was given to lock_write, and we're relocking, so check
 
907
                # that the given token actually matches the one we already have.
 
908
                if token != self._lock_token:
 
909
                    raise errors.TokenMismatch(token, self._lock_token)
 
910
            self._lock_count += 1
 
911
        return self._lock_token
 
912
 
 
913
    def _unlock(self, branch_token, repo_token):
 
914
        path = self.bzrdir._path_for_remote_call(self._client)
 
915
        response = self._client.call('Branch.unlock', path, branch_token,
 
916
                                     repo_token)
 
917
        if response == ('ok',):
 
918
            return
 
919
        elif response[0] == 'TokenMismatch':
 
920
            raise errors.TokenMismatch(
 
921
                str((branch_token, repo_token)), '(remote tokens)')
 
922
        else:
 
923
            raise errors.UnexpectedSmartServerResponse(response)
 
924
 
 
925
    def unlock(self):
 
926
        self._lock_count -= 1
 
927
        if not self._lock_count:
 
928
            self._clear_cached_state()
 
929
            mode = self._lock_mode
 
930
            self._lock_mode = None
 
931
            if self._real_branch is not None:
 
932
                if not self._leave_lock:
 
933
                    # If this RemoteBranch will remove the physical lock for the
 
934
                    # repository, make sure the _real_branch doesn't do it
 
935
                    # first.  (Because the _real_branch's repository is set to
 
936
                    # be the RemoteRepository.)
 
937
                    self._real_branch.repository.leave_lock_in_place()
 
938
                self._real_branch.unlock()
 
939
            if mode != 'w':
 
940
                # Only write-locked branched need to make a remote method call
 
941
                # to perfom the unlock.
 
942
                return
 
943
            assert self._lock_token, 'Locked, but no token!'
 
944
            branch_token = self._lock_token
 
945
            repo_token = self._repo_lock_token
 
946
            self._lock_token = None
 
947
            self._repo_lock_token = None
 
948
            if not self._leave_lock:
 
949
                self._unlock(branch_token, repo_token)
 
950
 
 
951
    def break_lock(self):
 
952
        self._ensure_real()
 
953
        return self._real_branch.break_lock()
 
954
 
 
955
    def leave_lock_in_place(self):
 
956
        self._leave_lock = True
 
957
 
 
958
    def dont_leave_lock_in_place(self):
 
959
        self._leave_lock = False
 
960
 
 
961
    def last_revision_info(self):
 
962
        """See Branch.last_revision_info()."""
 
963
        path = self.bzrdir._path_for_remote_call(self._client)
 
964
        response = self._client.call('Branch.last_revision_info', path)
 
965
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
966
        revno = int(response[1])
 
967
        last_revision = response[2]
 
968
        return (revno, last_revision)
 
969
 
 
970
    def _gen_revision_history(self):
 
971
        """See Branch._gen_revision_history()."""
 
972
        path = self.bzrdir._path_for_remote_call(self._client)
 
973
        response = self._client.call_expecting_body(
 
974
            'Branch.revision_history', path)
 
975
        assert response[0][0] == 'ok', ('unexpected response code %s'
 
976
                                        % (response[0],))
 
977
        result = response[1].read_body_bytes().split('\x00')
 
978
        if result == ['']:
 
979
            return []
 
980
        return result
 
981
 
 
982
    @needs_write_lock
 
983
    def set_revision_history(self, rev_history):
 
984
        # Send just the tip revision of the history; the server will generate
 
985
        # the full history from that.  If the revision doesn't exist in this
 
986
        # branch, NoSuchRevision will be raised.
 
987
        path = self.bzrdir._path_for_remote_call(self._client)
 
988
        if rev_history == []:
 
989
            rev_id = 'null:'
 
990
        else:
 
991
            rev_id = rev_history[-1]
 
992
        self._clear_cached_state()
 
993
        response = self._client.call('Branch.set_last_revision',
 
994
            path, self._lock_token, self._repo_lock_token, rev_id)
 
995
        if response[0] == 'NoSuchRevision':
 
996
            raise NoSuchRevision(self, rev_id)
 
997
        else:
 
998
            assert response == ('ok',), (
 
999
                'unexpected response code %r' % (response,))
 
1000
        self._cache_revision_history(rev_history)
 
1001
 
 
1002
    def get_parent(self):
 
1003
        self._ensure_real()
 
1004
        return self._real_branch.get_parent()
 
1005
        
 
1006
    def set_parent(self, url):
 
1007
        self._ensure_real()
 
1008
        return self._real_branch.set_parent(url)
 
1009
        
 
1010
    def get_config(self):
 
1011
        return RemoteBranchConfig(self)
 
1012
 
 
1013
    def sprout(self, to_bzrdir, revision_id=None):
 
1014
        # Like Branch.sprout, except that it sprouts a branch in the default
 
1015
        # format, because RemoteBranches can't be created at arbitrary URLs.
 
1016
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
 
1017
        # to_bzrdir.create_branch...
 
1018
        result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
 
1019
        self.copy_content_into(result, revision_id=revision_id)
 
1020
        result.set_parent(self.bzrdir.root_transport.base)
 
1021
        return result
 
1022
 
 
1023
    @needs_write_lock
 
1024
    def append_revision(self, *revision_ids):
 
1025
        self._ensure_real()
 
1026
        return self._real_branch.append_revision(*revision_ids)
 
1027
 
 
1028
    @needs_write_lock
 
1029
    def pull(self, source, overwrite=False, stop_revision=None,
 
1030
             **kwargs):
 
1031
        # FIXME: This asks the real branch to run the hooks, which means
 
1032
        # they're called with the wrong target branch parameter. 
 
1033
        # The test suite specifically allows this at present but it should be
 
1034
        # fixed.  It should get a _override_hook_target branch,
 
1035
        # as push does.  -- mbp 20070405
 
1036
        self._ensure_real()
 
1037
        self._real_branch.pull(
 
1038
            source, overwrite=overwrite, stop_revision=stop_revision,
 
1039
            **kwargs)
 
1040
 
 
1041
    @needs_read_lock
 
1042
    def push(self, target, overwrite=False, stop_revision=None):
 
1043
        self._ensure_real()
 
1044
        return self._real_branch.push(
 
1045
            target, overwrite=overwrite, stop_revision=stop_revision,
 
1046
            _override_hook_source_branch=self)
 
1047
 
 
1048
    def is_locked(self):
 
1049
        return self._lock_count >= 1
 
1050
 
 
1051
    def set_last_revision_info(self, revno, revision_id):
 
1052
        self._ensure_real()
 
1053
        self._clear_cached_state()
 
1054
        return self._real_branch.set_last_revision_info(revno, revision_id)
 
1055
 
 
1056
    def generate_revision_history(self, revision_id, last_rev=None,
 
1057
                                  other_branch=None):
 
1058
        self._ensure_real()
 
1059
        return self._real_branch.generate_revision_history(
 
1060
            revision_id, last_rev=last_rev, other_branch=other_branch)
 
1061
 
 
1062
    @property
 
1063
    def tags(self):
 
1064
        self._ensure_real()
 
1065
        return self._real_branch.tags
 
1066
 
 
1067
    def set_push_location(self, location):
 
1068
        self._ensure_real()
 
1069
        return self._real_branch.set_push_location(location)
 
1070
 
 
1071
    def update_revisions(self, other, stop_revision=None):
 
1072
        self._ensure_real()
 
1073
        return self._real_branch.update_revisions(
 
1074
            other, stop_revision=stop_revision)
 
1075
 
 
1076
 
 
1077
class RemoteBranchConfig(BranchConfig):
 
1078
 
 
1079
    def username(self):
 
1080
        self.branch._ensure_real()
 
1081
        return self.branch._real_branch.get_config().username()
 
1082
 
 
1083
    def _get_branch_data_config(self):
 
1084
        self.branch._ensure_real()
 
1085
        if self._branch_data_config is None:
 
1086
            self._branch_data_config = TreeConfig(self.branch._real_branch)
 
1087
        return self._branch_data_config
 
1088
 
 
1089
 
 
1090
def _extract_tar(tar, to_dir):
 
1091
    """Extract all the contents of a tarfile object.
 
1092
 
 
1093
    A replacement for extractall, which is not present in python2.4
 
1094
    """
 
1095
    for tarinfo in tar:
 
1096
        tar.extract(tarinfo, to_dir)