/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Robert Collins
  • Date: 2006-03-04 13:03:57 UTC
  • mfrom: (1505.1.30 bzr-bound-branch)
  • mto: This revision was merged to the branch mainline in revision 1590.
  • Revision ID: robertc@robertcollins.net-20060304130357-95990a95920f57bb
Update bound branch implementation to 0.8.

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
 
 
79
 
 
80
 
 
81
 
class WeaveError(Exception):
82
 
    """Exception in processing weave"""
83
 
 
84
 
 
85
 
class WeaveFormatError(WeaveError):
86
 
    """Weave invariant violated"""
87
 
    
 
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: Probably 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: Reweave can possibly be made faster by remembering diffs
 
61
# where the basis and destination are unchanged.
 
62
 
 
63
# FIXME: Sometimes we will be given a parents list for a revision
 
64
# that includes some redundant parents (i.e. already a parent of 
 
65
# something in the list.)  We should eliminate them.  This can 
 
66
# be done fairly efficiently because the sequence numbers constrain
 
67
# the possible relationships.
 
68
 
 
69
 
 
70
import os
 
71
import sha
 
72
from difflib import SequenceMatcher
 
73
 
 
74
from bzrlib.trace import mutter
 
75
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
 
76
        WeaveRevisionNotPresent, WeaveRevisionAlreadyPresent)
 
77
import bzrlib.errors as errors
 
78
from bzrlib.tsort import topo_sort
 
79
 
88
80
 
89
81
class Weave(object):
90
82
    """weave - versioned text file storage.
118
110
    The instruction can be '{' or '}' for an insertion block, and '['
119
111
    and ']' for a deletion block respectively.  The version is the
120
112
    integer version index.  There is no replace operator, only deletes
121
 
    and inserts.
 
113
    and inserts.  For '}', the end of an insertion, there is no
 
114
    version parameter because it always closes the most recently
 
115
    opened insertion.
122
116
 
123
117
    Constraints/notes:
124
118
 
160
154
        each version; the parent's parents are implied.
161
155
 
162
156
    _sha1s
163
 
        List of hex SHA-1 of each version, or None if not recorded.
 
157
        List of hex SHA-1 of each version.
 
158
 
 
159
    _names
 
160
        List of symbolic names for each version.  Each should be unique.
 
161
 
 
162
    _name_map
 
163
        For each name, the version number.
 
164
 
 
165
    _weave_name
 
166
        Descriptive name of this weave; typically the filename if known.
 
167
        Set by read_weave.
164
168
    """
165
169
 
166
 
    __slots__ = ['_weave', '_parents', '_sha1s']
 
170
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
 
171
                 '_weave_name']
167
172
    
168
 
    def __init__(self):
 
173
    def __init__(self, weave_name=None):
169
174
        self._weave = []
170
175
        self._parents = []
171
176
        self._sha1s = []
172
 
 
 
177
        self._names = []
 
178
        self._name_map = {}
 
179
        self._weave_name = weave_name
 
180
 
 
181
    def __repr__(self):
 
182
        return "Weave(%r)" % self._weave_name
 
183
 
 
184
 
 
185
    def copy(self):
 
186
        """Return a deep copy of self.
 
187
        
 
188
        The copy can be modified without affecting the original weave."""
 
189
        other = Weave()
 
190
        other._weave = self._weave[:]
 
191
        other._parents = self._parents[:]
 
192
        other._sha1s = self._sha1s[:]
 
193
        other._names = self._names[:]
 
194
        other._name_map = self._name_map.copy()
 
195
        other._weave_name = self._weave_name
 
196
        return other
173
197
 
174
198
    def __eq__(self, other):
175
199
        if not isinstance(other, Weave):
176
200
            return False
177
201
        return self._parents == other._parents \
178
 
               and self._weave == other._weave
 
202
               and self._weave == other._weave \
 
203
               and self._sha1s == other._sha1s 
 
204
 
179
205
    
180
 
 
181
206
    def __ne__(self, other):
182
207
        return not self.__eq__(other)
183
208
 
184
 
        
185
 
    def add(self, parents, text):
 
209
    def __contains__(self, name):
 
210
        return self._name_map.has_key(name)
 
211
 
 
212
    def maybe_lookup(self, name_or_index):
 
213
        """Convert possible symbolic name to index, or pass through indexes."""
 
214
        if isinstance(name_or_index, (int, long)):
 
215
            return name_or_index
 
216
        else:
 
217
            return self.lookup(name_or_index)
 
218
 
 
219
        
 
220
    def lookup(self, name):
 
