201
220
return self._parents == other._parents \
202
221
and self._weave == other._weave \
203
222
and self._sha1s == other._sha1s
206
224
def __ne__(self, other):
207
225
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)
220
def lookup(self, name):
227
def _idx_to_name(self, version):
228
return self._names[version]
230
def _lookup(self, name):
221
231
"""Convert symbolic version name to index."""
232
self.check_not_reserved_id(name)
223
234
return self._name_map[name]
225
raise WeaveRevisionNotPresent(name, self)
236
raise RevisionNotPresent(name, self._weave_name)
239
"""See VersionedFile.versions."""
228
240
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]
242
def has_version(self, version_id):
243
"""See VersionedFile.has_version."""
244
return (version_id in self._name_map)
246
__contains__ = has_version
248
def get_parents(self, version_id):
249
"""See VersionedFile.get_parent."""
250
return map(self._idx_to_name, self._parents[self._lookup(version_id)])
237
252
def _check_repeated_add(self, name, parents, text, sha1):
238
253
"""Check that a duplicated add is OK.
240
255
If it is, return the (old) index; otherwise raise an exception.
242
idx = self.lookup(name)
257
idx = self._lookup(name)
243
258
if sorted(self._parents[idx]) != sorted(parents) \
244
259
or sha1 != self._sha1s[idx]:
245
raise WeaveRevisionAlreadyPresent(name, self)
260
raise RevisionAlreadyPresent(name, self._weave_name)
248
def add(self, name, parents, text, sha1=None):
263
def _add_lines(self, version_id, parents, lines, parent_texts,
264
left_matching_blocks, nostore_sha, random_id, check_content):
265
"""See VersionedFile.add_lines."""
266
idx = self._add(version_id, lines, map(self._lookup, parents),
267
nostore_sha=nostore_sha)
268
return sha_strings(lines), sum(map(len, lines)), idx
270
def _add(self, version_id, lines, parents, sha1=None, nostore_sha=None):
249
271
"""Add a single text on top of the weave.
251
273
Returns the index number of the newly added version.
254
276
Symbolic name for this version.
255
277
(Typically the revision-id of the revision that added it.)
258
280
List or set of direct parent version numbers.
261
283
Sequence of lines to be added in the new version.
263
sha -- SHA-1 of the file, if known. This is trusted to be
285
:param nostore_sha: See VersionedFile.add_lines.
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)
287
assert isinstance(version_id, basestring)
288
self._check_lines_not_unicode(lines)
289
self._check_lines_are_lines(lines)
291
sha1 = sha_strings(lines)
292
if sha1 == nostore_sha:
293
raise errors.ExistingContent
294
if version_id in self._name_map:
295
return self._check_repeated_add(version_id, parents, lines, sha1)
275
297
self._check_versions(parents)
276
## self._check_lines(text)
298
## self._check_lines(lines)
277
299
new_version = len(self._parents)
280
301
# if we abort after here the (in-memory) weave will be corrupt because only
281
302
# some fields are updated
303
# XXX: FIXME implement a succeed-or-fail of the rest of this routine.
304
# - Robert Collins 20060226
282
305
self._parents.append(parents[:])
283
306
self._sha1s.append(sha1)
284
self._names.append(name)
285
self._name_map[name] = new_version
307
self._names.append(version_id)
308
self._name_map[version_id] = new_version
438
435
except IndexError:
439
436
raise IndexError("invalid version number %r" % i)
442
def annotate(self, name_or_index):
443
return list(self.annotate_iter(name_or_index))
446
def annotate_iter(self, name_or_index):
447
"""Yield list of (index-id, line) pairs for the specified version.
438
def _compatible_parents(self, my_parents, other_parents):
439
"""During join check that other_parents are joinable with my_parents.
441
Joinable is defined as 'is a subset of' - supersets may require
442
regeneration of diffs, but subsets do not.
444
return len(other_parents.difference(my_parents)) == 0
446
def annotate_iter(self, version_id):
447
"""Yield list of (version-id, line) pairs for the specified version.
449
449
The index indicates when the line originated in the weave."""
450
incls = [self.maybe_lookup(name_or_index)]
450
incls = [self._lookup(version_id)]
451
451
for origin, lineno, text in self._extract(incls):
458
(lineno, insert, deletes, text)
459
for each literal line.
452
yield self._idx_to_name(origin), text
454
def iter_lines_added_or_present_in_versions(self, version_ids=None,
456
"""See VersionedFile.iter_lines_added_or_present_in_versions()."""
457
if version_ids is None:
458
version_ids = self.versions()
459
version_ids = set(version_ids)
460
for lineno, inserted, deletes, line in self._walk_internal(version_ids):
461
# if inserted not in version_ids then it was inserted before the
462
# versions we care about, but because weaves cannot represent ghosts
463
# properly, we do not filter down to that
464
# if inserted not in version_ids: continue
470
def _walk_internal(self, version_ids=None):
471
"""Helper method for weave actions."""
555
def get_iter(self, name_or_index):
556
"""Yield lines for the specified version."""
557
incls = [self.maybe_lookup(name_or_index)]
562
# We don't have sha1 sums for multiple entries
564
for origin, lineno, line in self._extract(incls):
569
expected_sha1 = self._sha1s[index]
570
measured_sha1 = cur_sha.hexdigest()
571
if measured_sha1 != expected_sha1:
572
raise errors.WeaveInvalidChecksum(
573
'file %s, revision %s, expected: %s, measured %s'
574
% (self._weave_name, self._names[index],
575
expected_sha1, measured_sha1))
578
def get_text(self, name_or_index):
579
return ''.join(self.get_iter(name_or_index))
580
assert isinstance(version, int)
583
def get_lines(self, name_or_index):
584
return list(self.get_iter(name_or_index))
590
def get_sha1(self, name):
591
"""Get the stored sha1 sum for the given revision.
640
def _maybe_lookup(self, name_or_index):
641
"""Convert possible symbolic name to index, or pass through indexes.
593
:param name: The name of the version to lookup
595
return self._sha1s[self.lookup(name)]
597
def mash_iter(self, included):
598
"""Return composed version of multiple included versions."""
599
included = map(self.maybe_lookup, included)
600
for origin, lineno, text in self._extract(included):
604
def dump(self, to_file):
605
from pprint import pprint
606
print >>to_file, "Weave._weave = ",
607
pprint(self._weave, to_file)
608
print >>to_file, "Weave._parents = ",
609
pprint(self._parents, to_file)
613
def numversions(self):
645
if isinstance(name_or_index, (int, long)):
648
return self._lookup(name_or_index)
650
def get_lines(self, version_id):
651
"""See VersionedFile.get_lines()."""
652
int_index = self._maybe_lookup(version_id)
653
result = [line for (origin, lineno, line) in self._extract([int_index])]
654
expected_sha1 = self._sha1s[int_index]
655
measured_sha1 = sha_strings(result)
656
if measured_sha1 != expected_sha1:
657
raise errors.WeaveInvalidChecksum(
658
'file %s, revision %s, expected: %s, measured %s'
659
% (self._weave_name, version_id,
660
expected_sha1, measured_sha1))
663
def get_sha1(self, version_id):
664
"""See VersionedFile.get_sha1()."""
665
return self._sha1s[self._lookup(version_id)]
667
def get_sha1s(self, version_ids):
668
"""See VersionedFile.get_sha1s()."""
669
return [self._sha1s[self._lookup(v)] for v in version_ids]
671
def num_versions(self):
672
"""How many versions are in this weave?"""
614
673
l = len(self._parents)
615
674
assert l == len(self._sha1s)
620
return self.numversions()
677
__len__ = num_versions
622
679
def check(self, progress_bar=None):
623
# check no circular inclusions
624
for version in range(self.numversions()):
680
# TODO evaluate performance hit of using string sets in this routine.
681
# TODO: check no circular inclusions
682
# TODO: create a nested progress bar
683
for version in range(self.num_versions()):
625
684
inclusions = list(self._parents[version])
627
686
inclusions.sort()
652
715
update_text = 'checking %s' % (short_name,)
653
716
update_text = update_text[:25]
655
for lineno, insert, deleteset, line in self._walk():
718
for lineno, insert, deleteset, line in self._walk_internal():
657
720
progress_bar.update(update_text, lineno, nlines)
659
for j, j_inc in enumerate(inclusions):
722
for name, name_inclusions in inclusions.items():
660
723
# The active inclusion must be an ancestor,
661
724
# and no ancestors must have deleted this line,
662
725
# because we don't support resurrection.
663
if (insert in j_inc) and not (deleteset & j_inc):
664
sha1s[j].update(line)
726
if (insert in name_inclusions) and not (deleteset & name_inclusions):
727
sha1s[name].update(line)
666
for version in range(nv):
730
version = self._idx_to_name(i)
667
731
hd = sha1s[version].hexdigest()
668
expected = self._sha1s[version]
732
expected = self._sha1s[i]
669
733
if hd != expected:
670
734
raise errors.WeaveInvalidChecksum(
671
735
"mismatched sha1 for version %s: "
672
736
"got %s, expected %s"
673
% (self._names[version], hd, expected))
737
% (version, hd, expected))
675
739
# TODO: check insertions are properly nested, that there are
676
740
# no lines outside of insertion blocks, that deletions are
677
741
# properly paired, etc.
679
def _delta(self, included, lines):
680
"""Return changes from basis to new revision.
682
The old text for comparison is the union of included revisions.
684
This is used in inserting a new text.
686
Delta is returned as a sequence of
687
(weave1, weave2, newlines).
689
This indicates that weave1:weave2 of the old weave should be
690
replaced by the sequence of lines in newlines. Note that
691
these line numbers are positions in the total weave and don't
692
correspond to the lines in any extracted version, or even the
693
extracted union of included versions.
695
If line1=line2, this is a pure insert; if newlines=[] this is a
696
pure delete. (Similar to difflib.)
698
raise NotImplementedError()
701
def plan_merge(self, ver_a, ver_b):
702
"""Return pseudo-annotation indicating how the two versions merge.
704
This is computed between versions a and b and their common
707
Weave lines present in none of them are skipped entirely.
709
inc_a = self.inclusions([ver_a])
710
inc_b = self.inclusions([ver_b])
711
inc_c = inc_a & inc_b
713
for lineno, insert, deleteset, line in self._walk():
714
if deleteset & inc_c:
715
# killed in parent; can't be in either a or b
716
# not relevant to our work
717
yield 'killed-base', line
718
elif insert in inc_c:
719
# was inserted in base
720
killed_a = bool(deleteset & inc_a)
721
killed_b = bool(deleteset & inc_b)
722
if killed_a and killed_b:
723
yield 'killed-both', line
725
yield 'killed-a', line
727
yield 'killed-b', line
729
yield 'unchanged', line
730
elif insert in inc_a:
731
if deleteset & inc_a:
732
yield 'ghost-a', line
736
elif insert in inc_b:
737
if deleteset & inc_b:
738
yield 'ghost-b', line
742
# not in either revision
743
yield 'irrelevant', line
745
yield 'unchanged', '' # terminator
749
def weave_merge(self, plan):
753
# TODO: Return a structured form of the conflicts (e.g. 2-tuples for
754
# conflicted regions), rather than just inserting the markers.
756
# TODO: Show some version information (e.g. author, date) on
757
# conflicted regions.
758
for state, line in plan:
759
if state == 'unchanged' or state == 'killed-both':
760
# resync and flush queued conflicts changes if any
761
if not lines_a and not lines_b:
763
elif ch_a and not ch_b:
765
for l in lines_a: yield l
766
elif ch_b and not ch_a:
767
for l in lines_b: yield l
768
elif lines_a == lines_b:
769
for l in lines_a: yield l
772
for l in lines_a: yield l
774
for l in lines_b: yield l
781
if state == 'unchanged':
784
elif state == 'killed-a':
787
elif state == 'killed-b':
790
elif state == 'new-a':
793
elif state == 'new-b':
797
assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
802
def join(self, other):
804
"""Integrate versions from other into this weave.
806
The resulting weave contains all the history of both weaves;
807
any version you could retrieve from either self or other can be
808
retrieved from self after this call.
810
It is illegal for the two weaves to contain different values
811
or different parents for any version. See also reweave().
813
if other.numversions() == 0:
743
def _join(self, other, pb, msg, version_ids, ignore_missing):
744
"""Worker routine for join()."""
745
if not other.versions():
814
746
return # nothing to update, easy
749
# versions is never none, InterWeave checks this.
815
752
# two loops so that we do not change ourselves before verifying it
817
754
# work through in index order to make sure we get all dependencies
818
for other_idx, name in enumerate(other._names):
819
self._check_version_consistent(other, other_idx, name)
757
# get the selected versions only that are in other.versions.
758
version_ids = set(other.versions()).intersection(set(version_ids))
759
# pull in the referenced graph.
760
version_ids = other.get_ancestry(version_ids)
761
pending_graph = [(version, other.get_parents(version)) for
762
version in version_ids]
763
for name in topo_sort(pending_graph):
764
other_idx = other._name_map[name]
765
# returns True if we have it, False if we need it.
766
if not self._check_version_consistent(other, other_idx, name):
767
names_to_join.append((other_idx, name))
824
for other_idx, name in enumerate(other._names):
776
for other_idx, name in names_to_join:
825
777
# TODO: If all the parents of the other version are already
826
778
# present then we can avoid some work by just taking the delta
827
779
# and adjusting the offsets.
828
780
new_parents = self._imported_parents(other, other_idx)
829
781
sha1 = other._sha1s[other_idx]
786
pb.update(msg, merged, len(names_to_join))
833
if name in self._names:
834
idx = self.lookup(name)
835
n1 = map(other.idx_to_name, other._parents[other_idx] )
836
n2 = map(self.idx_to_name, self._parents[other_idx] )
837
if sha1 == self._sha1s[idx] and n1 == n2:
841
788
lines = other.get_lines(other_idx)
842
self.add(name, new_parents, lines, sha1)
789
self._add(name, lines, new_parents, sha1)
844
791
mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
845
merged,processed,self._weave_name, time.time( )-time0))
792
merged, processed, self._weave_name, time.time()-time0))
850
794
def _imported_parents(self, other, other_idx):
851
795
"""Return list of parents in self corresponding to indexes in other."""
853
797
for parent_idx in other._parents[other_idx]:
854
798
parent_name = other._names[parent_idx]
855
if parent_name not in self._names:
799
if parent_name not in self._name_map:
856
800
# should not be possible
857
801
raise WeaveError("missing parent {%s} of {%s} in %r"
858
802
% (parent_name, other._name_map[other_idx], self))
894
def reweave(self, other):
895
"""Reweave self with other."""
896
new_weave = reweave(self, other)
834
def _reweave(self, other, pb, msg):
835
"""Reweave self with other - internal helper for join().
837
:param other: The other weave to merge
838
:param pb: An optional progress bar, indicating how far done we are
839
:param msg: An optional message for the progress
841
new_weave = _reweave(self, other, pb=pb, msg=msg)
842
self._copy_weave_content(new_weave)
844
def _copy_weave_content(self, otherweave):
845
"""adsorb the content from otherweave."""
897
846
for attr in self.__slots__:
898
setattr(self, attr, getattr(new_weave, attr))
847
if attr != '_weave_name':
848
setattr(self, attr, copy(getattr(otherweave, attr)))
851
class WeaveFile(Weave):
852
"""A WeaveFile represents a Weave on disk and writes on change."""
854
WEAVE_SUFFIX = '.weave'
856
def __init__(self, name, transport, filemode=None, create=False, access_mode='w'):
857
"""Create a WeaveFile.
859
:param create: If not True, only open an existing knit.
861
super(WeaveFile, self).__init__(name, access_mode)
862
self._transport = transport
863
self._filemode = filemode
865
_read_weave_v5(self._transport.get(name + WeaveFile.WEAVE_SUFFIX), self)
866
except errors.NoSuchFile:
872
def _add_lines(self, version_id, parents, lines, parent_texts,
873
left_matching_blocks, nostore_sha, random_id, check_content):
874
"""Add a version and save the weave."""
875
self.check_not_reserved_id(version_id)
876
result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
877
parent_texts, left_matching_blocks, nostore_sha, random_id,
882
def _clone_text(self, new_version_id, old_version_id, parents):
883
"""See VersionedFile.clone_text."""
884
super(WeaveFile, self)._clone_text(new_version_id, old_version_id, parents)
887
def copy_to(self, name, transport):
888
"""See VersionedFile.copy_to()."""
889
# as we are all in memory always, just serialise to the new place.
891
write_weave_v5(self, sio)
893
transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
895
def create_empty(self, name, transport, filemode=None):
896
return WeaveFile(name, transport, filemode, create=True)
899
"""Save the weave."""
900
self._check_write_ok()
902
write_weave_v5(self, sio)
904
self._transport.put_file(self._weave_name + WeaveFile.WEAVE_SUFFIX,
910
"""See VersionedFile.get_suffixes()."""
911
return [WeaveFile.WEAVE_SUFFIX]
913
def join(self, other, pb=None, msg=None, version_ids=None,
914
ignore_missing=False):
915
"""Join other into self and save."""
916
super(WeaveFile, self).join(other, pb, msg, version_ids, ignore_missing)
920
def _reweave(wa, wb, pb=None, msg=None):
902
921
"""Combine two weaves and return the result.
904
923
This works even if a revision R has different parents in
909
928
might be possible but it should only be necessary to do
910
929
this operation rarely, when a new previously ghost version is
932
:param pb: An optional progress bar, indicating how far done we are
933
:param msg: An optional message for the progress
915
queue_a = range(wa.numversions())
916
queue_b = range(wb.numversions())
937
queue_a = range(wa.num_versions())
938
queue_b = range(wb.num_versions())
917
939
# first determine combined parents of all versions
918
940
# map from version name -> all parent names
919
941
combined_parents = _reweave_parent_graphs(wa, wb)
920
942
mutter("combined parents: %r", combined_parents)
921
943
order = topo_sort(combined_parents.iteritems())
922
944
mutter("order to reweave: %r", order)
949
for idx, name in enumerate(order):
951
pb.update(msg, idx, len(order))
924
952
if name in wa._name_map:
925
953
lines = wa.get_lines(name)
926
954
if name in wb._name_map:
927
assert lines == wb.get_lines(name)
955
lines_b = wb.get_lines(name)
957
mutter('Weaves differ on content. rev_id {%s}', name)
958
mutter('weaves: %s, %s', wa._weave_name, wb._weave_name)
960
lines = list(difflib.unified_diff(lines, lines_b,
961
wa._weave_name, wb._weave_name))
962
mutter('lines:\n%s', ''.join(lines))
963
raise errors.WeaveTextDiffers(name, wa, wb)
929
965
lines = wb.get_lines(name)
930
wr.add(name, combined_parents[name], lines)
966
wr._add(name, lines, [wr._lookup(i) for i in combined_parents[name]])
934
969
def _reweave_parent_graphs(wa, wb):
935
970
"""Return combined parent ancestry for two weaves.
1134
1162
print ' '.join(map(str, w._parents[int(argv[3])]))
1136
1164
elif cmd == 'plan-merge':
1165
# replaced by 'bzr weave-plan-merge'
1138
1167
for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1140
1169
print '%14s | %s' % (state, line),
1142
1170
elif cmd == 'merge':
1171
# replaced by 'bzr weave-merge-text'
1144
1173
p = w.plan_merge(int(argv[3]), int(argv[4]))
1145
1174
sys.stdout.writelines(w.weave_merge(p))
1147
elif cmd == 'mash-merge':
1153
v1, v2 = map(int, argv[3:5])
1155
basis = w.inclusions([v1]).intersection(w.inclusions([v2]))
1157
base_lines = list(w.mash_iter(basis))
1158
a_lines = list(w.get(v1))
1159
b_lines = list(w.get(v2))
1161
from bzrlib.merge3 import Merge3
1162
m3 = Merge3(base_lines, a_lines, b_lines)
1164
name_a = 'version %d' % v1
1165
name_b = 'version %d' % v2
1166
sys.stdout.writelines(m3.merge_lines(name_a=name_a, name_b=name_b))
1168
1176
raise ValueError('unknown command %r' % cmd)
1172
def profile_main(argv):
1173
import tempfile, hotshot, hotshot.stats
1175
prof_f = tempfile.NamedTemporaryFile()
1177
prof = hotshot.Profile(prof_f.name)
1179
ret = prof.runcall(main, argv)
1182
stats = hotshot.stats.load(prof_f.name)
1184
stats.sort_stats('cumulative')
1185
## XXX: Might like to write to stderr or the trace file instead but
1186
## print_stats seems hardcoded to stdout
1187
stats.print_stats(20)
1192
def lsprofile_main(argv):
1193
from bzrlib.lsprof import profile
1194
ret,stats = profile(main, argv)
1200
1179
if __name__ == '__main__':
1202
if '--profile' in sys.argv:
1204
args.remove('--profile')
1205
sys.exit(profile_main(args))
1206
elif '--lsprof' in sys.argv:
1208
args.remove('--lsprof')
1209
sys.exit(lsprofile_main(args))
1211
sys.exit(main(sys.argv))
1181
sys.exit(main(sys.argv))
1184
class InterWeave(InterVersionedFile):
1185
"""Optimised code paths for weave to weave operations."""
1187
_matching_file_from_factory = staticmethod(WeaveFile)
1188
_matching_file_to_factory = staticmethod(WeaveFile)
1191
def is_compatible(source, target):
1192
"""Be compatible with weaves."""
1194
return (isinstance(source, Weave) and
1195
isinstance(target, Weave))
1196
except AttributeError:
1199
def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
1200
"""See InterVersionedFile.join."""
1201
version_ids = self._get_source_version_ids(version_ids, ignore_missing)
1202
if self.target.versions() == [] and version_ids is None:
1203
self.target._copy_weave_content(self.source)
1206
self.target._join(self.source, pb, msg, version_ids, ignore_missing)
1207
except errors.WeaveParentMismatch:
1208
self.target._reweave(self.source, pb, msg)
1211
InterVersionedFile.register_optimiser(InterWeave)