~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Aaron Bentley
  • Date: 2005-09-13 02:42:07 UTC
  • mto: (1185.1.16)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: aaron.bentley@utoronto.ca-20050913024207-489d573af4b76c4d
Fixed issues with pull not having a default location after branch

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