~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Robert Collins
  • Date: 2005-10-08 08:45:42 UTC
  • Revision ID: robertc@robertcollins.net-20051008084542-48ea5a99756f970e
add rm alias to remove

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
"""Weave - storage of related text file versions"""
23
23
 
24
 
# before intset (r923) 2000 versions in 41.5s
25
 
# with intset (r926) 2000 versions in 93s !!!
26
 
# better to just use plain sets.
27
 
 
28
 
# making _extract build and return a list, rather than being a generator
29
 
# takes 37.94s
30
 
 
31
 
# with python -O, r923 does 2000 versions in 36.87s
32
 
 
33
 
# with optimizations to avoid mutating lists - 35.75!  I guess copying
34
 
# all the elements every time costs more than the small manipulations.
35
 
# a surprisingly small change.
36
 
 
37
 
# r931, which avoids using a generator for extract, does 36.98s
38
 
 
39
 
# with memoized inclusions, takes 41.49s; not very good
40
 
 
41
 
# with slots, takes 37.35s; without takes 39.16, a bit surprising
42
 
 
43
 
# with the delta calculation mixed in with the add method, rather than
44
 
# separated, takes 36.78s
45
 
 
46
 
# with delta folded in and mutation of the list, 36.13s
47
 
 
48
 
# with all this and simplification of add code, 33s 
49
 
 
50
 
 
51
 
# TODO: Perhaps have copy method for Weave instances?
52
24
 
53
25
# XXX: If we do weaves this way, will a merge still behave the same
54
26
# way if it's done in a different order?  That's a pretty desirable
59
31
# binaries, perhaps by naively splitting on \n or perhaps using
60
32
# something like a rolling checksum.
61
33
 
62
 
# TODO: Track version names as well as indexes. 
63
 
 
64
34
# TODO: End marker for each version so we can stop reading?
65
35
 
66
36
# TODO: Check that no insertion occurs inside a deletion that was
73
43
 
74
44
# TODO: Parallel-extract that passes back each line along with a
75
45
# description of which revisions include it.  Nice for checking all
76
 
# shas in parallel.
77
 
 
78
 
 
 
46
# shas or calculating stats in parallel.
 
47
 
 
48
# TODO: Using a single _extract routine and then processing the output
 
49
# is probably inefficient.  It's simple enough that we can afford to
 
50
# have slight specializations for different ways its used: annotate,
 
51
# basis for add, get, etc.
 
52
 
 
53
# TODO: Perhaps the API should work only in names to hide the integer
 
54
# indexes from the user?
 
55
 
 
56
# TODO: Is there any potential performance win by having an add()
 
57
# variant that is passed a pre-cooked version of the single basis
 
58
# version?
 
59
 
 
60
# TODO: Have a way to go back and insert a revision V1 that is a parent 
 
61
# of an already-stored revision V2.  This means some lines previously 
 
62
# counted as new in V2 will be discovered to have actually come from V1.  
 
63
# It is probably necessary to insert V1, then compute a whole new diff 
 
64
# from the mashed ancestors to V2.  This must be repeated for every 
 
65
# direct child of V1.  The deltas from V2 to its descendents won't change,
 
66
# although their location within the weave may change.  It may be possible
 
67
# to just adjust the location of those instructions rather than 
 
68
# re-weaving the whole thing.  This is expected to be a fairly rare
 
69
# operation, only used when inserting data that was previously a ghost.
 
70
 
 
71
 
 
72
 
 
73
 
 
74
import sha
 
75
from difflib import SequenceMatcher
 
76
 
 
77
from bzrlib.trace import mutter
79
78
 
80
79
 
81
80
class WeaveError(Exception):
84
83
 
85
84
class WeaveFormatError(WeaveError):
86
85
    """Weave invariant violated"""
 
86
 
 
87
 
 
88
class WeaveParentMismatch(WeaveError):
 
89
    """Parents are mismatched between two revisions."""
87
90
    
88
91
 
89
92
class Weave(object):
118
121
    The instruction can be '{' or '}' for an insertion block, and '['
119
122
    and ']' for a deletion block respectively.  The version is the
120
123
    integer version index.  There is no replace operator, only deletes
121
 
    and inserts.
 
124
    and inserts.  For '}', the end of an insertion, there is no
 
125
    version parameter because it always closes the most recently
 
126
    opened insertion.
122
127
 
123
128
    Constraints/notes:
