/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

Implement RemoteBranch.lock_write/unlock as smart operations.

Because Branch.lock_write/unlock actually also lock/unlock the repository, I've
slightly changed lock_write's interface to accept and return 'tokens' rather
than 'token'.  i.e. a 2-tuple of (branch token, repo token), or None.

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
from urlparse import urlparse
 
22
 
 
23
from bzrlib import branch, errors, repository
 
24
from bzrlib.branch import BranchReferenceFormat
 
25
from bzrlib.bzrdir import BzrDir, BzrDirFormat, RemoteBzrDirFormat
 
26
from bzrlib.errors import NoSuchRevision
 
27
from bzrlib.revision import NULL_REVISION
 
28
from bzrlib.smart import client, vfs
 
29
from bzrlib.urlutils import unescape
 
30
 
 
31
# Note: RemoteBzrDirFormat is in bzrdir.py
 
32
 
 
33
class RemoteBzrDir(BzrDir):
 
34
    """Control directory on a remote server, accessed by HPSS."""
 
35
 
 
36
    def __init__(self, transport, _client=None):
 
37
        """Construct a RemoteBzrDir.
 
38
 
 
39
        :param _client: Private parameter for testing. Disables probing and the
 
40
            use of a real bzrdir.
 
41
        """
 
42
        BzrDir.__init__(self, transport, RemoteBzrDirFormat())
 
43
        if _client is not None:
 
44
            self.client = _client
 
45
            return
 
46
 
 
47
        self.client = transport.get_smart_client()
 
48
        # this object holds a delegated bzrdir that uses file-level operations
 
49
        # to talk to the other side
 
50
        # XXX: We should go into find_format, but not allow it to find
 
51
        # RemoteBzrDirFormat and make sure it finds the real underlying format.
 
52
        self._real_bzrdir = None
 
53
        
 
54
        self._ensure_real()
 
55
        smartclient = client.SmartClient(self.client)
 
56
        path = self._path_for_remote_call(smartclient)
 
57
        #self._real_bzrdir._format.probe_transport(transport)
 
58
        response = smartclient.call('probe_dont_use', path)
 
59
        if response == ('no',):
 
60
            raise errors.NotBranchError(path=transport.base)
 
61
 
 
62
    def _ensure_real(self):
 
63
        """Ensure that there is a _real_bzrdir set.
 
64
 
 
65
        used before calls to self._real_bzrdir.
 
66
        """
 
67
        if not self._real_bzrdir:
 
68
            default_format = BzrDirFormat.get_default_format()
 
69
            self._real_bzrdir = default_format.open(self.root_transport,
 
70
                _found=True)
 
71
 
 
72
    def create_repository(self, shared=False):
 
73
        return RemoteRepository(
 
74
            self, self._real_bzrdir.create_repository(shared=shared))
 
75
 
 
76
    def create_branch(self):
 
77
        real_branch = self._real_bzrdir.create_branch()
 
78
        return RemoteBranch(self, self.find_repository(), real_branch)
 
79
 
 
80
    def create_workingtree(self, revision_id=None):
 
81
        real_workingtree = self._real_bzrdir.create_workingtree(revision_id=revision_id)
 
82
        return RemoteWorkingTree(self, real_workingtree)
 
83
 
 
84
    def open_branch(self, _unsupported=False):
 
85
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
 
86
        smartclient = client.SmartClient(self.client)
 
87
        path = self._path_for_remote_call(smartclient)
 
88
        response = smartclient.call('BzrDir.open_branch', path)
 
89
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
90
        if response[0] != 'ok':
 
91
            # this should probably be a regular translate no ?
 
92
            raise errors.NotBranchError(path=self.root_transport.base)
 
93
        if response[1] == '':
 
94
            # branch at this location.
 
95
            return RemoteBranch(self, self.find_repository())
 
96
        else:
 
97
            # a branch reference, use the existing BranchReference logic.
 
98
            format = BranchReferenceFormat()
 
99
            return format.open(self, _found=True, location=response[1])
 
100
 
 
101
    def open_repository(self):
 
102
        smartclient = client.SmartClient(self.client)
 
103
        path = self._path_for_remote_call(smartclient)
 
104
        response = smartclient.call('BzrDir.find_repository', path)
 
