220
201
return self._parents == other._parents \
221
202
and self._weave == other._weave \
222
203
and self._sha1s == other._sha1s
224
206
def __ne__(self, other):
225
207
return not self.__eq__(other)
227
def _idx_to_name(self, version):
228
return self._names[version]
230
def _lookup(self, name):
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):
231
221
"""Convert symbolic version name to index."""
232
self.check_not_reserved_id(name)
234
223
return self._name_map[name]
236
raise RevisionNotPresent(name, self._weave_name)
225
raise WeaveRevisionNotPresent(name, self)
239
"""See VersionedFile.versions."""
240
228
return self._names[:]
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)])
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]
252
237
def _check_repeated_add(self, name, parents, text, sha1):
253
238
"""Check that a duplicated add is OK.
255
240
If it is, return the (old) index; otherwise raise an exception.
257
idx = self._lookup(name)
242
idx = self.lookup(name)
258
243
if sorted(self._parents[idx]) != sorted(parents) \
259
244
or sha1 != self._sha1s[idx]:
260
raise RevisionAlreadyPresent(name, self._weave_name)
245
raise WeaveRevisionAlreadyPresent(name, self)
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):
248
def add(self, name, parents, text, sha1=None):
271
249
"""Add a single text on top of the weave.
273
251
Returns the index number of the newly added version.
276
254
Symbolic name for this version.
277
255
(Typically the revision-id of the revision that added it.)
280
258
List or set of direct parent version numbers.
283
261
Sequence of lines to be added in the new version.
285
:param nostore_sha: See VersionedFile.add_lines.
263
sha -- SHA-1 of the file, if known. This is trusted to be
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)
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)
297
275
self._check_versions(parents)
298
## self._check_lines(lines)
276
## self._check_lines(text)
299
277
new_version = len(self._parents)
301
280
# if we abort after here the (in-memory) weave will be corrupt because only
302
281
# some fields are updated
303
# XXX: FIXME implement a succeed-or-fail of the rest of this routine.
304
# - Robert Collins 20060226
305
282
self._parents.append(parents[:])
306
283
self._sha1s.append(sha1)
307
self._names.append(version_id)
308
self._name_map[version_id] = new_version
284
self._names.append(name)
285
self._name_map[name] = new_version
435
438
except IndexError:
436
439
raise IndexError("invalid version number %r" % i)
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.
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.
449
449
The index indicates when the line originated in the weave."""
450
incls = [self._lookup(version_id)]
450
incls = [self.maybe_lookup(name_or_index)]
451
451
for origin, lineno, text in self._extract(incls):
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."""
458
(lineno, insert, deletes, text)
459
for each literal line.
640
def _maybe_lookup(self, name_or_index):
641
"""Convert possible symbolic name to index, or pass through indexes.
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.
593
:param name: The name of the version to lookup
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?"""
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):
673
614
l = len(self._parents)
674
615
assert l == len(self._sha1s)
677
__len__ = num_versions
620
return self.numversions()
679
622
def check(self, progress_bar=None):
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()):
623
# check no circular inclusions
624
for version in range(self.numversions()):
684
625
inclusions = list(self._parents[version])
686
627
inclusions.sort()
715
652
update_text = 'checking %s' % (short_name,)
716
653
update_text = update_text[:25]
718
for lineno, insert, deleteset, line in self._walk_internal():
655
for lineno, insert, deleteset, line in self._walk():
720
657
progress_bar.update(update_text, lineno, nlines)
722
for name, name_inclusions in inclusions.items():
659
for j, j_inc in enumerate(inclusions):
723
660
# The active inclusion must be an ancestor,
724
661
# and no ancestors must have deleted this line,
725
662
# because we don't support resurrection.
726
if (insert in name_inclusions) and not (deleteset & name_inclusions):
727
sha1s[name].update(line)
663
if (insert in j_inc) and not (deleteset & j_inc):
664
sha1s[j].update(line)
730
version = self._idx_to_name(i)
666
for version in range(nv):
731
667
hd = sha1s[version].hexdigest()
732
expected = self._sha1s[i]
668
expected = self._sha1s[version]
733
669
if hd != expected:
734
670
raise errors.WeaveInvalidChecksum(
735
671
"mismatched sha1 for version %s: "
736
672
"got %s, expected %s"
737
% (version, hd, expected))
673
% (self._names[version], hd, expected))
739
675
# TODO: check insertions are properly nested, that there are
740
676
# no lines outside of insertion blocks, that deletions are
741
677
# properly paired, etc.
743
def _join(self, other, pb, msg, version_ids, ignore_missing):
744
"""Worker routine for join()."""
745
if not other.versions():
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:
746
814
return # nothing to update, easy
749
# versions is never none, InterWeave checks this.
752
815
# two loops so that we do not change ourselves before verifying it
754
817
# 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))
776
for other_idx, name in names_to_join:
824
for other_idx, name in enumerate(other._names):
777
825
# TODO: If all the parents of the other version are already
778
826
# present then we can avoid some work by just taking the delta
779
827
# and adjusting the offsets.
780
828
new_parents = self._imported_parents(other, other_idx)
781
829
sha1 = other._sha1s[other_idx]
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:
786
pb.update(msg, merged, len(names_to_join))
788
841
lines = other.get_lines(other_idx)
789
self._add(name, lines, new_parents, sha1)
842
self.add(name, new_parents, lines, sha1)
791
844
mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
792
merged, processed, self._weave_name, time.time()-time0))
845
merged,processed,self._weave_name, time.time( )-time0))
794
850
def _imported_parents(self, other, other_idx):
795
851
"""Return list of parents in self corresponding to indexes in other."""
797
853
for parent_idx in other._parents[other_idx]:
798
854
parent_name = other._names[parent_idx]
799
if parent_name not in self._name_map:
855
if parent_name not in self._names:
800
856
# should not be possible
801
857
raise WeaveError("missing parent {%s} of {%s} in %r"
802
858
% (parent_name, other._name_map[other_idx], self))
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."""
894
def reweave(self, other):
895
"""Reweave self with other."""
896
new_weave = reweave(self, other)
846
897
for attr in self.__slots__:
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):
898
setattr(self, attr, getattr(new_weave, attr))
921
902
"""Combine two weaves and return the result.
923
904
This works even if a revision R has different parents in
928
909
might be possible but it should only be necessary to do
929
910
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
937
queue_a = range(wa.num_versions())
938
queue_b = range(wb.num_versions())
915
queue_a = range(wa.numversions())
916
queue_b = range(wb.numversions())
939
917
# first determine combined parents of all versions
940
918
# map from version name -> all parent names
941
919
combined_parents = _reweave_parent_graphs(wa, wb)
942
920
mutter("combined parents: %r", combined_parents)
943
921
order = topo_sort(combined_parents.iteritems())
944
922
mutter("order to reweave: %r", order)
949
for idx, name in enumerate(order):
951
pb.update(msg, idx, len(order))
952
924
if name in wa._name_map:
953
925
lines = wa.get_lines(name)
954
926
if name in wb._name_map:
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)
927
assert lines == wb.get_lines(name)
965
929
lines = wb.get_lines(name)
966
wr._add(name, lines, [wr._lookup(i) for i in combined_parents[name]])
930
wr.add(name, combined_parents[name], lines)
969
934
def _reweave_parent_graphs(wa, wb):
970
935
"""Return combined parent ancestry for two weaves.
1162
1134
print ' '.join(map(str, w._parents[int(argv[3])]))
1164
1136
elif cmd == 'plan-merge':
1165
# replaced by 'bzr weave-plan-merge'
1167
1138
for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1169
1140
print '%14s | %s' % (state, line),
1170
1142
elif cmd == 'merge':
1171
# replaced by 'bzr weave-merge-text'
1173
1144
p = w.plan_merge(int(argv[3]), int(argv[4]))
1174
1145
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))
1176
1168
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)
1179
1200
if __name__ == '__main__':
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)
1205
self.target._join(self.source, pb, msg, version_ids, ignore_missing)
1208
InterVersionedFile.register_optimiser(InterWeave)
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))