221
        """Convert symbolic version name to index."""
 
222
        try:
 
223
            return self._name_map[name]
 
224
        except KeyError:
 
225
            raise WeaveRevisionNotPresent(name, self)
 
226
 
 
227
    def names(self):
 
228
        return self._names[:]
 
229
 
 
230
    def iter_names(self):
 
231
        """Yield a list of all names in this weave."""
 
232
        return iter(self._names)
 
233
 
 
234
    def idx_to_name(self, version):
 
235
        return self._names[version]
 
236
 
 
237
    def _check_repeated_add(self, name, parents, text, sha1):
 
238
        """Check that a duplicated add is OK.
 
239
 
 
240
        If it is, return the (old) index; otherwise raise an exception.
 
241
        """
 
242
        idx = self.lookup(name)
 
243
        if sorted(self._parents[idx]) != sorted(parents) \
 
244
            or sha1 != self._sha1s[idx]:
 
245
            raise WeaveRevisionAlreadyPresent(name, self)
 
246
        return idx
 
247
        
 
248
    def add(self, name, parents, text, sha1=None):
186
249
        """Add a single text on top of the weave.
187
250
  
188
251
        Returns the index number of the newly added version.
189
252
 
 
253
        name
 
254
            Symbolic name for this version.
 
255
            (Typically the revision-id of the revision that added it.)
 
256
 
190
257
        parents
191
258
            List or set of direct parent version numbers.
192
259
            
193
260
        text
194
 
            Sequence of lines to be added in the new version."""
195
 
 
 
261
            Sequence of lines to be added in the new version.
 
262
 
 
263
        sha -- SHA-1 of the file, if known.  This is trusted to be
 
264
            correct if supplied.
 
265
        """
 
266
        from bzrlib.osutils import sha_strings
 
267
 
 
268
        assert isinstance(name, basestring)
 
269
        if sha1 is None:
 
270
            sha1 = sha_strings(text)
 
271
        if name in self._name_map:
 
272
            return self._check_repeated_add(name, parents, text, sha1)
 
273
 
 
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)
199
278
 
200
 
        import sha
201
 
        s = sha.new()
202
 
        map(s.update, text)
203
 
        sha1 = s.hexdigest()
204
 
        del s
205
279
 
206
 
        # if we abort after here the weave will be corrupt
207
 
        self._parents.append(frozenset(parents))
 
280
        # if we abort after here the (in-memory) weave will be corrupt because only
 
281
        # some fields are updated
 
282
        # XXX: FIXME implement a succeed-or-fail of the rest of this routine.
 
283
        #      - Robert Collins 20060226
 
284
        self._parents.append(parents[:])
208
285
        self._sha1s.append(sha1)
 
286
        self._names.append(name)
 
287
        self._name_map[name] = new_version
209
288
 
210
289
            
211
290
        if not parents:
216
295
            if text:
217
296
                self._weave.append(('{', new_version))
218
297
                self._weave.extend(text)
219
 
                self._weave.append(('}', new_version))
 
298
                self._weave.append(('}', None))
220
299
        
221
300
            return new_version
222
301
 
238
317
            basis_lineno.append(lineno)
239
318
            basis_lines.append(line)
240
319
 
241
 
        # another small special case: a merge, producing the same text as auto-merge
 
320
        # another small special case: a merge, producing the same text
 
321
        # as auto-merge
242
322
        if text == basis_lines:
243
 
            return new_version            
 
323
            return new_version
244
324
 
245
325
        # add a sentinal, because we can also match against the final line
246
326
        basis_lineno.append(len(self._weave))
252
332
        #print 'basis_lines:', basis_lines
253
333
        #print 'new_lines:  ', lines
254
334
 
255
 
        from difflib import SequenceMatcher
256
335
        s = SequenceMatcher(None, basis_lines, text)
257
336
 
258
337
        # offset gives the number of lines that have been inserted
287
366
                # we don't destroy ourselves
288
367
                i = i2 + offset
289
368
                self._weave[i:i] = ([('{', new_version)] 
290
 
                                + text[j1:j2] 
291
 
                                + [('}', new_version)])
 
369
                                    + text[j1:j2] 
 
370
                                    + [('}', None)])
292
371
                offset += 2 + (j2 - j1)
293
372
 
294
373
        return new_version
295
374
 
 
375
    def add_identical(self, old_rev_id, new_rev_id, parents):
 
376
        """Add an identical text to old_rev_id as new_rev_id."""
 
377
        old_lines = self.get(self.lookup(old_rev_id))
 
378
        self.add(new_rev_id, parents, old_lines)
296
379
 
297
380
    def inclusions(self, versions):
298
381
        """Return set of all ancestors of given version(s)."""
299
382
        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)
 
383
        for v in xrange(max(versions), 0, -1):
 
384
            if v in i:
 
385
                # include all its parents
 
386
                i.update(self._parents[v])
 
387
        return i
 
388
        ## except IndexError:
 
389
        ##     raise ValueError("version %d not present in weave" % v)
 
390
 
 
391
 
 
392
    def parents(self, version):
 
393
        return self._parents[version]
 
394
 
 
395
 
 
396
    def parent_names(self, version):
 
397
        """Return version names for parents of a version."""
 
398
        return map(self.idx_to_name, self._parents[self.lookup(version)])
310
399
 
311
400
 
312
401
    def minimal_parents(self, version):
352
441
                raise IndexError("invalid version number %r" % i)
353
442
 
354
443
    
355
 
    def annotate(self, index):
356
 
        return list(self.annotate_iter(index))
357
 
 
358
 
 
359
 
    def annotate_iter(self, version):
 
444
    def annotate(self, name_or_index):
 
445
        return list(self.annotate_iter(name_or_index))
 
446
 
 
447
 
 
448
    def annotate_iter(self, name_or_index):
360
449
        """Yield list of (index-id, line) pairs for the specified version.