124
129
 
160
165
        each version; the parent's parents are implied.
161
166
 
162
167
    _sha1s
163
 
        List of hex SHA-1 of each version, or None if not recorded.
 
168
        List of hex SHA-1 of each version.
 
169
 
 
170
    _names
 
171
        List of symbolic names for each version.  Each should be unique.
 
172
 
 
173
    _name_map
 
174
        For each name, the version number.
 
175
 
 
176
    _weave_name
 
177
        Descriptive name of this weave; typically the filename if known.
 
178
        Set by read_weave.
164
179
    """
165
180
 
166
 
    __slots__ = ['_weave', '_parents', '_sha1s']
 
181
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
 
182
                 '_weave_name']
167
183
    
168
 
    def __init__(self):
 
184
    def __init__(self, weave_name=None):
169
185
        self._weave = []
170
186
        self._parents = []
171
187
        self._sha1s = []
172
 
 
 
188
        self._names = []
 
189
        self._name_map = {}
 
190
        self._weave_name = weave_name
 
191
 
 
192
 
 
193
    def copy(self):
 
194
        """Return a deep copy of self.
 
195
        
 
196
        The copy can be modified without affecting the original weave."""
 
197
        other = Weave()
 
198
        other._weave = self._weave[:]
 
199
        other._parents = self._parents[:]
 
200
        other._sha1s = self._sha1s[:]
 
201
        other._names = self._names[:]
 
202
        other._name_map = self._name_map.copy()
 
203
        other._weave_name = self._weave_name
 
204
        return other
173
205
 
174
206
    def __eq__(self, other):
175
207
        if not isinstance(other, Weave):
176
208
            return False
177
209
        return self._parents == other._parents \
178
 
               and self._weave == other._weave
 
210
               and self._weave == other._weave \
 
211
               and self._sha1s == other._sha1s 
 
212
 
179
213
    
180
 
 
181
214
    def __ne__(self, other):
182
215
        return not self.__eq__(other)
183
216
 
184
 
        
185
 
    def add(self, parents, text):
 
217
 
 
218
    def maybe_lookup(self, name_or_index):
 
219
        """Convert possible symbolic name to index, or pass through indexes."""
 
220
        if isinstance(name_or_index, (int, long)):
 
221
            return name_or_index
 
222
        else:
 
223
            return self.lookup(name_or_index)
 
224
 
 
225
        
 
226
    def lookup(self, name):
 
227
        """Convert symbolic version name to index."""
 
228
        try:
 
229
            return self._name_map[name]
 
230
        except KeyError:
 
231
            raise WeaveError("name %r not present in weave %r" %
 
232
                             (name, self._weave_name))
 
233
 
 
234
 
 
235
    def iter_names(self):
 
236
        """Yield a list of all names in this weave."""
 
237
        return iter(self._names)
 
238
 
 
239
    def idx_to_name(self, version):
 
240
        return self._names[version]
 
241
 
 
242
 
 
243
    def _check_repeated_add(self, name, parents, text, sha1):
 
244
        """Check that a duplicated add is OK.
 
245
 
 
246
        If it is, return the (old) index; otherwise raise an exception.
 
247
        """
 
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)            
 
255
        return idx
 
256
        
 
257
 
 
258
        
 
259
    def add(self, name, parents, text, sha1=None):
186
260
        """Add a single text on top of the weave.
187
261
  
188
262
        Returns the index number of the newly added version.
189
263
 
 
264
        name
 
265
            Symbolic name for this version.
 
266
            (Typically the revision-id of the revision that added it.)
 
267
 
190
268
        parents
191
269
            List or set of direct parent version numbers.
192
270
            
193
271
        text
194
 
            Sequence of lines to be added in the new version."""
195
 
 
 
272
            Sequence of lines to be added in the new version.
 
273
 
 
274
        sha -- SHA-1 of the file, if known.  This is trusted to be
 
275
            correct if supplied.
 
276
        """
 
277
        from bzrlib.osutils import sha_strings
 
278
 
 
279
        assert isinstance(name, basestring)
 
280
        if sha1 is None:
 
281
            sha1 = sha_strings(text)
 
282
        if name in self._name_map:
 
283
            return self._check_repeated_add(name, parents, text, sha1)
 
284
 
 
285
        parents = map(self.maybe_lookup, parents)
196
286
        self._check_versions(parents)
197
287
        ## self._check_lines(text)
