209
class _MPDiffGenerator(object):
210
"""Pull out the functionality for generating mp_diffs."""
212
def __init__(self, vf, keys):
214
# This is the order the keys were requested in
215
self.ordered_keys = tuple(keys)
216
# keys + their parents, what we need to compute the diffs
217
self.needed_keys = ()
218
# Map from key: mp_diff
220
# Map from key: parents_needed (may have ghosts)
222
# Parents that aren't present
223
self.ghost_parents = ()
224
# Map from parent_key => number of children for this text
226
# Content chunks that are cached while we still need them
229
def _find_needed_keys(self):
230
"""Find the set of keys we need to request.
232
This includes all the original keys passed in, and the non-ghost
233
parents of those keys.
235
:return: (needed_keys, refcounts)
236
needed_keys is the set of all texts we need to extract
237
refcounts is a dict of {key: num_children} letting us know when we
238
no longer need to cache a given parent text
240
# All the keys and their parents
241
needed_keys = set(self.ordered_keys)
242
parent_map = self.vf.get_parent_map(needed_keys)
243
self.parent_map = parent_map
244
# TODO: Should we be using a different construct here? I think this
245
# uses difference_update internally, and we expect the result to
247
missing_keys = needed_keys.difference(parent_map)
249
raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
250
# Parents that might be missing. They are allowed to be ghosts, but we
251
# should check for them
253
setdefault = refcounts.setdefault
255
for child_key, parent_keys in parent_map.iteritems():
257
# Ghost? We should never get here
259
maybe_ghosts.update(p for p in parent_keys if p not in needed_keys)
260
needed_keys.update(parent_keys)
261
for p in parent_keys:
262
refcounts[p] = setdefault(p, 0) + 1
263
# Remove any parents that are actually ghosts
264
self.ghost_parents = maybe_ghosts.difference(
265
self.vf.get_parent_map(maybe_ghosts))
266
needed_keys.difference_update(self.ghost_parents)
267
self.needed_keys = needed_keys
268
self.refcounts = refcounts
269
return needed_keys, refcounts
271
def _compute_diff(self, key, parent_lines, lines):
272
"""Compute a single mp_diff, and store it in self._diffs"""
273
if len(parent_lines) > 0:
274
# XXX: _extract_blocks is not usefully defined anywhere...
275
# It was meant to extract the left-parent diff without
276
# having to recompute it for Knit content (pack-0.92,
277
# etc). That seems to have regressed somewhere
278
left_parent_blocks = self.vf._extract_blocks(key,
279
parent_lines[0], lines)
281
left_parent_blocks = None
282
diff = multiparent.MultiParent.from_lines(lines,
283
parent_lines, left_parent_blocks)
284
self.diffs[key] = diff
286
def _process_one_record(self, record):
287
this_chunks = record.get_bytes_as('chunked')
288
if record.key in self.parent_map:
289
# This record should be ready to diff, since we requested
290
# content in 'topological' order
291
parent_keys = self.parent_map.pop(record.key)
292
# If a VersionedFile claims 'no-graph' support, then it may return
293
# None for any parent request, so we replace it with an empty tuple
294
if parent_keys is None:
297
for p in parent_keys:
298
# Alternatively we could check p not in self.needed_keys, but
299
# ghost_parents should be tiny versus huge
300
if p in self.ghost_parents:
302
refcount = self.refcounts[p]
303
if refcount == 1: # Last child reference
304
self.refcounts.pop(p)
305
parent_chunks = self.chunks.pop(p)
307
self.refcounts[p] = refcount - 1
308
parent_chunks = self.chunks[p]
309
p_lines = osutils.chunks_to_lines(parent_chunks)
310
# TODO: Should we cache the line form? We did the
311
# computation to get it, but storing it this way will
312
# be less memory efficient...
313
parent_lines.append(p_lines)
315
lines = osutils.chunks_to_lines(this_chunks)
316
# Since we needed the lines, we'll go ahead and cache them this way
318
self._compute_diff(record.key, parent_lines, lines)
320
# Is this content required for any more children?
321
if record.key in self.refcounts:
322
self.chunks[record.key] = this_chunks
324
def _extract_diffs(self):
325
needed_keys, refcounts = self._find_needed_keys()
326
for record in self.vf.get_record_stream(needed_keys,
327
'topological', True):
328
if record.storage_kind == 'absent':
329
raise errors.RevisionNotPresent(record.key, self.vf)
330
self._process_one_record(record)
331
# At this point, we should have *no* remaining content
332
assert not self.parent_map
333
assert set(self.refcounts) == self.ghost_parents
335
def compute_diffs(self):
336
self._extract_diffs()
337
dpop = self.diffs.pop
338
return [dpop(k) for k in self.ordered_keys]
209
341
class VersionedFile(object):
210
342
"""Versioned text file storage.
1048
1184
def make_mpdiffs(self, keys):
1049
1185
"""Create multiparent diffs for specified keys."""
1050
keys_order = tuple(keys)
1051
keys = frozenset(keys)
1052
knit_keys = set(keys)
1053
parent_map = self.get_parent_map(keys)
1054
for parent_keys in parent_map.itervalues():
1056
knit_keys.update(parent_keys)
1057
missing_keys = keys - set(parent_map)
1059
raise errors.RevisionNotPresent(list(missing_keys)[0], self)
1060
# We need to filter out ghosts, because we can't diff against them.
1061
maybe_ghosts = knit_keys - keys
1062
ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
1063
knit_keys.difference_update(ghosts)
1065
chunks_to_lines = osutils.chunks_to_lines
1066
for record in self.get_record_stream(knit_keys, 'topological', True):
1067
lines[record.key] = chunks_to_lines(record.get_bytes_as('chunked'))
1068
# line_block_dict = {}
1069
# for parent, blocks in record.extract_line_blocks():
1070
# line_blocks[parent] = blocks
1071
# line_blocks[record.key] = line_block_dict
1073
for key in keys_order:
1075
parents = parent_map[key] or []
1076
# Note that filtering knit_keys can lead to a parent difference
1077
# between the creation and the application of the mpdiff.
1078
parent_lines = [lines[p] for p in parents if p in knit_keys]
1079
if len(parent_lines) > 0:
1080
left_parent_blocks = self._extract_blocks(key, parent_lines[0],
1083
left_parent_blocks = None
1084
diffs.append(multiparent.MultiParent.from_lines(target,
1085
parent_lines, left_parent_blocks))
1186
generator = _MPDiffGenerator(self, keys)
1187
return generator.compute_diffs()
1189
def get_annotator(self):
1190
return annotate.Annotator(self)
1088
1192
missing_keys = index._missing_keys_from_parent_map