361
450
 
362
451
        The index indicates when the line originated in the weave."""
363
 
        for origin, lineno, text in self._extract([version]):
 
452
        incls = [self.maybe_lookup(name_or_index)]
 
453
        for origin, lineno, text in self._extract(incls):
364
454
            yield origin, text
365
455
 
366
 
 
367
456
    def _walk(self):
368
457
        """Walk the weave.
369
458
 
384
473
                if c == '{':
385
474
                    istack.append(v)
386
475
                elif c == '}':
387
 
                    oldv = istack.pop()
 
476
                    istack.pop()
388
477
                elif c == '[':
389
478
                    assert v not in dset
390
479
                    dset.add(v)
391
480
                elif c == ']':
392
481
                    dset.remove(v)
393
482
                else:
394
 
                    raise WeaveFormatError('unexpected instruction %r'
395
 
                                           % v)
 
483
                    raise WeaveFormatError('unexpected instruction %r' % v)
396
484
            else:
397
485
                assert isinstance(l, basestring)
398
486
                assert istack
399
487
                yield lineno, istack[-1], dset, l
400
488
            lineno += 1
401
489
 
402
 
 
 
490
        if istack:
 
491
            raise WeaveFormatError("unclosed insertion blocks "
 
492
                    "at end of weave: %s" % istack)
 
493
        if dset:
 
494
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
 
495
                                   % dset)
403
496
 
404
497
    def _extract(self, versions):
405
498
        """Yield annotation of lines in included set.
410
503
 
411
504
        The set typically but not necessarily corresponds to a version.
412
505
        """
 
506
        for i in versions:
 
507
            if not isinstance(i, int):
 
508
                raise ValueError(i)
 
509
            
413
510
        included = self.inclusions(versions)
414
511
 
415
512
        istack = []
431
528
                    assert v not in istack
432
529
                    istack.append(v)
433
530
                elif c == '}':
434
 
                    oldv = istack.pop()
435
 
                    assert oldv == v
 
531
                    istack.pop()
436
532
                elif c == '[':
437
533
                    if v in included:
438
534
                        assert v not in dset
449
545
                if isactive:
450
546
                    result.append((istack[-1], lineno, l))
451
547
            lineno += 1
452
 
 
453
548
        if istack:
454
 
            raise WFE("unclosed insertion blocks at end of weave",
455
 
                                   istack)
 
549
            raise WeaveFormatError("unclosed insertion blocks "
 
550
                    "at end of weave: %s" % istack)
456
551
        if dset:
457
 
            raise WFE("unclosed deletion blocks at end of weave",
458
 
                                   dset)
459
 
 
 
552
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
 
553
                                   % dset)
460
554
        return result
461
 
    
462
 
 
463
 
 
464
 
    def get_iter(self, version):
 
555
 
 
556
 
 
557
    def get_iter(self, name_or_index):
465
558
        """Yield lines for the specified version."""
466
 
        for origin, lineno, line in self._extract([version]):
 
559
        incls = [self.maybe_lookup(name_or_index)]
 
560
        if len(incls) == 1:
 
561
            index = incls[0]
 
562
            cur_sha = sha.new()
 
563
        else:
 
564
            # We don't have sha1 sums for multiple entries
 
565
            cur_sha = None
 