198
288
        new_version = len(self._parents)
199
289
 
200
 
        import sha
201
 
        s = sha.new()
202
 
        map(s.update, text)
203
 
        sha1 = s.hexdigest()
204
 
        del s
205
290
 
206
 
        # if we abort after here the weave will be corrupt
207
 
        self._parents.append(frozenset(parents))
 
291
        # if we abort after here the (in-memory) weave will be corrupt because only
 
292
        # some fields are updated
 
293
        self._parents.append(parents[:])
208
294
        self._sha1s.append(sha1)
 
295
        self._names.append(name)
 
296
        self._name_map[name] = new_version
209
297
 
210
298
            
211
299
        if not parents:
216
304
            if text:
217
305
                self._weave.append(('{', new_version))
218
306
                self._weave.extend(text)
219
 
                self._weave.append(('}', new_version))
 
307
                self._weave.append(('}', None))
220
308
        
221
309
            return new_version
222
310
 
238
326
            basis_lineno.append(lineno)
239
327
            basis_lines.append(line)
240
328
 
241
 
        # another small special case: a merge, producing the same text as auto-merge
 
329
        # another small special case: a merge, producing the same text
 
330
        # as auto-merge
242
331
        if text == basis_lines:
243
332
            return new_version            
244
333
 
252
341
        #print 'basis_lines:', basis_lines
253
342
        #print 'new_lines:  ', lines
254
343
 
255
 
        from difflib import SequenceMatcher
256
344
        s = SequenceMatcher(None, basis_lines, text)
257
345
 
258
346
        # offset gives the number of lines that have been inserted
287
375
                # we don't destroy ourselves
288
376
                i = i2 + offset
289
377
                self._weave[i:i] = ([('{', new_version)] 
290
 
                                + text[j1:j2] 
291
 
                                + [('}', new_version)])
 
378
                                    + text[j1:j2] 
 
379
                                    + [('}', None)])
292
380
                offset += 2 + (j2 - j1)
293
381
 
294
382
        return new_version
295
383
 
 
384
    def add_identical(self, old_rev_id, new_rev_id, parents):
 
385
        """Add an identical text to old_rev_id as new_rev_id."""
 
386
        old_lines = self.get(self.lookup(old_rev_id))
 
387
        self.add(new_rev_id, parents, old_lines)
296
388
 
297
389
    def inclusions(self, versions):
298
390
        """Return set of all ancestors of given version(s)."""
299
391
        i = set(versions)
300
 
        v = max(versions)
301
 
        try:
302
 
            while v >= 0:
303
 
                if v in i:
304
 
                    # include all its parents
305
 
                    i.update(self._parents[v])
306
 
                v -= 1
307
 
            return i
308
 
        except IndexError:
309
 
            raise ValueError("version %d not present in weave" % v)
 
392
        for v in xrange(max(versions), 0, -1):
 
393
            if v in i:
 
394
                # include all its parents
 
395
                i.update(self._parents[v])
 
396
        return i
 
397
        ## except IndexError:
 
398
        ##     raise ValueError("version %d not present in weave" % v)
 
399
 
 
400
 
 
401
    def parents(self, version):
 
402
        return self._parents[version]
 
403
 
 
404
 
 
405
    def parent_names(self, version):
 
406
        """Return version names for parents of a version."""
 
407
        return map(self.idx_to_name, self._parents[self.lookup(version)])
310
408
 
311
409
 
312
410
    def minimal_parents(self, version):
352
450
                raise IndexError("invalid version number %r" % i)
353
451
 
354
452
    
355
 
    def annotate(self, index):
356
 
        return list(self.annotate_iter(index))
357
 
 
358
 
 
359
 
    def annotate_iter(self, version):
 
453
    def annotate(self, name_or_index):
 
454
        return list(self.annotate_iter(name_or_index))
 
455
 
 
456
 
 
457
    def annotate_iter(self, name_or_index):
360
458
        """Yield list of (index-id, line) pairs for the specified version.
361
459
 
362
460
        The index indicates when the line originated in the weave."""
363
 
        for origin, lineno, text in self._extract([version]):
 
461
        incls = [self.maybe_lookup(name_or_index)]
 
462
        for origin, lineno, text in self._extract(incls):
364
463
            yield origin, text
365
464
 
366
465
 
384
483
                if c == '{':
385
484
                    istack.append(v)
386
485
                elif c == '}':
