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
97
from bzrlib.osutils import sha_strings
93
100
class WeaveError(Exception):
183
190
For each name, the version number.
193
Descriptive name of this weave; typically the filename if known.
186
__slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map']
197
__slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
200
def __init__(self, weave_name=None):
190
202
self._parents = []
193
205
self._name_map = {}
206
self._weave_name = weave_name
196
209
def __eq__(self, other):
205
218
return not self.__eq__(other)
221
def maybe_lookup(self, name_or_index):
222
"""Convert possible symbolic name to index, or pass through indexes."""
223
if isinstance(name_or_index, (int, long)):
226
return self.lookup(name_or_index)
208
229
def lookup(self, name):
230
"""Convert symbolic version name to index."""
210
232
return self._name_map[name]
212
raise WeaveError("name %s not present in weave" % name)
215
def add(self, name, parents, text):
234
raise WeaveError("name %r not present in weave %r" %
235
(name, self._weave_name))
238
def idx_to_name(self, version):
239
return self._names[version]
242
def _check_repeated_add(self, name, parents, text, sha1):
243
"""Check that a duplicated add is OK.
245
If it is, return the (old) index; otherwise raise an exception.
247
idx = self.lookup(name)
248
if sorted(self._parents[idx]) != sorted(parents):
249
raise WeaveError("name \"%s\" already present in weave "
250
"with different parents" % name)
251
if sha1 != self._sha1s[idx]:
252
raise WeaveError("name \"%s\" already present in weave "
253
"with different text" % name)
258
def add(self, name, parents, text, sha1=None):
216
259
"""Add a single text on top of the weave.
218
261
Returns the index number of the newly added version.
225
268
List or set of direct parent version numbers.
228
Sequence of lines to be added in the new version."""
271
Sequence of lines to be added in the new version.
273
sha -- SHA-1 of the file, if known. This is trusted to be
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):
775
836
Display composite of all selected versions.
776
837
weave merge WEAVEFILE VERSION1 VERSION2 > OUT
777
838
Auto-merge two versions and display conflicts.
839
weave diff WEAVEFILE VERSION1 VERSION2
840
Show differences between two versions.
849
912
sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
915
from difflib import unified_diff
918
v1, v2 = map(int, argv[3:5])
921
diff_gen = unified_diff(lines1, lines2,
922
'%s version %d' % (fn, v1),
923
'%s version %d' % (fn, v2))
924
sys.stdout.writelines(diff_gen)
851
926
elif cmd == 'annotate':
853
928
# newline is added to all lines regardless; too hard to get