566
        for origin, lineno, line in self._extract(incls):
 
567
            if cur_sha:
 
568
                cur_sha.update(line)
467
569
            yield line
468
 
 
469
 
 
470
 
    def get(self, index):
471
 
        return list(self.get_iter(index))
472
 
 
 
570
        if cur_sha:
 
571
            expected_sha1 = self._sha1s[index]
 
572
            measured_sha1 = cur_sha.hexdigest() 
 
573
            if measured_sha1 != expected_sha1:
 
574
                raise errors.WeaveInvalidChecksum(
 
575
                        'file %s, revision %s, expected: %s, measured %s' 
 
576
                        % (self._weave_name, self._names[index],
 
577
                           expected_sha1, measured_sha1))
 
578
 
 
579
 
 
580
    def get_text(self, name_or_index):
 
581
        return ''.join(self.get_iter(name_or_index))
 
582
        assert isinstance(version, int)
 
583
 
 
584
 
 
585
    def get_lines(self, name_or_index):
 
586
        return list(self.get_iter(name_or_index))
 
587
 
 
588
 
 
589
    get = get_lines
 
590
 
 
591
 
 
592
    def get_sha1(self, name):
 
593
        """Get the stored sha1 sum for the given revision.
 
594
        
 
595
        :param name: The name of the version to lookup
 
596
        """
 
597
        return self._sha1s[self.lookup(name)]
473
598
 
474
599
    def mash_iter(self, included):
475
600
        """Return composed version of multiple included versions."""
 
601
        included = map(self.maybe_lookup, included)
476
602
        for origin, lineno, text in self._extract(included):
477
603
            yield text
478
604
 
495
621
    def __len__(self):
496
622
        return self.numversions()
497
623
 
498
 
 
499
624
    def check(self, progress_bar=None):
500
625
        # check no circular inclusions
501
626
        for version in range(self.numversions()):
506
631
                    raise WeaveFormatError("invalid included version %d for index %d"
507
632
                                           % (inclusions[-1], version))
508
633
 
509
 
        # try extracting all versions; this is a bit slow and parallel
510
 
        # extraction could be used
511
 
        import sha
 
634
        # try extracting all versions; parallel extraction is used
512
635
        nv = self.numversions()
 
636
        sha1s = [sha.new() for i in range(nv)]
 
637
        texts = [[] for i in range(nv)]
 
638
        inclusions = []
 
639
        for i in range(nv):
 
640
            # For creating the ancestry, IntSet is much faster (3.7s vs 0.17s)
 
641
            # The problem is that set membership is much more expensive
 
642
            new_inc = set([i])
 
643
            for p in self._parents[i]:
 
644
                new_inc.update(inclusions[p])
 
645
 
 
646
            #assert set(new_inc) == self.inclusions([i]), 'failed %s != %s' % (new_inc, self.inclusions([i]))
 
647
            inclusions.append(new_inc)
 
648
 
 
649
        nlines = len(self._weave)
 
650
 
 
651
        update_text = 'checking weave'
 
652
        if self._weave_name:
 
653
            short_name = os.path.basename(self._weave_name)
 
654
            update_text = 'checking %s' % (short_name,)
 
655
            update_text = update_text[:25]
 
656
 
 
657
        for lineno, insert, deleteset, line in self._walk():
 
658
            if progress_bar:
 
659
                progress_bar.update(update_text, lineno, nlines)
 
660
 
 
661
            for j, j_inc in enumerate(inclusions):
 
662
                # The active inclusion must be an ancestor,
 
663
                # and no ancestors must have deleted this line,
 
664
                # because we don't support resurrection.
 
665
                if (insert in j_inc) and not (deleteset & j_inc):
 
666
                    sha1s[j].update(line)
 
667
 
513
668
        for version in range(nv):
514
 
            if progress_bar:
515
 
                progress_bar.update('checking text', version, nv)
516
 
            s = sha.new()
517
 
            for l in self.get_iter(version):
518
 
                s.update(l)
519
 
            hd = s.hexdigest()
 
669
            hd = sha1s[version].hexdigest()
520
670
            expected = self._sha1s[version]
521
671
            if hd != expected:
522
 
                raise WeaveError("mismatched sha1 for version %d; "
523
 
                                 "got %s, expected %s"
524
 
                                 % (version, hd, expected))
 
672
                raise errors.WeaveInvalidChecksum(
 
673
                        "mismatched sha1 for version %s: "
 
674
                        "got %s, expected %s"
 
675
                        % (self._names[version], hd, expected))
525
676
 
526
677
        # TODO: check insertions are properly nested, that there are
