41
40
from bzrlib.repository import _strip_NULL_ghosts, network_format_registry
42
41
from bzrlib import revision as _mod_revision
43
from bzrlib.versionedfile import (
45
record_to_fulltext_bytes,
42
from bzrlib.util import bencode
43
from bzrlib.versionedfile import NetworkRecordStream, record_to_fulltext_bytes
49
46
class SmartServerRepositoryRequest(SmartServerRequest):
77
def recreate_search(self, repository, search_bytes, discard_excess=False):
78
"""Recreate a search from its serialised form.
80
:param discard_excess: If True, and the search refers to data we don't
81
have, just silently accept that fact - the verb calling
82
recreate_search trusts that clients will look for missing things
83
they expected and get it from elsewhere.
74
def recreate_search(self, repository, search_bytes):
85
75
lines = search_bytes.split('\n')
86
76
if lines[0] == 'ancestry-of':
88
78
search_result = graph.PendingAncestryResult(heads, repository)
89
79
return search_result, None
90
80
elif lines[0] == 'search':
91
return self.recreate_search_from_recipe(repository, lines[1:],
92
discard_excess=discard_excess)
81
return self.recreate_search_from_recipe(repository, lines[1:])
94
83
return (None, FailedSmartServerResponse(('BadSearch',)))
96
def recreate_search_from_recipe(self, repository, lines,
97
discard_excess=False):
98
"""Recreate a specific revision search (vs a from-tip search).
100
:param discard_excess: If True, and the search refers to data we don't
101
have, just silently accept that fact - the verb calling
102
recreate_search trusts that clients will look for missing things
103
they expected and get it from elsewhere.
85
def recreate_search_from_recipe(self, repository, lines):
105
86
start_keys = set(lines[0].split(' '))
106
87
exclude_keys = set(lines[1].split(' '))
107
88
revision_count = int(lines[2])
117
98
search.stop_searching_any(exclude_keys.intersection(next_revs))
118
99
search_result = search.get_result()
119
if (not discard_excess and
120
search_result.get_recipe()[3] != revision_count):
100
if search_result.get_recipe()[3] != revision_count:
121
101
# we got back a different amount of data than expected, this
122
102
# gets reported as NoSuchRevision, because less revisions
123
103
# indicates missing revisions, and more should never happen as
287
267
return SuccessfulSmartServerResponse(('ok', ), '\n'.join(lines))
290
class SmartServerRepositoryGetRevIdForRevno(SmartServerRepositoryReadLocked):
292
def do_readlocked_repository_request(self, repository, revno,
294
"""Find the revid for a given revno, given a known revno/revid pair.
299
found_flag, result = repository.get_rev_id_for_revno(revno, known_pair)
300
except errors.RevisionNotPresent, err:
301
if err.revision_id != known_pair[1]:
302
raise AssertionError(
303
'get_rev_id_for_revno raised RevisionNotPresent for '
304
'non-initial revision: ' + err.revision_id)
305
return FailedSmartServerResponse(
306
('nosuchrevision', err.revision_id))
308
return SuccessfulSmartServerResponse(('ok', result))
310
earliest_revno, earliest_revid = result
311
return SuccessfulSmartServerResponse(
312
('history-incomplete', earliest_revno, earliest_revid))
315
270
class SmartServerRequestHasRevision(SmartServerRepositoryRequest):
317
272
def do_repository_request(self, repository, revision_id):
420
375
self._to_format = network_format_registry.get(to_network_name)
421
if self._should_fake_unknown():
422
return FailedSmartServerResponse(
423
('UnknownMethod', 'Repository.get_stream'))
424
376
return None # Signal that we want a body.
426
def _should_fake_unknown(self):
427
"""Return True if we should return UnknownMethod to the client.
429
This is a workaround for bugs in pre-1.19 clients that claim to
430
support receiving streams of CHK repositories. The pre-1.19 client
431
expects inventory records to be serialized in the format defined by
432
to_network_name, but in pre-1.19 (at least) that format definition
433
tries to use the xml5 serializer, which does not correctly handle
434
rich-roots. After 1.19 the client can also accept inventory-deltas
435
(which avoids this issue), and those clients will use the
436
Repository.get_stream_1.19 verb instead of this one.
437
So: if this repository is CHK, and the to_format doesn't match,
438
we should just fake an UnknownSmartMethod error so that the client
439
will fallback to VFS, rather than sending it a stream we know it
442
from_format = self._repository._format
443
to_format = self._to_format
444
if not from_format.supports_chks:
445
# Source not CHK: that's ok
447
if (to_format.supports_chks and
448
from_format.repository_class is to_format.repository_class and
449
from_format._serializer == to_format._serializer):
450
# Source is CHK, but target matches: that's ok
451
# (e.g. 2a->2a, or CHK2->2a)
453
# Source is CHK, and target is not CHK or incompatible CHK. We can't
454
# generate a compatible stream.
457
378
def do_body(self, body_bytes):
458
379
repository = self._repository
459
380
repository.lock_read()
461
search_result, error = self.recreate_search(repository, body_bytes,
382
search_result, error = self.recreate_search(repository, body_bytes)
463
383
if error is not None:
464
384
repository.unlock()
489
409
repository.unlock()
492
class SmartServerRepositoryGetStream_1_19(SmartServerRepositoryGetStream):
494
def _should_fake_unknown(self):
495
"""Returns False; we don't need to workaround bugs in 1.19+ clients."""
499
412
def _stream_to_byte_stream(stream, src_format):
500
413
"""Convert a record stream to a self delimited byte stream."""
501
414
pack_writer = pack.ContainerSerialiser()
505
418
for record in substream:
506
419
if record.storage_kind in ('chunked', 'fulltext'):
507
420
serialised = record_to_fulltext_bytes(record)
508
elif record.storage_kind == 'inventory-delta':
509
serialised = record_to_inventory_delta_bytes(record)
510
elif record.storage_kind == 'absent':
511
raise ValueError("Absent factory for %s" % (record.key,))
513
422
serialised = record.get_bytes_as(record.storage_kind)
697
606
return SuccessfulSmartServerResponse(('ok', ))
700
class SmartServerRepositoryInsertStream_1_19(SmartServerRepositoryInsertStreamLocked):
701
"""Insert a record stream from a RemoteSink into a repository.
703
Same as SmartServerRepositoryInsertStreamLocked, except:
704
- the lock token argument is optional
705
- servers that implement this verb accept 'inventory-delta' records in the
711
def do_repository_request(self, repository, resume_tokens, lock_token=None):
712
"""StreamSink.insert_stream for a remote repository."""
713
SmartServerRepositoryInsertStreamLocked.do_repository_request(
714
self, repository, resume_tokens, lock_token)
717
609
class SmartServerRepositoryInsertStream(SmartServerRepositoryInsertStreamLocked):
718
610
"""Insert a record stream from a RemoteSink into an unlocked repository.