105
        assert response[0] in ('ok', 'norepository'), \
 
106
            'unexpected response code %s' % (response,)
 
107
        if response[0] == 'norepository':
 
108
            raise errors.NoRepositoryPresent(self)
 
109
        if response[1] == '':
 
110
            return RemoteRepository(self)
 
111
        else:
 
112
            raise errors.NoRepositoryPresent(self)
 
113
 
 
114
    def open_workingtree(self):
 
115
        return RemoteWorkingTree(self, self._real_bzrdir.open_workingtree())
 
116
 
 
117
    def _path_for_remote_call(self, client):
 
118
        """Return the path to be used for this bzrdir in a remote call."""
 
119
        return client.remote_path_from_transport(self.root_transport)
 
120
 
 
121
    def get_branch_transport(self, branch_format):
 
122
        return self._real_bzrdir.get_branch_transport(branch_format)
 
123
 
 
124
    def get_repository_transport(self, repository_format):
 
125
        return self._real_bzrdir.get_repository_transport(repository_format)
 
126
 
 
127
    def get_workingtree_transport(self, workingtree_format):
 
128
        return self._real_bzrdir.get_workingtree_transport(workingtree_format)
 
129
 
 
130
    def can_convert_format(self):
 
131
        """Upgrading of remote bzrdirs is not supported yet."""
 
132
        return False
 
133
 
 
134
    def needs_format_conversion(self, format=None):
 
135
        """Upgrading of remote bzrdirs is not supported yet."""
 
136
        return False
 
137
 
 
138
 
 
139
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
140
    """Format for repositories accessed over rpc.
 
141
 
 
142
    Instances of this repository are represented by RemoteRepository
 
143
    instances.
 
144
    """
 
145
 
 
146
    _matchingbzrdir = RemoteBzrDirFormat
 
147
 
 
148
    def initialize(self, a_bzrdir, shared=False):
 
149
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
150
        return a_bzrdir.create_repository(shared=shared)
 
151
    
 
152
    def open(self, a_bzrdir):
 
153
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
154
        return a_bzrdir.open_repository()
 
155
 
 
156
    def get_format_description(self):
 
157
        return 'bzr remote repository'
 
158
 
 
159
    def __eq__(self, other):
 
160
        return self.__class__ == other.__class__
 
161
 
 
162
    rich_root_data = False
 
163
 
 
164
 
 
165
class RemoteRepository(object):
 
166
    """Repository accessed over rpc.
 
167
 
 
168
    For the moment everything is delegated to IO-like operations over
 
169
    the transport.
 
170
    """
 
171
 
 
172
    def __init__(self, remote_bzrdir, real_repository=None, _client=None):
 
173
        """Create a RemoteRepository instance.
 
174
        
 
175
        :param remote_bzrdir: The bzrdir hosting this repository.
 
176
        :param real_repository: If not None, a local implementation of the
 
177
            repository logic for the repository, usually accessing the data
 
178
            via the VFS.
 
179
        :param _client: Private testing parameter - override the smart client
 
180
            to be used by the repository.
 
181
        """
 
182
        if real_repository:
 
183
            self._real_repository = real_repository
 
184
        else:
 
185
            self._real_repository = None
 
186
        self.bzrdir = remote_bzrdir
 
187
        if _client is None:
 
188
            self._client = client.SmartClient(self.bzrdir.client)
 
189
        else:
 
190
            self._client = _client
 
191
        self._format = RemoteRepositoryFormat()
 
192
        self._lock_mode = None
 
193
        self._lock_token = None
 
194
        self._lock_count = 0
 
195
        self._leave_lock = False
 
196
 
 
197
    def _ensure_real(self):
 
198
        """Ensure that there is a _real_repository set.
 
199
 
 
200
        used before calls to self._real_repository.
 
201
        """
 
202
        if not self._real_repository:
 
203
            self.bzrdir._ensure_real()
 
204
            self._real_repository = self.bzrdir._real_bzrdir.open_repository()
 
205
 
 
206
    def get_revision_graph(self, revision_id=None):
 
207
        """See Repository.get_revision_graph()."""
 
208
        if revision_id is None:
 
209
            revision_id = ''
 
