~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

MergeĀ inĀ upstream.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#! /usr/bin/python
2
2
 
3
3
# Copyright (C) 2005 Canonical Ltd
4
 
#
 
4
 
5
5
# This program is free software; you can redistribute it and/or modify
6
6
# it under the terms of the GNU General Public License as published by
7
7
# the Free Software Foundation; either version 2 of the License, or
8
8
# (at your option) any later version.
9
 
#
 
9
 
10
10
# This program is distributed in the hope that it will be useful,
11
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
13
# GNU General Public License for more details.
14
 
#
 
14
 
15
15
# You should have received a copy of the GNU General Public License
16
16
# along with this program; if not, write to the Free Software
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27
27
# property.
28
28
 
29
29
# TODO: Nothing here so far assumes the lines are really \n newlines,
30
 
# rather than being split up in some other way.  We could accommodate
 
30
# rather than being split up in some other way.  We could accomodate
31
31
# binaries, perhaps by naively splitting on \n or perhaps using
32
32
# something like a rolling checksum.
33
33
 
66
66
# be done fairly efficiently because the sequence numbers constrain
67
67
# the possible relationships.
68
68
 
69
 
# FIXME: the conflict markers should be *7* characters
70
69
 
71
 
from copy import copy
72
70
from cStringIO import StringIO
 
71
from difflib import SequenceMatcher
73
72
import os
74
73
import sha
75
74
import time
76
 
import warnings
77
75
 
78
 
from bzrlib import (
79
 
    progress,
80
 
    )
 
76
from bzrlib.trace import mutter
81
77
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
82
78
        RevisionAlreadyPresent,
83
79
        RevisionNotPresent,
84
 
        UnavailableRepresentation,
85
80
        WeaveRevisionAlreadyPresent,
86
81
        WeaveRevisionNotPresent,
87
82
        )
88
83
import bzrlib.errors as errors
89
 
from bzrlib.osutils import dirname, sha_strings, split_lines
90
 
import bzrlib.patiencediff
91
 
from bzrlib.revision import NULL_REVISION
 
84
from bzrlib.osutils import sha_strings
92
85
from bzrlib.symbol_versioning import *
93
 
from bzrlib.trace import mutter
94
86
from bzrlib.tsort import topo_sort
95
 
from bzrlib.versionedfile import (
96
 
    AbsentContentFactory,
97
 
    adapter_registry,
98
 
    ContentFactory,
99
 
    VersionedFile,
100
 
    )
 
87
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
101
88
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
102
89
 
103
90
 
104
 
class WeaveContentFactory(ContentFactory):
105
 
    """Content factory for streaming from weaves.
106
 
 
107
 
    :seealso ContentFactory:
108
 
    """
109
 
 
110
 
    def __init__(self, version, weave):
111
 
        """Create a WeaveContentFactory for version from weave."""
112
 
        ContentFactory.__init__(self)
113
 
        self.sha1 = weave.get_sha1s([version])[version]
114
 
        self.key = (version,)
115
 
        parents = weave.get_parent_map([version])[version]
116
 
        self.parents = tuple((parent,) for parent in parents)
117
 
        self.storage_kind = 'fulltext'
118
 
        self._weave = weave
119
 
 
120
 
    def get_bytes_as(self, storage_kind):
121
 
        if storage_kind == 'fulltext':
122
 
            return self._weave.get_text(self.key[-1])
123
 
        else:
124
 
            raise UnavailableRepresentation(self.key, storage_kind, 'fulltext')
125
 
 
126
 
 
127
91
class Weave(VersionedFile):
128
92
    """weave - versioned text file storage.
129
93
    
214
178
    """
215
179
 
216
180
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
217
 
                 '_weave_name', '_matcher', '_allow_reserved']
 
181
                 '_weave_name']
218
182
    
219
 
    def __init__(self, weave_name=None, access_mode='w', matcher=None,
220
 
                 get_scope=None, allow_reserved=False):
221
 
        """Create a weave.
222
 
 
223
 
        :param get_scope: A callable that returns an opaque object to be used
224
 
            for detecting when this weave goes out of scope (should stop
225
 
            answering requests or allowing mutation).
226
 
        """
227
 
        super(Weave, self).__init__(access_mode)
 
183
    def __init__(self, weave_name=None):
228
184
        self._weave = []
229
185
        self._parents = []
230
186
        self._sha1s = []
231
187
        self._names = []
232
188
        self._name_map = {}
233
189
        self._weave_name = weave_name
234
 
        if matcher is None:
235
 
            self._matcher = bzrlib.patiencediff.PatienceSequenceMatcher
236
 
        else:
237
 
            self._matcher = matcher
238
 
        if get_scope is None:
239
 
            get_scope = lambda:None
240
 
        self._get_scope = get_scope
241
 
        self._scope = get_scope()
242
 
        self._access_mode = access_mode
243
 
        self._allow_reserved = allow_reserved
244
190
 
245
191
    def __repr__(self):
246
192
        return "Weave(%r)" % self._weave_name
247
193
 
248
 
    def _check_write_ok(self):
249
 
        """Is the versioned file marked as 'finished' ? Raise if it is."""
250
 
        if self._get_scope() != self._scope:
251
 
            raise errors.OutSideTransaction()
