201
210
return self._parents == other._parents \
202
211
and self._weave == other._weave \
203
212
and self._sha1s == other._sha1s
206
214
def __ne__(self, other):
207
215
return not self.__eq__(other)
209
def __contains__(self, name):
210
return self._name_map.has_key(name)
212
def maybe_lookup(self, name_or_index):
213
"""Convert possible symbolic name to index, or pass through indexes."""
214
if isinstance(name_or_index, (int, long)):
217
return self.lookup(name_or_index)
217
@deprecated_method(zero_eight)
218
def idx_to_name(self, index):
219
"""Old public interface, the public interface is all names now."""
222
def _idx_to_name(self, version):
223
return self._names[version]
225
@deprecated_method(zero_eight)
220
226
def lookup(self, name):
227
"""Backwards compatability thunk:
229
Return name, as name is valid in the api now, and spew deprecation
234
def _lookup(self, name):
221
235
"""Convert symbolic version name to index."""
223
237
return self._name_map[name]
225
raise WeaveRevisionNotPresent(name, self)
239
raise RevisionNotPresent(name, self._weave_name)
241
@deprecated_method(zero_eight)
242
def iter_names(self):
243
"""Deprecated convenience function, please see VersionedFile.names()."""
244
return iter(self.names())
246
@deprecated_method(zero_eight)
248
"""See Weave.versions for the current api."""
249
return self.versions()
252
"""See VersionedFile.versions."""
228
253
return self._names[:]
230
def iter_names(self):
231
"""Yield a list of all names in this weave."""
232
return iter(self._names)
234
def idx_to_name(self, version):
235
return self._names[version]
255
def has_version(self, version_id):
256
"""See VersionedFile.has_version."""
257
return self._name_map.has_key(version_id)
259
__contains__ = has_version
261
def get_parents(self, version_id):
262
"""See VersionedFile.get_parent."""
263
return map(self._idx_to_name, self._parents[self._lookup(version_id)])
237
265
def _check_repeated_add(self, name, parents, text, sha1):
238
266
"""Check that a duplicated add is OK.
240
268
If it is, return the (old) index; otherwise raise an exception.
242
idx = self.lookup(name)
270
idx = self._lookup(name)
243
271
if sorted(self._parents[idx]) != sorted(parents) \
244
272
or sha1 != self._sha1s[idx]:
245
raise WeaveRevisionAlreadyPresent(name, self)
273
raise RevisionAlreadyPresent(name, self._weave_name)
276
@deprecated_method(zero_eight)
277
def add_identical(self, old_rev_id, new_rev_id, parents):
278
"""Please use Weave.clone_text now."""
279
return self.clone_text(new_rev_id, old_rev_id, parents)
281
def add_lines(self, version_id, parents, lines):
282
"""See VersionedFile.add_lines."""
283
return self._add(version_id, lines, map(self._lookup, parents))
285
@deprecated_method(zero_eight)
248
286
def add(self, name, parents, text, sha1=None):
287
"""See VersionedFile.add_lines for the non deprecated api."""
288
return self._add(name, text, map(self._maybe_lookup, parents), sha1)
290
def _add(self, version_id, lines, parents, sha1=None):
249
291
"""Add a single text on top of the weave.
251
293
Returns the index number of the newly added version.
254
296
Symbolic name for this version.
255
297
(Typically the revision-id of the revision that added it.)
258
300
List or set of direct parent version numbers.
261
303
Sequence of lines to be added in the new version.
263
sha -- SHA-1 of the file, if known. This is trusted to be
266
from bzrlib.osutils import sha_strings
268
assert isinstance(name, basestring)
270
sha1 = sha_strings(text)
271
if name in self._name_map:
272
return self._check_repeated_add(name, parents, text, sha1)
274
parents = map(self.maybe_lookup, parents)
306
assert isinstance(version_id, basestring)
308
sha1 = sha_strings(lines)
309
if version_id in self._name_map:
310
return self._check_repeated_add(version_id, parents, lines, sha1)
275
312
self._check_versions(parents)
276
## self._check_lines(text)
313
## self._check_lines(lines)
277
314
new_version = len(self._parents)
280
316
# if we abort after here the (in-memory) weave will be corrupt because only
281
317
# some fields are updated
282
318
# XXX: FIXME implement a succeed-or-fail of the rest of this routine.
283
319
# - Robert Collins 20060226
284
320
self._parents.append(parents[:])
285
321
self._sha1s.append(sha1)
286
self._names.append(name)
287
self._name_map[name] = new_version
322
self._names.append(version_id)
323
self._name_map[version_id] = new_version
388
424
## except IndexError:
389
425
## raise ValueError("version %d not present in weave" % v)
392
def parents(self, version):
393
return self._parents[version]
396
def parent_names(self, version):
397
"""Return version names for parents of a version."""
398
return map(self.idx_to_name, self._parents[self.lookup(version)])
401
def minimal_parents(self, version):
402
"""Find the minimal set of parents for the version."""
403
included = self._parents[version]
427
@deprecated_method(zero_eight)
428
def inclusions(self, version_ids):
429
"""Deprecated - see VersionedFile.get_ancestry for the replacement."""
408
li.sort(reverse=True)
416
gotit.update(self.inclusions(pv))
418
assert mininc[0] >= 0
419
assert mininc[-1] < version
432
if isinstance(version_ids[0], int):
433
return [self._idx_to_name(v) for v in self._inclusions(version_ids)]
435
return self.get_ancestry(version_ids)
437
def get_ancestry(self, version_ids):
438
"""See VersionedFile.get_ancestry."""
439
if isinstance(version_ids, basestring):
440
version_ids = [version_ids]
441
i = self._inclusions([self._lookup(v) for v in version_ids])
442
return [self._idx_to_name(v) for v in i]
424
444
def _check_lines(self, text):
425
445
if not isinstance(text, list):
440
460
except IndexError:
441
461
raise IndexError("invalid version number %r" % i)
463
def _compatible_parents(self, my_parents, other_parents):
464
"""During join check that other_parents are joinable with my_parents.
466
Joinable is defined as 'is a subset of' - supersets may require
467
regeneration of diffs, but subsets do not.
469
return len(other_parents.difference(my_parents)) == 0
471
def annotate(self, version_id):
472
if isinstance(version_id, int):
473
warn('Weave.annotate(int) is deprecated. Please use version names'
474
' in all circumstances as of 0.8',
479
for origin, lineno, text in self._extract([version_id]):
480
result.append((origin, text))
483
return super(Weave, self).annotate(version_id)
444
def annotate(self, name_or_index):
445
return list(self.annotate_iter(name_or_index))
448
def annotate_iter(self, name_or_index):
449
"""Yield list of (index-id, line) pairs for the specified version.
485
def annotate_iter(self, version_id):
486
"""Yield list of (version-id, line) pairs for the specified version.
451
488
The index indicates when the line originated in the weave."""
452
incls = [self.maybe_lookup(name_or_index)]
489
incls = [self._lookup(version_id)]
453
490
for origin, lineno, text in self._extract(incls):
491
yield self._idx_to_name(origin), text
493
@deprecated_method(zero_eight)
495
"""_walk has become walk, a supported api."""
460
(lineno, insert, deletes, text)
461
for each literal line.
498
def walk(self, version_ids=None):
499
"""See VersionedFile.walk."""
576
635
% (self._weave_name, self._names[index],
577
636
expected_sha1, measured_sha1))
580
def get_text(self, name_or_index):
581
return ''.join(self.get_iter(name_or_index))
582
assert isinstance(version, int)
585
def get_lines(self, name_or_index):
586
return list(self.get_iter(name_or_index))
638
@deprecated_method(zero_eight)
639
def get(self, version_id):
640
"""Please use either Weave.get_text or Weave.get_lines as desired."""
641
return self.get_lines(version_id)
643
def get_lines(self, version_id):
644
"""See VersionedFile.get_lines()."""
645
return list(self._get_iter(version_id))
592
647
def get_sha1(self, name):
593
648
"""Get the stored sha1 sum for the given revision.
595
650
:param name: The name of the version to lookup
597
return self._sha1s[self.lookup(name)]
599
def mash_iter(self, included):
600
"""Return composed version of multiple included versions."""
601
included = map(self.maybe_lookup, included)
602
for origin, lineno, text in self._extract(included):
606
def dump(self, to_file):
607
from pprint import pprint
608
print >>to_file, "Weave._weave = ",
609
pprint(self._weave, to_file)
610
print >>to_file, "Weave._parents = ",
611
pprint(self._parents, to_file)
652
return self._sha1s[self._lookup(name)]
654
@deprecated_method(zero_eight)
615
655
def numversions(self):
656
"""How many versions are in this weave?
658
Deprecated in favour of num_versions.
660
return self.num_versions()
662
def num_versions(self):
663
"""How many versions are in this weave?"""
616
664
l = len(self._parents)
617
665
assert l == len(self._sha1s)
622
return self.numversions()
668
__len__ = num_versions
624
670
def check(self, progress_bar=None):
671
# TODO evaluate performance hit of using string sets in this routine.
625
672
# check no circular inclusions
626
for version in range(self.numversions()):
673
for version in range(self.num_versions()):
627
674
inclusions = list(self._parents[version])
629
676
inclusions.sort()
654
705
update_text = 'checking %s' % (short_name,)
655
706
update_text = update_text[:25]
657
for lineno, insert, deleteset, line in self._walk():
708
for lineno, insert, deleteset, line in self.walk():
659
710
progress_bar.update(update_text, lineno, nlines)
661
for j, j_inc in enumerate(inclusions):
712
for name, name_inclusions in inclusions.items():
662
713
# The active inclusion must be an ancestor,
663
714
# and no ancestors must have deleted this line,
664
715
# because we don't support resurrection.
665
if (insert in j_inc) and not (deleteset & j_inc):
666
sha1s[j].update(line)
716
if (insert in name_inclusions) and not (deleteset & name_inclusions):
717
sha1s[name].update(line)
668
for version in range(nv):
720
version = self._idx_to_name(i)
669
721
hd = sha1s[version].hexdigest()
670
expected = self._sha1s[version]
722
expected = self._sha1s[i]
671
723
if hd != expected:
672
724
raise errors.WeaveInvalidChecksum(
673
725
"mismatched sha1 for version %s: "
674
726
"got %s, expected %s"
675
% (self._names[version], hd, expected))
727
% (version, hd, expected))
677
729
# TODO: check insertions are properly nested, that there are
678
730
# no lines outside of insertion blocks, that deletions are
679
731
# properly paired, etc.
681
def _delta(self, included, lines):
682
"""Return changes from basis to new revision.
684
The old text for comparison is the union of included revisions.
686
This is used in inserting a new text.
688
Delta is returned as a sequence of
689
(weave1, weave2, newlines).
691
This indicates that weave1:weave2 of the old weave should be
692
replaced by the sequence of lines in newlines. Note that
693
these line numbers are positions in the total weave and don't
694
correspond to the lines in any extracted version, or even the
695
extracted union of included versions.
697
If line1=line2, this is a pure insert; if newlines=[] this is a
698
pure delete. (Similar to difflib.)
700
raise NotImplementedError()
703
def plan_merge(self, ver_a, ver_b):
704
"""Return pseudo-annotation indicating how the two versions merge.
706
This is computed between versions a and b and their common
709
Weave lines present in none of them are skipped entirely.
711
inc_a = self.inclusions([ver_a])
712
inc_b = self.inclusions([ver_b])
713
inc_c = inc_a & inc_b
715
for lineno, insert, deleteset, line in self._walk():
716
if deleteset & inc_c:
717
# killed in parent; can't be in either a or b
718
# not relevant to our work
719
yield 'killed-base', line
720
elif insert in inc_c:
721
# was inserted in base
722
killed_a = bool(deleteset & inc_a)
723
killed_b = bool(deleteset & inc_b)
724
if killed_a and killed_b:
725
yield 'killed-both', line
727
yield 'killed-a', line
729
yield 'killed-b', line
731
yield 'unchanged', line
732
elif insert in inc_a:
733
if deleteset & inc_a:
734
yield 'ghost-a', line
738
elif insert in inc_b:
739
if deleteset & inc_b:
740
yield 'ghost-b', line
744
# not in either revision
745
yield 'irrelevant', line
747
yield 'unchanged', '' # terminator
751
def weave_merge(self, plan, a_marker='<<<<<<< \n', b_marker='>>>>>>> \n'):
755
# TODO: Return a structured form of the conflicts (e.g. 2-tuples for
756
# conflicted regions), rather than just inserting the markers.
758
# TODO: Show some version information (e.g. author, date) on
759
# conflicted regions.
760
for state, line in plan:
761
if state == 'unchanged' or state == 'killed-both':
762
# resync and flush queued conflicts changes if any
763
if not lines_a and not lines_b:
765
elif ch_a and not ch_b:
767
for l in lines_a: yield l
768
elif ch_b and not ch_a:
769
for l in lines_b: yield l
770
elif lines_a == lines_b:
771
for l in lines_a: yield l
774
for l in lines_a: yield l
776
for l in lines_b: yield l
783
if state == 'unchanged':
786
elif state == 'killed-a':
789
elif state == 'killed-b':
792
elif state == 'new-a':
795
elif state == 'new-b':
799
assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
804
def join(self, other, pb=None, msg=None):
806
"""Integrate versions from other into this weave.
808
The resulting weave contains all the history of both weaves;
809
any version you could retrieve from either self or other can be
810
retrieved from self after this call.
812
It is illegal for the two weaves to contain different values
813
or different parents for any version. See also reweave().
815
:param other: The other weave to pull into this one
816
:param pb: An optional progress bar
817
:param msg: An optional message to display for progress
819
if other.numversions() == 0:
733
def _join(self, other, pb, msg, version_ids, ignore_missing):
734
"""Worker routine for join()."""
735
if not other.versions():
820
736
return # nothing to update, easy
739
for version_id in version_ids:
740
if not other.has_version(version_id) and not ignore_missing:
741
raise RevisionNotPresent(version_id, self._weave_name)
743
version_ids = other.versions()
821
745
# two loops so that we do not change ourselves before verifying it
823
747
# work through in index order to make sure we get all dependencies
824
748
names_to_join = []
826
for other_idx, name in enumerate(other._names):
750
# get the selected versions only that are in other.versions.
751
version_ids = set(other.versions()).intersection(set(version_ids))
752
# pull in the referenced graph.
753
version_ids = other.get_ancestry(version_ids)
754
pending_graph = [(version, other.get_parents(version)) for
755
version in version_ids]
756
for name in topo_sort(pending_graph):
757
other_idx = other._name_map[name]
827
758
self._check_version_consistent(other, other_idx, name)
828
759
sha1 = other._sha1s[other_idx]
832
763
if name in self._name_map:
833
idx = self.lookup(name)
834
n1 = set(map(other.idx_to_name, other._parents[other_idx]))
835
n2 = set(map(self.idx_to_name, self._parents[idx]))
764
idx = self._lookup(name)
765
n1 = set(map(other._idx_to_name, other._parents[other_idx]))
766
n2 = set(map(self._idx_to_name, self._parents[idx]))
836
767
if sha1 == self._sha1s[idx] and n1 == n2:
835
@deprecated_method(zero_eight)
906
836
def reweave(self, other, pb=None, msg=None):
907
"""Reweave self with other.
837
"""reweave has been superceded by plain use of join."""
838
return self.join(other, pb, msg)
840
def _reweave(self, other, pb, msg):
841
"""Reweave self with other - internal helper for join().
909
843
:param other: The other weave to merge
910
844
:param pb: An optional progress bar, indicating how far done we are
911
845
:param msg: An optional message for the progress
913
new_weave = reweave(self, other, pb=pb, msg=msg)
847
new_weave = _reweave(self, other, pb=pb, msg=msg)
914
848
for attr in self.__slots__:
915
setattr(self, attr, getattr(new_weave, attr))
849
if attr != '_weave_name':
850
setattr(self, attr, getattr(new_weave, attr))
853
class WeaveFile(Weave):
854
"""A WeaveFile represents a Weave on disk and writes on change."""
856
WEAVE_SUFFIX = '.weave'
858
def __init__(self, name, transport, mode=None, create=False):
859
"""Create a WeaveFile.
861
:param create: If not True, only open an existing knit.
863
super(WeaveFile, self).__init__(name)
864
self._transport = transport
867
_read_weave_v5(self._transport.get(name + WeaveFile.WEAVE_SUFFIX), self)
868
except errors.NoSuchFile:
874
def add_lines(self, version_id, parents, lines):
875
"""Add a version and save the weave."""
876
super(WeaveFile, self).add_lines(version_id, parents, lines)
879
def copy_to(self, name, transport):
880
"""See VersionedFile.copy_to()."""
881
# as we are all in memory always, just serialise to the new place.
883
write_weave_v5(self, sio)
885
transport.put(name + WeaveFile.WEAVE_SUFFIX, sio, self._mode)
887
def create_empty(self, name, transport, mode=None):
888
return WeaveFile(name, transport, mode, create=True)
891
"""Save the weave."""
893
write_weave_v5(self, sio)
895
self._transport.put(self._weave_name + WeaveFile.WEAVE_SUFFIX,
901
"""See VersionedFile.get_suffixes()."""
902
return [WeaveFile.WEAVE_SUFFIX]
904
def join(self, other, pb=None, msg=None, version_ids=None,
905
ignore_missing=False):
906
"""Join other into self and save."""
907
super(WeaveFile, self).join(other, pb, msg, version_ids, ignore_missing)
911
@deprecated_function(zero_eight)
918
912
def reweave(wa, wb, pb=None, msg=None):
913
"""reweaving is deprecation, please just use weave.join()."""
914
_reweave(wa, wb, pb, msg)
916
def _reweave(wa, wb, pb=None, msg=None):
919
917
"""Combine two weaves and return the result.
921
919
This works even if a revision R has different parents in