/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: Andrew Bennetts
  • Date: 2008-04-02 00:14:00 UTC
  • mfrom: (3324 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3756.
  • Revision ID: andrew.bennetts@canonical.com-20080402001400-r1pqse38i03dl97w
Merge from bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2009 Canonical Ltd
 
1
#! /usr/bin/python
 
2
 
 
3
# Copyright (C) 2005 Canonical Ltd
2
4
#
3
5
# This program is free software; you can redistribute it and/or modify
4
6
# it under the terms of the GNU General Public License as published by
12
14
#
13
15
# You should have received a copy of the GNU General Public License
14
16
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
18
 
17
19
# Author: Martin Pool <mbp@canonical.com>
18
20
 
59
61
# where the basis and destination are unchanged.
60
62
 
61
63
# FIXME: Sometimes we will be given a parents list for a revision
62
 
# that includes some redundant parents (i.e. already a parent of
63
 
# something in the list.)  We should eliminate them.  This can
 
64
# that includes some redundant parents (i.e. already a parent of 
 
65
# something in the list.)  We should eliminate them.  This can 
64
66
# be done fairly efficiently because the sequence numbers constrain
65
67
# the possible relationships.
66
68
 
69
71
from copy import copy
70
72
from cStringIO import StringIO
71
73
import os
 
74
import sha
 
75
import time
 
76
import warnings
72
77
 
73
78
from bzrlib.lazy_import import lazy_import
74
79
lazy_import(globals(), """
75
80
from bzrlib import tsort
76
81
""")
77
82
from bzrlib import (
78
 
    errors,
79
 
    osutils,
 
83
    progress,
80
84
    )
 
85
from bzrlib.trace import mutter
81
86
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
82
87
        RevisionAlreadyPresent,
83
88
        RevisionNotPresent,
84
 
        UnavailableRepresentation,
 
89
        WeaveRevisionAlreadyPresent,
 
90
        WeaveRevisionNotPresent,
85
91
        )
86
 
from bzrlib.osutils import dirname, sha, sha_strings, split_lines
 
92
import bzrlib.errors as errors
 
93
from bzrlib.osutils import sha_strings
87
94
import bzrlib.patiencediff
88
 
from bzrlib.revision import NULL_REVISION
89
 
from bzrlib.symbol_versioning import *
90
 
from bzrlib.trace import mutter
91
 
from bzrlib.versionedfile import (
92
 
    AbsentContentFactory,
93
 
    adapter_registry,
94
 
    ContentFactory,
95
 
    sort_groupcompress,
96
 
    VersionedFile,
97
 
    )
 
95
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
98
96
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
99
97
 
100
98
 
101
 
class WeaveContentFactory(ContentFactory):
102
 
    """Content factory for streaming from weaves.
103
 
 
104
 
    :seealso ContentFactory:
105
 
    """
106
 
 
107
 
    def __init__(self, version, weave):
108
 
        """Create a WeaveContentFactory for version from weave."""
109
 
        ContentFactory.__init__(self)
110
 
        self.sha1 = weave.get_sha1s([version])[version]
111
 
        self.key = (version,)
112
 
        parents = weave.get_parent_map([version])[version]
113
 
        self.parents = tuple((parent,) for parent in parents)
114
 
        self.storage_kind = 'fulltext'
115
 
        self._weave = weave
116
 
 
117
 
    def get_bytes_as(self, storage_kind):
118
 
        if storage_kind == 'fulltext':
119
 
            return self._weave.get_text(self.key[-1])
120
 
        elif storage_kind == 'chunked':
121
 
            return self._weave.get_lines(self.key[-1])
122
 
        else:
123
 
            raise UnavailableRepresentation(self.key, storage_kind, 'fulltext')
124
 
 
125
 
 
126
99
class Weave(VersionedFile):
127
100
    """weave - versioned text file storage.
128
 
 
 
101
    
129
102
    A Weave manages versions of line-based text files, keeping track
130
103
    of the originating version for each line.
131
104
 
177
150
 
178
151
    * It doesn't seem very useful to have an active insertion
179
152
      inside an inactive insertion, but it might happen.
180
 
 
 
153
      
181
154
    * Therefore, all instructions are always"considered"; that
182
155
      is passed onto and off the stack.  An outer inactive block
183
156
      doesn't disable an inner block.
213
186
    """
214
187
 
215
188
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
216
 
                 '_weave_name', '_matcher', '_allow_reserved']
217
 
 
218
 
    def __init__(self, weave_name=None, access_mode='w', matcher=None,
219
 
                 get_scope=None, allow_reserved=False):
220
 
        """Create a weave.
221
 
 
222
 
        :param get_scope: A callable that returns an opaque object to be used
223
 
            for detecting when this weave goes out of scope (should stop
224
 
            answering requests or allowing mutation).
225
 
        """
226
 
        super(Weave, self).__init__()
 
189
                 '_weave_name', '_matcher']
 