252
 
        if self._access_mode != 'w':
253
 
            raise errors.ReadOnlyObjectDirtiedError(self)
254
 
 
255
194
    def copy(self):
256
195
        """Return a deep copy of self.
257
196
        
275
214
    def __ne__(self, other):
276
215
        return not self.__eq__(other)
277
216
 
 
217
    @deprecated_method(zero_eight)
 
218
    def idx_to_name(self, index):
 
219
        """Old public interface, the public interface is all names now."""
 
220
        return index
 
221
 
278
222
    def _idx_to_name(self, version):
279
223
        return self._names[version]
280
224
 
 
225
    @deprecated_method(zero_eight)
 
226
    def lookup(self, name):
 
227
        """Backwards compatability thunk:
 
228
 
 
229
        Return name, as name is valid in the api now, and spew deprecation
 
230
        warnings everywhere.
 
231
        """
 
232
        return name
 
233
 
281
234
    def _lookup(self, name):
282
235
        """Convert symbolic version name to index."""
283
 
        if not self._allow_reserved:
284
 
            self.check_not_reserved_id(name)
285
236
        try:
286
237
            return self._name_map[name]
287
238
        except KeyError:
288
239
            raise RevisionNotPresent(name, self._weave_name)
289
240
 
 
241
    @deprecated_method(zero_eight)
 
242
    def iter_names(self):
 
243
        """Deprecated convenience function, please see VersionedFile.names()."""
 
244
        return iter(self.names())
 
245
 
 
246
    @deprecated_method(zero_eight)
 
247
    def names(self):
 
248
        """See Weave.versions for the current api."""
 
249
        return self.versions()
 
250
 
290
251
    def versions(self):
291
252
        """See VersionedFile.versions."""
292
253
        return self._names[:]
293
254
 
294
255
    def has_version(self, version_id):
295
256
        """See VersionedFile.has_version."""
296
 
        return (version_id in self._name_map)
 
257
        return self._name_map.has_key(version_id)
297
258
 
298
259
    __contains__ = has_version
299
260
 
300
 
    def get_record_stream(self, versions, ordering, include_delta_closure):
301
 
        """Get a stream of records for versions.
302
 
 
303
 
        :param versions: The versions to include. Each version is a tuple
304
 
            (version,).
305
 
        :param ordering: Either 'unordered' or 'topological'. A topologically
306
 
            sorted stream has compression parents strictly before their
307
 
            children.
308
 
        :param include_delta_closure: If True then the closure across any
309
 
            compression parents will be included (in the opaque data).
310
 
        :return: An iterator of ContentFactory objects, each of which is only
311
 
            valid until the iterator is advanced.
312
 
        """
313
 
        versions = [version[-1] for version in versions]
314
 
        if ordering == 'topological':
315
 
            parents = self.get_parent_map(versions)
316
 
            new_versions = topo_sort(parents)
317
 
            new_versions.extend(set(versions).difference(set(parents)))
318
 
            versions = new_versions
319
 
        for version in versions:
320
 
            if version in self:
321
 
                yield WeaveContentFactory(version, self)
322
 
            else:
323
 
                yield AbsentContentFactory((version,))
324
 
 
325
 
    def get_parent_map(self, version_ids):
326
 
        """See VersionedFile.get_parent_map."""
327
 
        result = {}
328
 
        for version_id in version_ids:
329
 
            if version_id == NULL_REVISION:
330
 
                parents = ()
331
 
            else:
332
 
                try:
333
 
                    parents = tuple(
334
 
                        map(self._idx_to_name,
335
 
                            self._parents[self._lookup(version_id)]))
336
 
                except RevisionNotPresent:
337
 
                    continue
338
 
            result[version_id] = parents
339
 
        return result
340
 
 
341
 
    def get_parents_with_ghosts(self, version_id):
342
 
        raise NotImplementedError(self.get_parents_with_ghosts)
343
 
 
344
 
    def insert_record_stream(self, stream):
345
 
        """Insert a record stream into this versioned file.
346
 
 
347
 
        :param stream: A stream of records to insert. 
348
 
        :return: None
349
 
        :seealso VersionedFile.get_record_stream:
350
 
        """
351
 
        adapters = {}
352
 
        for record in stream:
353
 
            # Raise an error when a record is missing.
354
 
            if record.storage_kind == 'absent':
355
 
                raise RevisionNotPresent([record.key[0]], self)
356
 
            # adapt to non-tuple interface
357
 
            parents = [parent[0] for parent in record.parents]
358
 
            if record.storage_kind == 'fulltext':
359
 
                self.add_lines(record.key[0], parents,
360
 
                    split_lines(record.get_bytes_as('fulltext')))
361
 
            else:
362
 
                adapter_key = record.storage_kind, 'fulltext'
363
 
                try:
364
 
                    adapter = adapters[adapter_key]
365
 
                except KeyError:
366
 
                    adapter_factory = adapter_registry.get(adapter_key)
367
 
                    adapter = adapter_factory(self)
368
 
                    adapters[adapter_key] = adapter
369
 
                lines = split_lines(adapter.get_bytes(
370
 
                    record, record.get_bytes_as(record.storage_kind)))
371
 
                try:
372
 
                    self.add_lines(record.key[0], parents, lines)
373
 
                except RevisionAlreadyPresent:
374
 
                    pass
 
261
    def get_parents(self, version_id):
 
262
        """See VersionedFile.get_parent."""
 
263
        return map(self._idx_to_name, self._parents[self._lookup(version_id)])
375
264
 
376
265
    def _check_repeated_add(self, name, parents, text, sha1):
377
266
        """Check that a duplicated add is OK.
