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?
91
from cStringIO import StringIO
93
from bzrlib.osutils import sha_strings
81
96
class WeaveError(Exception):
160
177
each version; the parent's parents are implied.
163
List of hex SHA-1 of each version, or None if not recorded.
180
List of hex SHA-1 of each version.
183
List of symbolic names for each version. Each should be unique.
186
For each name, the version number.
189
Descriptive name of this weave; typically the filename if known.
166
__slots__ = ['_weave', '_parents', '_sha1s']
193
__slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
196
def __init__(self, weave_name=None):
170
198
self._parents = []
202
self._weave_name = weave_name
174
205
def __eq__(self, other):
175
206
if not isinstance(other, Weave):
177
208
return self._parents == other._parents \
178
and self._weave == other._weave
209
and self._weave == other._weave \
210
and self._sha1s == other._sha1s
181
213
def __ne__(self, other):
182
214
return not self.__eq__(other)
185
def add(self, parents, text):
217
def maybe_lookup(self, name_or_index):
218
"""Convert possible symbolic name to index, or pass through indexes."""
219
if isinstance(name_or_index, (int, long)):
222
return self.lookup(name_or_index)
225
def lookup(self, name):
226
"""Convert symbolic version name to index."""
228
return self._name_map[name]
230
raise WeaveError("name %r not present in weave %r" %
231
(name, self._weave_name))
234
def idx_to_name(self, version):
235
return self._names[version]
238
def _check_repeated_add(self, name, parents, text):
239
"""Check that a duplicated add is OK.
241
If it is, return the (old) index; otherwise raise an exception.
243
idx = self.lookup(name)
244
if sorted(self._parents[idx]) != sorted(parents):
245
raise WeaveError("name \"%s\" already present in weave "
246
"with different parents" % name)
247
new_sha1 = sha_strings(text)
248
if new_sha1 != self._sha1s[idx]:
249
raise WeaveError("name \"%s\" already present in weave "
250
"with different text" % name)
255
def add(self, name, parents, text):
186
256
"""Add a single text on top of the weave.
188
258
Returns the index number of the newly added version.
261
Symbolic name for this version.
262
(Typically the revision-id of the revision that added it.)
191
265
List or set of direct parent version numbers.
194
268
Sequence of lines to be added in the new version."""
270
assert isinstance(name, basestring)
271
if name in self._name_map:
272
return self._check_repeated_add(name, parents, text)
274
parents = map(self.maybe_lookup, parents)
196
275
self._check_versions(parents)
197
276
## self._check_lines(text)
198
277
new_version = len(self._parents)
279
sha1 = sha_strings(text)
206
# if we abort after here the weave will be corrupt
207
self._parents.append(frozenset(parents))
281
# if we abort after here the (in-memory) weave will be corrupt because only
282
# some fields are updated
283
self._parents.append(parents[:])
208
284
self._sha1s.append(sha1)
285
self._names.append(name)
286
self._name_map[name] = new_version
352
435
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):
438
def annotate(self, name_or_index):
439
return list(self.annotate_iter(name_or_index))
442
def annotate_iter(self, name_or_index):
360
443
"""Yield list of (index-id, line) pairs for the specified version.
362
445
The index indicates when the line originated in the weave."""
363
for origin, lineno, text in self._extract([version]):
446
incls = [self.maybe_lookup(name_or_index)]
447
for origin, lineno, text in self._extract(incls):
364
448
yield origin, text
464
def get_iter(self, version):
551
def get_iter(self, name_or_index):
465
552
"""Yield lines for the specified version."""
466
for origin, lineno, line in self._extract([version]):
553
incls = [self.maybe_lookup(name_or_index)]
554
for origin, lineno, line in self._extract(incls):
470
def get(self, index):
471
return list(self.get_iter(index))
558
def get_text(self, version):
559
assert isinstance(version, int)
561
s.writelines(self.get_iter(version))
565
def get(self, name_or_index):
566
return list(self.get_iter(name_or_index))
474
569
def mash_iter(self, included):
674
"""Show some text information about the weave."""
675
print '%6s %40s %20s' % ('ver', 'sha1', 'parents')
676
for i in (6, 40, 20):
768
"""Show the weave's table-of-contents"""
769
print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
770
for i in (6, 50, 10, 10):
679
773
for i in range(w.numversions()):
680
774
sha1 = w._sha1s[i]
681
print '%6d %40s %s' % (i, sha1, ' '.join(map(str, w._parents[i])))
776
parent_str = ' '.join(map(str, w._parents[i]))
777
print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
737
836
% weave init foo.weave
739
% weave add foo.weave < foo.txt
838
% weave add foo.weave ver0 < foo.txt
742
841
(create updated version)
744
843
% weave get foo.weave 0 | diff -u - foo.txt
745
% weave add foo.weave 0 < foo.txt
844
% weave add foo.weave ver1 0 < foo.txt
748
847
% weave get foo.weave 0 > foo.txt (create forked version)
750
% weave add foo.weave 0 < foo.txt
849
% weave add foo.weave ver2 0 < foo.txt
753
852
% weave merge foo.weave 1 2 > foo.txt (merge them)
754
853
% vi foo.txt (resolve conflicts)
755
% weave add foo.weave 1 2 < foo.txt (commit merged version)
854
% weave add foo.weave merged 1 2 < foo.txt (commit merged version)
777
883
elif cmd == 'add':
779
885
# at the moment, based on everything in the file
780
parents = map(int, argv[3:])
887
parents = map(int, argv[4:])
781
888
lines = sys.stdin.readlines()
782
ver = w.add(parents, lines)
889
ver = w.add(name, parents, lines)
783
890
write_weave(w, file(argv[2], 'wb'))
784
print 'added version %d' % ver
891
print 'added version %r %d' % (name, ver)
785
892
elif cmd == 'init':
787
894
if os.path.exists(fn):
865
972
raise ValueError('unknown command %r' % cmd)
976
def profile_main(argv):
977
import tempfile, hotshot, hotshot.stats
979
prof_f = tempfile.NamedTemporaryFile()
981
prof = hotshot.Profile(prof_f.name)
983
ret = prof.runcall(main, argv)
986
stats = hotshot.stats.load(prof_f.name)
988
stats.sort_stats('cumulative')
989
## XXX: Might like to write to stderr or the trace file instead but
990
## print_stats seems hardcoded to stdout
991
stats.print_stats(20)
868
996
if __name__ == '__main__':
870
sys.exit(main(sys.argv))
998
if '--profile' in sys.argv:
1000
args.remove('--profile')
1001
sys.exit(profile_main(args))
1003
sys.exit(main(sys.argv))