210
        elif revision_id == NULL_REVISION:
 
211
            return {}
 
212
 
 
213
        path = self.bzrdir._path_for_remote_call(self._client)
 
214
        response = self._client.call2('Repository.get_revision_graph', path, revision_id.encode('utf8'))
 
215
        assert response[0][0] in ('ok', 'nosuchrevision'), 'unexpected response code %s' % (response[0],)
 
216
        if response[0][0] == 'ok':
 
217
            coded = response[1].read_body_bytes()
 
218
            lines = coded.decode('utf8').split('\n')
 
219
            revision_graph = {}
 
220
            # FIXME
 
221
            for line in lines:
 
222
                d = list(line.split())
 
223
                revision_graph[d[0]] = d[1:]
 
224
                
 
225
            return revision_graph
 
226
        else:
 
227
            assert response[1] != ''
 
228
            raise NoSuchRevision(self, revision_id)
 
229
 
 
230
    def has_revision(self, revision_id):
 
231
        """See Repository.has_revision()."""
 
232
        path = self.bzrdir._path_for_remote_call(self._client)
 
233
        response = self._client.call('Repository.has_revision', path, revision_id.encode('utf8'))
 
234
        assert response[0] in ('ok', 'no'), 'unexpected response code %s' % (response,)
 
235
        return response[0] == 'ok'
 
236
 
 
237
    def gather_stats(self, revid, committers=None):
 
238
        """See Repository.gather_stats()."""
 
239
        path = self.bzrdir._path_for_remote_call(self._client)
 
240
        if revid in (None, NULL_REVISION):
 
241
            fmt_revid = ''
 
242
        else:
 
243
            fmt_revid = revid.encode('utf8')
 
244
        if committers is None:
 
245
            fmt_committers = 'no'
 
246
        else:
 
247
            fmt_committers = 'yes'
 
248
        response = self._client.call2('Repository.gather_stats', path,
 
249
                                      fmt_revid, fmt_committers)
 
250
        assert response[0][0] == 'ok', \
 
251
            'unexpected response code %s' % (response[0],)
 
252
 
 
253
        body = response[1].read_body_bytes()
 
254
        result = {}
 
255
        for line in body.split('\n'):
 
256
            if not line:
 
257
                continue
 
258
            key, val_text = line.split(':')
 
259
            if key in ('revisions', 'size', 'committers'):
 
260
                result[key] = int(val_text)
 
261
            elif key in ('firstrev', 'latestrev'):
 
262
                values = val_text.split(' ')[1:]
 
263
                result[key] = (float(values[0]), long(values[1]))
 
264
 
 
265
        return result
 
266
 
 
267
    def get_physical_lock_status(self):
 
268
        """See Repository.get_physical_lock_status()."""
 
269
        return False
 
270
 
 
271
    def is_shared(self):
 
272
        """See Repository.is_shared()."""
 
273
        path = self.bzrdir._path_for_remote_call(self._client)
 
274
        response = self._client.call('Repository.is_shared', path)
 
275
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
276
        return response[0] == 'yes'
 
277
 
 
278
    def lock_read(self):
 
279
        # wrong eventually - want a local lock cache context
 
280
        if not self._lock_mode:
 
281
            self._lock_mode = 'r'
 
282
            self._lock_count = 1
 
283
            if self._real_repository is not None:
 
284
                self._real_repository.lock_read()
 
285
        else:
 
286
            self._lock_count += 1
 
287
 
 
288
    def _lock_write(self, token):
 
289
        path = self.bzrdir._path_for_remote_call(self._client)
 
290
        if token is None:
 
291
            token = ''
 
292
        response = self._client.call('Repository.lock_write', path, token)
 
293
        if response[0] == 'ok':
 
294
            ok, token = response
 
295
            return token
 
296
        elif response[0] == 'LockContention':
 
297
            raise errors.LockContention('(remote lock)')
 
298
        else:
 
299
            assert False, 'unexpected response code %s' % (response,)
 
300
 
 
301
    def lock_write(self, token=None):
 
302
        if not self._lock_mode:
 
303
            self._lock_token = self._lock_write(token)
 
304
            assert self._lock_token, 'Remote server did not return a token!'
 
305
            if self._real_repository is not None:
 