190
    
 
191
    def __init__(self, weave_name=None, access_mode='w', matcher=None):
 
192
        super(Weave, self).__init__(access_mode)
227
193
        self._weave = []
228
194
        self._parents = []
229
195
        self._sha1s = []
234
200
            self._matcher = bzrlib.patiencediff.PatienceSequenceMatcher
235
201
        else:
236
202
            self._matcher = matcher
237
 
        if get_scope is None:
238
 
            get_scope = lambda:None
239
 
        self._get_scope = get_scope
240
 
        self._scope = get_scope()
241
 
        self._access_mode = access_mode
242
 
        self._allow_reserved = allow_reserved
243
203
 
244
204
    def __repr__(self):
245
205
        return "Weave(%r)" % self._weave_name
246
206
 
247
 
    def _check_write_ok(self):
248
 
        """Is the versioned file marked as 'finished' ? Raise if it is."""
249
 
        if self._get_scope() != self._scope:
250
 
            raise errors.OutSideTransaction()
251
 
        if self._access_mode != 'w':
252
 
            raise errors.ReadOnlyObjectDirtiedError(self)
253
 
 
254
207
    def copy(self):
255
208
        """Return a deep copy of self.
256
 
 
 
209
        
257
210
        The copy can be modified without affecting the original weave."""
258
211
        other = Weave()
259
212
        other._weave = self._weave[:]
269
222
            return False
270
223
        return self._parents == other._parents \
271
224
               and self._weave == other._weave \
272
 
               and self._sha1s == other._sha1s
273
 
 
 
225
               and self._sha1s == other._sha1s 
 
226
    
274
227
    def __ne__(self, other):
275
228
        return not self.__eq__(other)
276
229
 
279
232
 
280
233
    def _lookup(self, name):
281
234
        """Convert symbolic version name to index."""
282
 
        if not self._allow_reserved:
283
 
            self.check_not_reserved_id(name)
 
235
        self.check_not_reserved_id(name)
284
236
        try:
285
237
            return self._name_map[name]
286
238
        except KeyError:
296
248
 
297
249
    __contains__ = has_version
298
250
 
299
 
    def get_record_stream(self, versions, ordering, include_delta_closure):
300
 
        """Get a stream of records for versions.
301
 
 
302
 
        :param versions: The versions to include. Each version is a tuple
303
 
            (version,).
304
 
        :param ordering: Either 'unordered' or 'topological'. A topologically
305
 
            sorted stream has compression parents strictly before their
306
 
            children.
307
 
        :param include_delta_closure: If True then the closure across any
308
 
            compression parents will be included (in the opaque data).
309
 
        :return: An iterator of ContentFactory objects, each of which is only
310
 
            valid until the iterator is advanced.
311
 
        """
312
 
        versions = [version[-1] for version in versions]
313
 
        if ordering == 'topological':
314
 
            parents = self.get_parent_map(versions)
315
 
            new_versions = tsort.topo_sort(parents)
316
 
            new_versions.extend(set(versions).difference(set(parents)))
317
 
            versions = new_versions
318
 
        elif ordering == 'groupcompress':
319
 
            parents = self.get_parent_map(versions)
320
 
            new_versions = sort_groupcompress(parents)
321
 
            new_versions.extend(set(versions).difference(set(parents)))
322
 
            versions = new_versions
323
 
        for version in versions:
324
 
            if version in self:
325
 
                yield WeaveContentFactory(version, self)
326
 
            else:
327
 
                yield AbsentContentFactory((version,))
328
 
 
329
251
    def get_parent_map(self, version_ids):
330
252
        """See VersionedFile.get_parent_map."""
331
253
        result = {}
332
254
        for version_id in version_ids:
333
 
            if version_id == NULL_REVISION:
334
 
                parents = ()
335
 
            else:
336
 
                try:
337
 
                    parents = tuple(
338
 
                        map(self._idx_to_name,
339
 
                            self._parents[self._lookup(version_id)]))
340
 
                except RevisionNotPresent:
341
 
                    continue
342
 
            result[version_id] = parents
 
255
            try:
 
256
                result[version_id] = tuple(
 
257
                    map(self._idx_to_name, self._parents[self._lookup(version_id)]))
 
258
            except RevisionNotPresent:
 
259
                pass
343
260
        return result
344
261
 
345
262
    def get_parents_with_ghosts(self, version_id):
346
263
        raise NotImplementedError(self.get_parents_with_ghosts)
347
264
 
348
 
    def insert_record_stream(self, stream):
349
 
        """Insert a record stream into this versioned file.
350
 
 
351
 
        :param stream: A stream of records to insert.
352
 
        :return: None
353
 
        :seealso VersionedFile.get_record_stream:
354
 
        """
355
 
        adapters = {}
356
 
        for record in stream:
357
 
            # Raise an error when a record is missing.
358
 
            if record.storage_kind == 'absent':
