261
262
__contains__ = has_version
264
def get_delta(self, version_id):
265
"""See VersionedFile.get_delta."""
266
return self.get_deltas([version_id])[version_id]
268
def get_deltas(self, version_ids):
269
"""See VersionedFile.get_deltas."""
270
version_ids = self.get_ancestry(version_ids)
271
for version_id in version_ids:
272
if not self.has_version(version_id):
273
raise RevisionNotPresent(version_id, self)
274
# try extracting all versions; parallel extraction is used
275
nv = self.num_versions()
281
last_parent_lines = {}
283
parent_inclusions = {}
288
# its simplest to generate a full set of prepared variables.
290
name = self._names[i]
291
sha1s[name] = self.get_sha1(name)
292
parents_list = self.get_parents(name)
294
parent = parents_list[0]
295
parents[name] = parent
296
parent_inclusions[name] = inclusions[parent]
299
parent_inclusions[name] = set()
300
# we want to emit start, finish, replacement_length, replacement_lines tuples.
301
diff_hunks[name] = []
302
current_hunks[name] = [0, 0, 0, []] # #start, finish, repl_length, repl_tuples
303
parent_linenums[name] = 0
305
parent_noeols[name] = False
306
last_parent_lines[name] = None
307
new_inc = set([name])
308
for p in self._parents[i]:
309
new_inc.update(inclusions[self._idx_to_name(p)])
310
# debug only, known good so far.
311
#assert set(new_inc) == set(self.get_ancestry(name)), \
312
# 'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
313
inclusions[name] = new_inc
315
nlines = len(self._weave)
317
for lineno, inserted, deletes, line in self._walk_internal():
318
# a line is active in a version if:
319
# insert is in the versions inclusions
321
# deleteset & the versions inclusions is an empty set.
322
# so - if we have a included by mapping - version is included by
323
# children, we get a list of children to examine for deletes affect
324
# ing them, which is less than the entire set of children.
325
for version_id in version_ids:
326
# The active inclusion must be an ancestor,
327
# and no ancestors must have deleted this line,
328
# because we don't support resurrection.
329
parent_inclusion = parent_inclusions[version_id]
330
inclusion = inclusions[version_id]
331
parent_active = inserted in parent_inclusion and not (deletes & parent_inclusion)
332
version_active = inserted in inclusion and not (deletes & inclusion)
333
if not parent_active and not version_active:
334
# unrelated line of ancestry
336
elif parent_active and version_active:
338
parent_linenum = parent_linenums[version_id]
339
if current_hunks[version_id] != [parent_linenum, parent_linenum, 0, []]:
340
diff_hunks[version_id].append(tuple(current_hunks[version_id]))
342
current_hunks[version_id] = [parent_linenum, parent_linenum, 0, []]
343
parent_linenums[version_id] = parent_linenum
346
noeols[version_id] = True
349
elif parent_active and not version_active:
351
current_hunks[version_id][1] += 1
352
parent_linenums[version_id] += 1
353
last_parent_lines[version_id] = line
354
elif not parent_active and version_active:
356
# noeol only occurs at the end of a file because we
357
# diff linewise. We want to show noeol changes as a
358
# empty diff unless the actual eol-less content changed.
361
if last_parent_lines[version_id][-1] != '\n':
362
parent_noeols[version_id] = True
363
except (TypeError, IndexError):
366
if theline[-1] != '\n':
367
noeols[version_id] = True
371
parent_should_go = False
373
if parent_noeols[version_id] == noeols[version_id]:
374
# no noeol toggle, so trust the weaves statement
375
# that this line is changed.
377
if parent_noeols[version_id]:
378
theline = theline + '\n'
379
elif parent_noeols[version_id]:
380
# parent has no eol, we do:
381
# our line is new, report as such..
383
elif noeols[version_id]:
384
# append a eol so that it looks like
386
theline = theline + '\n'
387
if parents[version_id] is not None:
388
#if last_parent_lines[version_id] is not None:
389
parent_should_go = True
390
if last_parent_lines[version_id] != theline:
393
#parent_should_go = False
395
current_hunks[version_id][2] += 1
396
current_hunks[version_id][3].append((inserted, theline))
398
# last hunk last parent line is not eaten
399
current_hunks[version_id][1] -= 1
400
if current_hunks[version_id][1] < 0:
401
current_hunks[version_id][1] = 0
402
# import pdb;pdb.set_trace()
403
# assert current_hunks[version_id][1] >= 0
407
version = self._idx_to_name(i)
408
if current_hunks[version] != [0, 0, 0, []]:
409
diff_hunks[version].append(tuple(current_hunks[version]))
411
for version_id in version_ids:
412
result[version_id] = (
416
diff_hunks[version_id],
263
420
def get_parents(self, version_id):
264
421
"""See VersionedFile.get_parent."""
265
422
return map(self._idx_to_name, self._parents[self._lookup(version_id)])
553
712
raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
715
def plan_merge(self, ver_a, ver_b):
716
"""Return pseudo-annotation indicating how the two versions merge.
718
This is computed between versions a and b and their common
721
Weave lines present in none of them are skipped entirely.
723
inc_a = set(self.get_ancestry([ver_a]))
724
inc_b = set(self.get_ancestry([ver_b]))
725
inc_c = inc_a & inc_b
727
for lineno, insert, deleteset, line in\
728
self.walk([ver_a, ver_b]):
729
if deleteset & inc_c:
730
# killed in parent; can't be in either a or b
731
# not relevant to our work
732
yield 'killed-base', line
733
elif insert in inc_c:
734
# was inserted in base
735
killed_a = bool(deleteset & inc_a)
736
killed_b = bool(deleteset & inc_b)
737
if killed_a and killed_b:
738
yield 'killed-both', line
740
yield 'killed-a', line
742
yield 'killed-b', line
744
yield 'unchanged', line
745
elif insert in inc_a:
746
if deleteset & inc_a:
747
yield 'ghost-a', line
751
elif insert in inc_b:
752
if deleteset & inc_b:
753
yield 'ghost-b', line
757
# not in either revision
758
yield 'irrelevant', line
760
yield 'unchanged', '' # terminator
556
762
def _extract(self, versions):
557
763
"""Yield annotation of lines in included set.
580
787
WFE = WeaveFormatError
790
# 449 0 4474.6820 2356.5590 bzrlib.weave:556(_extract)
791
# +285282 0 1676.8040 1676.8040 +<isinstance>
792
# 1.6 seconds in 'isinstance'.
793
# changing the first isinstance:
794
# 449 0 2814.2660 1577.1760 bzrlib.weave:556(_extract)
795
# +140414 0 762.8050 762.8050 +<isinstance>
796
# note that the inline time actually dropped (less function calls)
797
# and total processing time was halved.
798
# we're still spending ~1/4 of the method in isinstance though.
799
# so lets hard code the acceptable string classes we expect:
800
# 449 0 1202.9420 786.2930 bzrlib.weave:556(_extract)
801
# +71352 0 377.5560 377.5560 +<method 'append' of 'list'
803
# yay, down to ~1/4 the initial extract time, and our inline time
804
# has shrunk again, with isinstance no longer dominating.
805
# tweaking the stack inclusion test to use a set gives:
806
# 449 0 1122.8030 713.0080 bzrlib.weave:556(_extract)
807
# +71352 0 354.9980 354.9980 +<method 'append' of 'list'
809
# - a 5% win, or possibly just noise. However with large istacks that
810
# 'in' test could dominate, so I'm leaving this change in place -
811
# when its fast enough to consider profiling big datasets we can review.
582
816
for l in self._weave:
583
if isinstance(l, tuple):
817
if l.__class__ == tuple:
587
assert v not in istack
825
iset.remove(istack.pop())
592
827
if v in included:
593
828
assert v not in dset
636
871
return self._lookup(name_or_index)
638
def _get_iter(self, version_id):
639
"""Yield lines for the specified version."""
640
incls = [self._maybe_lookup(version_id)]
645
# We don't have sha1 sums for multiple entries
647
for origin, lineno, line in self._extract(incls):
652
expected_sha1 = self._sha1s[index]
653
measured_sha1 = cur_sha.hexdigest()
654
if measured_sha1 != expected_sha1:
655
raise errors.WeaveInvalidChecksum(
656
'file %s, revision %s, expected: %s, measured %s'
657
% (self._weave_name, self._names[index],
658
expected_sha1, measured_sha1))
660
873
@deprecated_method(zero_eight)
661
874
def get(self, version_id):
662
875
"""Please use either Weave.get_text or Weave.get_lines as desired."""
665
878
def get_lines(self, version_id):
666
879
"""See VersionedFile.get_lines()."""
667
return list(self._get_iter(version_id))
880
int_index = self._maybe_lookup(version_id)
881
result = [line for (origin, lineno, line) in self._extract([int_index])]
882
expected_sha1 = self._sha1s[int_index]
883
measured_sha1 = sha_strings(result)
884
if measured_sha1 != expected_sha1:
885
raise errors.WeaveInvalidChecksum(
886
'file %s, revision %s, expected: %s, measured %s'
887
% (self._weave_name, version_id,
888
expected_sha1, measured_sha1))
669
def get_sha1(self, name):
670
"""Get the stored sha1 sum for the given revision.
672
:param name: The name of the version to lookup
674
return self._sha1s[self._lookup(name)]
891
def get_sha1(self, version_id):
892
"""See VersionedFile.get_sha1()."""
893
return self._sha1s[self._lookup(version_id)]
676
895
@deprecated_method(zero_eight)
677
896
def numversions(self):