384
273
            raise RevisionAlreadyPresent(name, self._weave_name)
385
274
        return idx
386
275
 
387
 
    def _add_lines(self, version_id, parents, lines, parent_texts,
388
 
       left_matching_blocks, nostore_sha, random_id, check_content):
 
276
    @deprecated_method(zero_eight)
 
277
    def add_identical(self, old_rev_id, new_rev_id, parents):
 
278
        """Please use Weave.clone_text now."""
 
279
        return self.clone_text(new_rev_id, old_rev_id, parents)
 
280
 
 
281
    def add_lines(self, version_id, parents, lines):
389
282
        """See VersionedFile.add_lines."""
390
 
        idx = self._add(version_id, lines, map(self._lookup, parents),
391
 
            nostore_sha=nostore_sha)
392
 
        return sha_strings(lines), sum(map(len, lines)), idx
393
 
 
394
 
    def _add(self, version_id, lines, parents, sha1=None, nostore_sha=None):
 
283
        return self._add(version_id, lines, map(self._lookup, parents))
 
284
 
 
285
    @deprecated_method(zero_eight)
 
286
    def add(self, name, parents, text, sha1=None):
 
287
        """See VersionedFile.add_lines for the non deprecated api."""
 
288
        return self._add(name, text, map(self._maybe_lookup, parents), sha1)
 
289
 
 
290
    def _add(self, version_id, lines, parents, sha1=None):
395
291
        """Add a single text on top of the weave.
396
292
  
397
293
        Returns the index number of the newly added version.
405
301
            
406
302
        lines
407
303
            Sequence of lines to be added in the new version.
 
304
        """
408
305
 
409
 
        :param nostore_sha: See VersionedFile.add_lines.
410
 
        """
411
 
        self._check_lines_not_unicode(lines)
412
 
        self._check_lines_are_lines(lines)
 
306
        assert isinstance(version_id, basestring)
413
307
        if not sha1:
414
308
            sha1 = sha_strings(lines)
415
 
        if sha1 == nostore_sha:
416
 
            raise errors.ExistingContent
417
309
        if version_id in self._name_map:
418
310
            return self._check_repeated_add(version_id, parents, lines, sha1)
419
311
 
463
355
        # another small special case: a merge, producing the same text
464
356
        # as auto-merge
465
357
        if lines == basis_lines:
466
 
            return new_version
 
358
            return new_version            
467
359
 
468
 
        # add a sentinel, because we can also match against the final line
 
360
        # add a sentinal, because we can also match against the final line
469
361
        basis_lineno.append(len(self._weave))
470
362
 
471
363
        # XXX: which line of the weave should we really consider
475
367
        #print 'basis_lines:', basis_lines
476
368
        #print 'new_lines:  ', lines
477
369
 
478
 
        s = self._matcher(None, basis_lines, lines)
 
370
        s = SequenceMatcher(None, basis_lines, lines)
479
371
 
480
372
        # offset gives the number of lines that have been inserted
481
373
        # into the weave up to the current point; if the original edit instruction
488
380
            #print 'raw match', tag, i1, i2, j1, j2
489
381
            if tag == 'equal':
490
382
                continue
 
383
 
491
384
            i1 = basis_lineno[i1]
492
385
            i2 = basis_lineno[i2]
 
386
 
 
387
            assert 0 <= j1 <= j2 <= len(lines)
 
388
 
 
389
            #print tag, i1, i2, j1, j2
 
390
 
493
391
            # the deletion and insertion are handled separately.
494
392
            # first delete the region.
495
393
            if i1 != i2:
508
406
                offset += 2 + (j2 - j1)
509
407
        return new_version
510
408
 
 
409
    def clone_text(self, new_version_id, old_version_id, parents):
 
410
        """See VersionedFile.clone_text."""
 
411
        old_lines = self.get_text(old_version_id)
 
412
        self.add_lines(new_version_id, parents, old_lines)
 
413
 
511
414
    def _inclusions(self, versions):
512
415
        """Return set of all ancestors of given version(s)."""
513
 
        if not len(versions):
514
 
            return []
515
416
        i = set(versions)
516
417
        for v in xrange(max(versions), 0, -1):
517
418
            if v in i:
521
422
        ## except IndexError:
522
423
        ##     raise ValueError("version %d not present in weave" % v)
523
424
 
524
 
    def get_ancestry(self, version_ids, topo_sorted=True):
 
425
    @deprecated_method(zero_eight)
 
426
    def inclusions(self, version_ids):
 
427
        """Deprecated - see VersionedFile.get_ancestry for the replacement."""
 
428
        if not version_ids:
 
429
            return []
 
430
        if isinstance(version_ids[0], int):
 
431
            return [self._idx_to_name(v) for v in self._inclusions(version_ids)]
 
432
        else:
 
433
            return self.get_ancestry(version_ids)
 
434
 
 
435
    def get_ancestry(self, version_ids):
525
436
        """See VersionedFile.get_ancestry."""
526
437
        if isinstance(version_ids, basestring):
527
438
            version_ids = [version_ids]
556
467
        return len(other_parents.difference(my_parents)) == 0
557
468
 
558
469
    def annotate(self, version_id):
559
 
        """Return a list of (version-id, line) tuples for version_id.
 