306
                self._real_repository.lock_write(token=self._lock_token)
 
307
            if token is not None:
 
308
                self._leave_lock = True
 
309
            else:
 
310
                self._leave_lock = False
 
311
            self._lock_mode = 'w'
 
312
            self._lock_count = 1
 
313
        elif self._lock_mode == 'r':
 
314
            raise errors.ReadOnlyTransaction
 
315
        else:
 
316
            self._lock_count += 1
 
317
        return self._lock_token
 
318
 
 
319
    def leave_lock_in_place(self):
 
320
        self._leave_lock = True
 
321
 
 
322
    def dont_leave_lock_in_place(self):
 
323
        self._leave_lock = False
 
324
 
 
325
    def _set_real_repository(self, repository):
 
326
        """Set the _real_repository for this repository.
 
327
 
 
328
        :param repository: The repository to fallback to for non-hpss
 
329
            implemented operations.
 
330
        """
 
331
        self._real_repository = repository
 
332
        if self._lock_mode == 'w':
 
333
            # if we are already locked, the real repository must be able to
 
334
            # acquire the lock with our token.
 
335
            self._real_repository.lock_write(self._lock_token)
 
336
 
 
337
    def _unlock(self, token):
 
338
        path = self.bzrdir._path_for_remote_call(self._client)
 
339
        response = self._client.call('Repository.unlock', path, token)
 
340
        if response == ('ok',):
 
341
            return
 
342
        elif response[0] == 'TokenMismatch':
 
343
            raise errors.TokenMismatch(token, '(remote token)')
 
344
        else:
 
345
            assert False, 'unexpected response code %s' % (response,)
 
346
 
 
347
    def unlock(self):
 
348
        self._lock_count -= 1
 
349
        if not self._lock_count:
 
350
            mode = self._lock_mode
 
351
            self._lock_mode = None
 
352
            if self._real_repository is not None:
 
353
                self._real_repository.unlock()
 
354
            if mode != 'w':
 
355
                return
 
356
            assert self._lock_token, 'Locked, but no token!'
 
357
            token = self._lock_token
 
358
            self._lock_token = None
 
359
            if not self._leave_lock:
 
360
                self._unlock(token)
 
361
 
 
362
    def break_lock(self):
 
363
        # should hand off to the network
 
364
        self._ensure_real()
 
365
        return self._real_repository.break_lock()
 
366
 
 
367
 
 
368
class RemoteBranchLockableFiles(object):
 
369
    """A 'LockableFiles' implementation that talks to a smart server.
 
370
    
 
371
    This is not a public interface class.
 
372
    """
 
373
 
 
374
    def __init__(self, bzrdir, _client):
 
375
        self.bzrdir = bzrdir
 
376
        self._client = _client
 
377
 
 
378
    def get(self, path):
 
379
        """'get' a remote path as per the LockableFiles interface.
 
380
 
 
381
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
382
             just retrieve a file, instead we ask the smart server to generate
 
383
             a configuration for us - which is retrieved as an INI file.
 
384
        """
 
385
        assert path == 'branch.conf'
 
386
        path = self.bzrdir._path_for_remote_call(self._client)
 
387
        response = self._client.call2('Branch.get_config_file', path)
 
388
        assert response[0][0] == 'ok', \
 
389
            'unexpected response code %s' % (response[0],)
 
390
        return StringIO(response[1].read_body_bytes())
 
391
 
 
392
 
 
393
class RemoteBranchFormat(branch.BranchFormat):
 
394
 
 
395
    def get_format_description(self):
 
396
        return 'Remote BZR Branch'
 
397
 
 
398
    def open(self, a_bzrdir):
 
399
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
400
        return a_bzrdir.open_branch()
 
401
 
 
402
    def initialize(self, a_bzrdir):
 
403
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
404
        return a_bzrdir.create_branch()
 
405
 
 
406
 
 
407
class RemoteBranch(branch.Branch):
 
408
    """Branch stored on a server accessed by HPSS RPC.
 
409
 
 
410
    At the moment most operations are mapped down to simple file operations.
 
411
    """
 
412
 
 
413
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
414
        _client=None):
 
