20
20
from cStringIO import StringIO
22
from bzrlib import branch, errors, lockdir, repository
23
29
from bzrlib.branch import Branch, BranchReferenceFormat
24
30
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
25
31
from bzrlib.config import BranchConfig, TreeConfig
26
32
from bzrlib.decorators import needs_read_lock, needs_write_lock
27
33
from bzrlib.errors import NoSuchRevision
28
34
from bzrlib.lockable_files import LockableFiles
29
from bzrlib.revision import NULL_REVISION
35
from bzrlib.pack import ContainerReader
30
36
from bzrlib.smart import client, vfs
37
from bzrlib.symbol_versioning import (
31
41
from bzrlib.trace import note
33
43
# Note: RemoteBzrDirFormat is in bzrdir.py
75
85
self._real_bzrdir.create_repository(shared=shared)
76
86
return self.open_repository()
88
def destroy_repository(self):
89
"""See BzrDir.destroy_repository"""
91
self._real_bzrdir.destroy_repository()
78
93
def create_branch(self):
79
94
self._ensure_real()
80
95
real_branch = self._real_bzrdir.create_branch()
81
96
return RemoteBranch(self, self.find_repository(), real_branch)
83
def create_workingtree(self, revision_id=None):
98
def destroy_branch(self):
99
"""See BzrDir.destroy_branch"""
101
self._real_bzrdir.destroy_branch()
103
def create_workingtree(self, revision_id=None, from_branch=None):
84
104
raise errors.NotLocalUrl(self.transport.base)
86
106
def find_branch_format(self):
105
125
elif response == ('nobranch',):
106
126
raise errors.NotBranchError(path=self.root_transport.base)
108
assert False, 'unexpected response code %r' % (response,)
128
raise errors.UnexpectedSmartServerResponse(response)
110
130
def open_branch(self, _unsupported=False):
111
131
assert _unsupported == False, 'unsupported flag support not implemented yet.'
177
197
Instances of this repository are represented by RemoteRepository
180
The RemoteRepositoryFormat is parameterised during construction
200
The RemoteRepositoryFormat is parameterized during construction
181
201
to reflect the capabilities of the real, remote format. Specifically
182
202
the attributes rich_root_data and supports_tree_reference are set
183
203
on a per instance basis, and are not set (and should not be) at
243
263
self._lock_token = None
244
264
self._lock_count = 0
245
265
self._leave_lock = False
267
# These depend on the actual remote format, so force them off for
268
# maximum compatibility. XXX: In future these should depend on the
269
# remote repository instance, but this is irrelevant until we perform
270
# reconcile via an RPC call.
271
self._reconcile_does_inventory_gc = False
272
self._reconcile_fixes_text_parents = False
273
self._reconcile_backsup_inventory = False
274
self.base = self.bzrdir.transport.base
277
return "%s(%s)" % (self.__class__.__name__, self.base)
281
def abort_write_group(self):
282
"""Complete a write group on the decorated repository.
284
Smart methods peform operations in a single step so this api
285
is not really applicable except as a compatibility thunk
286
for older plugins that don't use e.g. the CommitBuilder
290
return self._real_repository.abort_write_group()
292
def commit_write_group(self):
293
"""Complete a write group on the decorated repository.
295
Smart methods peform operations in a single step so this api
296
is not really applicable except as a compatibility thunk
297
for older plugins that don't use e.g. the CommitBuilder
301
return self._real_repository.commit_write_group()
247
303
def _ensure_real(self):
248
304
"""Ensure that there is a _real_repository set.
254
310
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
255
311
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
313
def find_text_key_references(self):
314
"""Find the text key references within the repository.
316
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
317
revision_ids. Each altered file-ids has the exact revision_ids that
318
altered it listed explicitly.
319
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
320
to whether they were referred to by the inventory of the
321
revision_id that they contain. The inventory texts from all present
322
revision ids are assessed to generate this report.
325
return self._real_repository.find_text_key_references()
327
def _generate_text_key_index(self):
328
"""Generate a new text key index for the repository.
330
This is an expensive function that will take considerable time to run.
332
:return: A dict mapping (file_id, revision_id) tuples to a list of
333
parents, also (file_id, revision_id) tuples.
336
return self._real_repository._generate_text_key_index()
257
338
def get_revision_graph(self, revision_id=None):
258
339
"""See Repository.get_revision_graph()."""
259
340
if revision_id is None:
261
elif revision_id == NULL_REVISION:
342
elif revision.is_null(revision_id):
264
345
path = self.bzrdir._path_for_remote_call(self._client)
294
375
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
295
376
return response[0] == 'yes'
378
def has_same_location(self, other):
379
return (self.__class__ == other.__class__ and
380
self.bzrdir.transport.base == other.bzrdir.transport.base)
382
def get_graph(self, other_repository=None):
383
"""Return the graph for this repository format"""
385
return self._real_repository.get_graph(other_repository)
297
387
def gather_stats(self, revid=None, committers=None):
298
388
"""See Repository.gather_stats()."""
299
389
path = self.bzrdir._path_for_remote_call(self._client)
300
if revid in (None, NULL_REVISION):
390
# revid can be None to indicate no revisions, not just NULL_REVISION
391
if revid is None or revision.is_null(revid):
303
394
fmt_revid = revid
418
def find_branches(self, using=False):
419
"""See Repository.find_branches()."""
420
# should be an API call to the server.
422
return self._real_repository.find_branches(using=using)
327
424
def get_physical_lock_status(self):
328
425
"""See Repository.get_physical_lock_status()."""
426
# should be an API call to the server.
428
return self._real_repository.get_physical_lock_status()
430
def is_in_write_group(self):
431
"""Return True if there is an open write group.
433
write groups are only applicable locally for the smart server..
435
if self._real_repository:
436
return self._real_repository.is_in_write_group()
439
return self._lock_count >= 1
331
441
def is_shared(self):
332
442
"""See Repository.is_shared()."""
357
470
raise errors.LockContention('(remote lock)')
358
471
elif response[0] == 'UnlockableTransport':
359
472
raise errors.UnlockableTransport(self.bzrdir.root_transport)
473
elif response[0] == 'LockFailed':
474
raise errors.LockFailed(response[1], response[2])
361
assert False, 'unexpected response code %s' % (response,)
476
raise errors.UnexpectedSmartServerResponse(response)
363
478
def lock_write(self, token=None):
364
479
if not self._lock_mode:
365
480
self._lock_token = self._remote_lock_write(token)
366
assert self._lock_token, 'Remote server did not return a token!'
481
# if self._lock_token is None, then this is something like packs or
482
# svn where we don't get to lock the repo, or a weave style repository
483
# where we cannot lock it over the wire and attempts to do so will
367
485
if self._real_repository is not None:
368
486
self._real_repository.lock_write(token=self._lock_token)
369
487
if token is not None:
376
494
raise errors.ReadOnlyError(self)
378
496
self._lock_count += 1
379
return self._lock_token
497
return self._lock_token or None
381
499
def leave_lock_in_place(self):
500
if not self._lock_token:
501
raise NotImplementedError(self.leave_lock_in_place)
382
502
self._leave_lock = True
384
504
def dont_leave_lock_in_place(self):
505
if not self._lock_token:
506
raise NotImplementedError(self.dont_leave_lock_in_place)
385
507
self._leave_lock = False
387
509
def _set_real_repository(self, repository):
399
521
elif self._lock_mode == 'r':
400
522
self._real_repository.lock_read()
524
def start_write_group(self):
525
"""Start a write group on the decorated repository.
527
Smart methods peform operations in a single step so this api
528
is not really applicable except as a compatibility thunk
529
for older plugins that don't use e.g. the CommitBuilder
533
return self._real_repository.start_write_group()
402
535
def _unlock(self, token):
403
536
path = self.bzrdir._path_for_remote_call(self._client)
538
# with no token the remote repository is not persistently locked.
404
540
response = self._client.call('Repository.unlock', path, token)
405
541
if response == ('ok',):
407
543
elif response[0] == 'TokenMismatch':
408
544
raise errors.TokenMismatch(token, '(remote token)')
410
assert False, 'unexpected response code %s' % (response,)
546
raise errors.UnexpectedSmartServerResponse(response)
412
548
def unlock(self):
413
549
self._lock_count -= 1
414
if not self._lock_count:
415
mode = self._lock_mode
416
self._lock_mode = None
550
if self._lock_count > 0:
552
old_mode = self._lock_mode
553
self._lock_mode = None
555
# The real repository is responsible at present for raising an
556
# exception if it's in an unfinished write group. However, it
557
# normally will *not* actually remove the lock from disk - that's
558
# done by the server on receiving the Repository.unlock call.
559
# This is just to let the _real_repository stay up to date.
417
560
if self._real_repository is not None:
418
561
self._real_repository.unlock()
563
# The rpc-level lock should be released even if there was a
564
# problem releasing the vfs-based lock.
420
566
# Only write-locked repositories need to make a remote method
421
567
# call to perfom the unlock.
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:
568
old_token = self._lock_token
569
self._lock_token = None
570
if not self._leave_lock:
571
self._unlock(old_token)
429
573
def break_lock(self):
430
574
# should hand off to the network
432
576
return self._real_repository.break_lock()
434
578
def _get_tarball(self, compression):
435
"""Return a TemporaryFile containing a repository tarball"""
579
"""Return a TemporaryFile containing a repository tarball.
581
Returns None if the server does not support sending tarballs.
437
584
path = self.bzrdir._path_for_remote_call(self._client)
438
585
response, protocol = self._client.call_expecting_body(
439
586
'Repository.tarball', path, compression)
440
assert response[0] in ('ok', 'failure'), \
441
'unexpected response code %s' % (response,)
442
587
if response[0] == 'ok':
443
588
# Extract the tarball and return it
444
589
t = tempfile.NamedTemporaryFile()
446
591
t.write(protocol.read_body_bytes())
450
raise errors.SmartServerError(error_code=response)
594
if (response == ('error', "Generic bzr smart protocol error: "
595
"bad request 'Repository.tarball'") or
596
response == ('error', "Generic bzr smart protocol error: "
597
"bad request u'Repository.tarball'")):
598
protocol.cancel_read_body()
600
raise errors.UnexpectedSmartServerResponse(response)
452
602
def sprout(self, to_bzrdir, revision_id=None):
453
603
# TODO: Option to control what format is created?
454
to_repo = to_bzrdir.create_repository()
455
self._copy_repository_tarball(to_repo, revision_id)
605
dest_repo = self._real_repository._format.initialize(to_bzrdir,
607
dest_repo.fetch(self, revision_id=revision_id)
458
610
### These methods are just thin shims to the VFS object for now.
461
613
self._ensure_real()
462
614
return self._real_repository.revision_tree(revision_id)
616
def get_serializer_format(self):
618
return self._real_repository.get_serializer_format()
464
620
def get_commit_builder(self, branch, parents, config, timestamp=None,
465
621
timezone=None, committer=None, revprops=None,
466
622
revision_id=None):
470
626
builder = self._real_repository.get_commit_builder(branch, parents,
471
627
config, timestamp=timestamp, timezone=timezone,
472
628
committer=committer, revprops=revprops, revision_id=revision_id)
473
# Make the builder use this RemoteRepository rather than the real one.
474
builder.repository = self
477
631
@needs_write_lock
516
670
def fetch(self, source, revision_id=None, pb=None):
671
if self.has_same_location(source):
672
# check that last_revision is in 'from' and then return a
674
if (revision_id is not None and
675
not revision.is_null(revision_id)):
676
self.get_revision(revision_id)
517
678
self._ensure_real()
518
679
return self._real_repository.fetch(
519
680
source, revision_id=revision_id, pb=pb)
682
def create_bundle(self, target, base, fileobj, format=None):
684
self._real_repository.create_bundle(target, base, fileobj, format)
522
687
def control_weaves(self):
523
688
self._ensure_real()
524
689
return self._real_repository.control_weaves
527
def get_ancestry(self, revision_id):
692
def get_ancestry(self, revision_id, topo_sorted=True):
528
693
self._ensure_real()
529
return self._real_repository.get_ancestry(revision_id)
694
return self._real_repository.get_ancestry(revision_id, topo_sorted)
532
697
def get_inventory_weave(self):
537
702
self._ensure_real()
538
703
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
705
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
707
return self._real_repository._get_versioned_file_checker(
708
revisions, revision_versions_cache)
710
def iter_files_bytes(self, desired_files):
711
"""See Repository.iter_file_bytes.
714
return self._real_repository.iter_files_bytes(desired_files)
541
717
def get_signature_text(self, revision_id):
542
718
self._ensure_real()
586
762
return self._real_repository.get_revision_reconcile(revision_id)
589
def check(self, revision_ids):
765
def check(self, revision_ids=None):
590
766
self._ensure_real()
591
return self._real_repository.check(revision_ids)
767
return self._real_repository.check(revision_ids=revision_ids)
593
769
def copy_content_into(self, destination, revision_id=None):
594
770
self._ensure_real()
595
771
return self._real_repository.copy_content_into(
596
772
destination, revision_id=revision_id)
598
def _copy_repository_tarball(self, destination, revision_id=None):
774
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
599
775
# get a tarball of the remote repository, and copy from that into the
601
777
from bzrlib import osutils
618
797
osutils.rmtree(tmpdir)
621
# TODO: if the server doesn't support this operation, maybe do it the
622
# slow way using the _real_repository?
624
801
# TODO: Suggestion from john: using external tar is much faster than
625
802
# python's tarfile library, but it may not work on windows.
806
"""Compress the data within the repository.
808
This is not currently implemented within the smart server.
811
return self._real_repository.pack()
627
813
def set_make_working_trees(self, new_value):
628
814
raise NotImplementedError(self.set_make_working_trees)
655
841
return self._real_repository.store_revision_signature(
656
842
gpg_strategy, plaintext, revision_id)
844
def add_signature_text(self, revision_id, signature):
846
return self._real_repository.add_signature_text(revision_id, signature)
658
848
def has_signature_for_revision_id(self, revision_id):
659
849
self._ensure_real()
660
850
return self._real_repository.has_signature_for_revision_id(revision_id)
852
def get_data_stream(self, revision_ids):
853
path = self.bzrdir._path_for_remote_call(self._client)
854
response, protocol = self._client.call_expecting_body(
855
'Repository.stream_knit_data_for_revisions', path, *revision_ids)
856
if response == ('ok',):
857
return self._deserialise_stream(protocol)
858
elif (response == ('error', "Generic bzr smart protocol error: "
859
"bad request 'Repository.stream_knit_data_for_revisions'") or
860
response == ('error', "Generic bzr smart protocol error: "
861
"bad request u'Repository.stream_knit_data_for_revisions'")):
862
protocol.cancel_read_body()
864
return self._real_repository.get_data_stream(revision_ids)
866
raise errors.UnexpectedSmartServerResponse(response)
868
def _deserialise_stream(self, protocol):
869
buffer = StringIO(protocol.read_body_bytes())
870
reader = ContainerReader(buffer)
871
for record_names, read_bytes in reader.iter_records():
873
# These records should have only one name, and that name
874
# should be a one-element tuple.
875
[name_tuple] = record_names
877
raise errors.SmartProtocolError(
878
'Repository data stream had invalid record name %r'
880
yield name_tuple, read_bytes(None)
882
def insert_data_stream(self, stream):
884
self._real_repository.insert_data_stream(stream)
886
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
888
return self._real_repository.item_keys_introduced_by(revision_ids,
891
def revision_graph_can_have_wrong_parents(self):
892
# The answer depends on the remote repo format.
894
return self._real_repository.revision_graph_can_have_wrong_parents()
896
def _find_inconsistent_revision_parents(self):
898
return self._real_repository._find_inconsistent_revision_parents()
900
def _check_for_inconsistent_revision_parents(self):
902
return self._real_repository._check_for_inconsistent_revision_parents()
904
def _make_parents_provider(self):
906
return self._real_repository._make_parents_provider()
663
909
class RemoteBranchLockableFiles(LockableFiles):
664
910
"""A 'LockableFiles' implementation that talks to a smart server.
736
987
# We intentionally don't call the parent class's __init__, because it
737
988
# will try to assign to self.tags, which is a property in this subclass.
738
989
# And the parent's __init__ doesn't do much anyway.
990
self._revision_id_to_revno_cache = None
739
991
self._revision_history_cache = None
740
992
self.bzrdir = remote_bzrdir
741
993
if _client is not None:
742
994
self._client = _client
744
self._client = client._SmartClient(self.bzrdir._medium)
996
self._client = client._SmartClient(self.bzrdir._shared_medium)
745
997
self.repository = remote_repository
746
998
if real_branch is not None:
747
999
self._real_branch = real_branch
839
1091
raise errors.UnlockableTransport(self.bzrdir.root_transport)
840
1092
elif response[0] == 'ReadOnlyError':
841
1093
raise errors.ReadOnlyError(self)
1094
elif response[0] == 'LockFailed':
1095
raise errors.LockFailed(response[1], response[2])
843
assert False, 'unexpected response code %r' % (response,)
1097
raise errors.UnexpectedSmartServerResponse(response)
845
1099
def lock_write(self, token=None):
846
1100
if not self._lock_mode:
878
1132
if token != self._lock_token:
879
1133
raise errors.TokenMismatch(token, self._lock_token)
880
1134
self._lock_count += 1
881
return self._lock_token
1135
return self._lock_token or None
883
1137
def _unlock(self, branch_token, repo_token):
884
1138
path = self.bzrdir._path_for_remote_call(self._client)
885
1139
response = self._client.call('Branch.unlock', path, branch_token,
887
1141
if response == ('ok',):
889
1143
elif response[0] == 'TokenMismatch':
890
1144
raise errors.TokenMismatch(
891
1145
str((branch_token, repo_token)), '(remote tokens)')
893
assert False, 'unexpected response code %s' % (response,)
1147
raise errors.UnexpectedSmartServerResponse(response)
895
1149
def unlock(self):
896
1150
self._lock_count -= 1
985
1244
# format, because RemoteBranches can't be created at arbitrary URLs.
986
1245
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
987
1246
# to_bzrdir.create_branch...
988
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1248
result = self._real_branch._format.initialize(to_bzrdir)
989
1249
self.copy_content_into(result, revision_id=revision_id)
990
1250
result.set_parent(self.bzrdir.root_transport.base)
993
1253
@needs_write_lock
994
def append_revision(self, *revision_ids):
996
return self._real_branch.append_revision(*revision_ids)
999
1254
def pull(self, source, overwrite=False, stop_revision=None,
1001
1256
# FIXME: This asks the real branch to run the hooks, which means
1038
1293
self._ensure_real()
1039
1294
return self._real_branch.set_push_location(location)
1041
def update_revisions(self, other, stop_revision=None):
1296
def update_revisions(self, other, stop_revision=None, overwrite=False):
1042
1297
self._ensure_real()
1043
1298
return self._real_branch.update_revisions(
1044
other, stop_revision=stop_revision)
1299
other, stop_revision=stop_revision, overwrite=overwrite)
1047
1302
class RemoteBranchConfig(BranchConfig):