527
678
        # no lines outside of insertion blocks, that deletions are
528
679
        # properly paired, etc.
529
680
 
530
 
 
531
 
 
532
 
    def merge(self, merge_versions):
533
 
        """Automerge and mark conflicts between versions.
534
 
 
535
 
        This returns a sequence, each entry describing alternatives
536
 
        for a chunk of the file.  Each of the alternatives is given as
537
 
        a list of lines.
538
 
 
539
 
        If there is a chunk of the file where there's no diagreement,
540
 
        only one alternative is given.
541
 
        """
542
 
 
543
 
        # approach: find the included versions common to all the
544
 
        # merged versions
545
 
        raise NotImplementedError()
546
 
 
547
 
 
548
 
 
549
681
    def _delta(self, included, lines):
550
682
        """Return changes from basis to new revision.
551
683
 
565
697
        If line1=line2, this is a pure insert; if newlines=[] this is a
566
698
        pure delete.  (Similar to difflib.)
567
699
        """
568
 
 
 
700
        raise NotImplementedError()
569
701
 
570
702
            
571
703
    def plan_merge(self, ver_a, ver_b):
616
748
 
617
749
 
618
750
 
619
 
    def weave_merge(self, plan):
 
751
    def weave_merge(self, plan, a_marker='<<<<<<< \n', b_marker='>>>>>>> \n'):
620
752
        lines_a = []
621
753
        lines_b = []
622
754
        ch_a = ch_b = False
623
 
 
 
755
        # TODO: Return a structured form of the conflicts (e.g. 2-tuples for
 
756
        # conflicted regions), rather than just inserting the markers.
 
757
        # 
 
758
        # TODO: Show some version information (e.g. author, date) on 
 
759
        # conflicted regions.
624
760
        for state, line in plan:
625
761
            if state == 'unchanged' or state == 'killed-both':
626
762
                # resync and flush queued conflicts changes if any
634
770
                elif lines_a == lines_b:
635
771
                    for l in lines_a: yield l
636
772
                else:
637
 
                    yield '<<<<\n'
 
773
                    yield a_marker
638
774
                    for l in lines_a: yield l
639
 
                    yield '====\n'
 
775
                    yield '=======\n'
640
776
                    for l in lines_b: yield l
641
 
                    yield '>>>>\n'
 
777
                    yield b_marker
642
778
 
643
779
                del lines_a[:]
644
780
                del lines_b[:]
664
800
                                 'killed-both'), \
665
801
                       state
666
802
 
667
 
                
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):
 
803
 
 
804
    def join(self, other, pb=None, msg=None):
 
805
        import sys, time
 
806
        """Integrate versions from other into this weave.
 
807
 
 
808
        The resulting weave contains all the history of both weaves; 
 
809
        any version you could retrieve from either self or other can be 
 
810
        retrieved from self after this call.
 
811
 
 
812
        It is illegal for the two weaves to contain different values 
 
813
        or different parents for any version.  See also reweave().
 
814
 
 
815
        :param other: The other weave to pull into this one
 
816
        :param pb: An optional progress bar
 
817
        :param msg: An optional message to display for progress
 
818
        """
 
819
        if other.numversions() == 0:
 
820
            return          # nothing to update, easy
 
821
        # two loops so that we do not change ourselves before verifying it
 
822
        # will be ok
 
823
        # work through in index order to make sure we get all dependencies
 
824
        names_to_join = []
 
825
        processed = 0
 
826
        for other_idx, name in enumerate(other._names):
 
827
            self._check_version_consistent(other, other_idx, name)
 
828
            sha1 = other._sha1s[other_idx]
 
829
 
 
830
            processed += 1
 
831
 
 
832
            if name in self._name_map:
 
833
                idx = self.lookup(name)
 
834
                n1 = set(map(other.idx_to_name, other._parents[other_idx]))
 
835
                n2 = set(map(self.idx_to_name, self._parents[idx]))
 
836
                if sha1 ==  self._sha1s[idx] and n1 == n2:
 
837
                        continue
 
838
 
 
839
            names_to_join.append((other_idx, name))
 
840
 
 
841
        if pb and not msg:
 
842
            msg = 'weave join'
 
843
 
 
844
        merged = 0
 
845
        time0 = time.time( )
 
846
        for other_idx, name in names_to_join:
 
847
            # TODO: If all the parents of the other version are already
 
848
            # present then we can avoid some work by just taking the delta
 
849
            # and adjusting the offsets.
 
850
            new_parents = self._imported_parents(other, other_idx)
 
851
            sha1 = other._sha1s[other_idx]
 