359
 
                raise RevisionNotPresent([record.key[0]], self)
360
 
            # adapt to non-tuple interface
361
 
            parents = [parent[0] for parent in record.parents]
362
 
            if (record.storage_kind == 'fulltext'
363
 
                or record.storage_kind == 'chunked'):
364
 
                self.add_lines(record.key[0], parents,
365
 
                    osutils.chunks_to_lines(record.get_bytes_as('chunked')))
366
 
            else:
367
 
                adapter_key = record.storage_kind, 'fulltext'
368
 
                try:
369
 
                    adapter = adapters[adapter_key]
370
 
                except KeyError:
371
 
                    adapter_factory = adapter_registry.get(adapter_key)
372
 
                    adapter = adapter_factory(self)
373
 
                    adapters[adapter_key] = adapter
374
 
                lines = split_lines(adapter.get_bytes(record))
375
 
                try:
376
 
                    self.add_lines(record.key[0], parents, lines)
377
 
                except RevisionAlreadyPresent:
378
 
                    pass
379
 
 
380
265
    def _check_repeated_add(self, name, parents, text, sha1):
381
266
        """Check that a duplicated add is OK.
382
267
 
397
282
 
398
283
    def _add(self, version_id, lines, parents, sha1=None, nostore_sha=None):
399
284
        """Add a single text on top of the weave.
400
 
 
 
285
  
401
286
        Returns the index number of the newly added version.
402
287
 
403
288
        version_id
404
289
            Symbolic name for this version.
405
290
            (Typically the revision-id of the revision that added it.)
406
 
            If None, a name will be allocated based on the hash. (sha1:SHAHASH)
407
291
 
408
292
        parents
409
293
            List or set of direct parent version numbers.
410
 
 
 
294
            
411
295
        lines
412
296
            Sequence of lines to be added in the new version.
413
297
 
414
298
        :param nostore_sha: See VersionedFile.add_lines.
415
299
        """
 
300
        assert isinstance(version_id, basestring)
416
301
        self._check_lines_not_unicode(lines)
417
302
        self._check_lines_are_lines(lines)
418
303
        if not sha1:
419
304
            sha1 = sha_strings(lines)
420
305
        if sha1 == nostore_sha:
421
306
            raise errors.ExistingContent
422
 
        if version_id is None:
423
 
            version_id = "sha1:" + sha1
424
307
        if version_id in self._name_map:
425
308
            return self._check_repeated_add(version_id, parents, lines, sha1)
426
309
 
437
320
        self._names.append(version_id)
438
321
        self._name_map[version_id] = new_version
439
322
 
440
 
 
 
323
            
441
324
        if not parents:
442
325
            # special case; adding with no parents revision; can do
443
326
            # this more quickly by just appending unconditionally.
454
337
            if sha1 == self._sha1s[pv]:
455
338
                # special case: same as the single parent
456
339
                return new_version
457
 
 
 
340
            
458
341
 
459
342
        ancestors = self._inclusions(parents)
460
343
 
495
378
            #print 'raw match', tag, i1, i2, j1, j2
496
379
            if tag == 'equal':
497
380
                continue
 
381
 
498
382
            i1 = basis_lineno[i1]
499
383
            i2 = basis_lineno[i2]
 
384
 
 
385
            assert 0 <= j1 <= j2 <= len(lines)
 
386
 
 
387
            #print tag, i1, i2, j1, j2
 
388
 
500
389
            # the deletion and insertion are handled separately.
501
390
            # first delete the region.
502
391
            if i1 != i2:
509
398
                # i2; we want to insert after this region to make sure
510
399
                # we don't destroy ourselves
511
400
                i = i2 + offset
512
 
                self._weave[i:i] = ([('{', new_version)]
513
 
                                    + lines[j1:j2]
 
401
                self._weave[i:i] = ([('{', new_version)] 
 
402
                                    + lines[j1:j2] 
514
403
                                    + [('}', None)])
515
404
                offset += 2 + (j2 - j1)
516
405
        return new_version
517
406
 
 
407
    def _clone_text(self, new_version_id, old_version_id, parents):
 
408
        """See VersionedFile.clone_text."""
 
409
        old_lines = self.get_text(old_version_id)
 
410
        self.add_lines(new_version_id, parents, old_lines)
 
411
 
518
412
    def _inclusions(self, versions):
519
413
        """Return set of all ancestors of given version(s)."""
520
414
        if not len(versions):
543
437
            if not isinstance(l, basestring):
544
438
                raise ValueError("text line should be a string or unicode, not %s"
545
439
                                 % type(l))
546
 
 
 
440
        
547
441
 
548
442
 
549
443
    def _check_versions(self, indexes):
557
451
    def _compatible_parents(self, my_parents, other_parents):
558
452
        """During join check that other_parents are joinable with my_parents.
559
453
 
560
 
        Joinable is defined as 'is a subset of' - supersets may require
 
454
        Joinable is defined as 'is a subset of' - supersets may require 