470
        if isinstance(version_id, int):
 
471
            warn('Weave.annotate(int) is deprecated. Please use version names'
 
472
                 ' in all circumstances as of 0.8',
 
473
                 DeprecationWarning,
 
474
                 stacklevel=2
 
475
                 )
 
476
            result = []
 
477
            for origin, lineno, text in self._extract([version_id]):
 
478
                result.append((origin, text))
 
479
            return result
 
480
        else:
 
481
            return super(Weave, self).annotate(version_id)
 
482
    
 
483
    def annotate_iter(self, version_id):
 
484
        """Yield list of (version-id, line) pairs for the specified version.
560
485
 
561
486
        The index indicates when the line originated in the weave."""
562
487
        incls = [self._lookup(version_id)]
563
 
        return [(self._idx_to_name(origin), text) for origin, lineno, text in
564
 
            self._extract(incls)]
565
 
 
566
 
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
567
 
                                                pb=None):
568
 
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
569
 
        if version_ids is None:
570
 
            version_ids = self.versions()
571
 
        version_ids = set(version_ids)
572
 
        for lineno, inserted, deletes, line in self._walk_internal(version_ids):
573
 
            # if inserted not in version_ids then it was inserted before the
574
 
            # versions we care about, but because weaves cannot represent ghosts
575
 
            # properly, we do not filter down to that
576
 
            # if inserted not in version_ids: continue
577
 
            if line[-1] != '\n':
578
 
                yield line + '\n', inserted
579
 
            else:
580
 
                yield line, inserted
581
 
 
582
 
    def _walk_internal(self, version_ids=None):
583
 
        """Helper method for weave actions."""
 
488
        for origin, lineno, text in self._extract(incls):
 
489
            yield self._idx_to_name(origin), text
 
490
 
 
491
    @deprecated_method(zero_eight)
 
492
    def _walk(self):
 
493
        """_walk has become walk, a supported api."""
 
494
        return self.walk()
 
495
 
 
496
    def walk(self, version_ids=None):
 
497
        """See VersionedFile.walk."""
584
498
        
585
499
        istack = []
586
500
        dset = set()
588
502
        lineno = 0         # line of weave, 0-based
589
503
 
590
504
        for l in self._weave:
591
 
            if l.__class__ == tuple:
 
505
            if isinstance(l, tuple):
592
506
                c, v = l
593
507
                isactive = None
594
508
                if c == '{':
595
 
                    istack.append(self._names[v])
 
509
                    istack.append(self._idx_to_name(v))
596
510
                elif c == '}':
597
511
                    istack.pop()
598
512
                elif c == '[':
599
 
                    dset.add(self._names[v])
 
513
                    assert self._idx_to_name(v) not in dset
 
514
                    dset.add(self._idx_to_name(v))
600
515
                elif c == ']':
601
 
                    dset.remove(self._names[v])
 
516
                    dset.remove(self._idx_to_name(v))
602
517
                else:
603
518
                    raise WeaveFormatError('unexpected instruction %r' % v)
604
519
            else:
605
 
                yield lineno, istack[-1], frozenset(dset), l
 
520
                assert isinstance(l, basestring)
 
521
                assert istack
 
522
                yield lineno, istack[-1], dset.copy(), l
606
523
            lineno += 1
607
524
 
608
525
        if istack:
612
529
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
613
530
                                   % dset)
614
531
 
615
 
    def plan_merge(self, ver_a, ver_b):
616
 
        """Return pseudo-annotation indicating how the two versions merge.
617
 
 
618
 
        This is computed between versions a and b and their common
619
 
        base.
620
 
 
621
 
        Weave lines present in none of them are skipped entirely.
622
 
        """
623
 
        inc_a = set(self.get_ancestry([ver_a]))
624
 
        inc_b = set(self.get_ancestry([ver_b]))
625
 
        inc_c = inc_a & inc_b
626
 
 
627
 
        for lineno, insert, deleteset, line in self._walk_internal([ver_a, ver_b]):
628
 
            if deleteset & inc_c:
629
 
                # killed in parent; can't be in either a or b
630
 
                # not relevant to our work
631
 
                yield 'killed-base', line
632
 
            elif insert in inc_c:
633
 
                # was inserted in base
634
 
                killed_a = bool(deleteset & inc_a)
635
 
                killed_b = bool(deleteset & inc_b)
636
 
                if killed_a and killed_b:
637
 
                    yield 'killed-both', line
638
 
                elif killed_a:
639
 
                    yield 'killed-a', line
640
 
                elif killed_b:
641
 
                    yield 'killed-b', line
642
 
                else:
643
 
                    yield 'unchanged', line
644
 
            elif insert in inc_a:
645
 
                if deleteset & inc_a:
646
 
                    yield 'ghost-a', line
647
 
                else:
648
 
                    # new in A; not in B
649
 
                    yield 'new-a', line
650
 
            elif insert in inc_b:
651
 
                if deleteset & inc_b:
