3
# Copyright (C) 2005 Canonical Ltd
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
# Author: Martin Pool <mbp@canonical.com>
22
"""Weave - storage of related text file versions"""
25
# XXX: If we do weaves this way, will a merge still behave the same
26
# way if it's done in a different order? That's a pretty desirable
29
# TODO: Nothing here so far assumes the lines are really \n newlines,
30
# rather than being split up in some other way. We could accomodate
31
# binaries, perhaps by naively splitting on \n or perhaps using
32
# something like a rolling checksum.
34
# TODO: End marker for each version so we can stop reading?
36
# TODO: Check that no insertion occurs inside a deletion that was
37
# active in the version of the insertion.
39
# TODO: In addition to the SHA-1 check, perhaps have some code that
40
# checks structural constraints of the weave: ie that insertions are
41
# properly nested, that there is no text outside of an insertion, that
42
# insertions or deletions are not repeated, etc.
44
# TODO: Parallel-extract that passes back each line along with a
45
# description of which revisions include it. Nice for checking all
46
# shas or calculating stats in parallel.
48
# TODO: Using a single _extract routine and then processing the output
49
# is probably inefficient. It's simple enough that we can afford to
50
# have slight specializations for different ways its used: annotate,
51
# basis for add, get, etc.
53
# TODO: Probably the API should work only in names to hide the integer
54
# indexes from the user.
56
# TODO: Is there any potential performance win by having an add()
57
# variant that is passed a pre-cooked version of the single basis
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.
71
from difflib import SequenceMatcher
73
from bzrlib.trace import mutter
76
class WeaveError(Exception):
77
"""Exception in processing weave"""
80
class WeaveFormatError(WeaveError):
81
"""Weave invariant violated"""
84
class WeaveParentMismatch(WeaveError):
85
"""Parents are mismatched between two revisions."""
89
"""weave - versioned text file storage.
91
A Weave manages versions of line-based text files, keeping track
92
of the originating version for each line.
94
To clients the "lines" of the file are represented as a list of strings.
95
These strings will typically have terminal newline characters, but
96
this is not required. In particular files commonly do not have a newline
97
at the end of the file.
99
Texts can be identified in either of two ways:
101
* a nonnegative index number.
103
* a version-id string.
105
Typically the index number will be valid only inside this weave and
106
the version-id is used to reference it in the larger world.
108
The weave is represented as a list mixing edit instructions and
109
literal text. Each entry in _weave can be either a string (or
110
unicode), or a tuple. If a string, it means that the given line
111
should be output in the currently active revisions.
113
If a tuple, it gives a processing instruction saying in which
114
revisions the enclosed lines are active. The tuple has the form
115
(instruction, version).
117
The instruction can be '{' or '}' for an insertion block, and '['
118
and ']' for a deletion block respectively. The version is the
119
integer version index. There is no replace operator, only deletes
120
and inserts. For '}', the end of an insertion, there is no
121
version parameter because it always closes the most recently
126
* A later version can delete lines that were introduced by any
127
number of ancestor versions; this implies that deletion
128
instructions can span insertion blocks without regard to the
129
insertion block's nesting.
131
* Similarly, deletions need not be properly nested with regard to
132
each other, because they might have been generated by
133
independent revisions.
135
* Insertions are always made by inserting a new bracketed block
136
into a single point in the previous weave. This implies they
137
can nest but not overlap, and the nesting must always have later
138
insertions on the inside.
140
* It doesn't seem very useful to have an active insertion
141
inside an inactive insertion, but it might happen.
143
* Therefore, all instructions are always"considered"; that
144
is passed onto and off the stack. An outer inactive block
145
doesn't disable an inner block.
147
* Lines are enabled if the most recent enclosing insertion is
148
active and none of the enclosing deletions are active.
150
* There is no point having a deletion directly inside its own
151
insertion; you might as well just not write it. And there
152
should be no way to get an earlier version deleting a later
156
Text of the weave; list of control instruction tuples and strings.
159
List of parents, indexed by version number.
160
It is only necessary to store the minimal set of parents for
161
each version; the parent's parents are implied.
164
List of hex SHA-1 of each version.
167
List of symbolic names for each version. Each should be unique.
170
For each name, the version number.
173
Descriptive name of this weave; typically the filename if known.
177
__slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
180
def __init__(self, weave_name=None):
186
self._weave_name = weave_name
190
"""Return a deep copy of self.
192
The copy can be modified without affecting the original weave."""
194
other._weave = self._weave[:]
195
other._parents = self._parents[:]
196
other._sha1s = self._sha1s[:]
197
other._names = self._names[:]
198
other._name_map = self._name_map.copy()
199
other._weave_name = self._weave_name
202
def __eq__(self, other):
203
if not isinstance(other, Weave):
205
return self._parents == other._parents \
206
and self._weave == other._weave \
207
and self._sha1s == other._sha1s
210
def __ne__(self, other):
211
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):
223
"""Convert symbolic version name to index."""
225
return self._name_map[name]
227
raise WeaveError("name %r not present in weave %r" %
228
(name, self._weave_name))
231
return self._names[:]
233
def iter_names(self):
234
"""Yield a list of all names in this weave."""
235
return iter(self._names)
237
def idx_to_name(self, version):
238
return self._names[version]
241
def _check_repeated_add(self, name, parents, text, sha1):
242
"""Check that a duplicated add is OK.
244
If it is, return the (old) index; otherwise raise an exception.
246
idx = self.lookup(name)
247
if sorted(self._parents[idx]) != sorted(parents):
248
raise WeaveError("name \"%s\" already present in weave "
249
"with different parents" % name)
250
if sha1 != self._sha1s[idx]:
251
raise WeaveError("name \"%s\" already present in weave "
252
"with different text" % name)
257
def add(self, name, parents, text, sha1=None):
258
"""Add a single text on top of the weave.
260
Returns the index number of the newly added version.
263
Symbolic name for this version.
264
(Typically the revision-id of the revision that added it.)
267
List or set of direct parent version numbers.
270
Sequence of lines to be added in the new version.
272
sha -- SHA-1 of the file, if known. This is trusted to be
275
from bzrlib.osutils import sha_strings
277
assert isinstance(name, basestring)
279
sha1 = sha_strings(text)
280
if name in self._name_map:
281
return self._check_repeated_add(name, parents, text, sha1)
283
parents = map(self.maybe_lookup, parents)
284
self._check_versions(parents)
285
## self._check_lines(text)
286
new_version = len(self._parents)
289
# if we abort after here the (in-memory) weave will be corrupt because only
290
# some fields are updated
291
self._parents.append(parents[:])
292
self._sha1s.append(sha1)
293
self._names.append(name)
294
self._name_map[name] = new_version
298
# special case; adding with no parents revision; can do
299
# this more quickly by just appending unconditionally.
300
# even more specially, if we're adding an empty text we
301
# need do nothing at all.
303
self._weave.append(('{', new_version))
304
self._weave.extend(text)
305
self._weave.append(('}', None))
309
if len(parents) == 1:
310
pv = list(parents)[0]
311
if sha1 == self._sha1s[pv]:
312
# special case: same as the single parent
316
ancestors = self.inclusions(parents)
320
# basis a list of (origin, lineno, line)
323
for origin, lineno, line in self._extract(ancestors):
324
basis_lineno.append(lineno)
325
basis_lines.append(line)
327
# another small special case: a merge, producing the same text
329
if text == basis_lines:
332
# add a sentinal, because we can also match against the final line
333
basis_lineno.append(len(self._weave))
335
# XXX: which line of the weave should we really consider
336
# matches the end of the file? the current code says it's the
337
# last line of the weave?
339
#print 'basis_lines:', basis_lines
340
#print 'new_lines: ', lines
342
s = SequenceMatcher(None, basis_lines, text)
344
# offset gives the number of lines that have been inserted
345
# into the weave up to the current point; if the original edit instruction
346
# says to change line A then we actually change (A+offset)
349
for tag, i1, i2, j1, j2 in s.get_opcodes():
350
# i1,i2 are given in offsets within basis_lines; we need to map them
351
# back to offsets within the entire weave
352
#print 'raw match', tag, i1, i2, j1, j2
356
i1 = basis_lineno[i1]
357
i2 = basis_lineno[i2]
359
assert 0 <= j1 <= j2 <= len(text)
361
#print tag, i1, i2, j1, j2
363
# the deletion and insertion are handled separately.
364
# first delete the region.
366
self._weave.insert(i1+offset, ('[', new_version))
367
self._weave.insert(i2+offset+1, (']', new_version))
371
# there may have been a deletion spanning up to
372
# i2; we want to insert after this region to make sure
373
# we don't destroy ourselves
375
self._weave[i:i] = ([('{', new_version)]
378
offset += 2 + (j2 - j1)
382
def add_identical(self, old_rev_id, new_rev_id, parents):
383
"""Add an identical text to old_rev_id as new_rev_id."""
384
old_lines = self.get(self.lookup(old_rev_id))
385
self.add(new_rev_id, parents, old_lines)
387
def inclusions(self, versions):
388
"""Return set of all ancestors of given version(s)."""
390
for v in xrange(max(versions), 0, -1):
392
# include all its parents
393
i.update(self._parents[v])
395
## except IndexError:
396
## raise ValueError("version %d not present in weave" % v)
399
def parents(self, version):
400
return self._parents[version]
403
def parent_names(self, version):
404
"""Return version names for parents of a version."""
405
return map(self.idx_to_name, self._parents[self.lookup(version)])
408
def minimal_parents(self, version):
409
"""Find the minimal set of parents for the version."""
410
included = self._parents[version]
415
li.sort(reverse=True)
423
gotit.update(self.inclusions(pv))
425
assert mininc[0] >= 0
426
assert mininc[-1] < version
431
def _check_lines(self, text):
432
if not isinstance(text, list):
433
raise ValueError("text should be a list, not %s" % type(text))
436
if not isinstance(l, basestring):
437
raise ValueError("text line should be a string or unicode, not %s"
442
def _check_versions(self, indexes):
443
"""Check everything in the sequence of indexes is valid"""
448
raise IndexError("invalid version number %r" % i)
451
def annotate(self, name_or_index):
452
return list(self.annotate_iter(name_or_index))
455
def annotate_iter(self, name_or_index):
456
"""Yield list of (index-id, line) pairs for the specified version.
458
The index indicates when the line originated in the weave."""
459
incls = [self.maybe_lookup(name_or_index)]
460
for origin, lineno, text in self._extract(incls):
468
(lineno, insert, deletes, text)
469
for each literal line.
475
lineno = 0 # line of weave, 0-based
477
for l in self._weave:
478
if isinstance(l, tuple):
491
raise WeaveFormatError('unexpected instruction %r'
494
assert isinstance(l, basestring)
496
yield lineno, istack[-1], dset, l
501
def _extract(self, versions):
502
"""Yield annotation of lines in included set.
504
Yields a sequence of tuples (origin, lineno, text), where
505
origin is the origin version, lineno the index in the weave,
506
and text the text of the line.
508
The set typically but not necessarily corresponds to a version.
511
if not isinstance(i, int):
514
included = self.inclusions(versions)
519
lineno = 0 # line of weave, 0-based
525
WFE = WeaveFormatError
527
for l in self._weave:
528
if isinstance(l, tuple):
532
assert v not in istack
546
assert isinstance(l, basestring)
548
isactive = (not dset) and istack and (istack[-1] in included)
550
result.append((istack[-1], lineno, l))
554
raise WFE("unclosed insertion blocks at end of weave",
557
raise WFE("unclosed deletion blocks at end of weave",
564
def get_iter(self, name_or_index):
565
"""Yield lines for the specified version."""
566
incls = [self.maybe_lookup(name_or_index)]
567
for origin, lineno, line in self._extract(incls):
571
def get_text(self, name_or_index):
572
return ''.join(self.get_iter(name_or_index))
573
assert isinstance(version, int)
576
def get_lines(self, name_or_index):
577
return list(self.get_iter(name_or_index))
583
def mash_iter(self, included):
584
"""Return composed version of multiple included versions."""
585
included = map(self.maybe_lookup, included)
586
for origin, lineno, text in self._extract(included):
590
def dump(self, to_file):
591
from pprint import pprint
592
print >>to_file, "Weave._weave = ",
593
pprint(self._weave, to_file)
594
print >>to_file, "Weave._parents = ",
595
pprint(self._parents, to_file)
599
def numversions(self):
600
l = len(self._parents)
601
assert l == len(self._sha1s)
606
return self.numversions()
609
def check(self, progress_bar=None):
610
# check no circular inclusions
611
for version in range(self.numversions()):
612
inclusions = list(self._parents[version])
615
if inclusions[-1] >= version:
616
raise WeaveFormatError("invalid included version %d for index %d"
617
% (inclusions[-1], version))
619
# try extracting all versions; this is a bit slow and parallel
620
# extraction could be used
621
nv = self.numversions()
622
for version in range(nv):
624
progress_bar.update('checking text', version, nv)
626
for l in self.get_iter(version):
629
expected = self._sha1s[version]
631
raise WeaveError("mismatched sha1 for version %d; "
632
"got %s, expected %s"
633
% (version, hd, expected))
635
# TODO: check insertions are properly nested, that there are
636
# no lines outside of insertion blocks, that deletions are
637
# properly paired, etc.
641
def merge(self, merge_versions):
642
"""Automerge and mark conflicts between versions.
644
This returns a sequence, each entry describing alternatives
645
for a chunk of the file. Each of the alternatives is given as
648
If there is a chunk of the file where there's no diagreement,
649
only one alternative is given.
651
# approach: find the included versions common to all the
653
raise NotImplementedError()
657
def _delta(self, included, lines):
658
"""Return changes from basis to new revision.
660
The old text for comparison is the union of included revisions.
662
This is used in inserting a new text.
664
Delta is returned as a sequence of
665
(weave1, weave2, newlines).
667
This indicates that weave1:weave2 of the old weave should be
668
replaced by the sequence of lines in newlines. Note that
669
these line numbers are positions in the total weave and don't
670
correspond to the lines in any extracted version, or even the
671
extracted union of included versions.
673
If line1=line2, this is a pure insert; if newlines=[] this is a
674
pure delete. (Similar to difflib.)
676
raise NotImplementedError()
679
def plan_merge(self, ver_a, ver_b):
680
"""Return pseudo-annotation indicating how the two versions merge.
682
This is computed between versions a and b and their common
685
Weave lines present in none of them are skipped entirely.
687
inc_a = self.inclusions([ver_a])
688
inc_b = self.inclusions([ver_b])
689
inc_c = inc_a & inc_b
691
for lineno, insert, deleteset, line in self._walk():
692
if deleteset & inc_c:
693
# killed in parent; can't be in either a or b
694
# not relevant to our work
695
yield 'killed-base', line
696
elif insert in inc_c:
697
# was inserted in base
698
killed_a = bool(deleteset & inc_a)
699
killed_b = bool(deleteset & inc_b)
700
if killed_a and killed_b:
701
yield 'killed-both', line
703
yield 'killed-a', line
705
yield 'killed-b', line
707
yield 'unchanged', line
708
elif insert in inc_a:
709
if deleteset & inc_a:
710
yield 'ghost-a', line
714
elif insert in inc_b:
715
if deleteset & inc_b:
716
yield 'ghost-b', line
720
# not in either revision
721
yield 'irrelevant', line
723
yield 'unchanged', '' # terminator
727
def weave_merge(self, plan):
732
for state, line in plan:
733
if state == 'unchanged' or state == 'killed-both':
734
# resync and flush queued conflicts changes if any
735
if not lines_a and not lines_b:
737
elif ch_a and not ch_b:
739
for l in lines_a: yield l
740
elif ch_b and not ch_a:
741
for l in lines_b: yield l
742
elif lines_a == lines_b:
743
for l in lines_a: yield l
746
for l in lines_a: yield l
748
for l in lines_b: yield l
755
if state == 'unchanged':
758
elif state == 'killed-a':
761
elif state == 'killed-b':
764
elif state == 'new-a':
767
elif state == 'new-b':
771
assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
776
def join(self, other):
777
"""Integrate versions from other into this weave.
779
The resulting weave contains all the history of both weaves;
780
any version you could retrieve from either self or other can be
781
retrieved from self after this call.
783
It is illegal for the two weaves to contain different values
784
or different parents for any version. See also reweave().
786
if other.numversions() == 0:
787
return # nothing to update, easy
788
# two loops so that we do not change ourselves before verifying it
790
# work through in index order to make sure we get all dependencies
791
for other_idx, name in enumerate(other._names):
792
if self._check_version_consistent(other, other_idx, name):
794
for other_idx, name in enumerate(other._names):
795
# TODO: If all the parents of the other version are already
796
# present then we can avoid some work by just taking the delta
797
# and adjusting the offsets.
798
new_parents = self._imported_parents(other, other_idx)
799
lines = other.get_lines(other_idx)
800
sha1 = other._sha1s[other_idx]
801
self.add(name, new_parents, lines, sha1)
804
def _imported_parents(self, other, other_idx):
805
"""Return list of parents in self corresponding to indexes in other."""
807
for parent_idx in other._parents[other_idx]:
808
parent_name = other._names[parent_idx]
809
if parent_name not in self._names:
810
# should not be possible
811
raise WeaveError("missing parent {%s} of {%s} in %r"
812
% (parent_name, other._name_map[other_idx], self))
813
new_parents.append(self._name_map[parent_name])
816
def _check_version_consistent(self, other, other_idx, name):
817
"""Check if a version in consistent in this and other.
819
To be consistent it must have:
822
* the same direct parents (by name, not index, and disregarding
825
If present & correct return True;
826
if not present in self return False;
827
if inconsistent raise error."""
828
this_idx = self._name_map.get(name, -1)
830
if self._sha1s[this_idx] != other._sha1s[other_idx]:
831
raise WeaveError("inconsistent texts for version {%s} "
832
"when joining weaves"
834
self_parents = self._parents[this_idx]
835
other_parents = other._parents[other_idx]
836
n1 = [self._names[i] for i in self_parents]
837
n2 = [other._names[i] for i in other_parents]
841
raise WeaveParentMismatch("inconsistent parents "
842
"for version {%s}: %s vs %s" % (name, n1, n2))
848
def reweave(self, other):
849
"""Reweave self with other."""
850
new_weave = reweave(self, other)
851
for attr in self.__slots__:
852
setattr(self, attr, getattr(new_weave, attr))
856
"""Combine two weaves and return the result.
858
This works even if a revision R has different parents in
859
wa and wb. In the resulting weave all the parents are given.
861
This is done by just building up a new weave, maintaining ordering
862
of the versions in the two inputs. More efficient approaches
863
might be possible but it should only be necessary to do
864
this operation rarely, when a new previously ghost version is
869
queue_a = range(wa.numversions())
870
queue_b = range(wb.numversions())
871
# first determine combined parents of all versions
872
# map from version name -> all parent names
873
combined_parents = _reweave_parent_graphs(wa, wb)
874
mutter("combined parents: %r", combined_parents)
875
order = _make_reweave_order(wa._names, wb._names, combined_parents)
876
mutter("order to reweave: %r", order)
878
if name in wa._name_map:
879
lines = wa.get_lines(name)
880
if name in wb._name_map:
881
assert lines == wb.get_lines(name)
883
lines = wb.get_lines(name)
884
wr.add(name, combined_parents[name], lines)
888
def _reweave_parent_graphs(wa, wb):
889
"""Return combined parent ancestry for two weaves.
891
Returned as a list of (version_name, set(parent_names))"""
893
for weave in [wa, wb]:
894
for idx, name in enumerate(weave._names):
895
p = combined.setdefault(name, set())
896
p.update(map(weave.idx_to_name, weave._parents[idx]))
900
def _make_reweave_order(wa_order, wb_order, combined_parents):
901
"""Return an order to reweave versions respecting parents."""
905
next_a = next_b = None
906
len_a = len(wa_order)
907
len_b = len(wb_order)
908
while ia < len(wa_order) or ib < len(wb_order):
910
next_a = wa_order[ia]
914
if combined_parents[next_a].issubset(done):
916
result.append(next_a)
920
next_b = wb_order[ib]
924
elif combined_parents[next_b].issubset(done):
926
result.append(next_b)
929
raise WeaveError("don't know how to reweave at {%s} and {%s}"
935
"""Show the weave's table-of-contents"""
936
print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
937
for i in (6, 50, 10, 10):
940
for i in range(w.numversions()):
943
parent_str = ' '.join(map(str, w._parents[i]))
944
print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
948
def weave_stats(weave_file, pb):
949
from bzrlib.weavefile import read_weave
951
wf = file(weave_file, 'rb')
953
# FIXME: doesn't work on pipes
954
weave_size = wf.tell()
958
for i in range(vers):
959
pb.update('checking sizes', i, vers)
960
for origin, lineno, line in w._extract([i]):
965
print 'versions %9d' % vers
966
print 'weave file %9d bytes' % weave_size
967
print 'total contents %9d bytes' % total
968
print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
971
print 'average size %9d bytes' % avg
972
print 'relative size %9.2fx' % (float(weave_size) / float(avg))
976
print """bzr weave tool
978
Experimental tool for weave algorithm.
982
Create an empty weave file
983
weave get WEAVEFILE VERSION
984
Write out specified version.
985
weave check WEAVEFILE
986
Check consistency of all versions.
988
Display table of contents.
989
weave add WEAVEFILE NAME [BASE...] < NEWTEXT
990
Add NEWTEXT, with specified parent versions.
991
weave annotate WEAVEFILE VERSION
992
Display origin of each line.
993
weave mash WEAVEFILE VERSION...
994
Display composite of all selected versions.
995
weave merge WEAVEFILE VERSION1 VERSION2 > OUT
996
Auto-merge two versions and display conflicts.
997
weave diff WEAVEFILE VERSION1 VERSION2
998
Show differences between two versions.
1002
% weave init foo.weave
1004
% weave add foo.weave ver0 < foo.txt
1007
(create updated version)
1009
% weave get foo.weave 0 | diff -u - foo.txt
1010
% weave add foo.weave ver1 0 < foo.txt
1013
% weave get foo.weave 0 > foo.txt (create forked version)
1015
% weave add foo.weave ver2 0 < foo.txt
1018
% weave merge foo.weave 1 2 > foo.txt (merge them)
1019
% vi foo.txt (resolve conflicts)
1020
% weave add foo.weave merged 1 2 < foo.txt (commit merged version)
1032
# in case we're run directly from the subdirectory
1033
sys.path.append('..')
1035
from bzrlib.weavefile import write_weave, read_weave
1036
from bzrlib.progress import ProgressBar
1051
return read_weave(file(argv[2], 'rb'))
1057
# at the moment, based on everything in the file
1059
parents = map(int, argv[4:])
1060
lines = sys.stdin.readlines()
1061
ver = w.add(name, parents, lines)
1062
write_weave(w, file(argv[2], 'wb'))
1063
print 'added version %r %d' % (name, ver)
1066
if os.path.exists(fn):
1067
raise IOError("file exists")
1069
write_weave(w, file(fn, 'wb'))
1070
elif cmd == 'get': # get one version
1072
sys.stdout.writelines(w.get_iter(int(argv[3])))
1074
elif cmd == 'mash': # get composite
1076
sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
1079
from difflib import unified_diff
1082
v1, v2 = map(int, argv[3:5])
1085
diff_gen = unified_diff(lines1, lines2,
1086
'%s version %d' % (fn, v1),
1087
'%s version %d' % (fn, v2))
1088
sys.stdout.writelines(diff_gen)
1090
elif cmd == 'annotate':
1092
# newline is added to all lines regardless; too hard to get
1093
# reasonable formatting otherwise
1095
for origin, text in w.annotate(int(argv[3])):
1096
text = text.rstrip('\r\n')
1098
print ' | %s' % (text)
1100
print '%5d | %s' % (origin, text)
1106
elif cmd == 'stats':
1107
weave_stats(argv[2], ProgressBar())
1109
elif cmd == 'check':
1114
print '%d versions ok' % w.numversions()
1116
elif cmd == 'inclusions':
1118
print ' '.join(map(str, w.inclusions([int(argv[3])])))
1120
elif cmd == 'parents':
1122
print ' '.join(map(str, w._parents[int(argv[3])]))
1124
elif cmd == 'plan-merge':
1126
for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1128
print '%14s | %s' % (state, line),
1130
elif cmd == 'merge':
1132
p = w.plan_merge(int(argv[3]), int(argv[4]))
1133
sys.stdout.writelines(w.weave_merge(p))
1135
elif cmd == 'mash-merge':
1141
v1, v2 = map(int, argv[3:5])
1143
basis = w.inclusions([v1]).intersection(w.inclusions([v2]))
1145
base_lines = list(w.mash_iter(basis))
1146
a_lines = list(w.get(v1))
1147
b_lines = list(w.get(v2))
1149
from bzrlib.merge3 import Merge3
1150
m3 = Merge3(base_lines, a_lines, b_lines)
1152
name_a = 'version %d' % v1
1153
name_b = 'version %d' % v2
1154
sys.stdout.writelines(m3.merge_lines(name_a=name_a, name_b=name_b))
1156
raise ValueError('unknown command %r' % cmd)
1160
def profile_main(argv):
1161
import tempfile, hotshot, hotshot.stats
1163
prof_f = tempfile.NamedTemporaryFile()
1165
prof = hotshot.Profile(prof_f.name)
1167
ret = prof.runcall(main, argv)
1170
stats = hotshot.stats.load(prof_f.name)
1172
stats.sort_stats('cumulative')
1173
## XXX: Might like to write to stderr or the trace file instead but
1174
## print_stats seems hardcoded to stdout
1175
stats.print_stats(20)
1180
if __name__ == '__main__':
1182
if '--profile' in sys.argv:
1184
args.remove('--profile')
1185
sys.exit(profile_main(args))
1187
sys.exit(main(sys.argv))