62
def recreate_search(self, repository, recipe_bytes):
63
lines = recipe_bytes.split('\n')
64
start_keys = set(lines[0].split(' '))
65
exclude_keys = set(lines[1].split(' '))
66
revision_count = int(lines[2])
67
repository.lock_read()
69
search = repository.get_graph()._make_breadth_first_searcher(
73
next_revs = search.next()
76
search.stop_searching_any(exclude_keys.intersection(next_revs))
77
search_result = search.get_result()
78
if search_result.get_recipe()[2] != revision_count:
79
# we got back a different amount of data than expected, this
80
# gets reported as NoSuchRevision, because less revisions
81
# indicates missing revisions, and more should never happen as
82
# the excludes list considers ghosts and ensures that ghost
83
# filling races are not a problem.
84
return (None, FailedSmartServerResponse(('NoSuchRevision',)))
62
90
class SmartServerRepositoryGetParentMap(SmartServerRepositoryRequest):
91
"""Bzr 1.2+ - get parent data for revisions during a graph search."""
64
93
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
94
"""Get parent details for some revisions.
74
96
All the parents for revision_ids are returned. Additionally up to 64KB
75
97
of additional parent data found by performing a breadth first search
76
from revision_ids is returned.
98
from revision_ids is returned. The verb takes a body containing the
99
current search state, see do_body for details.
78
101
:param repository: The repository to query in.
79
102
:param revision_ids: The utf8 encoded revision_id to answer for.
104
self._revision_ids = revision_ids
105
return None # Signal that we want a body.
107
def do_body(self, body_bytes):
108
"""Process the current search state and perform the parent lookup.
80
110
: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).
111
encoded flattened list of the parents of the revisions (the same
112
format as Repository.get_revision_graph) which has been gzipped.
114
repository = self._repository
115
repository.lock_read()
117
return self._do_repository_request(body_bytes)
121
def _do_repository_request(self, body_bytes):
122
repository = self._repository
123
revision_ids = set(self._revision_ids)
124
search, error = self.recreate_search(repository, body_bytes)
125
if error is not None:
127
# TODO might be nice to start up the search again; but thats not
128
# written or tested yet.
129
client_seen_revs = set(search.get_result().get_keys())
130
# Always include the requested ids.
131
client_seen_revs.difference_update(revision_ids)
85
133
repo_graph = repository.get_graph()
96
144
# adjust for the wire
97
145
if parents == (_mod_revision.NULL_REVISION,):
99
# add parents to the result
100
result[revision_id] = parents
101
147
# prepare the next query
102
148
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:
149
if revision_id not in client_seen_revs:
150
# Client does not have this revision, give it to it.
151
# add parents to the result
152
result[revision_id] = parents
153
# Approximate the serialized cost of this revision_id.
154
size_so_far += 2 + len(revision_id) + sum(map(len, parents))
155
# get all the directly asked for parents, and then flesh out to
156
# 64K (compressed) or so. We do one level of depth at a time to
157
# stay in sync with the client. The 185000 magic number is
158
# estimated compression ratio taken from bzr.dev itself.
159
if first_loop_done and size_so_far > 185000:
110
162
# don't query things we've already queried
111
163
next_revs.difference_update(queried_revs)
112
164
first_loop_done = True
114
for revision, parents in result.items():
166
# sorting trivially puts lexographically similar revision ids together.
168
for revision, parents in sorted(result.items()):
115
169
lines.append(' '.join((revision, ) + tuple(parents)))
117
return SuccessfulSmartServerResponse(('ok', ), '\n'.join(lines))
171
return SuccessfulSmartServerResponse(
172
('ok', ), bytes_to_gzip('\n'.join(lines)))
120
175
class SmartServerRepositoryGetRevisionGraph(SmartServerRepositoryRequest):
342
397
"""Bzr 1.1+ streaming pull."""
344
399
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
400
repository = self._repository
350
401
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)
403
search, error = self.recreate_search(repository, body_bytes)
404
if error is not None:
406
stream = repository.get_data_stream_for_search(search.get_result())
369
407
except Exception:
408
# On non-error, unlocking is done by the body stream handler.
370
409
repository.unlock()
372
411
return SuccessfulSmartServerResponse(('ok',),