652
 
                    yield 'ghost-b', line
653
 
                else:
654
 
                    yield 'new-b', line
655
 
            else:
656
 
                # not in either revision
657
 
                yield 'irrelevant', line
658
 
 
659
532
    def _extract(self, versions):
660
533
        """Yield annotation of lines in included set.
661
534
 
672
545
        included = self._inclusions(versions)
673
546
 
674
547
        istack = []
675
 
        iset = set()
676
548
        dset = set()
677
549
 
678
550
        lineno = 0         # line of weave, 0-based
683
555
 
684
556
        WFE = WeaveFormatError
685
557
 
686
 
        # wow. 
687
 
        #  449       0   4474.6820   2356.5590   bzrlib.weave:556(_extract)
688
 
        #  +285282   0   1676.8040   1676.8040   +<isinstance>
689
 
        # 1.6 seconds in 'isinstance'.
690
 
        # changing the first isinstance:
691
 
        #  449       0   2814.2660   1577.1760   bzrlib.weave:556(_extract)
692
 
        #  +140414   0    762.8050    762.8050   +<isinstance>
693
 
        # note that the inline time actually dropped (less function calls)
694
 
        # and total processing time was halved.
695
 
        # we're still spending ~1/4 of the method in isinstance though.
696
 
        # so lets hard code the acceptable string classes we expect:
697
 
        #  449       0   1202.9420    786.2930   bzrlib.weave:556(_extract)
698
 
        # +71352     0    377.5560    377.5560   +<method 'append' of 'list' 
699
 
        #                                          objects>
700
 
        # yay, down to ~1/4 the initial extract time, and our inline time
701
 
        # has shrunk again, with isinstance no longer dominating.
702
 
        # tweaking the stack inclusion test to use a set gives:
703
 
        #  449       0   1122.8030    713.0080   bzrlib.weave:556(_extract)
704
 
        # +71352     0    354.9980    354.9980   +<method 'append' of 'list' 
705
 
        #                                          objects>
706
 
        # - a 5% win, or possibly just noise. However with large istacks that
707
 
        # 'in' test could dominate, so I'm leaving this change in place -
708
 
        # when its fast enough to consider profiling big datasets we can review.
709
 
 
710
 
              
711
 
             
712
 
 
713
558
        for l in self._weave:
714
 
            if l.__class__ == tuple:
 
559
            if isinstance(l, tuple):
715
560
                c, v = l
716
561
                isactive = None
717
562
                if c == '{':
 
563
                    assert v not in istack
718
564
                    istack.append(v)
719
 
                    iset.add(v)
720
565
                elif c == '}':
721
 
                    iset.remove(istack.pop())
 
566
                    istack.pop()
722
567
                elif c == '[':
723
568
                    if v in included:
 
569
                        assert v not in dset
724
570
                        dset.add(v)
725
 
                elif c == ']':
 
571
                else:
 
572
                    assert c == ']'
726
573
                    if v in included:
 
574
                        assert v in dset
727
575
                        dset.remove(v)
728
 
                else:
729
 
                    raise AssertionError()
730
576
            else:
 
577
                assert isinstance(l, basestring)
731
578
                if isactive is None:
732
579
                    isactive = (not dset) and istack and (istack[-1] in included)
733
580
                if isactive:
741
588
                                   % dset)
742
589
        return result
743
590
 
 
591
    @deprecated_method(zero_eight)
 
592
    def get_iter(self, name_or_index):
 
593
        """Deprecated, please do not use. Lookups are not not needed.
 
594
        
 
595
        Please use get_lines now.
 
596
        """
 
597
        return self._get_iter(self._maybe_lookup(name_or_index))
 
598
 
 
599
    @deprecated_method(zero_eight)
 
600
    def maybe_lookup(self, name_or_index):
 
601
        """Deprecated, please do not use. Lookups are not not needed."""
 
602
        return self._maybe_lookup(name_or_index)
 
603
 
744
604
    def _maybe_lookup(self, name_or_index):
745
605
        """Convert possible symbolic name to index, or pass through indexes.
746
606
        
751
611
        else:
752
612
            return self._lookup(name_or_index)
753
613
 
 
614
    def _get_iter(self, version_id):
 
615
        """Yield lines for the specified version."""
 
616
        incls = [self._maybe_lookup(version_id)]
 
617
        if len(incls) == 1:
 
618
            index = incls[0]
 
619
            cur_sha = sha.new()
 
620
        else:
 
621
            # We don't have sha1 sums for multiple entries
 
622
            cur_sha = None
 
623
        for origin, lineno, line in self._extract(incls):
 
624
            if cur_sha:
 
625
                cur_sha.update(line)
 
626
            yield line
 
627
        if cur_sha:
 
628
            expected_sha1 = self._sha1s[index]
 
629
            measured_sha1 = cur_sha.hexdigest() 
 
630
            if measured_sha1 != expected_sha1:
 
631
                raise errors.WeaveInvalidChecksum(
 
632
                        'file %s, revision %s, expected: %s, measured %s' 
 
633
                        % (self._weave_name, self._names[index],
 
634
                           expected_sha1, measured_sha1))
 
635
 
 
636
    @deprecated_method(zero_eight)
 
637
    def get(self, version_id):
 
