141
List of versions, indexed by index number.
145
List of parents, indexed by version number.
146
It is only necessary to store the minimal set of parents for
147
each version; the parent's parents are implied.
143
For each version we store the set (included_versions), which
144
lists the previous versions also considered active; the
145
versions included in those versions are included transitively.
146
So new versions created from nothing list []; most versions
147
have a single entry; some have more.
150
List of hex SHA-1 of each version, or None if not recorded.
149
152
def __init__(self):
155
158
def __eq__(self, other):
169
172
Returns the index number of the newly added version.
172
List or set of parent version numbers. This must normally include
173
the parents and the parent's parents, or wierd things might happen.
175
List or set of direct parent version numbers.
176
178
Sequence of lines to be added in the new version."""
177
self._check_versions(parents)
178
self._check_lines(text)
179
## self._check_versions(parents)
180
## self._check_lines(text)
180
181
idx = len(self._v)
183
delta = self._delta(self.inclusions(parents), text)
191
ancestors = self.inclusions(parents)
192
delta = self._delta(ancestors, text)
185
194
# offset gives the number of lines that have been inserted
186
195
# into the weave up to the current point; if the original edit instruction
222
228
self._l.append(('}', idx))
224
230
self._addversion(None)
232
self._sha1s.append(sha1)
229
237
def inclusions(self, versions):
230
"""Expand out everything included by versions."""
238
"""Return set of all ancestors of given version(s)."""
231
239
i = set(versions)
244
# include all its parents
249
raise ValueError("version %d not present in weave" % v)
252
def minimal_parents(self, version):
253
"""Find the minimal set of parents for the version."""
254
included = self._v[version]
259
li.sort(reverse=True)
267
gotit.update(self.inclusions(pv))
269
assert mininc[0] >= 0
270
assert mininc[-1] < version
237
274
def _addversion(self, parents):
239
self._v.append(frozenset(parents))
276
self._v.append(parents)
241
278
self._v.append(frozenset())
268
306
"""Yield list of (index-id, line) pairs for the specified version.
270
308
The index indicates when the line originated in the weave."""
271
included = self.inclusions([version])
272
for origin, lineno, text in self._extract(included):
309
for origin, lineno, text in self._extract([version]):
273
310
yield origin, text
276
def _extract(self, included):
313
def _extract(self, versions):
277
314
"""Yield annotation of lines in included set.
279
316
Yields a sequence of tuples (origin, lineno, text), where
283
320
The set typically but not necessarily corresponds to a version.
285
istack = [] # versions for which an insertion block is current
287
dset = set() # versions for which a deletion block is current
322
included = self.inclusions(versions)
291
327
lineno = 0 # line of weave, 0-based
293
# TODO: Probably only need to put included revisions in the istack
295
# TODO: Could split this into two functions, one that updates
296
# the stack and the other that processes the results -- but
297
# I'm not sure it's really needed.
299
331
WFE = WeaveFormatError
301
333
for l in self._l:
302
334
if isinstance(l, tuple):
305
if istack and (istack[-1] >= v):
306
raise WFE("improperly nested insertions %d>=%d on line %d"
307
% (istack[-1], v, lineno))
338
assert v not in istack
313
raise WFE("unmatched close of insertion %d on line %d"
316
raise WFE("mismatched close of insertion %d!=%d on line %d"
321
raise WFE("repeated deletion marker for version %d on line %d"
325
raise WFE("version %d deletes own text on line %d"
332
raise WFE("unmatched close of deletion %d on line %d"
335
raise WFE("invalid processing instruction %r on line %d"
338
353
assert isinstance(l, basestring)
340
raise WFE("literal at top level on line %d"
342
isactive = (istack[-1] in included) \
343
and not included.intersection(dset)
355
isactive = (not dset) and istack and (istack[-1] in included)
346
yield origin, lineno, l
357
yield istack[-1], lineno, l
379
390
pprint(self._v, to_file)
383
for vers_info in self._v:
385
for vi in vers_info[0]:
386
if vi < 0 or vi >= index:
394
def numversions(self):
396
assert l == len(self._sha1s)
400
def check(self, progress_bar=None):
401
# check no circular inclusions
402
for version in range(self.numversions()):
403
inclusions = list(self._v[version])
406
if inclusions[-1] >= version:
387
407
raise WeaveFormatError("invalid included version %d for index %d"
390
raise WeaveFormatError("repeated included version %d for index %d"
408
% (inclusions[-1], version))
410
# try extracting all versions; this is a bit slow and parallel
411
# extraction could be used
413
nv = self.numversions()
414
for version in range(nv):
416
progress_bar.update('checking text', version, nv)
418
for l in self.get_iter(version):
421
expected = self._sha1s[version]
423
raise WeaveError("mismatched sha1 for version %d; "
424
"got %s, expected %s"
425
% (version, hd, expected))
427
# TODO: check insertions are properly nested, that there are
428
# no lines outside of insertion blocks, that deletions are
429
# properly paired, etc.
433
def merge(self, merge_versions):
434
"""Automerge and mark conflicts between versions.
436
This returns a sequence, each entry describing alternatives
437
for a chunk of the file. Each of the alternatives is given as
440
If there is a chunk of the file where there's no diagreement,
441
only one alternative is given.
444
# approach: find the included versions common to all the
446
raise NotImplementedError()
412
466
If line1=line2, this is a pure insert; if newlines=[] this is a
413
467
pure delete. (Similar to difflib.)
416
self._check_versions(included)
418
##from pprint import pprint
420
# first get basis for comparison
421
# basis holds (lineno, origin, line)
427
469
# basis a list of (origin, lineno, line)
428
basis = list(self._extract(included))
430
# now make a parallel list with only the text, to pass to the differ
431
basis_lines = [line for (origin, lineno, line) in basis]
472
for origin, lineno, line in self._extract(included):
473
basis_lineno.append(lineno)
474
basis_lines.append(line)
433
476
# add a sentinal, because we can also match against the final line
434
basis.append((None, len(self._l), None))
477
basis_lineno.append(len(self._l))
436
479
# XXX: which line of the weave should we really consider
437
480
# matches the end of the file? the current code says it's the
507
def weave_info(filename, out):
508
"""Show some text information about the weave."""
509
from weavefile import read_weave
510
wf = file(filename, 'rb')
512
# FIXME: doesn't work on pipes
513
weave_size = wf.tell()
514
print >>out, "weave file size %d bytes" % weave_size
515
print >>out, "weave contains %d versions" % len(w._v)
518
print '%6s %6s %8s %40s %20s' % ('ver', 'lines', 'bytes', 'sha1', 'parents')
519
for i in (6, 6, 8, 40, 20):
522
for i in range(len(w._v)):
525
bytes = sum((len(a) for a in text))
527
print '%6d %6d %8d %40s' % (i, lines, bytes, sha1),
533
print >>out, "versions total %d bytes" % total
534
print >>out, "compression ratio %.3f" % (float(total)/float(weave_size))
538
print """bzr weave tool
540
Experimental tool for weave algorithm.
544
Create an empty weave file
545
weave get WEAVEFILE VERSION
546
Write out specified version.
547
weave check WEAVEFILE
548
Check consistency of all versions.
550
Display table of contents.
551
weave add WEAVEFILE [BASE...] < NEWTEXT
552
Add NEWTEXT, with specified parent versions.
553
weave annotate WEAVEFILE VERSION
554
Display origin of each line.
555
weave mash WEAVEFILE VERSION...
556
Display composite of all selected versions.
557
weave merge WEAVEFILE VERSION1 VERSION2 > OUT
558
Auto-merge two versions and display conflicts.
562
% weave init foo.weave
564
% weave add foo.weave < foo.txt
567
(create updated version)
569
% weave get foo.weave 0 | diff -u - foo.txt
570
% weave add foo.weave 0 < foo.txt
573
% weave get foo.weave 0 > foo.txt (create forked version)
575
% weave add foo.weave 0 < foo.txt
578
% weave merge foo.weave 1 2 > foo.txt (merge them)
579
% vi foo.txt (resolve conflicts)
580
% weave add foo.weave 1 2 < foo.txt (commit merged version)
471
from weavefile import write_weave_v1, read_weave_v1
589
from weavefile import write_weave, read_weave
590
from bzrlib.progress import ProgressBar
474
w = read_weave_v1(file(argv[2], 'rb'))
598
return read_weave(file(argv[2], 'rb'))
475
604
# at the moment, based on everything in the file
476
parents = set(range(len(w._v)))
605
parents = map(int, argv[3:])
477
606
lines = sys.stdin.readlines()
478
607
ver = w.add(parents, lines)
479
write_weave_v1(w, file(argv[2], 'wb'))
480
print 'added %d' % ver
608
write_weave(w, file(argv[2], 'wb'))
609
print 'added version %d' % ver
481
610
elif cmd == 'init':
483
612
if os.path.exists(fn):
484
613
raise IOError("file exists")
486
write_weave_v1(w, file(fn, 'wb'))
488
w = read_weave_v1(file(argv[2], 'rb'))
489
sys.stdout.writelines(w.getiter(int(argv[3])))
615
write_weave(w, file(fn, 'wb'))
616
elif cmd == 'get': # get one version
618
sys.stdout.writelines(w.get_iter(int(argv[3])))
620
elif cmd == 'mash': # get composite
622
sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
490
624
elif cmd == 'annotate':
491
w = read_weave_v1(file(argv[2], 'rb'))
492
626
# newline is added to all lines regardless; too hard to get
493
627
# reasonable formatting otherwise
500
634
print '%5d | %s' % (origin, text)
638
weave_info(argv[2], sys.stdout)
646
elif cmd == 'inclusions':
648
print ' '.join(map(str, w.inclusions([int(argv[3])])))
650
elif cmd == 'parents':
652
print ' '.join(map(str, w._v[int(argv[3])]))
660
v1, v2 = map(int, argv[3:5])
662
basis = w.inclusions([v1]).intersection(w.inclusions([v2]))
664
base_lines = list(w.mash_iter(basis))
665
a_lines = list(w.get(v1))
666
b_lines = list(w.get(v2))
668
from bzrlib.merge3 import Merge3
669
m3 = Merge3(base_lines, a_lines, b_lines)
671
name_a = 'version %d' % v1
672
name_b = 'version %d' % v2
673
sys.stdout.writelines(m3.merge_lines(name_a=name_a, name_b=name_b))
503
675
raise ValueError('unknown command %r' % cmd)