852
 
 
853
            merged += 1
 
854
 
 
855
            if pb:
 
856
                pb.update(msg, merged, len(names_to_join))
 
857
           
 
858
            lines = other.get_lines(other_idx)
 
859
            self.add(name, new_parents, lines, sha1)
 
860
 
 
861
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
 
862
                merged, processed, self._weave_name, time.time( )-time0))
 
863
 
 
864
    def _imported_parents(self, other, other_idx):
 
865
        """Return list of parents in self corresponding to indexes in other."""
 
866
        new_parents = []
 
867
        for parent_idx in other._parents[other_idx]:
 
868
            parent_name = other._names[parent_idx]
 
869
            if parent_name not in self._names:
 
870
                # should not be possible
 
871
                raise WeaveError("missing parent {%s} of {%s} in %r" 
 
872
                                 % (parent_name, other._name_map[other_idx], self))
 
873
            new_parents.append(self._name_map[parent_name])
 
874
        return new_parents
 
875
 
 
876
    def _check_version_consistent(self, other, other_idx, name):
 
877
        """Check if a version in consistent in this and other.
 
878
 
 
879
        To be consistent it must have:
 
880
 
 
881
         * the same text
 
882
         * the same direct parents (by name, not index, and disregarding
 
883
           order)
 
884
        
 
885
        If present & correct return True;
 
886
        if not present in self return False; 
 
887
        if inconsistent raise error."""
 
888
        this_idx = self._name_map.get(name, -1)
 
889
        if this_idx != -1:
 
890
            if self._sha1s[this_idx] != other._sha1s[other_idx]:
 
891
                raise WeaveError("inconsistent texts for version {%s} "
 
892
                                 "when joining weaves"
 
893
                                 % (name))
 
894
            self_parents = self._parents[this_idx]
 
895
            other_parents = other._parents[other_idx]
 
896
            n1 = set([self._names[i] for i in self_parents])
 
897
            n2 = set([other._names[i] for i in other_parents])
 
898
            if n1 != n2:
 
899
                raise WeaveParentMismatch("inconsistent parents "
 
900
                    "for version {%s}: %s vs %s" % (name, n1, n2))
 
901
            else:
 
902
                return True         # ok!
 
903
        else:
 
904
            return False
 
905
 
 
906
    def reweave(self, other, pb=None, msg=None):
 
907
        """Reweave self with other.
 
908
 
 
909
        :param other: The other weave to merge
 
910
        :param pb: An optional progress bar, indicating how far done we are
 
911
        :param msg: An optional message for the progress
 
912
        """
 
913
        new_weave = reweave(self, other, pb=pb, msg=msg)
 
914
        for attr in self.__slots__:
 
915
            setattr(self, attr, getattr(new_weave, attr))
 
916
 
 
917
 
 
918
def reweave(wa, wb, pb=None, msg=None):
 
919
    """Combine two weaves and return the result.
 
920
 
 
921
    This works even if a revision R has different parents in 
 
922
    wa and wb.  In the resulting weave all the parents are given.
 
923
 
 
924
    This is done by just building up a new weave, maintaining ordering 
 
925
    of the versions in the two inputs.  More efficient approaches
 
926
    might be possible but it should only be necessary to do 
 
927
    this operation rarely, when a new previously ghost version is 
 
928
    inserted.
 
929
 
 
930
    :param pb: An optional progress bar, indicating how far done we are
 
931
    :param msg: An optional message for the progress
 
932
    """
 
933
    wr = Weave()
 
934
    ia = ib = 0
 
935
    queue_a = range(wa.numversions())
 
936
    queue_b = range(wb.numversions())
 
937
    # first determine combined parents of all versions
 
938
    # map from version name -> all parent names
 
939
    combined_parents = _reweave_parent_graphs(wa, wb)
 
940
    mutter("combined parents: %r", combined_parents)
 
941
    order = topo_sort(combined_parents.iteritems())
 
942
    mutter("order to reweave: %r", order)
 
943
 
 
944
    if pb and not msg:
 
945
        msg = 'reweave'
 
946
 
 
947
    for idx, name in enumerate(order):
 
948
        if pb:
 
949
            pb.update(msg, idx, len(order))
 
950
        if name in wa._name_map:
 
951
            lines = wa.get_lines(name)
 
952
            if name in wb._name_map:
 
953
                lines_b = wb.get_lines(name)
 
954
                if lines != lines_b:
 
955
                    mutter('Weaves differ on content. rev_id {%s}', name)
 
956
                    mutter('weaves: %s, %s', wa._weave_name, wb._weave_name)
 