387
 
                    oldv = istack.pop()
 
486
                    istack.pop()
388
487
                elif c == '[':
389
488
                    assert v not in dset
390
489
                    dset.add(v)
410
509
 
411
510
        The set typically but not necessarily corresponds to a version.
412
511
        """
 
512
        for i in versions:
 
513
            if not isinstance(i, int):
 
514
                raise ValueError(i)
 
515
            
413
516
        included = self.inclusions(versions)
414
517
 
415
518
        istack = []
431
534
                    assert v not in istack
432
535
                    istack.append(v)
433
536
                elif c == '}':
434
 
                    oldv = istack.pop()
435
 
                    assert oldv == v
 
537
                    istack.pop()
436
538
                elif c == '[':
437
539
                    if v in included:
438
540
                        assert v not in dset
461
563
    
462
564
 
463
565
 
464
 
    def get_iter(self, version):
 
566
    def get_iter(self, name_or_index):
465
567
        """Yield lines for the specified version."""
466
 
        for origin, lineno, line in self._extract([version]):
 
568
        incls = [self.maybe_lookup(name_or_index)]
 
569
        for origin, lineno, line in self._extract(incls):
467
570
            yield line
468
571
 
469
572
 
470
 
    def get(self, index):
471
 
        return list(self.get_iter(index))
 
573
    def get_text(self, name_or_index):
 
574
        return ''.join(self.get_iter(name_or_index))
 
575
        assert isinstance(version, int)
 
576
 
 
577
 
 
578
    def get_lines(self, name_or_index):
 
579
        return list(self.get_iter(name_or_index))
 
580
 
 
581
 
 
582
    get = get_lines
472
583
 
473
584
 
474
585
    def mash_iter(self, included):
475
586
        """Return composed version of multiple included versions."""
 
587
        included = map(self.maybe_lookup, included)
476
588
        for origin, lineno, text in self._extract(included):
477
589
            yield text
478
590
 
508
620
 
509
621
        # try extracting all versions; this is a bit slow and parallel
510
622
        # extraction could be used
511
 
        import sha
512
623
        nv = self.numversions()
513
624
        for version in range(nv):
514
625
            if progress_bar:
539
650
        If there is a chunk of the file where there's no diagreement,
540
651
        only one alternative is given.
541
652
        """
542
 
 
543
653
        # approach: find the included versions common to all the
544
654
        # merged versions
545
655
        raise NotImplementedError()
565
675
        If line1=line2, this is a pure insert; if newlines=[] this is a
566
676
        pure delete.  (Similar to difflib.)
567
677
        """
568
 
 
 
678
        raise NotImplementedError()
569
679
 
570
680
            
571
681
    def plan_merge(self, ver_a, ver_b):
665
775
                       state
666
776
 
667
777
                
668
 
 
669
 
 
670
 
 
671
 
 
672
 
 
673
 
def weave_info(w):
674
 
    """Show some text information about the weave."""
675
 
    print '%6s %40s %20s' % ('ver', 'sha1', 'parents')
676
 
    for i in (6, 40, 20):
 
778
    def join(self, other):
 
779
        """Integrate versions from other into this weave.
 
780
 
 
781
        The resulting weave contains all the history of both weaves; 
 
782
        any version you could retrieve from either self or other can be 
 
783
        retrieved from self after this call.
 
784
 
 
785
        It is illegal for the two weaves to contain different values 
 
786
        or different parents for any version.  See also reweave().
 
787
        """
 
788
        if other.numversions() == 0:
 
789
            return          # nothing to update, easy
 
790
        # two loops so that we do not change ourselves before verifying it
 
791
        # will be ok
 
792
        # work through in index order to make sure we get all dependencies
 
793
        for other_idx, name in enumerate(other._names):
 
794
            if self._check_version_consistent(other, other_idx, name):
 
795
                continue
 
796
        for other_idx, name in enumerate(other._names):
 
797
            # TODO: If all the parents of the other version are already 
 
798
            # present then we can avoid some work by just taking the delta
 
799
            # and adjusting the offsets.
 
800
            new_parents = self._imported_parents(other, other_idx)
 
801
            lines = other.get_lines(other_idx)
 
802
            sha1 = other._sha1s[other_idx]
 
803
            self.add(name, new_parents, lines, sha1)
 
804
 
 
805
 
 
806
    def _imported_parents(self, other, other_idx):
 
807
        """Return list of parents in self corresponding to indexes in other."""
 
808
        new_parents = []
 
809
        for parent_idx in other._parents[other_idx]:
 
810
            parent_name = other._names[parent_idx]
 
811
            if parent_name not in self._names:
 
812
                # should not be possible
 
813
                raise WeaveError("missing parent {%s} of {%s} in %r" 
 
814
                                 % (parent_name, other._name_map[other_idx], self))
 
815
            new_parents.append(self._name_map[parent_name])
 
816
        return new_parents
 
817
 
 
818
    def _check_version_consistent(self, other, other_idx, name):
 
819
        """Check if a version in consistent in this and other.
 
