50
50
# have slight specializations for different ways its used: annotate,
51
51
# basis for add, get, etc.
53
# TODO: Perhaps the API should work only in names to hide the integer
54
# indexes from the user?
53
# TODO: Probably the API should work only in names to hide the integer
54
# indexes from the user.
56
56
# TODO: Is there any potential performance win by having an add()
57
57
# variant that is passed a pre-cooked version of the single basis
60
# TODO: Have a way to go back and insert a revision V1 that is a parent
61
# of an already-stored revision V2. This means some lines previously
62
# counted as new in V2 will be discovered to have actually come from V1.
63
# It is probably necessary to insert V1, then compute a whole new diff
64
# from the mashed ancestors to V2. This must be repeated for every
65
# direct child of V1. The deltas from V2 to its descendents won't change,
66
# although their location within the weave may change. It may be possible
67
# to just adjust the location of those instructions rather than
68
# re-weaving the whole thing. This is expected to be a fairly rare
69
# operation, only used when inserting data that was previously a ghost.
60
# TODO: Reweave can possibly be made faster by remembering diffs
61
# where the basis and destination are unchanged.
63
# FIXME: Sometimes we will be given a parents list for a revision
64
# that includes some redundant parents (i.e. already a parent of
65
# something in the list.) We should eliminate them. This can
66
# be done fairly efficiently because the sequence numbers constrain
67
# the possible relationships.
69
# FIXME: the conflict markers should be *7* characters
72
from cStringIO import StringIO
75
from difflib import SequenceMatcher
80
class WeaveError(Exception):
81
"""Exception in processing weave"""
84
class WeaveFormatError(WeaveError):
85
"""Weave invariant violated"""
81
from bzrlib.trace import mutter
82
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
83
RevisionAlreadyPresent,
85
WeaveRevisionAlreadyPresent,
86
WeaveRevisionNotPresent,
88
import bzrlib.errors as errors
89
from bzrlib.osutils import sha_strings
90
import bzrlib.patiencediff
91
from bzrlib.tsort import topo_sort
92
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
93
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
96
class Weave(VersionedFile):
89
97
"""weave - versioned text file storage.
91
99
A Weave manages versions of line-based text files, keeping track
205
220
return self._parents == other._parents \
206
221
and self._weave == other._weave \
207
222
and self._sha1s == other._sha1s
210
224
def __ne__(self, other):
211
225
return not self.__eq__(other)
214
def maybe_lookup(self, name_or_index):
215
"""Convert possible symbolic name to index, or pass through indexes."""
216
if isinstance(name_or_index, (int, long)):
219
return self.lookup(name_or_index)
222
def lookup(self, name):
227
def _idx_to_name(self, version):
228
return self._names[version]
230
def _lookup(self, name):
223
231
"""Convert symbolic version name to index."""
232
self.check_not_reserved_id(name)
225
234
return self._name_map[name]
227
raise WeaveError("name %r not present in weave %r" %
228
(name, self._weave_name))
231
def iter_names(self):
232
"""Yield a list of all names in this weave."""
233
return iter(self._names)
235
def idx_to_name(self, version):
236
return self._names[version]
236
raise RevisionNotPresent(name, self._weave_name)
239
"""See VersionedFile.versions."""
240
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_parent_map(self, version_ids):
249
"""See VersionedFile.get_parent_map."""
251
for version_id in version_ids:
253
result[version_id] = tuple(
254
map(self._idx_to_name, self._parents[self._lookup(version_id)]))
255
except RevisionNotPresent:
259
def get_parents_with_ghosts(self, version_id):
260
raise NotImplementedError(self.get_parents_with_ghosts)
239
262
def _check_repeated_add(self, name, parents, text, sha1):
240
263
"""Check that a duplicated add is OK.
242
265
If it is, return the (old) index; otherwise raise an exception.
244
idx = self.lookup(name)
245
if sorted(self._parents[idx]) != sorted(parents):
246
raise WeaveError("name \"%s\" already present in weave "
247
"with different parents" % name)
248
if sha1 != self._sha1s[idx]:
249
raise WeaveError("name \"%s\" already present in weave "
250
"with different text" % name)
267
idx = self._lookup(name)
268
if sorted(self._parents[idx]) != sorted(parents) \
269
or sha1 != self._sha1s[idx]:
270
raise RevisionAlreadyPresent(name, self._weave_name)
255
def add(self, name, parents, text, sha1=None):
273
def _add_lines(self, version_id, parents, lines, parent_texts,
274
left_matching_blocks, nostore_sha, random_id, check_content):
275
"""See VersionedFile.add_lines."""
276
idx = self._add(version_id, lines, map(self._lookup, parents),
277
nostore_sha=nostore_sha)
278
return sha_strings(lines), sum(map(len, lines)), idx
280
def _add(self, version_id, lines, parents, sha1=None, nostore_sha=None):
256
281
"""Add a single text on top of the weave.
258
283
Returns the index number of the newly added version.
261
286
Symbolic name for this version.
262
287
(Typically the revision-id of the revision that added it.)
265
290
List or set of direct parent version numbers.
268
293
Sequence of lines to be added in the new version.
270
sha -- SHA-1 of the file, if known. This is trusted to be
295
:param nostore_sha: See VersionedFile.add_lines.
273
from bzrlib.osutils import sha_strings
275
assert isinstance(name, basestring)
277
sha1 = sha_strings(text)
278
if name in self._name_map:
279
return self._check_repeated_add(name, parents, text, sha1)
281
parents = map(self.maybe_lookup, parents)
297
assert isinstance(version_id, basestring)
298
self._check_lines_not_unicode(lines)
299
self._check_lines_are_lines(lines)
301
sha1 = sha_strings(lines)
302
if sha1 == nostore_sha:
303
raise errors.ExistingContent
304
if version_id in self._name_map:
305
return self._check_repeated_add(version_id, parents, lines, sha1)
282
307
self._check_versions(parents)
283
## self._check_lines(text)
308
## self._check_lines(lines)
284
309
new_version = len(self._parents)
287
311
# if we abort after here the (in-memory) weave will be corrupt because only
288
312
# some fields are updated
313
# XXX: FIXME implement a succeed-or-fail of the rest of this routine.
314
# - Robert Collins 20060226
289
315
self._parents.append(parents[:])
290
316
self._sha1s.append(sha1)
291
self._names.append(name)
292
self._name_map[name] = new_version
317
self._names.append(version_id)
318
self._name_map[version_id] = new_version
440
445
except IndexError:
441
446
raise IndexError("invalid version number %r" % i)
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.
448
def _compatible_parents(self, my_parents, other_parents):
449
"""During join check that other_parents are joinable with my_parents.
451
Joinable is defined as 'is a subset of' - supersets may require
452
regeneration of diffs, but subsets do not.
454
return len(other_parents.difference(my_parents)) == 0
456
def annotate_iter(self, version_id):
457
"""Yield list of (version-id, line) pairs for the specified version.
451
459
The index indicates when the line originated in the weave."""
452
incls = [self.maybe_lookup(name_or_index)]
460
incls = [self._lookup(version_id)]
453
461
for origin, lineno, text in self._extract(incls):
461
(lineno, insert, deletes, text)
462
for each literal line.
462
yield self._idx_to_name(origin), text
464
def iter_lines_added_or_present_in_versions(self, version_ids=None,
466
"""See VersionedFile.iter_lines_added_or_present_in_versions()."""
467
if version_ids is None:
468
version_ids = self.versions()
469
version_ids = set(version_ids)
470
for lineno, inserted, deletes, line in self._walk_internal(version_ids):
471
# if inserted not in version_ids then it was inserted before the
472
# versions we care about, but because weaves cannot represent ghosts
473
# properly, we do not filter down to that
474
# if inserted not in version_ids: continue
476
yield line + '\n', inserted
480
def _walk_internal(self, version_ids=None):
481
"""Helper method for weave actions."""
468
486
lineno = 0 # line of weave, 0-based
470
488
for l in self._weave:
471
if isinstance(l, tuple):
489
if l.__class__ == tuple:
493
istack.append(self._names[v])
497
assert self._names[v] not in dset
498
dset.add(self._names[v])
500
dset.remove(self._names[v])
484
raise WeaveFormatError('unexpected instruction %r'
502
raise WeaveFormatError('unexpected instruction %r' % v)
487
assert isinstance(l, basestring)
504
assert l.__class__ in (str, unicode)
489
yield lineno, istack[-1], dset, l
506
yield lineno, istack[-1], frozenset(dset), l
510
raise WeaveFormatError("unclosed insertion blocks "
511
"at end of weave: %s" % istack)
513
raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
516
def plan_merge(self, ver_a, ver_b):
517
"""Return pseudo-annotation indicating how the two versions merge.
519
This is computed between versions a and b and their common
522
Weave lines present in none of them are skipped entirely.
524
inc_a = set(self.get_ancestry([ver_a]))
525
inc_b = set(self.get_ancestry([ver_b]))
526
inc_c = inc_a & inc_b
528
for lineno, insert, deleteset, line in self._walk_internal([ver_a, ver_b]):
529
if deleteset & inc_c:
530
# killed in parent; can't be in either a or b
531
# not relevant to our work
532
yield 'killed-base', line
533
elif insert in inc_c:
534
# was inserted in base
535
killed_a = bool(deleteset & inc_a)
536
killed_b = bool(deleteset & inc_b)
537
if killed_a and killed_b:
538
yield 'killed-both', line
540
yield 'killed-a', line
542
yield 'killed-b', line
544
yield 'unchanged', line
545
elif insert in inc_a:
546
if deleteset & inc_a:
547
yield 'ghost-a', line
551
elif insert in inc_b:
552
if deleteset & inc_b:
553
yield 'ghost-b', line
557
# not in either revision
558
yield 'irrelevant', line
560
yield 'unchanged', '' # terminator
494
562
def _extract(self, versions):
495
563
"""Yield annotation of lines in included set.
539
assert isinstance(l, basestring)
636
assert l.__class__ in (str, unicode)
540
637
if isactive is None:
541
638
isactive = (not dset) and istack and (istack[-1] in included)
543
640
result.append((istack[-1], lineno, l))
547
raise WFE("unclosed insertion blocks at end of weave",
643
raise WeaveFormatError("unclosed insertion blocks "
644
"at end of weave: %s" % istack)
550
raise WFE("unclosed deletion blocks at end of weave",
557
def get_iter(self, name_or_index):
558
"""Yield lines for the specified version."""
559
incls = [self.maybe_lookup(name_or_index)]
560
for origin, lineno, line in self._extract(incls):
564
def get_text(self, name_or_index):
565
return ''.join(self.get_iter(name_or_index))
566
assert isinstance(version, int)
569
def get_lines(self, name_or_index):
570
return list(self.get_iter(name_or_index))
576
def mash_iter(self, included):
577
"""Return composed version of multiple included versions."""
578
included = map(self.maybe_lookup, included)
579
for origin, lineno, text in self._extract(included):
583
def dump(self, to_file):
584
from pprint import pprint
585
print >>to_file, "Weave._weave = ",
586
pprint(self._weave, to_file)
587
print >>to_file, "Weave._parents = ",
588
pprint(self._parents, to_file)
592
def numversions(self):
646
raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
650
def _maybe_lookup(self, name_or_index):
651
"""Convert possible symbolic name to index, or pass through indexes.
655
if isinstance(name_or_index, (int, long)):
658
return self._lookup(name_or_index)
660
def get_lines(self, version_id):
661
"""See VersionedFile.get_lines()."""
662
int_index = self._maybe_lookup(version_id)
663
result = [line for (origin, lineno, line) in self._extract([int_index])]
664
expected_sha1 = self._sha1s[int_index]
665
measured_sha1 = sha_strings(result)
666
if measured_sha1 != expected_sha1:
667
raise errors.WeaveInvalidChecksum(
668
'file %s, revision %s, expected: %s, measured %s'
669
% (self._weave_name, version_id,
670
expected_sha1, measured_sha1))
673
def get_sha1(self, version_id):
674
"""See VersionedFile.get_sha1()."""
675
return self._sha1s[self._lookup(version_id)]
677
def get_sha1s(self, version_ids):
678
"""See VersionedFile.get_sha1s()."""
679
return [self._sha1s[self._lookup(v)] for v in version_ids]
681
def num_versions(self):
682
"""How many versions are in this weave?"""
593
683
l = len(self._parents)
594
684
assert l == len(self._sha1s)
599
return self.numversions()
687
__len__ = num_versions
602
689
def check(self, progress_bar=None):
603
# check no circular inclusions
604
for version in range(self.numversions()):
690
# TODO evaluate performance hit of using string sets in this routine.
691
# TODO: check no circular inclusions
692
# TODO: create a nested progress bar
693
for version in range(self.num_versions()):
605
694
inclusions = list(self._parents[version])
607
696
inclusions.sort()
609
698
raise WeaveFormatError("invalid included version %d for index %d"
610
699
% (inclusions[-1], version))
612
# try extracting all versions; this is a bit slow and parallel
613
# extraction could be used
614
nv = self.numversions()
615
for version in range(nv):
701
# try extracting all versions; parallel extraction is used
702
nv = self.num_versions()
707
# For creating the ancestry, IntSet is much faster (3.7s vs 0.17s)
708
# The problem is that set membership is much more expensive
709
name = self._idx_to_name(i)
710
sha1s[name] = sha.new()
712
new_inc = set([name])
713
for p in self._parents[i]:
714
new_inc.update(inclusions[self._idx_to_name(p)])
716
assert set(new_inc) == set(self.get_ancestry(name)), \
717
'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
718
inclusions[name] = new_inc
720
nlines = len(self._weave)
722
update_text = 'checking weave'
724
short_name = os.path.basename(self._weave_name)
725
update_text = 'checking %s' % (short_name,)
726
update_text = update_text[:25]
728
for lineno, insert, deleteset, line in self._walk_internal():
617
progress_bar.update('checking text', version, nv)
619
for l in self.get_iter(version):
622
expected = self._sha1s[version]
730
progress_bar.update(update_text, lineno, nlines)
732
for name, name_inclusions in inclusions.items():
733
# The active inclusion must be an ancestor,
734
# and no ancestors must have deleted this line,
735
# because we don't support resurrection.
736
if (insert in name_inclusions) and not (deleteset & name_inclusions):
737
sha1s[name].update(line)
740
version = self._idx_to_name(i)
741
hd = sha1s[version].hexdigest()
742
expected = self._sha1s[i]
623
743
if hd != expected:
624
raise WeaveError("mismatched sha1 for version %d; "
625
"got %s, expected %s"
626
% (version, hd, expected))
744
raise errors.WeaveInvalidChecksum(
745
"mismatched sha1 for version %s: "
746
"got %s, expected %s"
747
% (version, hd, expected))
628
749
# TODO: check insertions are properly nested, that there are
629
750
# no lines outside of insertion blocks, that deletions are
630
751
# properly paired, etc.
634
def merge(self, merge_versions):
635
"""Automerge and mark conflicts between versions.
637
This returns a sequence, each entry describing alternatives
638
for a chunk of the file. Each of the alternatives is given as
641
If there is a chunk of the file where there's no diagreement,
642
only one alternative is given.
644
# approach: find the included versions common to all the
646
raise NotImplementedError()
650
def _delta(self, included, lines):
651
"""Return changes from basis to new revision.
653
The old text for comparison is the union of included revisions.
655
This is used in inserting a new text.
657
Delta is returned as a sequence of
658
(weave1, weave2, newlines).
660
This indicates that weave1:weave2 of the old weave should be
661
replaced by the sequence of lines in newlines. Note that
662
these line numbers are positions in the total weave and don't
663
correspond to the lines in any extracted version, or even the
664
extracted union of included versions.
666
If line1=line2, this is a pure insert; if newlines=[] this is a
667
pure delete. (Similar to difflib.)
669
raise NotImplementedError()
672
def plan_merge(self, ver_a, ver_b):
673
"""Return pseudo-annotation indicating how the two versions merge.
675
This is computed between versions a and b and their common
678
Weave lines present in none of them are skipped entirely.
680
inc_a = self.inclusions([ver_a])
681
inc_b = self.inclusions([ver_b])
682
inc_c = inc_a & inc_b
684
for lineno, insert, deleteset, line in self._walk():
685
if deleteset & inc_c:
686
# killed in parent; can't be in either a or b
687
# not relevant to our work
688
yield 'killed-base', line
689
elif insert in inc_c:
690
# was inserted in base
691
killed_a = bool(deleteset & inc_a)
692
killed_b = bool(deleteset & inc_b)
693
if killed_a and killed_b:
694
yield 'killed-both', line
696
yield 'killed-a', line
698
yield 'killed-b', line
700
yield 'unchanged', line
701
elif insert in inc_a:
702
if deleteset & inc_a:
703
yield 'ghost-a', line
707
elif insert in inc_b:
708
if deleteset & inc_b:
709
yield 'ghost-b', line
713
# not in either revision
714
yield 'irrelevant', line
716
yield 'unchanged', '' # terminator
720
def weave_merge(self, plan):
725
for state, line in plan:
726
if state == 'unchanged' or state == 'killed-both':
727
# resync and flush queued conflicts changes if any
728
if not lines_a and not lines_b:
730
elif ch_a and not ch_b:
732
for l in lines_a: yield l
733
elif ch_b and not ch_a:
734
for l in lines_b: yield l
735
elif lines_a == lines_b:
736
for l in lines_a: yield l
739
for l in lines_a: yield l
741
for l in lines_b: yield l
748
if state == 'unchanged':
751
elif state == 'killed-a':
754
elif state == 'killed-b':
757
elif state == 'new-a':
760
elif state == 'new-b':
764
assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
769
def join(self, other):
770
"""Integrate versions from other into this weave.
772
The resulting weave contains all the history of both weaves;
773
any version you could retrieve from either self or other can be
774
retrieved from self after this call.
776
It is illegal for the two weaves to contain different values
779
if other.numversions() == 0:
753
def _join(self, other, pb, msg, version_ids, ignore_missing):
754
"""Worker routine for join()."""
755
if not other.versions():
780
756
return # nothing to update, easy
759
# versions is never none, InterWeave checks this.
762
# two loops so that we do not change ourselves before verifying it
781
764
# work through in index order to make sure we get all dependencies
782
for other_idx, name in enumerate(other._names):
783
# TODO: If all the parents of the other version are already
767
# get the selected versions only that are in other.versions.
768
version_ids = set(other.versions()).intersection(set(version_ids))
769
# pull in the referenced graph.
770
version_ids = other.get_ancestry(version_ids)
771
pending_parents = other.get_parent_map(version_ids)
772
pending_graph = pending_parents.items()
773
if len(pending_graph) != len(version_ids):
774
raise RevisionNotPresent(
775
set(version_ids) - set(pending_parents.keys()), self)
776
for name in topo_sort(pending_graph):
777
other_idx = other._name_map[name]
778
# returns True if we have it, False if we need it.
779
if not self._check_version_consistent(other, other_idx, name):
780
names_to_join.append((other_idx, name))
788
for other_idx, name in names_to_join:
789
# TODO: If all the parents of the other version are already
784
790
# present then we can avoid some work by just taking the delta
785
791
# and adjusting the offsets.
786
if self._check_version_consistent(other, other_idx, name):
788
792
new_parents = self._imported_parents(other, other_idx)
793
sha1 = other._sha1s[other_idx]
798
pb.update(msg, merged, len(names_to_join))
789
800
lines = other.get_lines(other_idx)
790
sha1 = other._sha1s[other_idx]
791
self.add(name, new_parents, lines, sha1)
801
self._add(name, lines, new_parents, sha1)
803
mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
804
merged, processed, self._weave_name, time.time()-time0))
794
806
def _imported_parents(self, other, other_idx):
795
807
"""Return list of parents in self corresponding to indexes in other."""
797
809
for parent_idx in other._parents[other_idx]:
798
810
parent_name = other._names[parent_idx]
799
if parent_name not in self._names:
811
if parent_name not in self._name_map:
800
812
# should not be possible
801
813
raise WeaveError("missing parent {%s} of {%s} in %r"
802
814
% (parent_name, other._name_map[other_idx], self))
812
830
this_idx = self._name_map.get(name, -1)
813
831
if this_idx != -1:
814
832
if self._sha1s[this_idx] != other._sha1s[other_idx]:
815
raise WeaveError("inconsistent texts for version {%s} in %r and %r"
816
% (name, self, other))
817
elif set(self._parents[this_idx]) != set(other._parents[other_idx]):
818
raise WeaveError("inconsistent parents for version {%s} in %r and %r"
819
% (name, self, other))
833
raise errors.WeaveTextDiffers(name, self, other)
834
self_parents = self._parents[this_idx]
835
other_parents = other._parents[other_idx]
836
n1 = set([self._names[i] for i in self_parents])
837
n2 = set([other._names[i] for i in other_parents])
838
if not self._compatible_parents(n1, n2):
839
raise WeaveParentMismatch("inconsistent parents "
840
"for version {%s}: %s vs %s" % (name, n1, n2))
821
842
return True # ok!
846
def _reweave(self, other, pb, msg):
847
"""Reweave self with other - internal helper for join().
849
:param other: The other weave to merge
850
:param pb: An optional progress bar, indicating how far done we are
851
:param msg: An optional message for the progress
853
new_weave = _reweave(self, other, pb=pb, msg=msg)
854
self._copy_weave_content(new_weave)
856
def _copy_weave_content(self, otherweave):
857
"""adsorb the content from otherweave."""
858
for attr in self.__slots__:
859
if attr != '_weave_name':
860
setattr(self, attr, copy(getattr(otherweave, attr)))
863
class WeaveFile(Weave):
864
"""A WeaveFile represents a Weave on disk and writes on change."""
866
WEAVE_SUFFIX = '.weave'
868
def __init__(self, name, transport, filemode=None, create=False, access_mode='w'):
869
"""Create a WeaveFile.
871
:param create: If not True, only open an existing knit.
873
super(WeaveFile, self).__init__(name, access_mode)
874
self._transport = transport
875
self._filemode = filemode
877
_read_weave_v5(self._transport.get(name + WeaveFile.WEAVE_SUFFIX), self)
878
except errors.NoSuchFile:
884
def _add_lines(self, version_id, parents, lines, parent_texts,
885
left_matching_blocks, nostore_sha, random_id, check_content):
886
"""Add a version and save the weave."""
887
self.check_not_reserved_id(version_id)
888
result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
889
parent_texts, left_matching_blocks, nostore_sha, random_id,
894
def _clone_text(self, new_version_id, old_version_id, parents):
895
"""See VersionedFile.clone_text."""
896
super(WeaveFile, self)._clone_text(new_version_id, old_version_id, parents)
899
def copy_to(self, name, transport):
900
"""See VersionedFile.copy_to()."""
901
# as we are all in memory always, just serialise to the new place.
903
write_weave_v5(self, sio)
905
transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
908
"""Save the weave."""
909
self._check_write_ok()
911
write_weave_v5(self, sio)
913
self._transport.put_file(self._weave_name + WeaveFile.WEAVE_SUFFIX,
919
"""See VersionedFile.get_suffixes()."""
920
return [WeaveFile.WEAVE_SUFFIX]
922
def join(self, other, pb=None, msg=None, version_ids=None,
923
ignore_missing=False):
924
"""Join other into self and save."""
925
super(WeaveFile, self).join(other, pb, msg, version_ids, ignore_missing)
929
def _reweave(wa, wb, pb=None, msg=None):
930
"""Combine two weaves and return the result.
932
This works even if a revision R has different parents in
933
wa and wb. In the resulting weave all the parents are given.
935
This is done by just building up a new weave, maintaining ordering
936
of the versions in the two inputs. More efficient approaches
937
might be possible but it should only be necessary to do
938
this operation rarely, when a new previously ghost version is
941
:param pb: An optional progress bar, indicating how far done we are
942
:param msg: An optional message for the progress
946
queue_a = range(wa.num_versions())
947
queue_b = range(wb.num_versions())
948
# first determine combined parents of all versions
949
# map from version name -> all parent names
950
combined_parents = _reweave_parent_graphs(wa, wb)
951
mutter("combined parents: %r", combined_parents)
952
order = topo_sort(combined_parents.iteritems())
953
mutter("order to reweave: %r", order)
958
for idx, name in enumerate(order):
960
pb.update(msg, idx, len(order))
961
if name in wa._name_map:
962
lines = wa.get_lines(name)
963
if name in wb._name_map:
964
lines_b = wb.get_lines(name)
966
mutter('Weaves differ on content. rev_id {%s}', name)
967
mutter('weaves: %s, %s', wa._weave_name, wb._weave_name)
969
lines = list(difflib.unified_diff(lines, lines_b,
970
wa._weave_name, wb._weave_name))
971
mutter('lines:\n%s', ''.join(lines))
972
raise errors.WeaveTextDiffers(name, wa, wb)
974
lines = wb.get_lines(name)
975
wr._add(name, lines, [wr._lookup(i) for i in combined_parents[name]])
978
def _reweave_parent_graphs(wa, wb):
979
"""Return combined parent ancestry for two weaves.
981
Returned as a list of (version_name, set(parent_names))"""
983
for weave in [wa, wb]:
984
for idx, name in enumerate(weave._names):
985
p = combined.setdefault(name, set())
986
p.update(map(weave._idx_to_name, weave._parents[idx]))
826
990
def weave_toc(w):
827
991
"""Show the weave's table-of-contents"""
1014
1171
print ' '.join(map(str, w._parents[int(argv[3])]))
1016
1173
elif cmd == 'plan-merge':
1174
# replaced by 'bzr weave-plan-merge'
1018
1176
for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1020
1178
print '%14s | %s' % (state, line),
1022
1179
elif cmd == 'merge':
1180
# replaced by 'bzr weave-merge-text'
1024
1182
p = w.plan_merge(int(argv[3]), int(argv[4]))
1025
1183
sys.stdout.writelines(w.weave_merge(p))
1027
elif cmd == 'mash-merge':
1033
v1, v2 = map(int, argv[3:5])
1035
basis = w.inclusions([v1]).intersection(w.inclusions([v2]))
1037
base_lines = list(w.mash_iter(basis))
1038
a_lines = list(w.get(v1))
1039
b_lines = list(w.get(v2))
1041
from bzrlib.merge3 import Merge3
1042
m3 = Merge3(base_lines, a_lines, b_lines)
1044
name_a = 'version %d' % v1
1045
name_b = 'version %d' % v2
1046
sys.stdout.writelines(m3.merge_lines(name_a=name_a, name_b=name_b))
1048
1185
raise ValueError('unknown command %r' % cmd)
1052
def profile_main(argv):
1053
import tempfile, hotshot, hotshot.stats
1055
prof_f = tempfile.NamedTemporaryFile()
1057
prof = hotshot.Profile(prof_f.name)
1059
ret = prof.runcall(main, argv)
1062
stats = hotshot.stats.load(prof_f.name)
1064
stats.sort_stats('cumulative')
1065
## XXX: Might like to write to stderr or the trace file instead but
1066
## print_stats seems hardcoded to stdout
1067
stats.print_stats(20)
1072
1188
if __name__ == '__main__':
1074
if '--profile' in sys.argv:
1076
args.remove('--profile')
1077
sys.exit(profile_main(args))
1079
sys.exit(main(sys.argv))
1190
sys.exit(main(sys.argv))
1193
class InterWeave(InterVersionedFile):
1194
"""Optimised code paths for weave to weave operations."""
1196
_matching_file_from_factory = staticmethod(WeaveFile)
1197
_matching_file_to_factory = staticmethod(WeaveFile)
1200
def is_compatible(source, target):
1201
"""Be compatible with weaves."""
1203
return (isinstance(source, Weave) and
1204
isinstance(target, Weave))
1205
except AttributeError:
1208
def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
1209
"""See InterVersionedFile.join."""
1210
version_ids = self._get_source_version_ids(version_ids, ignore_missing)
1211
if self.target.versions() == [] and version_ids is None:
1212
self.target._copy_weave_content(self.source)
1214
self.target._join(self.source, pb, msg, version_ids, ignore_missing)
1217
InterVersionedFile.register_optimiser(InterWeave)