24
25
from bzrlib import errors
25
26
from bzrlib.bzrdir import BzrDir
27
from bzrlib.pack import ContainerSerialiser
26
28
from bzrlib.smart.request import (
27
29
FailedSmartServerResponse,
28
30
SmartServerRequest,
29
31
SuccessfulSmartServerResponse,
33
from bzrlib import revision as _mod_revision
33
36
class SmartServerRepositoryRequest(SmartServerRequest):
46
49
transport = self._backing_transport.clone(path)
47
50
bzrdir = BzrDir.open_from_transport(transport)
48
repository = bzrdir.open_repository()
49
return self.do_repository_request(repository, *args)
51
# Save the repository for use with do_body.
52
self._repository = bzrdir.open_repository()
53
return self.do_repository_request(self._repository, *args)
55
def do_repository_request(self, repository, *args):
56
"""Override to provide an implementation for a verb."""
57
# No-op for verbs that take bodies (None as a result indicates a body
62
class SmartServerRepositoryGetParentMap(SmartServerRepositoryRequest):
64
def do_repository_request(self, repository, *revision_ids):
65
repository.lock_read()
67
return self._do_repository_request(repository, revision_ids)
71
def _do_repository_request(self, repository, revision_ids):
72
"""Get parent details for some revisions.
74
All the parents for revision_ids are returned. Additionally up to 64KB
75
of additional parent data found by performing a breadth first search
76
from revision_ids is returned.
78
:param repository: The repository to query in.
79
:param revision_ids: The utf8 encoded revision_id to answer for.
80
:return: A smart server response where the body contains an utf8
81
encoded flattened list of the parents of the revisions, (the same
82
format as Repository.get_revision_graph).
85
repo_graph = repository.get_graph()
89
next_revs = revision_ids
90
first_loop_done = False
92
queried_revs.update(next_revs)
93
parent_map = repo_graph.get_parent_map(next_revs)
95
for revision_id, parents in parent_map.iteritems():
97
if parents == (_mod_revision.NULL_REVISION,):
99
# add parents to the result
100
result[revision_id] = parents
101
# prepare the next query
102
next_revs.update(parents)
103
# Approximate the serialized cost of this revision_id.
104
size_so_far += 2 + len(revision_id) + sum(map(len, parents))
105
# get all the directly asked for parents, and then flesh out to
107
if first_loop_done and size_so_far > 65000:
110
# don't query things we've already queried
111
next_revs.difference_update(queried_revs)
112
first_loop_done = True
114
for revision, parents in result.items():
115
lines.append(' '.join((revision, ) + tuple(parents)))
117
return SuccessfulSmartServerResponse(('ok', ), '\n'.join(lines))
52
120
class SmartServerRepositoryGetRevisionGraph(SmartServerRepositoryRequest):
163
231
return FailedSmartServerResponse(('LockContention',))
164
232
except errors.UnlockableTransport:
165
233
return FailedSmartServerResponse(('UnlockableTransport',))
166
repository.leave_lock_in_place()
234
except errors.LockFailed, e:
235
return FailedSmartServerResponse(('LockFailed',
236
str(e.lock), str(e.why)))
237
if token is not None:
238
repository.leave_lock_in_place()
167
239
repository.unlock()
168
240
if token is None:
239
311
tarball.add(dirname, '.bzr') # recursive by default
316
class SmartServerRepositoryStreamKnitDataForRevisions(SmartServerRepositoryRequest):
317
"""Bzr <= 1.1 streaming pull, buffers all data on server."""
319
def do_repository_request(self, repository, *revision_ids):
320
repository.lock_read()
322
return self._do_repository_request(repository, revision_ids)
326
def _do_repository_request(self, repository, revision_ids):
327
stream = repository.get_data_stream_for_search(
328
repository.revision_ids_to_search_result(set(revision_ids)))
330
pack = ContainerSerialiser()
331
buffer.write(pack.begin())
333
for name_tuple, bytes in stream:
334
buffer.write(pack.bytes_record(bytes, [name_tuple]))
335
except errors.RevisionNotPresent, e:
336
return FailedSmartServerResponse(('NoSuchRevision', e.revision_id))
337
buffer.write(pack.end())
338
return SuccessfulSmartServerResponse(('ok',), buffer.getvalue())
341
class SmartServerRepositoryStreamRevisionsChunked(SmartServerRepositoryRequest):
342
"""Bzr 1.1+ streaming pull."""
344
def do_body(self, body_bytes):
345
lines = body_bytes.split('\n')
346
start_keys = set(lines[0].split(' '))
347
exclude_keys = set(lines[1].split(' '))
348
revision_count = int(lines[2])
349
repository = self._repository
350
repository.lock_read()
352
search = repository.get_graph()._make_breadth_first_searcher(
356
next_revs = search.next()
357
except StopIteration:
359
search.stop_searching_any(exclude_keys.intersection(next_revs))
360
search_result = search.get_result()
361
if search_result.get_recipe()[2] != revision_count:
362
# we got back a different amount of data than expected, this
363
# gets reported as NoSuchRevision, because less revisions
364
# indicates missing revisions, and more should never happen as
365
# the excludes list considers ghosts and ensures that ghost
366
# filling races are not a problem.
367
return FailedSmartServerResponse(('NoSuchRevision',))
368
stream = repository.get_data_stream_for_search(search_result)
372
return SuccessfulSmartServerResponse(('ok',),
373
body_stream=self.body_stream(stream, repository))
375
def body_stream(self, stream, repository):
376
pack = ContainerSerialiser()
379
for name_tuple, bytes in stream:
380
yield pack.bytes_record(bytes, [name_tuple])
381
except errors.RevisionNotPresent, e:
382
# This shouldn't be able to happen, but as we don't buffer
383
# everything it can in theory happen.
384
yield FailedSmartServerResponse(('NoSuchRevision', e.revision_id))