561
455
        regeneration of diffs, but subsets do not.
562
456
        """
563
457
        return len(other_parents.difference(my_parents)) == 0
564
458
 
565
 
    def annotate(self, version_id):
566
 
        """Return a list of (version-id, line) tuples for version_id.
 
459
    def annotate_iter(self, version_id):
 
460
        """Yield list of (version-id, line) pairs for the specified version.
567
461
 
568
462
        The index indicates when the line originated in the weave."""
569
463
        incls = [self._lookup(version_id)]
570
 
        return [(self._idx_to_name(origin), text) for origin, lineno, text in
571
 
            self._extract(incls)]
 
464
        for origin, lineno, text in self._extract(incls):
 
465
            yield self._idx_to_name(origin), text
572
466
 
573
467
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
574
468
                                                pb=None):
577
471
            version_ids = self.versions()
578
472
        version_ids = set(version_ids)
579
473
        for lineno, inserted, deletes, line in self._walk_internal(version_ids):
580
 
            if inserted not in version_ids: continue
 
474
            # if inserted not in version_ids then it was inserted before the
 
475
            # versions we care about, but because weaves cannot represent ghosts
 
476
            # properly, we do not filter down to that
 
477
            # if inserted not in version_ids: continue
581
478
            if line[-1] != '\n':
582
479
                yield line + '\n', inserted
583
480
            else:
585
482
 
586
483
    def _walk_internal(self, version_ids=None):
587
484
        """Helper method for weave actions."""
588
 
 
 
485
        
589
486
        istack = []
590
487
        dset = set()
591
488
 
600
497
                elif c == '}':
601
498
                    istack.pop()
602
499
                elif c == '[':
 
500
                    assert self._names[v] not in dset
603
501
                    dset.add(self._names[v])
604
502
                elif c == ']':
605
503
                    dset.remove(self._names[v])
606
504
                else:
607
505
                    raise WeaveFormatError('unexpected instruction %r' % v)
608
506
            else:
 
507
                assert l.__class__ in (str, unicode)
 
508
                assert istack
609
509
                yield lineno, istack[-1], frozenset(dset), l
610
510
            lineno += 1
611
511
 
660
560
                # not in either revision
661
561
                yield 'irrelevant', line
662
562
 
 
563
        yield 'unchanged', ''           # terminator
 
564
 
663
565
    def _extract(self, versions):
664
566
        """Yield annotation of lines in included set.
665
567
 
672
574
        for i in versions:
673
575
            if not isinstance(i, int):
674
576
                raise ValueError(i)
675
 
 
 
577
            
676
578
        included = self._inclusions(versions)
677
579
 
678
580
        istack = []
687
589
 
688
590
        WFE = WeaveFormatError
689
591
 
690
 
        # wow.
 
592
        # wow. 
691
593
        #  449       0   4474.6820   2356.5590   bzrlib.weave:556(_extract)
692
594
        #  +285282   0   1676.8040   1676.8040   +<isinstance>
693
595
        # 1.6 seconds in 'isinstance'.
699
601
        # we're still spending ~1/4 of the method in isinstance though.
700
602
        # so lets hard code the acceptable string classes we expect:
701
603
        #  449       0   1202.9420    786.2930   bzrlib.weave:556(_extract)
702
 
        # +71352     0    377.5560    377.5560   +<method 'append' of 'list'
 
604
        # +71352     0    377.5560    377.5560   +<method 'append' of 'list' 
703
605
        #                                          objects>
704
606
        # yay, down to ~1/4 the initial extract time, and our inline time
705
607
        # has shrunk again, with isinstance no longer dominating.
706
608
        # tweaking the stack inclusion test to use a set gives:
707
609
        #  449       0   1122.8030    713.0080   bzrlib.weave:556(_extract)
708
 
        # +71352     0    354.9980    354.9980   +<method 'append' of 'list'
 
610
        # +71352     0    354.9980    354.9980   +<method 'append' of 'list' 
709
611
        #                                          objects>
710
612
        # - a 5% win, or possibly just noise. However with large istacks that
711
613
        # 'in' test could dominate, so I'm leaving this change in place -
712
614
        # when its fast enough to consider profiling big datasets we can review.
713
615
 
714
 
 
715
 
 
 
616
              
 
617
             
716
618
 
717
619
        for l in self._weave:
718
620
            if l.__class__ == tuple:
719
621
                c, v = l
720
622
                isactive = None
721
623
                if c == '{':
 
624
                    assert v not in iset
722
625
                    istack.append(v)
723
626
                    iset.add(v)
724
627
                elif c == '}':
725
628
                    iset.remove(istack.pop())
726
629
                elif c == '[':
727
630
                    if v in included:
 
631
                        assert v not in dset
728
632
                        dset.add(v)
729
 
                elif c == ']':
 
633
                else:
 
634
                    assert c == ']'
730
635
                    if v in included:
 
636
                        assert v in dset
731
637
                        dset.remove(v)
732
 
                else:
733
 
                    raise AssertionError()
734
638
            else:
 
639
                assert l.__class__ in (str, unicode)
735
640
                if isactive is None:
736
641
                    isactive = (not dset) and istack and (istack[-1] in included)
737
642
                if isactive:
747
652
 
748
653
    def _maybe_lookup(self, name_or_index):
749
654
        """Convert possible symbolic name to index, or pass through indexes.
