75
76
# description of which revisions include it. Nice for checking all
76
77
# shas in parallel.
79
# TODO: Using a single _extract routine and then processing the output
80
# is probably inefficient. It's simple enough that we can afford to
81
# have slight specializations for different ways its used: annotate,
82
# basis for add, get, etc.
84
# TODO: Perhaps the API should work only in names to hide the integer
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
96
from cStringIO import StringIO
98
from bzrlib.osutils import sha_strings
81
101
class WeaveError(Exception):
160
182
each version; the parent's parents are implied.
163
List of hex SHA-1 of each version, or None if not recorded.
185
List of hex SHA-1 of each version.
188
List of symbolic names for each version. Each should be unique.
191
For each name, the version number.
194
Descriptive name of this weave; typically the filename if known.
166
__slots__ = ['_weave', '_parents', '_sha1s']
198
__slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
201
def __init__(self, weave_name=None):
170
203
self._parents = []
207
self._weave_name = weave_name
174
210
def __eq__(self, other):
175
211
if not isinstance(other, Weave):
177
213
return self._parents == other._parents \
178
and self._weave == other._weave
214
and self._weave == other._weave \
215
and self._sha1s == other._sha1s
181
218
def __ne__(self, other):
182
219
return not self.__eq__(other)
185
def add(self, parents, text):
222
def maybe_lookup(self, name_or_index):
223
"""Convert possible symbolic name to index, or pass through indexes."""
224
if isinstance(name_or_index, (int, long)):
227
return self.lookup(name_or_index)
230
def lookup(self, name):
231
"""Convert symbolic version name to index."""
233
return self._name_map[name]
235
raise WeaveError("name %r not present in weave %r" %
236
(name, self._weave_name))
239
def idx_to_name(self, version):
240
return self._names[version]
243
def _check_repeated_add(self, name, parents, text, sha1):
244
"""Check that a duplicated add is OK.
246
If it is, return the (old) index; otherwise raise an exception.
248
idx = self.lookup(name)
249
if sorted(self._parents[idx]) != sorted(parents):
250
raise WeaveError("name \"%s\" already present in weave "
251
"with different parents" % name)
252
if sha1 != self._sha1s[idx]:
253
raise WeaveError("name \"%s\" already present in weave "
254
"with different text" % name)
259
def add(self, name, parents, text, sha1=None):
186
260
"""Add a single text on top of the weave.
188
262
Returns the index number of the newly added version.
265
Symbolic name for this version.
266
(Typically the revision-id of the revision that added it.)
191
269
List or set of direct parent version numbers.
194
Sequence of lines to be added in the new version."""
272
Sequence of lines to be added in the new version.
274
sha -- SHA-1 of the file, if known. This is trusted to be
278
assert isinstance(name, basestring)
280
sha1 = sha_strings(text)
281
if name in self._name_map:
282
return self._check_repeated_add(name, parents, text, sha1)
284
parents = map(self.maybe_lookup, parents)
196
285
self._check_versions(parents)
197
286
## self._check_lines(text)
198
287
new_version = len(self._parents)
206
# if we abort after here the weave will be corrupt
207
self._parents.append(frozenset(parents))
290
# if we abort after here the (in-memory) weave will be corrupt because only
291
# some fields are updated
292
self._parents.append(parents[:])
208
293
self._sha1s.append(sha1)
294
self._names.append(name)
295
self._name_map[name] = new_version
297
384
def inclusions(self, versions):
298
385
"""Return set of all ancestors of given version(s)."""
299
386
i = set(versions)
304
# include all its parents
305
i.update(self._parents[v])
309
raise ValueError("version %d not present in weave" % v)
387
for v in xrange(max(versions), 0, -1):
389
# include all its parents
390
i.update(self._parents[v])
392
## except IndexError:
393
## raise ValueError("version %d not present in weave" % v)
396
def parents(self, version):
397
return self._parents[version]
312
400
def minimal_parents(self, version):
352
440
raise IndexError("invalid version number %r" % i)
355
def annotate(self, index):
356
return list(self.annotate_iter(index))
359
def annotate_iter(self, version):
443
def annotate(self, name_or_index):
444
return list(self.annotate_iter(name_or_index))
447
def annotate_iter(self, name_or_index):
360
448
"""Yield list of (index-id, line) pairs for the specified version.
362
450
The index indicates when the line originated in the weave."""
363
for origin, lineno, text in self._extract([version]):
451
incls = [self.maybe_lookup(name_or_index)]
452
for origin, lineno, text in self._extract(incls):
364
453
yield origin, text
464
def get_iter(self, version):
556
def get_iter(self, name_or_index):
465
557
"""Yield lines for the specified version."""
466
for origin, lineno, line in self._extract([version]):
558
incls = [self.maybe_lookup(name_or_index)]
559
for origin, lineno, line in self._extract(incls):
470
def get(self, index):
471
return list(self.get_iter(index))
563
def get_text(self, version):
564
assert isinstance(version, int)
566
s.writelines(self.get_iter(version))
570
def get(self, name_or_index):
571
return list(self.get_iter(name_or_index))
474
574
def mash_iter(self, included):
475
575
"""Return composed version of multiple included versions."""
576
included = map(self.maybe_lookup, included)
476
577
for origin, lineno, text in self._extract(included):
674
"""Show some text information about the weave."""
675
print '%6s %40s %20s' % ('ver', 'sha1', 'parents')
676
for i in (6, 40, 20):
774
"""Show the weave's table-of-contents"""
775
print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
776
for i in (6, 50, 10, 10):
679
779
for i in range(w.numversions()):
680
780
sha1 = w._sha1s[i]
681
print '%6d %40s %s' % (i, sha1, ' '.join(map(str, w._parents[i])))
782
parent_str = ' '.join(map(str, w._parents[i]))
783
print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
731
836
Display composite of all selected versions.
732
837
weave merge WEAVEFILE VERSION1 VERSION2 > OUT
733
838
Auto-merge two versions and display conflicts.
839
weave diff WEAVEFILE VERSION1 VERSION2
840
Show differences between two versions.
737
844
% weave init foo.weave
739
% weave add foo.weave < foo.txt
846
% weave add foo.weave ver0 < foo.txt
742
849
(create updated version)
744
851
% weave get foo.weave 0 | diff -u - foo.txt
745
% weave add foo.weave 0 < foo.txt
852
% weave add foo.weave ver1 0 < foo.txt
748
855
% weave get foo.weave 0 > foo.txt (create forked version)
750
% weave add foo.weave 0 < foo.txt
857
% weave add foo.weave ver2 0 < foo.txt
753
860
% weave merge foo.weave 1 2 > foo.txt (merge them)
754
861
% vi foo.txt (resolve conflicts)
755
% weave add foo.weave 1 2 < foo.txt (commit merged version)
862
% weave add foo.weave merged 1 2 < foo.txt (commit merged version)
777
891
elif cmd == 'add':
779
893
# at the moment, based on everything in the file
780
parents = map(int, argv[3:])
895
parents = map(int, argv[4:])
781
896
lines = sys.stdin.readlines()
782
ver = w.add(parents, lines)
897
ver = w.add(name, parents, lines)
783
898
write_weave(w, file(argv[2], 'wb'))
784
print 'added version %d' % ver
899
print 'added version %r %d' % (name, ver)
785
900
elif cmd == 'init':
787
902
if os.path.exists(fn):
797
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)
799
926
elif cmd == 'annotate':
801
928
# newline is added to all lines regardless; too hard to get
865
992
raise ValueError('unknown command %r' % cmd)
996
def profile_main(argv):
997
import tempfile, hotshot, hotshot.stats
999
prof_f = tempfile.NamedTemporaryFile()
1001
prof = hotshot.Profile(prof_f.name)
1003
ret = prof.runcall(main, argv)
1006
stats = hotshot.stats.load(prof_f.name)
1008
stats.sort_stats('cumulative')
1009
## XXX: Might like to write to stderr or the trace file instead but
1010
## print_stats seems hardcoded to stdout
1011
stats.print_stats(20)
868
1016
if __name__ == '__main__':
870
sys.exit(main(sys.argv))
1018
if '--profile' in sys.argv:
1020
args.remove('--profile')
1021
sys.exit(profile_main(args))
1023
sys.exit(main(sys.argv))