820
 
 
821
        To be consistent it must have:
 
822
 
 
823
         * the same text
 
824
         * the same direct parents (by name, not index, and disregarding
 
825
           order)
 
826
        
 
827
        If present & correct return True;
 
828
        if not present in self return False; 
 
829
        if inconsistent raise error."""
 
830
        this_idx = self._name_map.get(name, -1)
 
831
        if this_idx != -1:
 
832
            if self._sha1s[this_idx] != other._sha1s[other_idx]:
 
833
                raise WeaveError("inconsistent texts for version {%s} "
 
834
                                 "when joining weaves"
 
835
                                 % (name))
 
836
            self_parents = self._parents[this_idx]
 
837
            other_parents = other._parents[other_idx]
 
838
            n1 = [self._names[i] for i in self_parents]
 
839
            n2 = [other._names[i] for i in other_parents]
 
840
            n1.sort()
 
841
            n2.sort()
 
842
            if n1 != n2:
 
843
                raise WeaveParentMismatch("inconsistent parents "
 
844
                    "for version {%s}: %s vs %s" % (name, n1, n2))
 
845
            else:
 
846
                return True         # ok!
 
847
        else:
 
848
            return False
 
849
 
 
850
    def reweave(self, other):
 
851
        """Reweave self with other."""
 
852
        new_weave = reweave(self, other)
 
853
        for attr in self.__slots__:
 
854
            setattr(self, attr, getattr(new_weave, attr))
 
855
 
 
856
 
 
857
def reweave(wa, wb):
 
858
    """Combine two weaves and return the result.
 
859
 
 
860
    This works even if a revision R has different parents in 
 
861
    wa and wb.  In the resulting weave all the parents are given.
 
862
 
 
863
    This is done by just building up a new weave, maintaining ordering 
 
864
    of the versions in the two inputs.  More efficient approaches
 
865
    might be possible but it should only be necessary to do 
 
866
    this operation rarely, when a new previously ghost version is 
 
867
    inserted.
 
868
    """
 
869
    wr = Weave()
 
870
    ia = ib = 0
 
871
    queue_a = range(wa.numversions())
 
872
    queue_b = range(wb.numversions())
 
873
    # first determine combined parents of all versions
 
874
    # map from version name -> all parent names
 
875
    combined_parents = _reweave_parent_graphs(wa, wb)
 
876
    mutter("combined parents: %r", combined_parents)
 
877
    order = _make_reweave_order(wa._names, wb._names, combined_parents)
 
878
    mutter("order to reweave: %r", order)
 
879
    for name in order:
 
880
        if name in wa._name_map:
 
881
            lines = wa.get_lines(name)
 
882
            if name in wb._name_map:
 
883
                assert lines == wb.get_lines(name)
 
884
        else:
 
885
            lines = wb.get_lines(name)
 
886
        wr.add(name, combined_parents[name], lines)
 
887
    return wr
 
888
 
 
889
 
 
890
def _reweave_parent_graphs(wa, wb):
 
891
    """Return combined parent ancestry for two weaves.
 