750
 
 
 
655
        
751
656
        NOT FOR PUBLIC USE.
752
657
        """
753
658
        if isinstance(name_or_index, (int, long)):
763
668
        measured_sha1 = sha_strings(result)
764
669
        if measured_sha1 != expected_sha1:
765
670
            raise errors.WeaveInvalidChecksum(
766
 
                    'file %s, revision %s, expected: %s, measured %s'
 
671
                    'file %s, revision %s, expected: %s, measured %s' 
767
672
                    % (self._weave_name, version_id,
768
673
                       expected_sha1, measured_sha1))
769
674
        return result
770
675
 
 
676
    def get_sha1(self, version_id):
 
677
        """See VersionedFile.get_sha1()."""
 
678
        return self._sha1s[self._lookup(version_id)]
 
679
 
771
680
    def get_sha1s(self, version_ids):
772
681
        """See VersionedFile.get_sha1s()."""
773
 
        result = {}
774
 
        for v in version_ids:
775
 
            result[v] = self._sha1s[self._lookup(v)]
776
 
        return result
 
682
        return [self._sha1s[self._lookup(v)] for v in version_ids]
777
683
 
778
684
    def num_versions(self):
779
685
        """How many versions are in this weave?"""
780
686
        l = len(self._parents)
 
687
        assert l == len(self._sha1s)
781
688
        return l
782
689
 
783
690
    __len__ = num_versions
803
710
            # For creating the ancestry, IntSet is much faster (3.7s vs 0.17s)
804
711
            # The problem is that set membership is much more expensive
805
712
            name = self._idx_to_name(i)
806
 
            sha1s[name] = sha()
 
713
            sha1s[name] = sha.new()
807
714
            texts[name] = []
808
715
            new_inc = set([name])
809
716
            for p in self._parents[i]:
810
717
                new_inc.update(inclusions[self._idx_to_name(p)])
811
718
 
812
 
            if set(new_inc) != set(self.get_ancestry(name)):
813
 
                raise AssertionError(
814
 
                    'failed %s != %s'
815
 
                    % (set(new_inc), set(self.get_ancestry(name))))
 
719
            assert set(new_inc) == set(self.get_ancestry(name)), \
 
720
                'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
816
721
            inclusions[name] = new_inc
817
722
 
818
723
        nlines = len(self._weave)
848
753
        # no lines outside of insertion blocks, that deletions are
849
754
        # properly paired, etc.
850
755
 
 
756
    def _join(self, other, pb, msg, version_ids, ignore_missing):
 
757
        """Worker routine for join()."""
 
758
        if not other.versions():
 
759
            return          # nothing to update, easy
 
760
 
 
761
        if not version_ids:
 
762
            # versions is never none, InterWeave checks this.
 
763
            return 0
 
764
 
 
765
        # two loops so that we do not change ourselves before verifying it
 
766
        # will be ok
 
767
        # work through in index order to make sure we get all dependencies
 
768
        names_to_join = []
 
769
        processed = 0
 
770
        # get the selected versions only that are in other.versions.
 
771
        version_ids = set(other.versions()).intersection(set(version_ids))
 
772
        # pull in the referenced graph.
 
773
        version_ids = other.get_ancestry(version_ids)
 
774
        pending_parents = other.get_parent_map(version_ids)
 
775
        pending_graph = pending_parents.items()
 
776
        if len(pending_graph) != len(version_ids):
 
777
            raise RevisionNotPresent(
 
778
                set(version_ids) - set(pending_parents.keys()), self)
 
779
        for name in tsort.topo_sort(pending_graph):
 
780
            other_idx = other._name_map[name]
 
781
            # returns True if we have it, False if we need it.
 
782
            if not self._check_version_consistent(other, other_idx, name):
 
783
                names_to_join.append((other_idx, name))
 
784
            processed += 1
 
785
 
 
786
        if pb and not msg:
 
787
            msg = 'weave join'
 
788
 
 
789
        merged = 0
 
790
        time0 = time.time()
 
791
        for other_idx, name in names_to_join:
 
792
            # TODO: If all the parents of the other version are already
 
793
            # present then we can avoid some work by just taking the delta
 
794
            # and adjusting the offsets.
 
795
            new_parents = self._imported_parents(other, other_idx)
 
796
            sha1 = other._sha1s[other_idx]
 
797
 
 
798
            merged += 1
 
799
 
 
800
            if pb:
 
801
                pb.update(msg, merged, len(names_to_join))
 
802
           
 
803
            lines = other.get_lines(other_idx)
 
804
            self._add(name, lines, new_parents, sha1)
 
805
 
 
806
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
 
807
                merged, processed, self._weave_name, time.time()-time0))
 
808
 
851
809
    def _imported_parents(self, other, other_idx):
852
810
        """Return list of parents in self corresponding to indexes in other."""
853
811
        new_parents = []
855
813
            parent_name = other._names[parent_idx]
856
814
            if parent_name not in self._name_map:
857
815
                # should not be possible
858
 
                raise WeaveError("missing parent {%s} of {%s} in %r"
 
816
                raise WeaveError("missing parent {%s} of {%s} in %r" 
859
817
                                 % (parent_name, other._name_map[other_idx], self))
860
818
            new_parents.append(self._name_map[parent_name])
861
819
        return new_parents
868
826
         * the same text
869
827
         * the same direct parents (by name, not index, and disregarding
870
828
           order)
871
 
 
 
829
        
872
830
        If present & correct return True;
873
 
        if not present in self return False;
 
831
        if not present in self return False; 
874
832
        if inconsistent raise error."""