957
                    import difflib
 
958
                    lines = list(difflib.unified_diff(lines, lines_b,
 
959
                            wa._weave_name, wb._weave_name))
 
960
                    mutter('lines:\n%s', ''.join(lines))
 
961
                    raise errors.WeaveTextDiffers(name, wa, wb)
 
962
        else:
 
963
            lines = wb.get_lines(name)
 
964
        wr.add(name, combined_parents[name], lines)
 
965
    return wr
 
966
 
 
967
 
 
968
def _reweave_parent_graphs(wa, wb):
 
969
    """Return combined parent ancestry for two weaves.
 
970
    
 
971
    Returned as a list of (version_name, set(parent_names))"""
 
972
    combined = {}
 
973
    for weave in [wa, wb]:
 
974
        for idx, name in enumerate(weave._names):
 
975
            p = combined.setdefault(name, set())
 
976
            p.update(map(weave.idx_to_name, weave._parents[idx]))
 
977
    return combined
 
978
 
 
979
 
 
980
def weave_toc(w):
 
981
    """Show the weave's table-of-contents"""
 
982
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
 
983
    for i in (6, 50, 10, 10):
677
984
        print '-' * i,
678
985
    print
679
986
    for i in range(w.numversions()):
680
987
        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
 
988
        name = w._names[i]
 
989
        parent_str = ' '.join(map(str, w._parents[i]))
 
990
        print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
 
991
 
 
992
 
 
993
 
 
994
def weave_stats(weave_file, pb):
687
995
    from bzrlib.weavefile import read_weave
688
996
 
689
 
    pb = ProgressBar()
690
 
 
691
997
    wf = file(weave_file, 'rb')
692
998
    w = read_weave(wf)
693
999
    # FIXME: doesn't work on pipes
697
1003
    vers = len(w)
698
1004
    for i in range(vers):
699
1005
        pb.update('checking sizes', i, vers)
700
 
        for line in w.get_iter(i):
 
1006
        for origin, lineno, line in w._extract([i]):
701
1007
            total += len(line)
702
1008
 
703
1009
    pb.clear()
706
1012
    print 'weave file        %9d bytes' % weave_size
707
1013
    print 'total contents    %9d bytes' % total
708
1014
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
709
 
 
 
1015
    if vers:
 
1016
        avg = total/vers
 
1017
        print 'average size      %9d bytes' % avg
 
1018
        print 'relative size     %9.2fx' % (float(weave_size) / float(avg))
710
1019
 
711
1020
 
712
1021
def usage():
721
1030
        Write out specified version.
722
1031
    weave check WEAVEFILE
723
1032
        Check consistency of all versions.
724
 
    weave info WEAVEFILE
 
1033
    weave toc WEAVEFILE
725
1034
        Display table of contents.
726
 
    weave add WEAVEFILE [BASE...] < NEWTEXT
 
1035
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
727
1036
        Add NEWTEXT, with specified parent versions.
728
1037
    weave annotate WEAVEFILE VERSION
729
1038
        Display origin of each line.
731
1040
        Display composite of all selected versions.
732
1041
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
733
1042
        Auto-merge two versions and display conflicts.
 
1043
    weave diff WEAVEFILE VERSION1 VERSION2 
 
1044
        Show differences between two versions.
734
1045
 
735
1046
example:
736
1047
 
737
1048
    % weave init foo.weave
738
1049
    % vi foo.txt
739
 
    % weave add foo.weave < foo.txt
 
1050
    % weave add foo.weave ver0 < foo.txt
740
1051
    added version 0
741
1052
 
742
1053
    (create updated version)
743
1054
    % vi foo.txt
744
1055
    % weave get foo.weave 0 | diff -u - foo.txt
745
 
    % weave add foo.weave 0 < foo.txt
 
1056
    % weave add foo.weave ver1 0 < foo.txt
746
1057
    added version 1
747
1058
 
748
1059
    % weave get foo.weave 0 > foo.txt       (create forked version)
749
1060
    % vi foo.txt
750
 
    % weave add foo.weave 0 < foo.txt
 
1061
    % weave add foo.weave ver2 0 < foo.txt
751
1062
    added version 2
752
1063
 
753
1064
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
754
1065
    % vi foo.txt                            (resolve conflicts)
755
 
    % weave add foo.weave 1 2 < foo.txt     (commit merged version)     
 
1066
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
756
1067
    