638
        """Please use either Weave.get_text or Weave.get_lines as desired."""
 
639
        return self.get_lines(version_id)
 
640
 
754
641
    def get_lines(self, version_id):
755
642
        """See VersionedFile.get_lines()."""
756
 
        int_index = self._maybe_lookup(version_id)
757
 
        result = [line for (origin, lineno, line) in self._extract([int_index])]
758
 
        expected_sha1 = self._sha1s[int_index]
759
 
        measured_sha1 = sha_strings(result)
760
 
        if measured_sha1 != expected_sha1:
761
 
            raise errors.WeaveInvalidChecksum(
762
 
                    'file %s, revision %s, expected: %s, measured %s' 
763
 
                    % (self._weave_name, version_id,
764
 
                       expected_sha1, measured_sha1))
765
 
        return result
766
 
 
767
 
    def get_sha1s(self, version_ids):
768
 
        """See VersionedFile.get_sha1s()."""
769
 
        result = {}
770
 
        for v in version_ids:
771
 
            result[v] = self._sha1s[self._lookup(v)]
772
 
        return result
 
643
        return list(self._get_iter(version_id))
 
644
 
 
645
    def get_sha1(self, name):
 
646
        """Get the stored sha1 sum for the given revision.
 
647
        
 
648
        :param name: The name of the version to lookup
 
649
        """
 
650
        return self._sha1s[self._lookup(name)]
 
651
 
 
652
    @deprecated_method(zero_eight)
 
653
    def numversions(self):
 
654
        """How many versions are in this weave?
 
655
 
 
656
        Deprecated in favour of num_versions.
 
657
        """
 
658
        return self.num_versions()
773
659
 
774
660
    def num_versions(self):
775
661
        """How many versions are in this weave?"""
776
662
        l = len(self._parents)
 
663
        assert l == len(self._sha1s)
777
664
        return l
778
665
 
779
666
    __len__ = num_versions
780
667
 
781
668
    def check(self, progress_bar=None):
782
669
        # TODO evaluate performance hit of using string sets in this routine.
783
 
        # TODO: check no circular inclusions
784
 
        # TODO: create a nested progress bar
 
670
        # check no circular inclusions
785
671
        for version in range(self.num_versions()):
786
672
            inclusions = list(self._parents[version])
787
673
            if inclusions:
805
691
            for p in self._parents[i]:
806
692
                new_inc.update(inclusions[self._idx_to_name(p)])
807
693
 
808
 
            if set(new_inc) != set(self.get_ancestry(name)):
809
 
                raise AssertionError(
810
 
                    'failed %s != %s' 
811
 
                    % (set(new_inc), set(self.get_ancestry(name))))
 
694
            assert set(new_inc) == set(self.get_ancestry(name)), \
 
695
                'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
812
696
            inclusions[name] = new_inc
813
697
 
814
698
        nlines = len(self._weave)
819
703
            update_text = 'checking %s' % (short_name,)
820
704
            update_text = update_text[:25]
821
705
 
822
 
        for lineno, insert, deleteset, line in self._walk_internal():
 
706
        for lineno, insert, deleteset, line in self.walk():
823
707
            if progress_bar:
824
708
                progress_bar.update(update_text, lineno, nlines)
825
709
 
844
728
        # no lines outside of insertion blocks, that deletions are
845
729
        # properly paired, etc.
846
730
 
 
731
    def _join(self, other, pb, msg, version_ids):
 
732
        """Worker routine for join()."""
 
733
        if not other.versions():
 
734
            return          # nothing to update, easy
 
735
 
 
736
        if version_ids:
 
737
            for version_id in version_ids:
 
738
                if not self.has_version(version_id):
 
739
                    raise RevisionNotPresent(version_id, self._weave_name)
 
740
        assert version_ids == None
 
741
 
 
742
        # two loops so that we do not change ourselves before verifying it
 
743
        # will be ok
 
744
        # work through in index order to make sure we get all dependencies
 
745
        names_to_join = []
 
746
        processed = 0
 
747
        for other_idx, name in enumerate(other._names):
 
748
            self._check_version_consistent(other, other_idx, name)
 
749
            sha1 = other._sha1s[other_idx]
 
750
 
 
751
            processed += 1
 
752
 
 
753
            if name in self._name_map:
 
754
                idx = self._lookup(name)
 
755
                n1 = set(map(other._idx_to_name, other._parents[other_idx]))
 
756
                n2 = set(map(self._idx_to_name, self._parents[idx]))
 
757
                if sha1 ==  self._sha1s[idx] and n1 == n2:
 
758
                        continue
 
759
 
 
760
            names_to_join.append((other_idx, name))
 
761
 
 
762
        if pb and not msg:
 
763
            msg = 'weave join'
 
764
 
 
765
        merged = 0
 
766
        time0 = time.time()
 
767
        for other_idx, name in names_to_join:
 
768
            # TODO: If all the parents of the other version are already
 
769
            # present then we can avoid some work by just taking the delta
 
770
            # and adjusting the offsets.
 
771
            new_parents = self._imported_parents(other, other_idx)
 
772
            sha1 = other._sha1s[other_idx]
 
773
 
 
774
            merged += 1
 
775
 
 
776
            if pb:
 
777
                pb.update(msg, merged, len(names_to_join))
 