875
833
        this_idx = self._name_map.get(name, -1)
876
834
        if this_idx != -1:
909
867
    """A WeaveFile represents a Weave on disk and writes on change."""
910
868
 
911
869
    WEAVE_SUFFIX = '.weave'
912
 
 
913
 
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w', get_scope=None):
 
870
    
 
871
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w'):
914
872
        """Create a WeaveFile.
915
 
 
 
873
        
916
874
        :param create: If not True, only open an existing knit.
917
875
        """
918
 
        super(WeaveFile, self).__init__(name, access_mode, get_scope=get_scope,
919
 
            allow_reserved=False)
 
876
        super(WeaveFile, self).__init__(name, access_mode)
920
877
        self._transport = transport
921
878
        self._filemode = filemode
922
879
        try:
937
894
        self._save()
938
895
        return result
939
896
 
 
897
    def _clone_text(self, new_version_id, old_version_id, parents):
 
898
        """See VersionedFile.clone_text."""
 
899
        super(WeaveFile, self)._clone_text(new_version_id, old_version_id, parents)
 
900
        self._save
 
901
 
940
902
    def copy_to(self, name, transport):
941
903
        """See VersionedFile.copy_to()."""
942
904
        # as we are all in memory always, just serialise to the new place.
945
907
        sio.seek(0)
946
908
        transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
947
909
 
 
910
    def create_empty(self, name, transport, filemode=None):
 
911
        return WeaveFile(name, transport, filemode, create=True)
 
912
 
948
913
    def _save(self):
949
914
        """Save the weave."""
950
915
        self._check_write_ok()
951
916
        sio = StringIO()
952
917
        write_weave_v5(self, sio)
953
918
        sio.seek(0)
954
 
        bytes = sio.getvalue()
955
 
        path = self._weave_name + WeaveFile.WEAVE_SUFFIX
956
 
        try:
957
 
            self._transport.put_bytes(path, bytes, self._filemode)
958
 
        except errors.NoSuchFile:
959
 
            self._transport.mkdir(dirname(path))
960
 
            self._transport.put_bytes(path, bytes, self._filemode)
 
919
        self._transport.put_file(self._weave_name + WeaveFile.WEAVE_SUFFIX,
 
920
                                 sio,
 
921
                                 self._filemode)
961
922
 
962
923
    @staticmethod
963
924
    def get_suffixes():
964
925
        """See VersionedFile.get_suffixes()."""
965
926
        return [WeaveFile.WEAVE_SUFFIX]
966
927
 
967
 
    def insert_record_stream(self, stream):
968
 
        super(WeaveFile, self).insert_record_stream(stream)
 
928
    def join(self, other, pb=None, msg=None, version_ids=None,
 
929
             ignore_missing=False):
 
930
        """Join other into self and save."""
 
931
        super(WeaveFile, self).join(other, pb, msg, version_ids, ignore_missing)
969
932
        self._save()
970
933
 
971
934
 
972
935
def _reweave(wa, wb, pb=None, msg=None):
973
936
    """Combine two weaves and return the result.
974
937
 
975
 
    This works even if a revision R has different parents in
 
938
    This works even if a revision R has different parents in 
976
939
    wa and wb.  In the resulting weave all the parents are given.
977
940
 
978
 
    This is done by just building up a new weave, maintaining ordering
 
941
    This is done by just building up a new weave, maintaining ordering 
979
942
    of the versions in the two inputs.  More efficient approaches
980
 
    might be possible but it should only be necessary to do
981
 
    this operation rarely, when a new previously ghost version is
 
943
    might be possible but it should only be necessary to do 
 
944
    this operation rarely, when a new previously ghost version is 
982
945
    inserted.
983
946
 
984
947
    :param pb: An optional progress bar, indicating how far done we are
1018
981
        wr._add(name, lines, [wr._lookup(i) for i in combined_parents[name]])
1019
982
    return wr
1020
983
 
1021
 
 
1022
984
def _reweave_parent_graphs(wa, wb):
1023
985
    """Return combined parent ancestry for two weaves.