757
1068
"""
758
1069
    
761
1072
def main(argv):
762
1073
    import sys
763
1074
    import os
764
 
    from weavefile import write_weave, read_weave
 
1075
    try:
 
1076
        import bzrlib
 
1077
    except ImportError:
 
1078
        # in case we're run directly from the subdirectory
 
1079
        sys.path.append('..')
 
1080
        import bzrlib
 
1081
    from bzrlib.weavefile import write_weave, read_weave
765
1082
    from bzrlib.progress import ProgressBar
766
1083
 
767
 
    #import psyco
768
 
    #psyco.full()
 
1084
    try:
 
1085
        import psyco
 
1086
        psyco.full()
 
1087
    except ImportError:
 
1088
        pass
 
1089
 
 
1090
    if len(argv) < 2:
 
1091
        usage()
 
1092
        return 0
769
1093
 
770
1094
    cmd = argv[1]
771
1095
 
777
1101
    elif cmd == 'add':
778
1102
        w = readit()
779
1103
        # at the moment, based on everything in the file
780
 
        parents = map(int, argv[3:])
 
1104
        name = argv[3]
 
1105
        parents = map(int, argv[4:])
781
1106
        lines = sys.stdin.readlines()
782
 
        ver = w.add(parents, lines)
 
1107
        ver = w.add(name, parents, lines)
783
1108
        write_weave(w, file(argv[2], 'wb'))
784
 
        print 'added version %d' % ver
 
1109
        print 'added version %r %d' % (name, ver)
785
1110
    elif cmd == 'init':
786
1111
        fn = argv[2]
787
1112
        if os.path.exists(fn):
796
1121
        w = readit()
797
1122
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
798
1123
 
 
1124
    elif cmd == 'diff':
 
1125
        from difflib import unified_diff
 
1126
        w = readit()
 
1127
        fn = argv[2]
 
1128
        v1, v2 = map(int, argv[3:5])
 
1129
        lines1 = w.get(v1)
 
1130
        lines2 = w.get(v2)
 
1131
        diff_gen = unified_diff(lines1, lines2,
 
1132
                                '%s version %d' % (fn, v1),
 
1133
                                '%s version %d' % (fn, v2))
 
1134
        sys.stdout.writelines(diff_gen)
 
1135
            
799
1136
    elif cmd == 'annotate':
800
1137
        w = readit()
801
1138
        # newline is added to all lines regardless; too hard to get
809
1146
                print '%5d | %s' % (origin, text)
810
1147
                lasto = origin
811
1148
                
812
 
    elif cmd == 'info':
813
 
        weave_info(readit())
 
1149
    elif cmd == 'toc':
 
1150
        weave_toc(readit())
814
1151
 
815
1152
    elif cmd == 'stats':
816
 
        weave_stats(argv[2])
 
1153
        weave_stats(argv[2], ProgressBar())
817
1154
        
818
1155
    elif cmd == 'check':
819
1156
        w = readit()
865
1202
        raise ValueError('unknown command %r' % cmd)
866
1203
    
867
1204
 
 
1205
 
 
1206
def profile_main(argv): 
 
1207
    import tempfile, hotshot, hotshot.stats
 
1208
 
 
1209
    prof_f = tempfile.NamedTemporaryFile()
 
1210
 
 
1211
    prof = hotshot.Profile(prof_f.name)
 
1212
 
 
1213
    ret = prof.runcall(main, argv)
 
1214
    prof.close()
 
1215
 
 
1216
    stats = hotshot.stats.load(prof_f.name)
 
1217
    #stats.strip_dirs()
 
1218
    stats.sort_stats('cumulative')
 
1219
    ## XXX: Might like to write to stderr or the trace file instead but
 
1220
    ## print_stats seems hardcoded to stdout
 
1221
    stats.print_stats(20)
 
1222
            
 
1223
    return ret
 
1224
 
 
1225
 
 
1226
def lsprofile_main(argv): 
 
1227
    from bzrlib.lsprof import profile
 
1228
    ret,stats = profile(main, argv)
 
1229
    stats.sort()
 
1230
    stats.pprint()
 
1231
    return ret
 
1232
 
 
1233
 
868
1234
if __name__ == '__main__':
869
1235
    import sys
870
 
    sys.exit(main(sys.argv))
 
1236
    if '--profile' in sys.argv:
 
1237
        args = sys.argv[:]
 
1238
        args.remove('--profile')
 
1239
        sys.exit(profile_main(args))
 
1240
    elif '--lsprof' in sys.argv:
 
1241
        args = sys.argv[:]
 
1242
        args.remove('--lsprof')
 
1243
        sys.exit(lsprofile_main(args))
 
1244
    else:
 
1245
        sys.exit(main(sys.argv))
 
1246