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
74
from bzrlib.errors import WeaveError, WeaveFormatError, WeaveParentMismatch, \
75
WeaveRevisionNotPresent, WeaveRevisionAlreadyPresent
79
"""weave - versioned text file storage.
81
A Weave manages versions of line-based text files, keeping track
82
of the originating version for each line.
84
To clients the "lines" of the file are represented as a list of strings.
85
These strings will typically have terminal newline characters, but
86
this is not required. In particular files commonly do not have a newline
87
at the end of the file.
89
Texts can be identified in either of two ways:
91
* a nonnegative index number.
93
* a version-id string.
95
Typically the index number will be valid only inside this weave and
96
the version-id is used to reference it in the larger world.
98
The weave is represented as a list mixing edit instructions and
99
literal text. Each entry in _weave can be either a string (or
100
unicode), or a tuple. If a string, it means that the given line
101
should be output in the currently active revisions.
103
If a tuple, it gives a processing instruction saying in which
104
revisions the enclosed lines are active. The tuple has the form
105
(instruction, version).
107
The instruction can be '{' or '}' for an insertion block, and '['
108
and ']' for a deletion block respectively. The version is the
109
integer version index. There is no replace operator, only deletes
110
and inserts. For '}', the end of an insertion, there is no
111
version parameter because it always closes the most recently
116
* A later version can delete lines that were introduced by any
117
number of ancestor versions; this implies that deletion
118
instructions can span insertion blocks without regard to the
119
insertion block's nesting.
121
* Similarly, deletions need not be properly nested with regard to
122
each other, because they might have been generated by
123
independent revisions.
125
* Insertions are always made by inserting a new bracketed block
126
into a single point in the previous weave. This implies they
127
can nest but not overlap, and the nesting must always have later
128
insertions on the inside.
130
* It doesn't seem very useful to have an active insertion
131
inside an inactive insertion, but it might happen.
133
* Therefore, all instructions are always"considered"; that
134
is passed onto and off the stack. An outer inactive block
135
doesn't disable an inner block.
137
* Lines are enabled if the most recent enclosing insertion is
138
active and none of the enclosing deletions are active.
140
* There is no point having a deletion directly inside its own
141
insertion; you might as well just not write it. And there
142
should be no way to get an earlier version deleting a later
146
Text of the weave; list of control instruction tuples and strings.
149
List of parents, indexed by version number.
150
It is only necessary to store the minimal set of parents for
151
each version; the parent's parents are implied.
154
List of hex SHA-1 of each version.
157
List of symbolic names for each version. Each should be unique.
160
For each name, the version number.
163
Descriptive name of this weave; typically the filename if known.
167
__slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
170
def __init__(self, weave_name=None):
176
self._weave_name = weave_name
180
"""Return a deep copy of self.
182
The copy can be modified without affecting the original weave."""
184
other._weave = self._weave[:]
185
other._parents = self._parents[:]
186
other._sha1s = self._sha1s[:]
187
other._names = self._names[:]
188
other._name_map = self._name_map.copy()
189
other._weave_name = self._weave_name
192
def __eq__(self, other):
193
if not isinstance(other, Weave):
195
return self._parents == other._parents \
196
and self._weave == other._weave \
197
and self._sha1s == other._sha1s
200
def __ne__(self, other):
201
return not self.__eq__(other)
204
def maybe_lookup(self, name_or_index):
205
"""Convert possible symbolic name to index, or pass through indexes."""
206
if isinstance(name_or_index, (int, long)):
209
return self.lookup(name_or_index)
212
def lookup(self, name):
213
"""Convert symbolic version name to index."""
215
return self._name_map[name]
217
raise WeaveRevisionNotPresent(name, self)
220
return self._names[:]
222
def iter_names(self):
223
"""Yield a list of all names in this weave."""
224
return iter(self._names)
226
def idx_to_name(self, version):
227
return self._names[version]
229
def _check_repeated_add(self, name, parents, text, sha1):
230
"""Check that a duplicated add is OK.
232
If it is, return the (old) index; otherwise raise an exception.
234
idx = self.lookup(name)
235
if sorted(self._parents[idx]) != sorted(parents) \
236
or sha1 != self._sha1s[idx]:
237
raise WeaveRevisionAlreadyPresent(name, self)
240
def add(self, name, parents, text, sha1=None):
241
"""Add a single text on top of the weave.
243
Returns the index number of the newly added version.
246
Symbolic name for this version.
247
(Typically the revision-id of the revision that added it.)
250
List or set of direct parent version numbers.
253
Sequence of lines to be added in the new version.
255
sha -- SHA-1 of the file, if known. This is trusted to be
258
from bzrlib.osutils import sha_strings
260
assert isinstance(name, basestring)
262
sha1 = sha_strings(text)
263
if name in self._name_map:
264
return self._check_repeated_add(name, parents, text, sha1)
266
parents = map(self.maybe_lookup, parents)
267
self._check_versions(parents)
268
## self._check_lines(text)
269
new_version = len(self._parents)
272
# if we abort after here the (in-memory) weave will be corrupt because only
273
# some fields are updated
274
self._parents.append(parents[:])
275
self._sha1s.append(sha1)
276
self._names.append(name)
277
self._name_map[name] = new_version
281
# special case; adding with no parents revision; can do
282
# this more quickly by just appending unconditionally.
283
# even more specially, if we're adding an empty text we
284
# need do nothing at all.
286
self._weave.append(('{', new_version))
287
self._weave.extend(text)
288
self._weave.append(('}', None))
292
if len(parents) == 1:
293
pv = list(parents)[0]
294
if sha1 == self._sha1s[pv]:
295
# special case: same as the single parent
299
ancestors = self.inclusions(parents)
303
# basis a list of (origin, lineno, line)
306
for origin, lineno, line in self._extract(ancestors):
307
basis_lineno.append(lineno)
308
basis_lines.append(line)
310
# another small special case: a merge, producing the same text
312
if text == basis_lines:
315
# add a sentinal, because we can also match against the final line
316
basis_lineno.append(len(self._weave))
318
# XXX: which line of the weave should we really consider
319
# matches the end of the file? the current code says it's the
320
# last line of the weave?
322
#print 'basis_lines:', basis_lines
323
#print 'new_lines: ', lines
325
s = SequenceMatcher(None, basis_lines, text)
327
# offset gives the number of lines that have been inserted
328
# into the weave up to the current point; if the original edit instruction
329
# says to change line A then we actually change (A+offset)
332
for tag, i1, i2, j1, j2 in s.get_opcodes():
333
# i1,i2 are given in offsets within basis_lines; we need to map them
334
# back to offsets within the entire weave
335
#print 'raw match', tag, i1, i2, j1, j2
339
i1 = basis_lineno[i1]
340
i2 = basis_lineno[i2]
342
assert 0 <= j1 <= j2 <= len(text)
344
#print tag, i1, i2, j1, j2
346
# the deletion and insertion are handled separately.
347
# first delete the region.
349
self._weave.insert(i1+offset, ('[', new_version))
350
self._weave.insert(i2+offset+1, (']', new_version))
354
# there may have been a deletion spanning up to
355
# i2; we want to insert after this region to make sure
356
# we don't destroy ourselves
358
self._weave[i:i] = ([('{', new_version)]
361
offset += 2 + (j2 - j1)
365
def add_identical(self, old_rev_id, new_rev_id, parents):
366
"""Add an identical text to old_rev_id as new_rev_id."""
367
old_lines = self.get(self.lookup(old_rev_id))
368
self.add(new_rev_id, parents, old_lines)
370
def inclusions(self, versions):
371
"""Return set of all ancestors of given version(s)."""
373
for v in xrange(max(versions), 0, -1):
375
# include all its parents
376
i.update(self._parents[v])
378
## except IndexError:
379
## raise ValueError("version %d not present in weave" % v)
382
def parents(self, version):
383
return self._parents[version]
386
def parent_names(self, version):
387
"""Return version names for parents of a version."""
388
return map(self.idx_to_name, self._parents[self.lookup(version)])
391
def minimal_parents(self, version):
392
"""Find the minimal set of parents for the version."""
393
included = self._parents[version]
398
li.sort(reverse=True)
406
gotit.update(self.inclusions(pv))
408
assert mininc[0] >= 0
409
assert mininc[-1] < version
414
def _check_lines(self, text):
415
if not isinstance(text, list):
416
raise ValueError("text should be a list, not %s" % type(text))
419
if not isinstance(l, basestring):
420
raise ValueError("text line should be a string or unicode, not %s"
425
def _check_versions(self, indexes):
426
"""Check everything in the sequence of indexes is valid"""
431
raise IndexError("invalid version number %r" % i)
434
def annotate(self, name_or_index):
435
return list(self.annotate_iter(name_or_index))
438
def annotate_iter(self, name_or_index):
439
"""Yield list of (index-id, line) pairs for the specified version.
441
The index indicates when the line originated in the weave."""
442
incls = [self.maybe_lookup(name_or_index)]
443
for origin, lineno, text in self._extract(incls):
450
(lineno, insert, deletes, text)
451
for each literal line.
457
lineno = 0 # line of weave, 0-based
459
for l in self._weave:
460
if isinstance(l, tuple):
473
raise WeaveFormatError('unexpected instruction %r' % v)
475
assert isinstance(l, basestring)
477
yield lineno, istack[-1], dset, l
482
def _extract(self, versions):
483
"""Yield annotation of lines in included set.
485
Yields a sequence of tuples (origin, lineno, text), where
486
origin is the origin version, lineno the index in the weave,
487
and text the text of the line.
489
The set typically but not necessarily corresponds to a version.
492
if not isinstance(i, int):
495
included = self.inclusions(versions)
500
lineno = 0 # line of weave, 0-based
506
WFE = WeaveFormatError
508
for l in self._weave:
509
if isinstance(l, tuple):
513
assert v not in istack
527
assert isinstance(l, basestring)
529
isactive = (not dset) and istack and (istack[-1] in included)
531
result.append((istack[-1], lineno, l))
534
raise WeaveFormatError("unclosed insertion blocks "
535
"at end of weave: %s" % istack)
537
raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
542
def get_iter(self, name_or_index):
543
"""Yield lines for the specified version."""
544
incls = [self.maybe_lookup(name_or_index)]
545
for origin, lineno, line in self._extract(incls):
549
def get_text(self, name_or_index):
550
return ''.join(self.get_iter(name_or_index))
551
assert isinstance(version, int)
554
def get_lines(self, name_or_index):
555
return list(self.get_iter(name_or_index))
561
def mash_iter(self, included):
562
"""Return composed version of multiple included versions."""
563
included = map(self.maybe_lookup, included)
564
for origin, lineno, text in self._extract(included):
568
def dump(self, to_file):
569
from pprint import pprint
570
print >>to_file, "Weave._weave = ",
571
pprint(self._weave, to_file)
572
print >>to_file, "Weave._parents = ",
573
pprint(self._parents, to_file)
577
def numversions(self):
578
l = len(self._parents)
579
assert l == len(self._sha1s)
584
return self.numversions()
587
def check(self, progress_bar=None):
588
# check no circular inclusions
589
for version in range(self.numversions()):
590
inclusions = list(self._parents[version])
593
if inclusions[-1] >= version:
594
raise WeaveFormatError("invalid included version %d for index %d"
595
% (inclusions[-1], version))
597
# try extracting all versions; this is a bit slow and parallel
598
# extraction could be used
599
nv = self.numversions()
600
for version in range(nv):
602
progress_bar.update('checking text', version, nv)
604
for l in self.get_iter(version):
607
expected = self._sha1s[version]
609
raise WeaveError("mismatched sha1 for version %d; "
610
"got %s, expected %s"
611
% (version, hd, expected))
613
# TODO: check insertions are properly nested, that there are
614
# no lines outside of insertion blocks, that deletions are
615
# properly paired, etc.
619
def merge(self, merge_versions):
620
"""Automerge and mark conflicts between versions.
622
This returns a sequence, each entry describing alternatives
623
for a chunk of the file. Each of the alternatives is given as
626
If there is a chunk of the file where there's no diagreement,
627
only one alternative is given.
629
# approach: find the included versions common to all the
631
raise NotImplementedError()
635
def _delta(self, included, lines):
636
"""Return changes from basis to new revision.
638
The old text for comparison is the union of included revisions.
640
This is used in inserting a new text.
642
Delta is returned as a sequence of
643
(weave1, weave2, newlines).
645
This indicates that weave1:weave2 of the old weave should be
646
replaced by the sequence of lines in newlines. Note that
647
these line numbers are positions in the total weave and don't
648
correspond to the lines in any extracted version, or even the
649
extracted union of included versions.
651
If line1=line2, this is a pure insert; if newlines=[] this is a
652
pure delete. (Similar to difflib.)
654
raise NotImplementedError()
657
def plan_merge(self, ver_a, ver_b):
658
"""Return pseudo-annotation indicating how the two versions merge.
660
This is computed between versions a and b and their common
663
Weave lines present in none of them are skipped entirely.
665
inc_a = self.inclusions([ver_a])
666
inc_b = self.inclusions([ver_b])
667
inc_c = inc_a & inc_b
669
for lineno, insert, deleteset, line in self._walk():
670
if deleteset & inc_c:
671
# killed in parent; can't be in either a or b
672
# not relevant to our work
673
yield 'killed-base', line
674
elif insert in inc_c:
675
# was inserted in base
676
killed_a = bool(deleteset & inc_a)
677
killed_b = bool(deleteset & inc_b)
678
if killed_a and killed_b:
679
yield 'killed-both', line
681
yield 'killed-a', line
683
yield 'killed-b', line
685
yield 'unchanged', line
686
elif insert in inc_a:
687
if deleteset & inc_a:
688
yield 'ghost-a', line
692
elif insert in inc_b:
693
if deleteset & inc_b:
694
yield 'ghost-b', line
698
# not in either revision
699
yield 'irrelevant', line
701
yield 'unchanged', '' # terminator
705
def weave_merge(self, plan):
710
for state, line in plan:
711
if state == 'unchanged' or state == 'killed-both':
712
# resync and flush queued conflicts changes if any
713
if not lines_a and not lines_b:
715
elif ch_a and not ch_b:
717
for l in lines_a: yield l
718
elif ch_b and not ch_a:
719
for l in lines_b: yield l
720
elif lines_a == lines_b:
721
for l in lines_a: yield l
724
for l in lines_a: yield l
726
for l in lines_b: yield l
733
if state == 'unchanged':
736
elif state == 'killed-a':
739
elif state == 'killed-b':
742
elif state == 'new-a':
745
elif state == 'new-b':
749
assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
754
def join(self, other):
755
"""Integrate versions from other into this weave.
757
The resulting weave contains all the history of both weaves;
758
any version you could retrieve from either self or other can be
759
retrieved from self after this call.
761
It is illegal for the two weaves to contain different values
762
or different parents for any version. See also reweave().
764
if other.numversions() == 0:
765
return # nothing to update, easy
766
# two loops so that we do not change ourselves before verifying it
768
# work through in index order to make sure we get all dependencies
769
for other_idx, name in enumerate(other._names):
770
if self._check_version_consistent(other, other_idx, name):
772
for other_idx, name in enumerate(other._names):
773
# TODO: If all the parents of the other version are already
774
# present then we can avoid some work by just taking the delta
775
# and adjusting the offsets.
776
new_parents = self._imported_parents(other, other_idx)
777
lines = other.get_lines(other_idx)
778
sha1 = other._sha1s[other_idx]
779
self.add(name, new_parents, lines, sha1)
782
def _imported_parents(self, other, other_idx):
783
"""Return list of parents in self corresponding to indexes in other."""
785
for parent_idx in other._parents[other_idx]:
786
parent_name = other._names[parent_idx]
787
if parent_name not in self._names:
788
# should not be possible
789
raise WeaveError("missing parent {%s} of {%s} in %r"
790
% (parent_name, other._name_map[other_idx], self))
791
new_parents.append(self._name_map[parent_name])
794
def _check_version_consistent(self, other, other_idx, name):
795
"""Check if a version in consistent in this and other.
797
To be consistent it must have:
800
* the same direct parents (by name, not index, and disregarding
803
If present & correct return True;
804
if not present in self return False;
805
if inconsistent raise error."""
806
this_idx = self._name_map.get(name, -1)
808
if self._sha1s[this_idx] != other._sha1s[other_idx]:
809
raise WeaveError("inconsistent texts for version {%s} "
810
"when joining weaves"
812
self_parents = self._parents[this_idx]
813
other_parents = other._parents[other_idx]
814
n1 = [self._names[i] for i in self_parents]
815
n2 = [other._names[i] for i in other_parents]
819
raise WeaveParentMismatch("inconsistent parents "
820
"for version {%s}: %s vs %s" % (name, n1, n2))
826
def reweave(self, other):
827
"""Reweave self with other."""
828
new_weave = reweave(self, other)
829
for attr in self.__slots__:
830
setattr(self, attr, getattr(new_weave, attr))
834
"""Combine two weaves and return the result.
836
This works even if a revision R has different parents in
837
wa and wb. In the resulting weave all the parents are given.
839
This is done by just building up a new weave, maintaining ordering
840
of the versions in the two inputs. More efficient approaches
841
might be possible but it should only be necessary to do
842
this operation rarely, when a new previously ghost version is
847
queue_a = range(wa.numversions())
848
queue_b = range(wb.numversions())
849
# first determine combined parents of all versions
850
# map from version name -> all parent names
851
combined_parents = _reweave_parent_graphs(wa, wb)
852
mutter("combined parents: %r", combined_parents)
853
order = _make_reweave_order(wa._names, wb._names, combined_parents)
854
mutter("order to reweave: %r", order)
856
if name in wa._name_map:
857
lines = wa.get_lines(name)
858
if name in wb._name_map:
859
assert lines == wb.get_lines(name)
861
lines = wb.get_lines(name)
862
wr.add(name, combined_parents[name], lines)
866
def _reweave_parent_graphs(wa, wb):
867
"""Return combined parent ancestry for two weaves.
869
Returned as a list of (version_name, set(parent_names))"""
871
for weave in [wa, wb]:
872
for idx, name in enumerate(weave._names):
873
p = combined.setdefault(name, set())
874
p.update(map(weave.idx_to_name, weave._parents[idx]))
878
def _make_reweave_order(wa_order, wb_order, combined_parents):
879
"""Return an order to reweave versions respecting parents."""
883
next_a = next_b = None
884
len_a = len(wa_order)
885
len_b = len(wb_order)
886
while ia < len(wa_order) or ib < len(wb_order):
888
next_a = wa_order[ia]
892
if combined_parents[next_a].issubset(done):
894
result.append(next_a)
898
next_b = wb_order[ib]
902
elif combined_parents[next_b].issubset(done):
904
result.append(next_b)
907
raise WeaveError("don't know how to reweave at {%s} and {%s}"
913
"""Show the weave's table-of-contents"""
914
print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
915
for i in (6, 50, 10, 10):
918
for i in range(w.numversions()):
921
parent_str = ' '.join(map(str, w._parents[i]))
922
print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
926
def weave_stats(weave_file, pb):
927
from bzrlib.weavefile import read_weave
929
wf = file(weave_file, 'rb')
931
# FIXME: doesn't work on pipes
932
weave_size = wf.tell()
936
for i in range(vers):
937
pb.update('checking sizes', i, vers)
938
for origin, lineno, line in w._extract([i]):
943
print 'versions %9d' % vers
944
print 'weave file %9d bytes' % weave_size
945
print 'total contents %9d bytes' % total
946
print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
949
print 'average size %9d bytes' % avg
950
print 'relative size %9.2fx' % (float(weave_size) / float(avg))
954
print """bzr weave tool
956
Experimental tool for weave algorithm.
960
Create an empty weave file
961
weave get WEAVEFILE VERSION
962
Write out specified version.
963
weave check WEAVEFILE
964
Check consistency of all versions.
966
Display table of contents.
967
weave add WEAVEFILE NAME [BASE...] < NEWTEXT
968
Add NEWTEXT, with specified parent versions.
969
weave annotate WEAVEFILE VERSION
970
Display origin of each line.
971
weave mash WEAVEFILE VERSION...
972
Display composite of all selected versions.
973
weave merge WEAVEFILE VERSION1 VERSION2 > OUT
974
Auto-merge two versions and display conflicts.
975
weave diff WEAVEFILE VERSION1 VERSION2
976
Show differences between two versions.
980
% weave init foo.weave
982
% weave add foo.weave ver0 < foo.txt
985
(create updated version)
987
% weave get foo.weave 0 | diff -u - foo.txt
988
% weave add foo.weave ver1 0 < foo.txt
991
% weave get foo.weave 0 > foo.txt (create forked version)
993
% weave add foo.weave ver2 0 < foo.txt
996
% weave merge foo.weave 1 2 > foo.txt (merge them)
997
% vi foo.txt (resolve conflicts)
998
% weave add foo.weave merged 1 2 < foo.txt (commit merged version)
1010
# in case we're run directly from the subdirectory
1011
sys.path.append('..')
1013
from bzrlib.weavefile import write_weave, read_weave
1014
from bzrlib.progress import ProgressBar
1029
return read_weave(file(argv[2], 'rb'))
1035
# at the moment, based on everything in the file
1037
parents = map(int, argv[4:])
1038
lines = sys.stdin.readlines()
1039
ver = w.add(name, parents, lines)
1040
write_weave(w, file(argv[2], 'wb'))
1041
print 'added version %r %d' % (name, ver)
1044
if os.path.exists(fn):
1045
raise IOError("file exists")
1047
write_weave(w, file(fn, 'wb'))
1048
elif cmd == 'get': # get one version
1050
sys.stdout.writelines(w.get_iter(int(argv[3])))
1052
elif cmd == 'mash': # get composite
1054
sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
1057
from difflib import unified_diff
1060
v1, v2 = map(int, argv[3:5])
1063
diff_gen = unified_diff(lines1, lines2,
1064
'%s version %d' % (fn, v1),
1065
'%s version %d' % (fn, v2))
1066
sys.stdout.writelines(diff_gen)
1068
elif cmd == 'annotate':
1070
# newline is added to all lines regardless; too hard to get
1071
# reasonable formatting otherwise
1073
for origin, text in w.annotate(int(argv[3])):
1074
text = text.rstrip('\r\n')
1076
print ' | %s' % (text)
1078
print '%5d | %s' % (origin, text)
1084
elif cmd == 'stats':
1085
weave_stats(argv[2], ProgressBar())
1087
elif cmd == 'check':
1092
print '%d versions ok' % w.numversions()
1094
elif cmd == 'inclusions':
1096
print ' '.join(map(str, w.inclusions([int(argv[3])])))
1098
elif cmd == 'parents':
1100
print ' '.join(map(str, w._parents[int(argv[3])]))
1102
elif cmd == 'plan-merge':
1104
for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1106
print '%14s | %s' % (state, line),
1108
elif cmd == 'merge':
1110
p = w.plan_merge(int(argv[3]), int(argv[4]))
1111
sys.stdout.writelines(w.weave_merge(p))
1113
elif cmd == 'mash-merge':
1119
v1, v2 = map(int, argv[3:5])
1121
basis = w.inclusions([v1]).intersection(w.inclusions([v2]))
1123
base_lines = list(w.mash_iter(basis))
1124
a_lines = list(w.get(v1))
1125
b_lines = list(w.get(v2))
1127
from bzrlib.merge3 import Merge3
1128
m3 = Merge3(base_lines, a_lines, b_lines)
1130
name_a = 'version %d' % v1
1131
name_b = 'version %d' % v2
1132
sys.stdout.writelines(m3.merge_lines(name_a=name_a, name_b=name_b))
1134
raise ValueError('unknown command %r' % cmd)
1138
def profile_main(argv):
1139
import tempfile, hotshot, hotshot.stats
1141
prof_f = tempfile.NamedTemporaryFile()
1143
prof = hotshot.Profile(prof_f.name)
1145
ret = prof.runcall(main, argv)
1148
stats = hotshot.stats.load(prof_f.name)
1150
stats.sort_stats('cumulative')
1151
## XXX: Might like to write to stderr or the trace file instead but
1152
## print_stats seems hardcoded to stdout
1153
stats.print_stats(20)
1158
if __name__ == '__main__':
1160
if '--profile' in sys.argv:
1162
args.remove('--profile')
1163
sys.exit(profile_main(args))
1165
sys.exit(main(sys.argv))