1024
 
 
 
986
    
1025
987
    Returned as a list of (version_name, set(parent_names))"""
1026
988
    combined = {}
1027
989
    for weave in [wa, wb]:
1029
991
            p = combined.setdefault(name, set())
1030
992
            p.update(map(weave._idx_to_name, weave._parents[idx]))
1031
993
    return combined
 
994
 
 
995
 
 
996
def weave_toc(w):
 
997
    """Show the weave's table-of-contents"""
 
998
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
 
999
    for i in (6, 50, 10, 10):
 
1000
        print '-' * i,
 
1001
    print
 
1002
    for i in range(w.num_versions()):
 
1003
        sha1 = w._sha1s[i]
 
1004
        name = w._names[i]
 
1005
        parent_str = ' '.join(map(str, w._parents[i]))
 
1006
        print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
 
1007
 
 
1008
 
 
1009
 
 
1010
def weave_stats(weave_file, pb):
 
1011
    from bzrlib.weavefile import read_weave
 
1012
 
 
1013
    wf = file(weave_file, 'rb')
 
1014
    w = read_weave(wf)
 
1015
    # FIXME: doesn't work on pipes
 
1016
    weave_size = wf.tell()
 
1017
 
 
1018
    total = 0
 
1019
    vers = len(w)
 
1020
    for i in range(vers):
 
1021
        pb.update('checking sizes', i, vers)
 
1022
        for origin, lineno, line in w._extract([i]):
 
1023
            total += len(line)
 
1024
 
 
1025
    pb.clear()
 
1026
 
 
1027
    print 'versions          %9d' % vers
 
1028
    print 'weave file        %9d bytes' % weave_size
 
1029
    print 'total contents    %9d bytes' % total
 
1030
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
 
1031
    if vers:
 
1032
        avg = total/vers
 
1033
        print 'average size      %9d bytes' % avg
 
1034
        print 'relative size     %9.2fx' % (float(weave_size) / float(avg))
 
1035
 
 
1036
 
 
1037
def usage():
 
1038
    print """bzr weave tool
 
1039
 
 
1040
Experimental tool for weave algorithm.
 
1041
 
 
1042
usage:
 
1043
    weave init WEAVEFILE
 
1044
        Create an empty weave file
 
1045
    weave get WEAVEFILE VERSION
 
1046
        Write out specified version.
 
1047
    weave check WEAVEFILE
 
1048
        Check consistency of all versions.
 
1049
    weave toc WEAVEFILE
 
1050
        Display table of contents.
 
1051
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
 
1052
        Add NEWTEXT, with specified parent versions.
 
1053
    weave annotate WEAVEFILE VERSION
 
1054
        Display origin of each line.
 
1055
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
 
1056
        Auto-merge two versions and display conflicts.
 
1057
    weave diff WEAVEFILE VERSION1 VERSION2 
 
1058
        Show differences between two versions.
 
1059
 
 
1060
example:
 
1061
 
 
1062
    % weave init foo.weave
 
1063
    % vi foo.txt
 
1064
    % weave add foo.weave ver0 < foo.txt
 
1065
    added version 0
 
1066
 
 
1067
    (create updated version)
 
1068
    % vi foo.txt
 
1069
    % weave get foo.weave 0 | diff -u - foo.txt
 
1070
    % weave add foo.weave ver1 0 < foo.txt
 
1071
    added version 1
 
1072
 
 
1073
    % weave get foo.weave 0 > foo.txt       (create forked version)
 
1074
    % vi foo.txt
 
1075
    % weave add foo.weave ver2 0 < foo.txt
 
1076
    added version 2
 
1077
 
 
1078
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
 
1079
    % vi foo.txt                            (resolve conflicts)
 
1080
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
 
1081
    
 
1082
"""
 
1083
    
 
1084
 
 
1085
 
 
1086
def main(argv):
 
1087
    import sys
 
1088
    import os
 
1089
    try:
 
1090
        import bzrlib
 
1091
    except ImportError:
 
1092
        # in case we're run directly from the subdirectory
 
1093
        sys.path.append('..')
 
1094
        import bzrlib
 
1095
    from bzrlib.weavefile import write_weave, read_weave
 
1096
    from bzrlib.progress import ProgressBar
 
1097
 
 
1098
    try:
 
1099
        import psyco
 
1100
        psyco.full()
 
1101
    except ImportError:
 
1102
        pass
 
1103
 
 
1104
    if len(argv) < 2:
 
1105
        usage()
 
1106
        return 0
 
1107
 
 
1108
    cmd = argv[1]
 
1109
 
 
1110
    def readit():
 
1111
        return read_weave(file(argv[2], 'rb'))
 
