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: Perhaps 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: 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.
75
from difflib import SequenceMatcher
77
from bzrlib.trace import mutter
80
class WeaveError(Exception):
81
"""Exception in processing weave"""
84
class WeaveFormatError(WeaveError):
85
"""Weave invariant violated"""
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
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]
239
def _check_repeated_add(self, name, parents, text, sha1):
240
"""Check that a duplicated add is OK.
242
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)
255
def add(self, name, parents, text, sha1=None):
256
"""Add a single text on top of the weave.
258
Returns the index number of the newly added version.
261
Symbolic name for this version.
262
(Typically the revision-id of the revision that added it.)
265
List or set of direct parent version numbers.
268
Sequence of lines to be added in the new version.
270
sha -- SHA-1 of the file, if known. This is trusted to be
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)
282
self._check_versions(parents)
283
## self._check_lines(text)
284
new_version = len(self._parents)
287
# if we abort after here the (in-memory) weave will be corrupt because only
288
# some fields are updated
289
self._parents.append(parents[:])
290
self._sha1s.append(sha1)
291
self._names.append(name)
292
self._name_map[name] = new_version
296
# special case; adding with no parents revision; can do
297
# this more quickly by just appending unconditionally.
298
# even more specially, if we're adding an empty text we
299
# need do nothing at all.
301
self._weave.append(('{', new_version))
302
self._weave.extend(text)
303
self._weave.append(('}', None))
307
if len(parents) == 1:
308
pv = list(parents)[0]
309
if sha1 == self._sha1s[pv]:
310
# special case: same as the single parent
314
ancestors = self.inclusions(parents)
318
# basis a list of (origin, lineno, line)
321
for origin, lineno, line in self._extract(ancestors):
322
basis_lineno.append(lineno)
323
basis_lines.append(line)
325
# another small special case: a merge, producing the same text
327
if text == basis_lines:
330
# add a sentinal, because we can also match against the final line
331
basis_lineno.append(len(self._weave))
333
# XXX: which line of the weave should we really consider
334
# matches the end of the file? the current code says it's the
335
# last line of the weave?
337
#print 'basis_lines:', basis_lines
338
#print 'new_lines: ', lines
340
s = SequenceMatcher(None, basis_lines, text)
342
# offset gives the number of lines that have been inserted
343
# into the weave up to the current point; if the original edit instruction
344
# says to change line A then we actually change (A+offset)
347
for tag, i1, i2, j1, j2 in s.get_opcodes():
348
# i1,i2 are given in offsets within basis_lines; we need to map them
349
# back to offsets within the entire weave
350
#print 'raw match', tag, i1, i2, j1, j2
354
i1 = basis_lineno[i1]
355
i2 = basis_lineno[i2]
357
assert 0 <= j1 <= j2 <= len(text)
359
#print tag, i1, i2, j1, j2
361
# the deletion and insertion are handled separately.
362
# first delete the region.
364
self._weave.insert(i1+offset, ('[', new_version))
365
self._weave.insert(i2+offset+1, (']', new_version))
369
# there may have been a deletion spanning up to
370
# i2; we want to insert after this region to make sure
371
# we don't destroy ourselves
373
self._weave[i:i] = ([('{', new_version)]
376
offset += 2 + (j2 - j1)
380
def add_identical(self, old_rev_id, new_rev_id, parents):
381
"""Add an identical text to old_rev_id as new_rev_id."""
382
old_lines = self.get(self.lookup(old_rev_id))
383
self.add(new_rev_id, parents, old_lines)
385
def inclusions(self, versions):
386
"""Return set of all ancestors of given version(s)."""
388
for v in xrange(max(versions), 0, -1):
390
# include all its parents
391
i.update(self._parents[v])
393
## except IndexError:
394
## raise ValueError("version %d not present in weave" % v)
397
def parents(self, version):
398
return self._parents[version]
401
def parent_names(self, version):
402
"""Return version names for parents of a version."""
403
return map(self.idx_to_name, self._parents[self.lookup(version)])
406
def minimal_parents(self, version):
407
"""Find the minimal set of parents for the version."""
408
included = self._parents[version]
413
li.sort(reverse=True)
421
gotit.update(self.inclusions(pv))
423
assert mininc[0] >= 0
424
assert mininc[-1] < version
429
def _check_lines(self, text):
430
if not isinstance(text, list):
431
raise ValueError("text should be a list, not %s" % type(text))
434
if not isinstance(l, basestring):
435
raise ValueError("text line should be a string or unicode, not %s"
440
def _check_versions(self, indexes):
441
"""Check everything in the sequence of indexes is valid"""
446
raise IndexError("invalid version number %r" % i)
449
def annotate(self, name_or_index):
450
return list(self.annotate_iter(name_or_index))
453
def annotate_iter(self, name_or_index):
454
"""Yield list of (index-id, line) pairs for the specified version.
456
The index indicates when the line originated in the weave."""
457
incls = [self.maybe_lookup(name_or_index)]
458
for origin, lineno, text in self._extract(incls):
466
(lineno, insert, deletes, text)
467
for each literal line.
473
lineno = 0 # line of weave, 0-based
475
for l in self._weave:
476
if isinstance(l, tuple):
489
raise WeaveFormatError('unexpected instruction %r'
492
assert isinstance(l, basestring)
494
yield lineno, istack[-1], dset, l
499
def _extract(self, versions):
500
"""Yield annotation of lines in included set.
502
Yields a sequence of tuples (origin, lineno, text), where
503
origin is the origin version, lineno the index in the weave,
504
and text the text of the line.
506
The set typically but not necessarily corresponds to a version.
509
if not isinstance(i, int):
512
included = self.inclusions(versions)
517
lineno = 0 # line of weave, 0-based
523
WFE = WeaveFormatError
525
for l in self._weave:
526
if isinstance(l, tuple):
530
assert v not in istack
544
assert isinstance(l, basestring)
546
isactive = (not dset) and istack and (istack[-1] in included)
548
result.append((istack[-1], lineno, l))
552
raise WFE("unclosed insertion blocks at end of weave",
555
raise WFE("unclosed deletion blocks at end of weave",
562
def get_iter(self, name_or_index):
563
"""Yield lines for the specified version."""
564
incls = [self.maybe_lookup(name_or_index)]
565
for origin, lineno, line in self._extract(incls):
569
def get_text(self, name_or_index):
570
return ''.join(self.get_iter(name_or_index))
571
assert isinstance(version, int)
574
def get_lines(self, name_or_index):
575
return list(self.get_iter(name_or_index))
581
def mash_iter(self, included):
582
"""Return composed version of multiple included versions."""
583
included = map(self.maybe_lookup, included)
584
for origin, lineno, text in self._extract(included):
588
def dump(self, to_file):
589
from pprint import pprint
590
print >>to_file, "Weave._weave = ",
591
pprint(self._weave, to_file)
592
print >>to_file, "Weave._parents = ",
593
pprint(self._parents, to_file)
597
def numversions(self):
598
l = len(self._parents)
599
assert l == len(self._sha1s)
604
return self.numversions()
607
def check(self, progress_bar=None):
608
# check no circular inclusions
609
for version in range(self.numversions()):
610
inclusions = list(self._parents[version])
613
if inclusions[-1] >= version:
614
raise WeaveFormatError("invalid included version %d for index %d"
615
% (inclusions[-1], version))
617
# try extracting all versions; this is a bit slow and parallel
618
# extraction could be used
619
nv = self.numversions()
620
for version in range(nv):
622
progress_bar.update('checking text', version, nv)
624
for l in self.get_iter(version):
627
expected = self._sha1s[version]
629
raise WeaveError("mismatched sha1 for version %d; "
630
"got %s, expected %s"
631
% (version, hd, expected))
633
# TODO: check insertions are properly nested, that there are
634
# no lines outside of insertion blocks, that deletions are
635
# properly paired, etc.
639
def merge(self, merge_versions):
640
"""Automerge and mark conflicts between versions.
642
This returns a sequence, each entry describing alternatives
643
for a chunk of the file. Each of the alternatives is given as
646
If there is a chunk of the file where there's no diagreement,
647
only one alternative is given.
649
# approach: find the included versions common to all the
651
raise NotImplementedError()
655
def _delta(self, included, lines):
656
"""Return changes from basis to new revision.
658
The old text for comparison is the union of included revisions.
660
This is used in inserting a new text.
662
Delta is returned as a sequence of
663
(weave1, weave2, newlines).
665
This indicates that weave1:weave2 of the old weave should be
666
replaced by the sequence of lines in newlines. Note that
667
these line numbers are positions in the total weave and don't
668
correspond to the lines in any extracted version, or even the
669
extracted union of included versions.
671
If line1=line2, this is a pure insert; if newlines=[] this is a
672
pure delete. (Similar to difflib.)
674
raise NotImplementedError()
677
def plan_merge(self, ver_a, ver_b):
678
"""Return pseudo-annotation indicating how the two versions merge.
680
This is computed between versions a and b and their common
683
Weave lines present in none of them are skipped entirely.
685
inc_a = self.inclusions([ver_a])
686
inc_b = self.inclusions([ver_b])
687
inc_c = inc_a & inc_b
689
for lineno, insert, deleteset, line in self._walk():
690
if deleteset & inc_c:
691
# killed in parent; can't be in either a or b
692
# not relevant to our work
693
yield 'killed-base', line
694
elif insert in inc_c:
695
# was inserted in base
696
killed_a = bool(deleteset & inc_a)
697
killed_b = bool(deleteset & inc_b)
698
if killed_a and killed_b:
699
yield 'killed-both', line
701
yield 'killed-a', line
703
yield 'killed-b', line
705
yield 'unchanged', line
706
elif insert in inc_a:
707
if deleteset & inc_a:
708
yield 'ghost-a', line
712
elif insert in inc_b:
713
if deleteset & inc_b:
714
yield 'ghost-b', line
718
# not in either revision
719
yield 'irrelevant', line
721
yield 'unchanged', '' # terminator
725
def weave_merge(self, plan):
730
for state, line in plan:
731
if state == 'unchanged' or state == 'killed-both':
732
# resync and flush queued conflicts changes if any
733
if not lines_a and not lines_b:
735
elif ch_a and not ch_b:
737
for l in lines_a: yield l
738
elif ch_b and not ch_a:
739
for l in lines_b: yield l
740
elif lines_a == lines_b:
741
for l in lines_a: yield l
744
for l in lines_a: yield l
746
for l in lines_b: yield l
753
if state == 'unchanged':
756
elif state == 'killed-a':
759
elif state == 'killed-b':
762
elif state == 'new-a':
765
elif state == 'new-b':
769
assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
774
def join(self, other):
775
"""Integrate versions from other into this weave.
777
The resulting weave contains all the history of both weaves;
778
any version you could retrieve from either self or other can be
779
retrieved from self after this call.
781
It is illegal for the two weaves to contain different values
782
or different parents for any version. See also reweave().
784
if other.numversions() == 0:
785
return # nothing to update, easy
786
# work through in index order to make sure we get all dependencies
787
for other_idx, name in enumerate(other._names):
788
# TODO: If all the parents of the other version are already
789
# present then we can avoid some work by just taking the delta
790
# and adjusting the offsets.
791
if self._check_version_consistent(other, other_idx, name):
793
new_parents = self._imported_parents(other, other_idx)
794
lines = other.get_lines(other_idx)
795
sha1 = other._sha1s[other_idx]
796
self.add(name, new_parents, lines, sha1)
799
def _imported_parents(self, other, other_idx):
800
"""Return list of parents in self corresponding to indexes in other."""
802
for parent_idx in other._parents[other_idx]:
803
parent_name = other._names[parent_idx]
804
if parent_name not in self._names:
805
# should not be possible
806
raise WeaveError("missing parent {%s} of {%s} in %r"
807
% (parent_name, other._name_map[other_idx], self))
808
new_parents.append(self._name_map[parent_name])
811
def _check_version_consistent(self, other, other_idx, name):
812
"""Check if a version in consistent in this and other.
814
To be consistent it must have:
817
* the same direct parents (by name, not index, and disregarding
820
If present & correct return True;
821
if not present in self return False;
822
if inconsistent raise error."""
823
this_idx = self._name_map.get(name, -1)
825
if self._sha1s[this_idx] != other._sha1s[other_idx]:
826
raise WeaveError("inconsistent texts for version {%s} "
827
"when joining weaves"
829
self_parents = self._parents[this_idx]
830
other_parents = other._parents[other_idx]
831
n1 = [self._names[i] for i in self_parents]
832
n2 = [other._names[i] for i in other_parents]
836
# XXX: Perhaps return a specific exception for this so
837
# we can retry through reweave().
838
raise WeaveError("inconsistent parents for version {%s}: "
848
"""Combine two weaves and return the result.
850
This works even if a revision R has different parents in
851
wa and wb. In the resulting weave all the parents are given.
853
This is done by just building up a new weave, maintaining ordering
854
of the versions in the two inputs. More efficient approaches
855
might be possible but it should only be necessary to do
856
this operation rarely, when a new previously ghost version is
861
queue_a = range(wa.numversions())
862
queue_b = range(wb.numversions())
863
# first determine combined parents of all versions
864
# map from version name -> all parent names
865
combined_parents = _reweave_parent_graphs(wa, wb)
866
mutter("combined parents: %r", combined_parents)
867
order = _make_reweave_order(wa._names, wb._names, combined_parents)
868
mutter("order to reweave: %r", order)
870
if name in wa._name_map:
871
lines = wa.get_lines(name)
872
if name in wb._name_map:
873
assert lines == wb.get_lines(name)
875
lines = wb.get_lines(name)
876
wr.add(name, combined_parents[name], lines)
880
def _reweave_parent_graphs(wa, wb):
881
"""Return combined parent ancestry for two weaves.
883
Returned as a list of (version_name, set(parent_names))"""
885
for weave in [wa, wb]:
886
for idx, name in enumerate(weave._names):
887
p = combined.setdefault(name, set())
888
p.update(map(weave.idx_to_name, weave._parents[idx]))
892
def _make_reweave_order(wa_order, wb_order, combined_parents):
893
"""Return an order to reweave versions respecting parents."""
897
next_a = next_b = None
898
len_a = len(wa_order)
899
len_b = len(wb_order)
900
while ia < len(wa_order) or ib < len(wb_order):
902
next_a = wa_order[ia]
906
if combined_parents[next_a].issubset(done):
908
result.append(next_a)
912
next_b = wb_order[ib]
916
elif combined_parents[next_b].issubset(done):
918
result.append(next_b)
921
raise WeaveError("don't know how to reweave at {%s} and {%s}"
927
"""Show the weave's table-of-contents"""
928
print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
929
for i in (6, 50, 10, 10):
932
for i in range(w.numversions()):
935
parent_str = ' '.join(map(str, w._parents[i]))
936
print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
940
def weave_stats(weave_file, pb):
941
from bzrlib.weavefile import read_weave
943
wf = file(weave_file, 'rb')
945
# FIXME: doesn't work on pipes
946
weave_size = wf.tell()
950
for i in range(vers):
951
pb.update('checking sizes', i, vers)
952
for origin, lineno, line in w._extract([i]):
957
print 'versions %9d' % vers
958
print 'weave file %9d bytes' % weave_size
959
print 'total contents %9d bytes' % total
960
print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
963
print 'average size %9d bytes' % avg
964
print 'relative size %9.2fx' % (float(weave_size) / float(avg))
968
print """bzr weave tool
970
Experimental tool for weave algorithm.
974
Create an empty weave file
975
weave get WEAVEFILE VERSION
976
Write out specified version.
977
weave check WEAVEFILE
978
Check consistency of all versions.
980
Display table of contents.
981
weave add WEAVEFILE NAME [BASE...] < NEWTEXT
982
Add NEWTEXT, with specified parent versions.
983
weave annotate WEAVEFILE VERSION
984
Display origin of each line.
985
weave mash WEAVEFILE VERSION...
986
Display composite of all selected versions.
987
weave merge WEAVEFILE VERSION1 VERSION2 > OUT
988
Auto-merge two versions and display conflicts.
989
weave diff WEAVEFILE VERSION1 VERSION2
990
Show differences between two versions.
994
% weave init foo.weave
996
% weave add foo.weave ver0 < foo.txt
999
(create updated version)
1001
% weave get foo.weave 0 | diff -u - foo.txt
1002
% weave add foo.weave ver1 0 < foo.txt
1005
% weave get foo.weave 0 > foo.txt (create forked version)
1007
% weave add foo.weave ver2 0 < foo.txt
1010
% weave merge foo.weave 1 2 > foo.txt (merge them)
1011
% vi foo.txt (resolve conflicts)
1012
% weave add foo.weave merged 1 2 < foo.txt (commit merged version)
1024
# in case we're run directly from the subdirectory
1025
sys.path.append('..')
1027
from bzrlib.weavefile import write_weave, read_weave
1028
from bzrlib.progress import ProgressBar
1043
return read_weave(file(argv[2], 'rb'))
1049
# at the moment, based on everything in the file
1051
parents = map(int, argv[4:])
1052
lines = sys.stdin.readlines()
1053
ver = w.add(name, parents, lines)
1054
write_weave(w, file(argv[2], 'wb'))
1055
print 'added version %r %d' % (name, ver)
1058
if os.path.exists(fn):
1059
raise IOError("file exists")
1061
write_weave(w, file(fn, 'wb'))
1062
elif cmd == 'get': # get one version
1064
sys.stdout.writelines(w.get_iter(int(argv[3])))
1066
elif cmd == 'mash': # get composite
1068
sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
1071
from difflib import unified_diff
1074
v1, v2 = map(int, argv[3:5])
1077
diff_gen = unified_diff(lines1, lines2,
1078
'%s version %d' % (fn, v1),
1079
'%s version %d' % (fn, v2))
1080
sys.stdout.writelines(diff_gen)
1082
elif cmd == 'annotate':
1084
# newline is added to all lines regardless; too hard to get
1085
# reasonable formatting otherwise
1087
for origin, text in w.annotate(int(argv[3])):
1088
text = text.rstrip('\r\n')
1090
print ' | %s' % (text)
1092
print '%5d | %s' % (origin, text)
1098
elif cmd == 'stats':
1099
weave_stats(argv[2], ProgressBar())
1101
elif cmd == 'check':
1106
print '%d versions ok' % w.numversions()
1108
elif cmd == 'inclusions':
1110
print ' '.join(map(str, w.inclusions([int(argv[3])])))
1112
elif cmd == 'parents':
1114
print ' '.join(map(str, w._parents[int(argv[3])]))
1116
elif cmd == 'plan-merge':
1118
for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1120
print '%14s | %s' % (state, line),
1122
elif cmd == 'merge':
1124
p = w.plan_merge(int(argv[3]), int(argv[4]))
1125
sys.stdout.writelines(w.weave_merge(p))
1127
elif cmd == 'mash-merge':
1133
v1, v2 = map(int, argv[3:5])
1135
basis = w.inclusions([v1]).intersection(w.inclusions([v2]))
1137
base_lines = list(w.mash_iter(basis))
1138
a_lines = list(w.get(v1))
1139
b_lines = list(w.get(v2))
1141
from bzrlib.merge3 import Merge3
1142
m3 = Merge3(base_lines, a_lines, b_lines)
1144
name_a = 'version %d' % v1
1145
name_b = 'version %d' % v2
1146
sys.stdout.writelines(m3.merge_lines(name_a=name_a, name_b=name_b))
1148
raise ValueError('unknown command %r' % cmd)
1152
def profile_main(argv):
1153
import tempfile, hotshot, hotshot.stats
1155
prof_f = tempfile.NamedTemporaryFile()
1157
prof = hotshot.Profile(prof_f.name)
1159
ret = prof.runcall(main, argv)
1162
stats = hotshot.stats.load(prof_f.name)
1164
stats.sort_stats('cumulative')
1165
## XXX: Might like to write to stderr or the trace file instead but
1166
## print_stats seems hardcoded to stdout
1167
stats.print_stats(20)
1172
if __name__ == '__main__':
1174
if '--profile' in sys.argv:
1176
args.remove('--profile')
1177
sys.exit(profile_main(args))
1179
sys.exit(main(sys.argv))