415
        """Create a RemoteBranch instance.
 
416
 
 
417
        :param real_branch: An optional local implementation of the branch
 
418
            format, usually accessing the data via the VFS.
 
419
        :param _client: Private parameter for testing.
 
420
        """
 
421
        self.bzrdir = remote_bzrdir
 
422
        if _client is not None:
 
423
            self._client = _client
 
424
        else:
 
425
            self._client = client.SmartClient(self.bzrdir.client)
 
426
        self.repository = remote_repository
 
427
        if real_branch is not None:
 
428
            self._real_branch = real_branch
 
429
            # Give the remote repository the matching real repo.
 
430
            self.repository._set_real_repository(self._real_branch.repository)
 
431
            # Give the branch the remote repository to let fast-pathing happen.
 
432
            self._real_branch.repository = self.repository
 
433
        else:
 
434
            self._real_branch = None
 
435
        # Fill out expected attributes of branch for bzrlib api users.
 
436
        self._format = RemoteBranchFormat()
 
437
        self.base = self.bzrdir.root_transport.base
 
438
        self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
 
439
        self._lock_mode = None
 
440
        self._lock_token = None
 
441
        self._lock_count = 0
 
442
        self._leave_lock = False
 
443
 
 
444
    def _ensure_real(self):
 
445
        """Ensure that there is a _real_branch set.
 
446
 
 
447
        used before calls to self._real_branch.
 
448
        """
 
449
        if not self._real_branch:
 
450
            assert vfs.vfs_enabled()
 
451
            self.bzrdir._ensure_real()
 
452
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
453
            # Give the remote repository the matching real repo.
 
454
            self.repository._set_real_repository(self._real_branch.repository)
 
455
            # Give the branch the remote repository to let fast-pathing happen.
 
456
            self._real_branch.repository = self.repository
 
457
 
 
458
    def get_physical_lock_status(self):
 
459
        """See Branch.get_physical_lock_status()."""
 
460
        # should be an API call to the server, as branches must be lockable.
 
461
        self._ensure_real()
 
462
        return self._real_branch.get_physical_lock_status()
 
463
 
 
464
    def lock_read(self):
 
465
        self._ensure_real()
 
466
        return self._real_branch.lock_read()
 
467
 
 
468
    def _lock_write(self, tokens):
 
469
        if tokens is None:
 
470
            branch_token = repo_token = ''
 
471
        else:
 
472
            branch_token, repo_token = tokens
 
473
        path = self.bzrdir._path_for_remote_call(self._client)
 
474
        response = self._client.call('Branch.lock_write', path, branch_token,
 
475
                                     repo_token)
 
476
        if response[0] == 'ok':
 
477
            ok, branch_token, repo_token = response
 
478
            return branch_token, repo_token
 
479
        elif response[0] == 'LockContention':
 
480
            raise errors.LockContention('(remote lock)')
 
481
        elif response[0] == 'TokenMismatch':
 
482
            raise errors.TokenMismatch(tokens, '(remote tokens)')
 
483
        else:
 
484
            assert False, 'unexpected response code %s' % (response,)
 
485
            
 
486
    def lock_write(self, tokens=None):
 
487
        if not self._lock_mode:
 
488
            remote_tokens = self._lock_write(tokens)
 
489
            self._lock_token, self._repo_lock_token = remote_tokens
 
490
            assert self._lock_token, 'Remote server did not return a token!'
 
491
            if self._real_branch is not None:
 
492
                self._real_branch.lock_write(tokens=remote_tokens)
 
493
            if tokens is not None:
 
494
                self._leave_lock = True
 
495
            else:
 
496
                # XXX: this case seems to be unreachable; tokens cannot be None.
 
497
                self._leave_lock = False
 
498
            self._lock_mode = 'w'
 
499
            self._lock_count = 1
 
500
        elif self._lock_mode == 'r':
 
501
            raise errors.ReadOnlyTransaction
 
502
        else:
 
503
            if tokens is not None:
 
504
                # Tokens were given to lock_write, and we're relocking, so check
 
505
                # that the given tokens actually match the ones we already have.
 
506
                held_tokens = (self._lock_token, self._repo_lock_token)
 
507
                if tokens != held_tokens:
 
508
                    raise errors.TokenMismatch(str(tokens), str(held_tokens))
 
509
            self._lock_count += 1
 