1112
    
 
1113
    if cmd == 'help':
 
1114
        usage()
 
1115
    elif cmd == 'add':
 
1116
        w = readit()
 
1117
        # at the moment, based on everything in the file
 
1118
        name = argv[3]
 
1119
        parents = map(int, argv[4:])
 
1120
        lines = sys.stdin.readlines()
 
1121
        ver = w.add(name, parents, lines)
 
1122
        write_weave(w, file(argv[2], 'wb'))
 
1123
        print 'added version %r %d' % (name, ver)
 
1124
    elif cmd == 'init':
 
1125
        fn = argv[2]
 
1126
        if os.path.exists(fn):
 
1127
            raise IOError("file exists")
 
1128
        w = Weave()
 
1129
        write_weave(w, file(fn, 'wb'))
 
1130
    elif cmd == 'get': # get one version
 
1131
        w = readit()
 
1132
        sys.stdout.writelines(w.get_iter(int(argv[3])))
 
1133
        
 
1134
    elif cmd == 'diff':
 
1135
        w = readit()
 
1136
        fn = argv[2]
 
1137
        v1, v2 = map(int, argv[3:5])
 
1138
        lines1 = w.get(v1)
 
1139
        lines2 = w.get(v2)
 
1140
        diff_gen = bzrlib.patiencediff.unified_diff(lines1, lines2,
 
1141
                                '%s version %d' % (fn, v1),
 
1142
                                '%s version %d' % (fn, v2))
 
1143
        sys.stdout.writelines(diff_gen)
 
1144
            
 
1145
    elif cmd == 'annotate':
 
1146
        w = readit()
 
1147
        # newline is added to all lines regardless; too hard to get
 
1148
        # reasonable formatting otherwise
 
1149
        lasto = None
 
1150
        for origin, text in w.annotate(int(argv[3])):
 
1151
            text = text.rstrip('\r\n')
 
1152
            if origin == lasto:
 
1153
                print '      | %s' % (text)
 
1154
            else:
 
1155
                print '%5d | %s' % (origin, text)
 
1156
                lasto = origin
 
1157
                
 
1158
    elif cmd == 'toc':
 
1159
        weave_toc(readit())
 
1160
 
 
1161
    elif cmd == 'stats':
 
1162
        weave_stats(argv[2], ProgressBar())
 
1163
        
 
1164
    elif cmd == 'check':
 
1165
        w = readit()
 
1166
        pb = ProgressBar()
 
1167
        w.check(pb)
 
1168
        pb.clear()
 
1169
        print '%d versions ok' % w.num_versions()
 
1170
 
 
1171
    elif cmd == 'inclusions':
 
1172
        w = readit()
 
1173
        print ' '.join(map(str, w.inclusions([int(argv[3])])))
 
1174
 
 
1175
    elif cmd == 'parents':
 
1176
        w = readit()
 
1177
        print ' '.join(map(str, w._parents[int(argv[3])]))
 
1178
 
 
1179
    elif cmd == 'plan-merge':
 
1180
        # replaced by 'bzr weave-plan-merge'
 
1181
        w = readit()
 
1182
        for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
 
1183
            if line:
 
1184
                print '%14s | %s' % (state, line),
 
1185
    elif cmd == 'merge':
 
1186
        # replaced by 'bzr weave-merge-text'
 
1187
        w = readit()
 
1188
        p = w.plan_merge(int(argv[3]), int(argv[4]))
 
1189
        sys.stdout.writelines(w.weave_merge(p))
 
1190
    else:
 
1191
        raise ValueError('unknown command %r' % cmd)
 
1192
    
 
1193
 
 
1194
if __name__ == '__main__':
 
1195
    import sys
 
1196
    sys.exit(main(sys.argv))
 
1197
 
 
1198
 
 
1199
class InterWeave(InterVersionedFile):
 
1200
    """Optimised code paths for weave to weave operations."""
 
1201
    
 
1202
    _matching_file_from_factory = staticmethod(WeaveFile)
 
1203
    _matching_file_to_factory = staticmethod(WeaveFile)
 
1204
    
 
1205
    @staticmethod
 
1206
    def is_compatible(source, target):
 
1207
        """Be compatible with weaves."""
 
1208
        try:
 
1209
            return (isinstance(source, Weave) and
 
1210
                    isinstance(target, Weave))
 
1211
        except AttributeError:
 
1212
            return False
 
1213
 
 
1214
    def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
 
1215
        """See InterVersionedFile.join."""
 
1216
        version_ids = self._get_source_version_ids(version_ids, ignore_missing)
 
1217
        if self.target.versions() == [] and version_ids is None:
 
1218
            self.target._copy_weave_content(self.source)
 
1219
            return
 
1220
        self.target._join(self.source, pb, msg, version_ids, ignore_missing)
 
1221
 
 
1222
 
 
1223
InterVersionedFile.register_optimiser(InterWeave)