892
    
 
893
    Returned as a list of (version_name, set(parent_names))"""
 
894
    combined = {}
 
895
    for weave in [wa, wb]:
 
896
        for idx, name in enumerate(weave._names):
 
897
            p = combined.setdefault(name, set())
 
898
            p.update(map(weave.idx_to_name, weave._parents[idx]))
 
899
    return combined
 
900
 
 
901
 
 
902
def _make_reweave_order(wa_order, wb_order, combined_parents):
 
903
    """Return an order to reweave versions respecting parents."""
 
904
    done = set()
 
905
    result = []
 
906
    ia = ib = 0
 
907
    next_a = next_b = None
 
908
    len_a = len(wa_order)
 
909
    len_b = len(wb_order)
 
910
    while ia < len(wa_order) or ib < len(wb_order):
 
911
        if ia < len_a:
 
912
            next_a = wa_order[ia]
 
913
            if next_a in done:
 
914
                ia += 1
 
915
                continue
 
916
            if combined_parents[next_a].issubset(done):
 
917
                ia += 1
 
918
                result.append(next_a)
 
919
                done.add(next_a)
 
920
                continue
 
921
        if ib < len_b:
 
922
            next_b = wb_order[ib]
 
923
            if next_b in done:
 
924
                ib += 1
 
925
                continue
 
926
            elif combined_parents[next_b].issubset(done):
 
927
                ib += 1
 
928
                result.append(next_b)
 
929
                done.add(next_b)
 
930
                continue
 
931
        raise WeaveError("don't know how to reweave at {%s} and {%s}"
 
932
                         % (next_a, next_b))
 
933
    return result
 
934
 
 
935
 
 
936
def weave_toc(w):
 
937
    """Show the weave's table-of-contents"""
 
938
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
 
939
    for i in (6, 50, 10, 10):
677
940
        print '-' * i,
678
941
    print
679
942
    for i in range(w.numversions()):
680
943
        sha1 = w._sha1s[i]
681
 
        print '%6d %40s %s' % (i, sha1, ' '.join(map(str, w._parents[i])))
682
 
 
683
 
 
684
 
 
685
 
def weave_stats(weave_file):
686
 
    from bzrlib.progress import ProgressBar
 
944
        name = w._names[i]
 
945
        parent_str = ' '.join(map(str, w._parents[i]))
 
946
        print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
 
947
 
 
948
 
 
949
 
 
950
def weave_stats(weave_file, pb):
687
951
    from bzrlib.weavefile import read_weave
688
952
 
689
 
    pb = ProgressBar()
690
 
 
691
953
    wf = file(weave_file, 'rb')
692
954
    w = read_weave(wf)
693
955
    # FIXME: doesn't work on pipes
697
959
    vers = len(w)
698
960
    for i in range(vers):
699
961
        pb.update('checking sizes', i, vers)
700
 
        for line in w.get_iter(i):
 
962
        for origin, lineno, line in w._extract([i]):
701
963
            total += len(line)
702
964
 
703
965
    pb.clear()
706
968
    print 'weave file        %9d bytes' % weave_size
707
969
    print 'total contents    %9d bytes' % total
708
970
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
709
 
 
 
971
    if vers:
 
972
        avg = total/vers
 
973
        print 'average size      %9d bytes' % avg
 
974
        print 'relative size     %9.2fx' % (float(weave_size) / float(avg))
710
975
 
711
976
 
712
977
def usage():
721
986
        Write out specified version.
722
987
    weave check WEAVEFILE
723
988
        Check consistency of all versions.
724
 
    weave info WEAVEFILE
 
989
    weave toc WEAVEFILE
725
990
        Display table of contents.
726
 
    weave add WEAVEFILE [BASE...] < NEWTEXT
 
991
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
727
992
        Add NEWTEXT, with specified parent versions.
728
993
    weave annotate WEAVEFILE VERSION
729
994
        Display origin of each line.
731
996
        Display composite of all selected versions.
732
997
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
733
998
        Auto-merge two versions and display conflicts.
 
999
    weave diff WEAVEFILE VERSION1 VERSION2 
 
1000
        Show differences between two versions.
734
1001
 
735
1002
example:
736
1003
 
737
1004
    % weave init foo.weave
738
1005
    % vi foo.txt
739
 
    % weave add foo.weave < foo.txt
 
1006
    % weave add foo.weave ver0 < foo.txt
740
1007
    added version 0
741
1008
 
742
1009
    (create updated version)
743
1010
    % vi foo.txt
744
1011
    % weave get foo.weave 0 | diff -u - foo.txt
745
 
    % weave add foo.weave 0 < foo.txt
 
1012
    % weave add foo.weave ver1 0 < foo.txt
746
1013
    added version 1
747
1014
 
748
1015
    % weave get foo.weave 0 > foo.txt       (create forked version)
749
1016
    % vi foo.txt
750
 
    % weave add foo.weave 0 < foo.txt
 
1017
    % weave add foo.weave ver2 0 < foo.txt
751
1018
    added version 2
752
1019
 
753
1020
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
754
1021
    % vi foo.txt                            (resolve conflicts)