510
        return self._lock_token, self._repo_lock_token
 
511
 
 
512
    def _unlock(self, branch_token, repo_token):
 
513
        path = self.bzrdir._path_for_remote_call(self._client)
 
514
        response = self._client.call('Branch.unlock', path, branch_token,
 
515
                                     repo_token)
 
516
        if response == ('ok',):
 
517
            return
 
518
        elif response[0] == 'TokenMismatch':
 
519
            raise errors.TokenMismatch(token, '(remote token)')
 
520
        else:
 
521
            assert False, 'unexpected response code %s' % (response,)
 
522
 
 
523
    def unlock(self):
 
524
        self._lock_count -= 1
 
525
        if not self._lock_count:
 
526
            mode = self._lock_mode
 
527
            self._lock_mode = None
 
528
            if self._real_branch is not None:
 
529
                self._real_branch.unlock()
 
530
            if mode != 'w':
 
531
                return
 
532
            assert self._lock_token, 'Locked, but no token!'
 
533
            branch_token = self._lock_token
 
534
            repo_token = self._repo_lock_token
 
535
            self._lock_token = None
 
536
            self._repo_lock_token = None
 
537
            if not self._leave_lock:
 
538
                self._unlock(branch_token, repo_token)
 
539
 
 
540
    def break_lock(self):
 
541
        self._ensure_real()
 
542
        return self._real_branch.break_lock()
 
543
 
 
544
    def leave_lock_in_place(self):
 
545
        self._leave_lock = True
 
546
 
 
547
    def dont_leave_lock_in_place(self):
 
548
        self._leave_lock = False
 
549
 
 
550
    def last_revision_info(self):
 
551
        """See Branch.last_revision_info()."""
 
552
        path = self.bzrdir._path_for_remote_call(self._client)
 
553
        response = self._client.call('Branch.last_revision_info', path)
 
554
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
555
        revno = int(response[1])
 
556
        last_revision = response[2].decode('utf8')
 
557
        if last_revision == '':
 
558
            last_revision = NULL_REVISION
 
559
        return (revno, last_revision)
 
560
 
 
561
    def revision_history(self):
 
562
        """See Branch.revision_history()."""
 
563
        # XXX: TODO: this does not cache the revision history for the duration
 
564
        # of a lock, which is a bug - see the code for regular branches
 
565
        # for details.
 
566
        path = self.bzrdir._path_for_remote_call(self._client)
 
567
        response = self._client.call2('Branch.revision_history', path)
 
568
        assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
 
569
        result = response[1].read_body_bytes().decode('utf8').split('\x00')
 
570
        if result == ['']:
 
571
            return []
 
572
        return result
 
573
 
 
574
    def set_revision_history(self, rev_history):
 
575
        # Send just the tip revision of the history; the server will generate
 
576
        # the full history from that.  If the revision doesn't exist in this
 
577
        # branch, NoSuchRevision will be raised.
 
578
        path = self.bzrdir._path_for_remote_call(self._client)
 
579
        if rev_history == []:
 
580
            rev_id = ''
 
581
        else:
 
582
            rev_id = rev_history[-1]
 
583
        response = self._client.call('Branch.set_last_revision', path, rev_id)
 
584
        if response[0] == 'NoSuchRevision':
 
585
            raise NoSuchRevision(self, rev_id)
 
586
        else:
 
587
            assert response == ('ok',), (
 
588
                'unexpected response code %r' % (response,))
 
589
 
 
590
    def get_parent(self):
 
591
        self._ensure_real()
 
592
        return self._real_branch.get_parent()
 
593
        
 
594
    def set_parent(self, url):
 
595
        self._ensure_real()
 
596
        return self._real_branch.set_parent(url)
 
597
        
 
598
 
 
599
class RemoteWorkingTree(object):
 
600
 
 
601
    def __init__(self, remote_bzrdir, real_workingtree):
 
602
        self.real_workingtree = real_workingtree
 
603
        self.bzrdir = remote_bzrdir
 
604
 
 
605
    def __getattr__(self, name):
 
606
        # XXX: temporary way to lazily delegate everything to the real
 
607
        # workingtree
 
608
        return getattr(self.real_workingtree, name)
 
609
 
 
610