84
84
# TODO: Perhaps the API should work only in names to hide the integer
85
85
# indexes from the user?
87
# TODO: Is there any potential performance win by having an add()
88
# variant that is passed a pre-cooked version of the single basis
94
from difflib import SequenceMatcher
183
189
For each name, the version number.
192
Descriptive name of this weave; typically the filename if known.
186
__slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map']
196
__slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
199
def __init__(self, weave_name=None):
190
201
self._parents = []
193
204
self._name_map = {}
205
self._weave_name = weave_name
196
208
def __eq__(self, other):
205
217
return not self.__eq__(other)
220
def maybe_lookup(self, name_or_index):
221
"""Convert possible symbolic name to index, or pass through indexes."""
222
if isinstance(name_or_index, (int, long)):
225
return self.lookup(name_or_index)
208
228
def lookup(self, name):
229
"""Convert symbolic version name to index."""
210
231
return self._name_map[name]
212
raise WeaveError("name %s not present in weave" % name)
215
def add(self, name, parents, text):
233
raise WeaveError("name %r not present in weave %r" %
234
(name, self._weave_name))
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):
216
258
"""Add a single text on top of the weave.
218
260
Returns the index number of the newly added version.
225
267
List or set of direct parent version numbers.
228
Sequence of lines to be added in the new version."""
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
230
277
assert isinstance(name, basestring)
279
sha1 = sha_strings(text)
231
280
if name in self._name_map:
232
raise WeaveError("name %r already present in weave" % name)
281
return self._check_repeated_add(name, parents, text, sha1)
283
parents = map(self.maybe_lookup, parents)
234
284
self._check_versions(parents)
235
285
## self._check_lines(text)
236
286
new_version = len(self._parents)
243
289
# if we abort after here the (in-memory) weave will be corrupt because only
244
290
# some fields are updated
293
339
#print 'basis_lines:', basis_lines
294
340
#print 'new_lines: ', lines
296
from difflib import SequenceMatcher
297
342
s = SequenceMatcher(None, basis_lines, text)
299
344
# offset gives the number of lines that have been inserted
338
383
def inclusions(self, versions):
339
384
"""Return set of all ancestors of given version(s)."""
340
385
i = set(versions)
345
# include all its parents
346
i.update(self._parents[v])
350
raise ValueError("version %d not present in weave" % v)
386
for v in xrange(max(versions), 0, -1):
388
# include all its parents
389
i.update(self._parents[v])
391
## except IndexError:
392
## raise ValueError("version %d not present in weave" % v)
395
def parents(self, version):
396
return self._parents[version]
353
399
def minimal_parents(self, version):
393
439
raise IndexError("invalid version number %r" % i)
396
def annotate(self, index):
397
return list(self.annotate_iter(index))
400
def annotate_iter(self, version):
442
def annotate(self, name_or_index):
443
return list(self.annotate_iter(name_or_index))
446
def annotate_iter(self, name_or_index):
401
447
"""Yield list of (index-id, line) pairs for the specified version.
403
449
The index indicates when the line originated in the weave."""
404
for origin, lineno, text in self._extract([version]):
450
incls = [self.maybe_lookup(name_or_index)]
451
for origin, lineno, text in self._extract(incls):
405
452
yield origin, text
504
def get_iter(self, version):
555
def get_iter(self, name_or_index):
505
556
"""Yield lines for the specified version."""
506
for origin, lineno, line in self._extract([version]):
557
incls = [self.maybe_lookup(name_or_index)]
558
for origin, lineno, line in self._extract(incls):
510
def get(self, index):
511
return list(self.get_iter(index))
562
def get_text(self, name_or_index):
563
return ''.join(self.get_iter(name_or_index))
564
assert isinstance(version, int)
567
def get_lines(self, name_or_index):
568
return list(self.get_iter(name_or_index))
514
574
def mash_iter(self, included):
515
575
"""Return composed version of multiple included versions."""
576
included = map(self.maybe_lookup, included)
516
577
for origin, lineno, text in self._extract(included):
726
def weave_stats(weave_file):
727
from bzrlib.progress import ProgressBar
787
def weave_stats(weave_file, pb):
728
788
from bzrlib.weavefile import read_weave
732
790
wf = file(weave_file, 'rb')
733
791
w = read_weave(wf)
734
792
# FIXME: doesn't work on pipes
775
833
Display composite of all selected versions.
776
834
weave merge WEAVEFILE VERSION1 VERSION2 > OUT
777
835
Auto-merge two versions and display conflicts.
836
weave diff WEAVEFILE VERSION1 VERSION2
837
Show differences between two versions.
808
from weavefile import write_weave, read_weave
871
# in case we're run directly from the subdirectory
872
sys.path.append('..')
874
from bzrlib.weavefile import write_weave, read_weave
809
875
from bzrlib.progress import ProgressBar
849
915
sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
918
from difflib import unified_diff
921
v1, v2 = map(int, argv[3:5])
924
diff_gen = unified_diff(lines1, lines2,
925
'%s version %d' % (fn, v1),
926
'%s version %d' % (fn, v2))
927
sys.stdout.writelines(diff_gen)
851
929
elif cmd == 'annotate':
853
931
# newline is added to all lines regardless; too hard to get