66
66
# be done fairly efficiently because the sequence numbers constrain
67
67
# the possible relationships.
69
# FIXME: the conflict markers should be *7* characters
72
from cStringIO import StringIO
72
from difflib import SequenceMatcher
77
from bzrlib.lazy_import import lazy_import
78
lazy_import(globals(), """
79
from bzrlib import tsort
74
from bzrlib.trace import mutter
84
75
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
85
RevisionAlreadyPresent,
87
UnavailableRepresentation,
88
WeaveRevisionAlreadyPresent,
89
WeaveRevisionNotPresent,
76
WeaveRevisionNotPresent, WeaveRevisionAlreadyPresent)
91
77
import bzrlib.errors as errors
92
from bzrlib.osutils import dirname, sha, sha_strings, split_lines
93
import bzrlib.patiencediff
94
from bzrlib.revision import NULL_REVISION
95
from bzrlib.symbol_versioning import *
96
from bzrlib.trace import mutter
97
from bzrlib.versionedfile import (
103
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
106
class WeaveContentFactory(ContentFactory):
107
"""Content factory for streaming from weaves.
109
:seealso ContentFactory:
112
def __init__(self, version, weave):
113
"""Create a WeaveContentFactory for version from weave."""
114
ContentFactory.__init__(self)
115
self.sha1 = weave.get_sha1s([version])[version]
116
self.key = (version,)
117
parents = weave.get_parent_map([version])[version]
118
self.parents = tuple((parent,) for parent in parents)
119
self.storage_kind = 'fulltext'
122
def get_bytes_as(self, storage_kind):
123
if storage_kind == 'fulltext':
124
return self._weave.get_text(self.key[-1])
126
raise UnavailableRepresentation(self.key, storage_kind, 'fulltext')
129
class Weave(VersionedFile):
78
from bzrlib.tsort import topo_sort
130
82
"""weave - versioned text file storage.
132
84
A Weave manages versions of line-based text files, keeping track
218
170
__slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
219
'_weave_name', '_matcher', '_allow_reserved']
221
def __init__(self, weave_name=None, access_mode='w', matcher=None,
222
get_scope=None, allow_reserved=False):
225
:param get_scope: A callable that returns an opaque object to be used
226
for detecting when this weave goes out of scope (should stop
227
answering requests or allowing mutation).
229
super(Weave, self).__init__()
173
def __init__(self, weave_name=None):
231
175
self._parents = []
234
178
self._name_map = {}
235
179
self._weave_name = weave_name
237
self._matcher = bzrlib.patiencediff.PatienceSequenceMatcher
239
self._matcher = matcher
240
if get_scope is None:
241
get_scope = lambda:None
242
self._get_scope = get_scope
243
self._scope = get_scope()
244
self._access_mode = access_mode
245
self._allow_reserved = allow_reserved
247
181
def __repr__(self):
248
182
return "Weave(%r)" % self._weave_name
250
def _check_write_ok(self):
251
"""Is the versioned file marked as 'finished' ? Raise if it is."""
252
if self._get_scope() != self._scope:
253
raise errors.OutSideTransaction()
254
if self._access_mode != 'w':
255
raise errors.ReadOnlyObjectDirtiedError(self)
258
186
"""Return a deep copy of self.
273
201
return self._parents == other._parents \
274
202
and self._weave == other._weave \
275
203
and self._sha1s == other._sha1s
277
206
def __ne__(self, other):
278
207
return not self.__eq__(other)
280
def _idx_to_name(self, version):
281
return self._names[version]
283
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):
284
221
"""Convert symbolic version name to index."""
285
if not self._allow_reserved:
286
self.check_not_reserved_id(name)
288
223
return self._name_map[name]
290
raise RevisionNotPresent(name, self._weave_name)
225
raise WeaveRevisionNotPresent(name, self)
293
"""See VersionedFile.versions."""
294
228
return self._names[:]
296
def has_version(self, version_id):
297
"""See VersionedFile.has_version."""
298
return (version_id in self._name_map)
300
__contains__ = has_version
302
def get_record_stream(self, versions, ordering, include_delta_closure):
303
"""Get a stream of records for versions.
305
:param versions: The versions to include. Each version is a tuple
307
:param ordering: Either 'unordered' or 'topological'. A topologically
308
sorted stream has compression parents strictly before their
310
:param include_delta_closure: If True then the closure across any
311
compression parents will be included (in the opaque data).
312
:return: An iterator of ContentFactory objects, each of which is only
313
valid until the iterator is advanced.
315
versions = [version[-1] for version in versions]
316
if ordering == 'topological':
317
parents = self.get_parent_map(versions)
318
new_versions = tsort.topo_sort(parents)
319
new_versions.extend(set(versions).difference(set(parents)))
320
versions = new_versions
321
for version in versions:
323
yield WeaveContentFactory(version, self)
325
yield AbsentContentFactory((version,))
327
def get_parent_map(self, version_ids):
328
"""See VersionedFile.get_parent_map."""
330
for version_id in version_ids:
331
if version_id == NULL_REVISION:
336
map(self._idx_to_name,
337
self._parents[self._lookup(version_id)]))
338
except RevisionNotPresent:
340
result[version_id] = parents
343
def get_parents_with_ghosts(self, version_id):
344
raise NotImplementedError(self.get_parents_with_ghosts)
346
def insert_record_stream(self, stream):
347
"""Insert a record stream into this versioned file.
349
:param stream: A stream of records to insert.
351
:seealso VersionedFile.get_record_stream:
354
for record in stream:
355
# Raise an error when a record is missing.
356
if record.storage_kind == 'absent':
357
raise RevisionNotPresent([record.key[0]], self)
358
# adapt to non-tuple interface
359
parents = [parent[0] for parent in record.parents]
360
if record.storage_kind == 'fulltext':
361
self.add_lines(record.key[0], parents,
362
split_lines(record.get_bytes_as('fulltext')))
364
adapter_key = record.storage_kind, 'fulltext'
366
adapter = adapters[adapter_key]
368
adapter_factory = adapter_registry.get(adapter_key)
369
adapter = adapter_factory(self)
370
adapters[adapter_key] = adapter
371
lines = split_lines(adapter.get_bytes(
372
record, record.get_bytes_as(record.storage_kind)))
374
self.add_lines(record.key[0], parents, lines)
375
except RevisionAlreadyPresent:
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]
378
237
def _check_repeated_add(self, name, parents, text, sha1):
379
238
"""Check that a duplicated add is OK.
381
240
If it is, return the (old) index; otherwise raise an exception.
383
idx = self._lookup(name)
242
idx = self.lookup(name)
384
243
if sorted(self._parents[idx]) != sorted(parents) \
385
244
or sha1 != self._sha1s[idx]:
386
raise RevisionAlreadyPresent(name, self._weave_name)
245
raise WeaveRevisionAlreadyPresent(name, self)
389
def _add_lines(self, version_id, parents, lines, parent_texts,
390
left_matching_blocks, nostore_sha, random_id, check_content):
391
"""See VersionedFile.add_lines."""
392
idx = self._add(version_id, lines, map(self._lookup, parents),
393
nostore_sha=nostore_sha)
394
return sha_strings(lines), sum(map(len, lines)), idx
396
def _add(self, version_id, lines, parents, sha1=None, nostore_sha=None):
248
def add(self, name, parents, text, sha1=None):
397
249
"""Add a single text on top of the weave.
399
251
Returns the index number of the newly added version.
402
254
Symbolic name for this version.
403
255
(Typically the revision-id of the revision that added it.)
406
258
List or set of direct parent version numbers.
409
261
Sequence of lines to be added in the new version.
411
:param nostore_sha: See VersionedFile.add_lines.
263
sha -- SHA-1 of the file, if known. This is trusted to be
413
self._check_lines_not_unicode(lines)
414
self._check_lines_are_lines(lines)
416
sha1 = sha_strings(lines)
417
if sha1 == nostore_sha:
418
raise errors.ExistingContent
419
if version_id in self._name_map:
420
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)
422
275
self._check_versions(parents)
423
## self._check_lines(lines)
276
## self._check_lines(text)
424
277
new_version = len(self._parents)
426
280
# if we abort after here the (in-memory) weave will be corrupt because only
427
281
# some fields are updated
428
# XXX: FIXME implement a succeed-or-fail of the rest of this routine.
429
# - Robert Collins 20060226
430
282
self._parents.append(parents[:])
431
283
self._sha1s.append(sha1)
432
self._names.append(version_id)
433
self._name_map[version_id] = new_version
284
self._names.append(name)
285
self._name_map[name] = new_version
549
438
except IndexError:
550
439
raise IndexError("invalid version number %r" % i)
552
def _compatible_parents(self, my_parents, other_parents):
553
"""During join check that other_parents are joinable with my_parents.
555
Joinable is defined as 'is a subset of' - supersets may require
556
regeneration of diffs, but subsets do not.
558
return len(other_parents.difference(my_parents)) == 0
560
def annotate(self, version_id):
561
"""Return a list of (version-id, line) tuples for version_id.
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.
563
449
The index indicates when the line originated in the weave."""
564
incls = [self._lookup(version_id)]
565
return [(self._idx_to_name(origin), text) for origin, lineno, text in
566
self._extract(incls)]
568
def iter_lines_added_or_present_in_versions(self, version_ids=None,
570
"""See VersionedFile.iter_lines_added_or_present_in_versions()."""
571
if version_ids is None:
572
version_ids = self.versions()
573
version_ids = set(version_ids)
574
for lineno, inserted, deletes, line in self._walk_internal(version_ids):
575
# if inserted not in version_ids then it was inserted before the
576
# versions we care about, but because weaves cannot represent ghosts
577
# properly, we do not filter down to that
578
# if inserted not in version_ids: continue
580
yield line + '\n', inserted
584
def _walk_internal(self, version_ids=None):
585
"""Helper method for weave actions."""
450
incls = [self.maybe_lookup(name_or_index)]
451
for origin, lineno, text in self._extract(incls):
458
(lineno, insert, deletes, text)
459
for each literal line.
686
519
WFE = WeaveFormatError
689
# 449 0 4474.6820 2356.5590 bzrlib.weave:556(_extract)
690
# +285282 0 1676.8040 1676.8040 +<isinstance>
691
# 1.6 seconds in 'isinstance'.
692
# changing the first isinstance:
693
# 449 0 2814.2660 1577.1760 bzrlib.weave:556(_extract)
694
# +140414 0 762.8050 762.8050 +<isinstance>
695
# note that the inline time actually dropped (less function calls)
696
# and total processing time was halved.
697
# we're still spending ~1/4 of the method in isinstance though.
698
# so lets hard code the acceptable string classes we expect:
699
# 449 0 1202.9420 786.2930 bzrlib.weave:556(_extract)
700
# +71352 0 377.5560 377.5560 +<method 'append' of 'list'
702
# yay, down to ~1/4 the initial extract time, and our inline time
703
# has shrunk again, with isinstance no longer dominating.
704
# tweaking the stack inclusion test to use a set gives:
705
# 449 0 1122.8030 713.0080 bzrlib.weave:556(_extract)
706
# +71352 0 354.9980 354.9980 +<method 'append' of 'list'
708
# - a 5% win, or possibly just noise. However with large istacks that
709
# 'in' test could dominate, so I'm leaving this change in place -
710
# when its fast enough to consider profiling big datasets we can review.
715
521
for l in self._weave:
716
if l.__class__ == tuple:
522
if isinstance(l, tuple):
526
assert v not in istack
723
iset.remove(istack.pop())
725
531
if v in included:
728
536
if v in included:
731
raise AssertionError()
540
assert isinstance(l, basestring)
733
541
if isactive is None:
734
542
isactive = (not dset) and istack and (istack[-1] in included)
746
def _maybe_lookup(self, name_or_index):
747
"""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
751
if isinstance(name_or_index, (int, long)):
754
return self._lookup(name_or_index)
756
def get_lines(self, version_id):
757
"""See VersionedFile.get_lines()."""
758
int_index = self._maybe_lookup(version_id)
759
result = [line for (origin, lineno, line) in self._extract([int_index])]
760
expected_sha1 = self._sha1s[int_index]
761
measured_sha1 = sha_strings(result)
762
if measured_sha1 != expected_sha1:
763
raise errors.WeaveInvalidChecksum(
764
'file %s, revision %s, expected: %s, measured %s'
765
% (self._weave_name, version_id,
766
expected_sha1, measured_sha1))
769
def get_sha1s(self, version_ids):
770
"""See VersionedFile.get_sha1s()."""
772
for v in version_ids:
773
result[v] = self._sha1s[self._lookup(v)]
776
def num_versions(self):
777
"""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):
778
614
l = len(self._parents)
615
assert l == len(self._sha1s)
781
__len__ = num_versions
620
return self.numversions()
783
622
def check(self, progress_bar=None):
784
# TODO evaluate performance hit of using string sets in this routine.
785
# TODO: check no circular inclusions
786
# TODO: create a nested progress bar
787
for version in range(self.num_versions()):
623
# check no circular inclusions
624
for version in range(self.numversions()):
788
625
inclusions = list(self._parents[version])
790
627
inclusions.sort()
821
652
update_text = 'checking %s' % (short_name,)
822
653
update_text = update_text[:25]
824
for lineno, insert, deleteset, line in self._walk_internal():
655
for lineno, insert, deleteset, line in self._walk():
826
657
progress_bar.update(update_text, lineno, nlines)
828
for name, name_inclusions in inclusions.items():
659
for j, j_inc in enumerate(inclusions):
829
660
# The active inclusion must be an ancestor,
830
661
# and no ancestors must have deleted this line,
831
662
# because we don't support resurrection.
832
if (insert in name_inclusions) and not (deleteset & name_inclusions):
833
sha1s[name].update(line)
663
if (insert in j_inc) and not (deleteset & j_inc):
664
sha1s[j].update(line)
836
version = self._idx_to_name(i)
666
for version in range(nv):
837
667
hd = sha1s[version].hexdigest()
838
expected = self._sha1s[i]
668
expected = self._sha1s[version]
839
669
if hd != expected:
840
670
raise errors.WeaveInvalidChecksum(
841
671
"mismatched sha1 for version %s: "
842
672
"got %s, expected %s"
843
% (version, hd, expected))
673
% (self._names[version], hd, expected))
845
675
# TODO: check insertions are properly nested, that there are
846
676
# no lines outside of insertion blocks, that deletions are
847
677
# 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, pb=None, msg=None):
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
:param other: The other weave to pull into this one
814
:param pb: An optional progress bar
815
:param msg: An optional message to display for progress
817
if other.numversions() == 0:
818
return # nothing to update, easy
819
# two loops so that we do not change ourselves before verifying it
821
# work through in index order to make sure we get all dependencies
824
for other_idx, name in enumerate(other._names):
825
self._check_version_consistent(other, other_idx, name)
826
sha1 = other._sha1s[other_idx]
830
if name in self._name_map:
831
idx = self.lookup(name)
832
n1 = set(map(other.idx_to_name, other._parents[other_idx]))
833
n2 = set(map(self.idx_to_name, self._parents[idx]))
834
if sha1 == self._sha1s[idx] and n1 == n2:
837
names_to_join.append((other_idx, name))
844
for other_idx, name in names_to_join:
845
# TODO: If all the parents of the other version are already
846
# present then we can avoid some work by just taking the delta
847
# and adjusting the offsets.
848
new_parents = self._imported_parents(other, other_idx)
849
sha1 = other._sha1s[other_idx]
854
pb.update(msg, merged, len(names_to_join))
856
lines = other.get_lines(other_idx)
857
self.add(name, new_parents, lines, sha1)
859
mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
860
merged, processed, self._weave_name, time.time( )-time0))
849
862
def _imported_parents(self, other, other_idx):
850
863
"""Return list of parents in self corresponding to indexes in other."""
852
865
for parent_idx in other._parents[other_idx]:
853
866
parent_name = other._names[parent_idx]
854
if parent_name not in self._name_map:
867
if parent_name not in self._names:
855
868
# should not be possible
856
869
raise WeaveError("missing parent {%s} of {%s} in %r"
857
870
% (parent_name, other._name_map[other_idx], self))
889
def _reweave(self, other, pb, msg):
890
"""Reweave self with other - internal helper for join().
904
def reweave(self, other, pb=None, msg=None):
905
"""Reweave self with other.
892
907
:param other: The other weave to merge
893
908
:param pb: An optional progress bar, indicating how far done we are
894
909
:param msg: An optional message for the progress
896
new_weave = _reweave(self, other, pb=pb, msg=msg)
897
self._copy_weave_content(new_weave)
899
def _copy_weave_content(self, otherweave):
900
"""adsorb the content from otherweave."""
911
new_weave = reweave(self, other, pb=pb, msg=msg)
901
912
for attr in self.__slots__:
902
if attr != '_weave_name':
903
setattr(self, attr, copy(getattr(otherweave, attr)))
906
class WeaveFile(Weave):
907
"""A WeaveFile represents a Weave on disk and writes on change."""
909
WEAVE_SUFFIX = '.weave'
911
def __init__(self, name, transport, filemode=None, create=False, access_mode='w', get_scope=None):
912
"""Create a WeaveFile.
914
:param create: If not True, only open an existing knit.
916
super(WeaveFile, self).__init__(name, access_mode, get_scope=get_scope,
917
allow_reserved=False)
918
self._transport = transport
919
self._filemode = filemode
921
_read_weave_v5(self._transport.get(name + WeaveFile.WEAVE_SUFFIX), self)
922
except errors.NoSuchFile:
928
def _add_lines(self, version_id, parents, lines, parent_texts,
929
left_matching_blocks, nostore_sha, random_id, check_content):
930
"""Add a version and save the weave."""
931
self.check_not_reserved_id(version_id)
932
result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
933
parent_texts, left_matching_blocks, nostore_sha, random_id,
938
def copy_to(self, name, transport):
939
"""See VersionedFile.copy_to()."""
940
# as we are all in memory always, just serialise to the new place.
942
write_weave_v5(self, sio)
944
transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
947
"""Save the weave."""
948
self._check_write_ok()
950
write_weave_v5(self, sio)
952
bytes = sio.getvalue()
953
path = self._weave_name + WeaveFile.WEAVE_SUFFIX
955
self._transport.put_bytes(path, bytes, self._filemode)
956
except errors.NoSuchFile:
957
self._transport.mkdir(dirname(path))
958
self._transport.put_bytes(path, bytes, self._filemode)
962
"""See VersionedFile.get_suffixes()."""
963
return [WeaveFile.WEAVE_SUFFIX]
965
def insert_record_stream(self, stream):
966
super(WeaveFile, self).insert_record_stream(stream)
969
@deprecated_method(one_five)
970
def join(self, other, pb=None, msg=None, version_ids=None,
971
ignore_missing=False):
972
"""Join other into self and save."""
973
super(WeaveFile, self).join(other, pb, msg, version_ids, ignore_missing)
977
def _reweave(wa, wb, pb=None, msg=None):
913
setattr(self, attr, getattr(new_weave, attr))
916
def reweave(wa, wb, pb=None, msg=None):
978
917
"""Combine two weaves and return the result.
980
919
This works even if a revision R has different parents in
1219
1166
print ' '.join(map(str, w._parents[int(argv[3])]))
1221
1168
elif cmd == 'plan-merge':
1222
# replaced by 'bzr weave-plan-merge'
1224
1170
for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1226
1172
print '%14s | %s' % (state, line),
1227
1174
elif cmd == 'merge':
1228
# replaced by 'bzr weave-merge-text'
1230
1176
p = w.plan_merge(int(argv[3]), int(argv[4]))
1231
1177
sys.stdout.writelines(w.weave_merge(p))
1179
elif cmd == 'mash-merge':
1185
v1, v2 = map(int, argv[3:5])
1187
basis = w.inclusions([v1]).intersection(w.inclusions([v2]))
1189
base_lines = list(w.mash_iter(basis))
1190
a_lines = list(w.get(v1))
1191
b_lines = list(w.get(v2))
1193
from bzrlib.merge3 import Merge3
1194
m3 = Merge3(base_lines, a_lines, b_lines)
1196
name_a = 'version %d' % v1
1197
name_b = 'version %d' % v2
1198
sys.stdout.writelines(m3.merge_lines(name_a=name_a, name_b=name_b))
1233
1200
raise ValueError('unknown command %r' % cmd)
1204
def profile_main(argv):
1205
import tempfile, hotshot, hotshot.stats
1207
prof_f = tempfile.NamedTemporaryFile()
1209
prof = hotshot.Profile(prof_f.name)
1211
ret = prof.runcall(main, argv)
1214
stats = hotshot.stats.load(prof_f.name)
1216
stats.sort_stats('cumulative')
1217
## XXX: Might like to write to stderr or the trace file instead but
1218
## print_stats seems hardcoded to stdout
1219
stats.print_stats(20)
1224
def lsprofile_main(argv):
1225
from bzrlib.lsprof import profile
1226
ret,stats = profile(main, argv)
1236
1232
if __name__ == '__main__':
1238
sys.exit(main(sys.argv))
1234
if '--profile' in sys.argv:
1236
args.remove('--profile')
1237
sys.exit(profile_main(args))
1238
elif '--lsprof' in sys.argv:
1240
args.remove('--lsprof')
1241
sys.exit(lsprofile_main(args))
1243
sys.exit(main(sys.argv))