755
 
    % weave add foo.weave 1 2 < foo.txt     (commit merged version)     
 
1022
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
756
1023
    
757
1024
"""
758
1025
    
761
1028
def main(argv):
762
1029
    import sys
763
1030
    import os
764
 
    from weavefile import write_weave, read_weave
 
1031
    try:
 
1032
        import bzrlib
 
1033
    except ImportError:
 
1034
        # in case we're run directly from the subdirectory
 
1035
        sys.path.append('..')
 
1036
        import bzrlib
 
1037
    from bzrlib.weavefile import write_weave, read_weave
765
1038
    from bzrlib.progress import ProgressBar
766
1039
 
767
 
    #import psyco
768
 
    #psyco.full()
 
1040
    try:
 
1041
        import psyco
 
1042
        psyco.full()
 
1043
    except ImportError:
 
1044
        pass
 
1045
 
 
1046
    if len(argv) < 2:
 
1047
        usage()
 
1048
        return 0
769
1049
 
770
1050
    cmd = argv[1]
771
1051
 
777
1057
    elif cmd == 'add':
778
1058
        w = readit()
779
1059
        # at the moment, based on everything in the file
780
 
        parents = map(int, argv[3:])
 
1060
        name = argv[3]
 
1061
        parents = map(int, argv[4:])
781
1062
        lines = sys.stdin.readlines()
782
 
        ver = w.add(parents, lines)
 
1063
        ver = w.add(name, parents, lines)
783
1064
        write_weave(w, file(argv[2], 'wb'))
784
 
        print 'added version %d' % ver
 
1065
        print 'added version %r %d' % (name, ver)
785
1066
    elif cmd == 'init':
786
1067
        fn = argv[2]
787
1068
        if os.path.exists(fn):
796
1077
        w = readit()
797
1078
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
798
1079
 
 
1080
    elif cmd == 'diff':
 
1081
        from difflib import unified_diff
 
1082
        w = readit()
 
1083
        fn = argv[2]
 
1084
        v1, v2 = map(int, argv[3:5])
 
1085
        lines1 = w.get(v1)
 
1086
        lines2 = w.get(v2)
 
1087
        diff_gen = unified_diff(lines1, lines2,
 
1088
                                '%s version %d' % (fn, v1),
 
1089
                                '%s version %d' % (fn, v2))
 
1090
        sys.stdout.writelines(diff_gen)
 
1091
            
799
1092
    elif cmd == 'annotate':
800
1093
        w = readit()
801
1094
        # newline is added to all lines regardless; too hard to get
809
1102
                print '%5d | %s' % (origin, text)
810
1103
                lasto = origin
811
1104
                
812
 
    elif cmd == 'info':
813
 
        weave_info(readit())
 
1105
    elif cmd == 'toc':
 
1106
        weave_toc(readit())
814
1107
 
815
1108
    elif cmd == 'stats':
816
 
        weave_stats(argv[2])
 
1109
        weave_stats(argv[2], ProgressBar())
817
1110
        
818
1111
    elif cmd == 'check':
819
1112
        w = readit()
865
1158
        raise ValueError('unknown command %r' % cmd)
866
1159
    
867
1160
 
 
1161
 
 
1162
def profile_main(argv): 
 
1163
    import tempfile, hotshot, hotshot.stats
 
1164
 
 
1165
    prof_f = tempfile.NamedTemporaryFile()
 
1166
 
 
1167
    prof = hotshot.Profile(prof_f.name)
 
1168
 
 
1169
    ret = prof.runcall(main, argv)
 
1170
    prof.close()
 
1171
 
 
1172
    stats = hotshot.stats.load(prof_f.name)
 
1173
    #stats.strip_dirs()
 
1174
    stats.sort_stats('cumulative')
 
1175
    ## XXX: Might like to write to stderr or the trace file instead but
 
1176
    ## print_stats seems hardcoded to stdout
 
1177
    stats.print_stats(20)
 
1178
            
 
1179
    return ret
 
1180
 
 
1181
 
868
1182
if __name__ == '__main__':
869
1183
    import sys
870
 
    sys.exit(main(sys.argv))
 
1184
    if '--profile' in sys.argv:
 
1185
        args = sys.argv[:]
 
1186
        args.remove('--profile')
 
1187
        sys.exit(profile_main(args))
 
1188
    else:
 
1189
        sys.exit(main(sys.argv))
 
1190