778
           
 
779
            lines = other.get_lines(other_idx)
 
780
            self._add(name, lines, new_parents, sha1)
 
781
 
 
782
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
 
783
                merged, processed, self._weave_name, time.time()-time0))
 
784
 
847
785
    def _imported_parents(self, other, other_idx):
848
786
        """Return list of parents in self corresponding to indexes in other."""
849
787
        new_parents = []
850
788
        for parent_idx in other._parents[other_idx]:
851
789
            parent_name = other._names[parent_idx]
852
 
            if parent_name not in self._name_map:
 
790
            if parent_name not in self._names:
853
791
                # should not be possible
854
792
                raise WeaveError("missing parent {%s} of {%s} in %r" 
855
793
                                 % (parent_name, other._name_map[other_idx], self))
884
822
        else:
885
823
            return False
886
824
 
 
825
    @deprecated_method(zero_eight)
 
826
    def reweave(self, other, pb=None, msg=None):
 
827
        """reweave has been superceded by plain use of join."""
 
828
        return self.join(other, pb, msg)
 
829
 
887
830
    def _reweave(self, other, pb, msg):
888
831
        """Reweave self with other - internal helper for join().
889
832
 
892
835
        :param msg: An optional message for the progress
893
836
        """
894
837
        new_weave = _reweave(self, other, pb=pb, msg=msg)
895
 
        self._copy_weave_content(new_weave)
896
 
 
897
 
    def _copy_weave_content(self, otherweave):
898
 
        """adsorb the content from otherweave."""
899
838
        for attr in self.__slots__:
900
839
            if attr != '_weave_name':
901
 
                setattr(self, attr, copy(getattr(otherweave, attr)))
 
840
                setattr(self, attr, getattr(new_weave, attr))
902
841
 
903
842
 
904
843
class WeaveFile(Weave):
906
845
 
907
846
    WEAVE_SUFFIX = '.weave'
908
847
    
909
 
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w', get_scope=None):
 
848
    def __init__(self, name, transport, mode=None, create=False):
910
849
        """Create a WeaveFile.
911
850
        
912
851
        :param create: If not True, only open an existing knit.
913
852
        """
914
 
        super(WeaveFile, self).__init__(name, access_mode, get_scope=get_scope,
915
 
            allow_reserved=False)
 
853
        super(WeaveFile, self).__init__(name)
916
854
        self._transport = transport
917
 
        self._filemode = filemode
 
855
        self._mode = mode
918
856
        try:
919
857
            _read_weave_v5(self._transport.get(name + WeaveFile.WEAVE_SUFFIX), self)
920
858
        except errors.NoSuchFile:
923
861
            # new file, save it
924
862
            self._save()
925
863
 
926
 
    def _add_lines(self, version_id, parents, lines, parent_texts,
927
 
        left_matching_blocks, nostore_sha, random_id, check_content):
 
864
    def add_lines(self, version_id, parents, lines):
928
865
        """Add a version and save the weave."""
929
 
        self.check_not_reserved_id(version_id)
930
 
        result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
931
 
            parent_texts, left_matching_blocks, nostore_sha, random_id,
932
 
            check_content)
 
866
        super(WeaveFile, self).add_lines(version_id, parents, lines)
933
867
        self._save()
934
 
        return result
935
868
 
936
869
    def copy_to(self, name, transport):
937
870
        """See VersionedFile.copy_to()."""
939
872
        sio = StringIO()
940
873
        write_weave_v5(self, sio)
941
874
        sio.seek(0)
942
 
        transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
 
875
        transport.put(name + WeaveFile.WEAVE_SUFFIX, sio, self._mode)
 
876
 
 
877
    def create_empty(self, name, transport, mode=None):
 
878
        return WeaveFile(name, transport, mode, create=True)
943
879
 
944
880
    def _save(self):
945
881
        """Save the weave."""
946
 
        self._check_write_ok()
947
882
        sio = StringIO()
948
883
        write_weave_v5(self, sio)
949
884
        sio.seek(0)
950
 
        bytes = sio.getvalue()
951
 
        path = self._weave_name + WeaveFile.WEAVE_SUFFIX
952
 
        try:
953
 
            self._transport.put_bytes(path, bytes, self._filemode)
954
 
        except errors.NoSuchFile:
955
 
            self._transport.mkdir(dirname(path))
956
 
            self._transport.put_bytes(path, bytes, self._filemode)
 
885
        self._transport.put(self._weave_name + WeaveFile.WEAVE_SUFFIX,
 
886
                            sio,
 
887
                            self._mode)
957
888
 
958
889
    @staticmethod
959
890
    def get_suffixes():
960
891
        """See VersionedFile.get_suffixes()."""
961
892
        return [WeaveFile.WEAVE_SUFFIX]
962
893
 
963
 
    def insert_record_stream(self, stream):
964
 
        super(WeaveFile, self).insert_record_stream(stream)
965
 
        self._save()
966
 
 
967
 
    @deprecated_method(one_five)
968
 
    def join(self, other, pb=None, msg=None, version_ids=None,
969
 
             ignore_missing=False):
 
894
    def join(self, other, pb=None, msg=None, version_ids=None):
970
895
        """Join other into self and save."""
971
 
        super(WeaveFile, self).join(other, pb, msg, version_ids, ignore_missing)
 
