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
97
from bzrlib.osutils import sha_strings
81
100
class WeaveError(Exception):
160
181
each version; the parent's parents are implied.
163
List of hex SHA-1 of each version, or None if not recorded.
184
List of hex SHA-1 of each version.
187
List of symbolic names for each version. Each should be unique.
190
For each name, the version number.
193
Descriptive name of this weave; typically the filename if known.
166
__slots__ = ['_weave', '_parents', '_sha1s']
197
__slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
200
def __init__(self, weave_name=None):
170
202
self._parents = []
206
self._weave_name = weave_name
174
209
def __eq__(self, other):
175
210
if not isinstance(other, Weave):
177
212
return self._parents == other._parents \
178
and self._weave == other._weave
213
and self._weave == other._weave \
214
and self._sha1s == other._sha1s
181
217
def __ne__(self, other):
182
218
return not self.__eq__(other)
185
def add(self, parents, text):
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)
229
def lookup(self, name):
230
"""Convert symbolic version name to index."""
232
return self._name_map[name]
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):
186
259
"""Add a single text on top of the weave.
188
261
Returns the index number of the newly added version.
264
Symbolic name for this version.
265
(Typically the revision-id of the revision that added it.)
191
268
List or set of direct parent version numbers.
194
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
277
assert isinstance(name, basestring)
279
sha1 = sha_strings(text)
280
if name in self._name_map:
281
return self._check_repeated_add(name, parents, text, sha1)
283
parents = map(self.maybe_lookup, parents)
196
284
self._check_versions(parents)
197
285
## self._check_lines(text)
198
286
new_version = len(self._parents)
206
# if we abort after here the weave will be corrupt
207
self._parents.append(frozenset(parents))
289
# if we abort after here the (in-memory) weave will be corrupt because only
290
# some fields are updated
291
self._parents.append(parents[:])
208
292
self._sha1s.append(sha1)
293
self._names.append(name)
294
self._name_map[name] = new_version
297
383
def inclusions(self, versions):
298
384
"""Return set of all ancestors of given version(s)."""
299
385
i = set(versions)
304
# include all its parents
305
i.update(self._parents[v])
309
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]
312
399
def minimal_parents(self, version):
352
439
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):
442
def annotate(self, name_or_index):
443
return list(self.annotate_iter(name_or_index))
446
def annotate_iter(self, name_or_index):
360
447
"""Yield list of (index-id, line) pairs for the specified version.
362
449
The index indicates when the line originated in the weave."""
363
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):
364
452
yield origin, text
464
def get_iter(self, version):
555
def get_iter(self, name_or_index):
465
556
"""Yield lines for the specified version."""
466
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):
470
def get(self, index):
471
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))
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))