896
        super(WeaveFile, self).join(other, pb, msg, version_ids)
972
897
        self._save()
973
898
 
974
899
 
 
900
@deprecated_function(zero_eight)
 
901
def reweave(wa, wb, pb=None, msg=None):
 
902
    """reweaving is deprecation, please just use weave.join()."""
 
903
    _reweave(wa, wb, pb, msg)
 
904
 
975
905
def _reweave(wa, wb, pb=None, msg=None):
976
906
    """Combine two weaves and return the result.
977
907
 
1051
981
    from bzrlib.weavefile import read_weave
1052
982
 
1053
983
    wf = file(weave_file, 'rb')
1054
 
    w = read_weave(wf)
 
984
    w = read_weave(wf, WeaveVersionedFile)
1055
985
    # FIXME: doesn't work on pipes
1056
986
    weave_size = wf.tell()
1057
987
 
1172
1102
        sys.stdout.writelines(w.get_iter(int(argv[3])))
1173
1103
        
1174
1104
    elif cmd == 'diff':
 
1105
        from difflib import unified_diff
1175
1106
        w = readit()
1176
1107
        fn = argv[2]
1177
1108
        v1, v2 = map(int, argv[3:5])
1178
1109
        lines1 = w.get(v1)
1179
1110
        lines2 = w.get(v2)
1180
 
        diff_gen = bzrlib.patiencediff.unified_diff(lines1, lines2,
 
1111
        diff_gen = unified_diff(lines1, lines2,
1181
1112
                                '%s version %d' % (fn, v1),
1182
1113
                                '%s version %d' % (fn, v2))
1183
1114
        sys.stdout.writelines(diff_gen)
1217
1148
        print ' '.join(map(str, w._parents[int(argv[3])]))
1218
1149
 
1219
1150
    elif cmd == 'plan-merge':
1220
 
        # replaced by 'bzr weave-plan-merge'
1221
1151
        w = readit()
1222
1152
        for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1223
1153
            if line:
1224
1154
                print '%14s | %s' % (state, line),
 
1155
 
1225
1156
    elif cmd == 'merge':
1226
 
        # replaced by 'bzr weave-merge-text'
1227
1157
        w = readit()
1228
1158
        p = w.plan_merge(int(argv[3]), int(argv[4]))
1229
1159
        sys.stdout.writelines(w.weave_merge(p))
 
1160
            
1230
1161
    else:
1231
1162
        raise ValueError('unknown command %r' % cmd)
1232
1163
    
1233
1164
 
 
1165
 
 
1166
def profile_main(argv): 
 
1167
    import tempfile, hotshot, hotshot.stats
 
1168
 
 
1169
    prof_f = tempfile.NamedTemporaryFile()
 
1170
 
 
1171
    prof = hotshot.Profile(prof_f.name)
 
1172
 
 
1173
    ret = prof.runcall(main, argv)
 
1174
    prof.close()
 
1175
 
 
1176
    stats = hotshot.stats.load(prof_f.name)
 
1177
    #stats.strip_dirs()
 
1178
    stats.sort_stats('cumulative')
 
1179
    ## XXX: Might like to write to stderr or the trace file instead but
 
1180
    ## print_stats seems hardcoded to stdout
 
1181
    stats.print_stats(20)
 
1182
            
 
1183
    return ret
 
1184
 
 
1185
 
 
1186
def lsprofile_main(argv): 
 
1187
    from bzrlib.lsprof import profile
 
1188
    ret,stats = profile(main, argv)
 
1189
    stats.sort()
 
1190
    stats.pprint()
 
1191
    return ret
 
1192
 
 
1193
 
1234
1194
if __name__ == '__main__':
1235
1195
    import sys
1236
 
    sys.exit(main(sys.argv))
 
1196
    if '--profile' in sys.argv:
 
1197
        args = sys.argv[:]
 
1198
        args.remove('--profile')
 
1199
        sys.exit(profile_main(args))
 
1200
    elif '--lsprof' in sys.argv:
 
1201
        args = sys.argv[:]
 
1202
        args.remove('--lsprof')
 
1203
        sys.exit(lsprofile_main(args))
 
1204
    else:
 
1205
        sys.exit(main(sys.argv))
 
1206
 
 
1207
 
 
1208
class InterWeave(InterVersionedFile):
 
1209
    """Optimised code paths for weave to weave operations."""
 
1210
    
 
1211
    _matching_file_factory = staticmethod(WeaveFile)
 
1212
    
 
1213
    @staticmethod
 
1214
    def is_compatible(source, target):
 
1215
        """Be compatible with weaves."""
 
1216
        try:
 
1217
            return (isinstance(source, Weave) and
 
1218
                    isinstance(target, Weave))
 
1219
        except AttributeError:
 
1220
            return False
 
1221
 
 
1222
    def join(self, pb=None, msg=None, version_ids=None):
 
1223
        """See InterVersionedFile.join."""
 
1224
        try:
 
1225
            self.target._join(self.source, pb, msg, version_ids)
 
1226
        except errors.WeaveParentMismatch:
 
1227
            self.target._reweave(self.source, pb, msg)
 
1228
 
 
1229
 
 
1230
InterVersionedFile.register_optimiser(InterWeave)