~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: John Arbash Meinel
  • Date: 2006-10-11 23:08:27 UTC
  • mto: This revision was merged to the branch mainline in revision 2080.
  • Revision ID: john@arbash-meinel.com-20061011230827-2bdfc45020695281
Change Copyright .. by Canonical to Copyright ... Canonical

Show diffs side-by-side

added added

removed removed

Lines of Context:
71
71
from copy import copy
72
72
from cStringIO import StringIO
73
73
import os
 
74
import sha
74
75
import time
75
76
import warnings
76
77
 
77
 
from bzrlib.lazy_import import lazy_import
78
 
lazy_import(globals(), """
79
 
from bzrlib import tsort
80
 
""")
81
78
from bzrlib import (
82
 
    errors,
83
 
    osutils,
84
79
    progress,
85
80
    )
 
81
from bzrlib.trace import mutter
86
82
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
87
83
        RevisionAlreadyPresent,
88
84
        RevisionNotPresent,
89
 
        UnavailableRepresentation,
90
85
        WeaveRevisionAlreadyPresent,
91
86
        WeaveRevisionNotPresent,
92
87
        )
93
 
from bzrlib.osutils import dirname, sha, sha_strings, split_lines
 
88
import bzrlib.errors as errors
 
89
from bzrlib.osutils import sha_strings
94
90
import bzrlib.patiencediff
95
 
from bzrlib.revision import NULL_REVISION
96
 
from bzrlib.symbol_versioning import *
97
 
from bzrlib.trace import mutter
98
 
from bzrlib.versionedfile import (
99
 
    AbsentContentFactory,
100
 
    adapter_registry,
101
 
    ContentFactory,
102
 
    VersionedFile,
103
 
    )
 
91
from bzrlib.symbol_versioning import (deprecated_method,
 
92
        deprecated_function,
 
93
        zero_eight,
 
94
        )
 
95
from bzrlib.tsort import topo_sort
 
96
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
104
97
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
105
98
 
106
99
 
107
 
class WeaveContentFactory(ContentFactory):
108
 
    """Content factory for streaming from weaves.
109
 
 
110
 
    :seealso ContentFactory:
111
 
    """
112
 
 
113
 
    def __init__(self, version, weave):
114
 
        """Create a WeaveContentFactory for version from weave."""
115
 
        ContentFactory.__init__(self)
116
 
        self.sha1 = weave.get_sha1s([version])[version]
117
 
        self.key = (version,)
118
 
        parents = weave.get_parent_map([version])[version]
119
 
        self.parents = tuple((parent,) for parent in parents)
120
 
        self.storage_kind = 'fulltext'
121
 
        self._weave = weave
122
 
 
123
 
    def get_bytes_as(self, storage_kind):
124
 
        if storage_kind == 'fulltext':
125
 
            return self._weave.get_text(self.key[-1])
126
 
        elif storage_kind == 'chunked':
127
 
            return self._weave.get_lines(self.key[-1])
128
 
        else:
129
 
            raise UnavailableRepresentation(self.key, storage_kind, 'fulltext')
130
 
 
131
 
 
132
100
class Weave(VersionedFile):
133
101
    """weave - versioned text file storage.
134
102
    
219
187
    """
220
188
 
221
189
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
222
 
                 '_weave_name', '_matcher', '_allow_reserved']
223
 
 
224
 
    def __init__(self, weave_name=None, access_mode='w', matcher=None,
225
 
                 get_scope=None, allow_reserved=False):
226
 
        """Create a weave.
227
 
 
228
 
        :param get_scope: A callable that returns an opaque object to be used
229
 
            for detecting when this weave goes out of scope (should stop
230
 
            answering requests or allowing mutation).
231
 
        """
232
 
        super(Weave, self).__init__()
 
190
                 '_weave_name', '_matcher']
 
191
    
 
192
    def __init__(self, weave_name=None, access_mode='w', matcher=None):
 
193
        super(Weave, self).__init__(access_mode)
233
194
        self._weave = []
234
195
        self._parents = []
235
196
        self._sha1s = []
240
201
            self._matcher = bzrlib.patiencediff.PatienceSequenceMatcher
241
202
        else:
242
203
            self._matcher = matcher
243
 
        if get_scope is None:
244
 
            get_scope = lambda:None
245
 
        self._get_scope = get_scope
246
 
        self._scope = get_scope()
247
 
        self._access_mode = access_mode
248
 
        self._allow_reserved = allow_reserved
249
204
 
250
205
    def __repr__(self):
251
206
        return "Weave(%r)" % self._weave_name
252
207
 
253
 
    def _check_write_ok(self):
254
 
        """Is the versioned file marked as 'finished' ? Raise if it is."""
255
 
        if self._get_scope() != self._scope:
256
 
            raise errors.OutSideTransaction()
257
 
        if self._access_mode != 'w':
258
 
            raise errors.ReadOnlyObjectDirtiedError(self)
259
 
 
260
208
    def copy(self):
261
209
        """Return a deep copy of self.
262
210
        
280
228
    def __ne__(self, other):
281
229
        return not self.__eq__(other)
282
230
 
 
231
    @deprecated_method(zero_eight)
 
232
    def idx_to_name(self, index):
 
233
        """Old public interface, the public interface is all names now."""
 
234
        return index
 
235
 
283
236
    def _idx_to_name(self, version):
284
237
        return self._names[version]
285
238
 
 
239
    @deprecated_method(zero_eight)
 
240
    def lookup(self, name):
 
241
        """Backwards compatibility thunk:
 
242
 
 
243
        Return name, as name is valid in the api now, and spew deprecation
 
244
        warnings everywhere.
 
245
        """
 
246
        return name
 
247
 
286
248
    def _lookup(self, name):
287
249
        """Convert symbolic version name to index."""
288
 
        if not self._allow_reserved:
289
 
            self.check_not_reserved_id(name)
290
250
        try:
291
251
            return self._name_map[name]
292
252
        except KeyError:
293
253
            raise RevisionNotPresent(name, self._weave_name)
294
254
 
 
255
    @deprecated_method(zero_eight)
 
256
    def iter_names(self):
 
257
        """Deprecated convenience function, please see VersionedFile.names()."""
 
258
        return iter(self.names())
 
259
 
 
260
    @deprecated_method(zero_eight)
 
261
    def names(self):
 
262
        """See Weave.versions for the current api."""
 
263
        return self.versions()
 
264
 
295
265
    def versions(self):
296
266
        """See VersionedFile.versions."""
297
267
        return self._names[:]
302
272
 
303
273
    __contains__ = has_version
304
274
 
305
 
    def get_record_stream(self, versions, ordering, include_delta_closure):
306
 
        """Get a stream of records for versions.
307
 
 
308
 
        :param versions: The versions to include. Each version is a tuple
309
 
            (version,).
310
 
        :param ordering: Either 'unordered' or 'topological'. A topologically
311
 
            sorted stream has compression parents strictly before their
312
 
            children.
313
 
        :param include_delta_closure: If True then the closure across any
314
 
            compression parents will be included (in the opaque data).
315
 
        :return: An iterator of ContentFactory objects, each of which is only
316
 
            valid until the iterator is advanced.
317
 
        """
318
 
        versions = [version[-1] for version in versions]
319
 
        if ordering == 'topological':
320
 
            parents = self.get_parent_map(versions)
321
 
            new_versions = tsort.topo_sort(parents)
322
 
            new_versions.extend(set(versions).difference(set(parents)))
323
 
            versions = new_versions
324
 
        for version in versions:
325
 
            if version in self:
326
 
                yield WeaveContentFactory(version, self)
327
 
            else:
328
 
                yield AbsentContentFactory((version,))
329
 
 
330
 
    def get_parent_map(self, version_ids):
331
 
        """See VersionedFile.get_parent_map."""
332
 
        result = {}
 
275
    def get_delta(self, version_id):
 
276
        """See VersionedFile.get_delta."""
 
277
        return self.get_deltas([version_id])[version_id]
 
278
 
 
279
    def get_deltas(self, version_ids):
 
280
        """See VersionedFile.get_deltas."""
 
281
        version_ids = self.get_ancestry(version_ids)
333
282
        for version_id in version_ids:
334
 
            if version_id == NULL_REVISION:
335
 
                parents = ()
336
 
            else:
337
 
                try:
338
 
                    parents = tuple(
339
 
                        map(self._idx_to_name,
340
 
                            self._parents[self._lookup(version_id)]))
341
 
                except RevisionNotPresent:
 
283
            if not self.has_version(version_id):
 
284
                raise RevisionNotPresent(version_id, self)
 
285
        # try extracting all versions; parallel extraction is used
 
286
        nv = self.num_versions()
 
287
        sha1s = {}
 
288
        deltas = {}
 
289
        texts = {}
 
290
        inclusions = {}
 
291
        noeols = {}
 
292
        last_parent_lines = {}
 
293
        parents = {}
 
294
        parent_inclusions = {}
 
295
        parent_linenums = {}
 
296
        parent_noeols = {}
 
297
        current_hunks = {}
 
298
        diff_hunks = {}
 
299
        # its simplest to generate a full set of prepared variables.
 
300
        for i in range(nv):
 
301
            name = self._names[i]
 
302
            sha1s[name] = self.get_sha1(name)
 
303
            parents_list = self.get_parents(name)
 
304
            try:
 
305
                parent = parents_list[0]
 
306
                parents[name] = parent
 
307
                parent_inclusions[name] = inclusions[parent]
 
308
            except IndexError:
 
309
                parents[name] = None
 
310
                parent_inclusions[name] = set()
 
311
            # we want to emit start, finish, replacement_length, replacement_lines tuples.
 
312
            diff_hunks[name] = []
 
313
            current_hunks[name] = [0, 0, 0, []] # #start, finish, repl_length, repl_tuples
 
314
            parent_linenums[name] = 0
 
315
            noeols[name] = False
 
316
            parent_noeols[name] = False
 
317
            last_parent_lines[name] = None
 
318
            new_inc = set([name])
 
319
            for p in self._parents[i]:
 
320
                new_inc.update(inclusions[self._idx_to_name(p)])
 
321
            # debug only, known good so far.
 
322
            #assert set(new_inc) == set(self.get_ancestry(name)), \
 
323
            #    'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
 
324
            inclusions[name] = new_inc
 
325
 
 
326
        nlines = len(self._weave)
 
327
 
 
328
        for lineno, inserted, deletes, line in self._walk_internal():
 
329
            # a line is active in a version if:
 
330
            # insert is in the versions inclusions
 
331
            # and
 
332
            # deleteset & the versions inclusions is an empty set.
 
333
            # so - if we have a included by mapping - version is included by
 
334
            # children, we get a list of children to examine for deletes affect
 
335
            # ing them, which is less than the entire set of children.
 
336
            for version_id in version_ids:  
 
337
                # The active inclusion must be an ancestor,
 
338
                # and no ancestors must have deleted this line,
 
339
                # because we don't support resurrection.
 
340
                parent_inclusion = parent_inclusions[version_id]
 
341
                inclusion = inclusions[version_id]
 
342
                parent_active = inserted in parent_inclusion and not (deletes & parent_inclusion)
 
343
                version_active = inserted in inclusion and not (deletes & inclusion)
 
344
                if not parent_active and not version_active:
 
345
                    # unrelated line of ancestry
342
346
                    continue
343
 
            result[version_id] = parents
 
347
                elif parent_active and version_active:
 
348
                    # shared line
 
349
                    parent_linenum = parent_linenums[version_id]
 
350
                    if current_hunks[version_id] != [parent_linenum, parent_linenum, 0, []]:
 
351
                        diff_hunks[version_id].append(tuple(current_hunks[version_id]))
 
352
                    parent_linenum += 1
 
353
                    current_hunks[version_id] = [parent_linenum, parent_linenum, 0, []]
 
354
                    parent_linenums[version_id] = parent_linenum
 
355
                    try:
 
356
                        if line[-1] != '\n':
 
357
                            noeols[version_id] = True
 
358
                    except IndexError:
 
359
                        pass
 
360
                elif parent_active and not version_active:
 
361
                    # deleted line
 
362
                    current_hunks[version_id][1] += 1
 
363
                    parent_linenums[version_id] += 1
 
364
                    last_parent_lines[version_id] = line
 
365
                elif not parent_active and version_active:
 
366
                    # replacement line
 
367
                    # noeol only occurs at the end of a file because we 
 
368
                    # diff linewise. We want to show noeol changes as a
 
369
                    # empty diff unless the actual eol-less content changed.
 
370
                    theline = line
 
371
                    try:
 
372
                        if last_parent_lines[version_id][-1] != '\n':
 
373
                            parent_noeols[version_id] = True
 
374
                    except (TypeError, IndexError):
 
375
                        pass
 
376
                    try:
 
377
                        if theline[-1] != '\n':
 
378
                            noeols[version_id] = True
 
379
                    except IndexError:
 
380
                        pass
 
381
                    new_line = False
 
382
                    parent_should_go = False
 
383
 
 
384
                    if parent_noeols[version_id] == noeols[version_id]:
 
385
                        # no noeol toggle, so trust the weaves statement
 
386
                        # that this line is changed.
 
387
                        new_line = True
 
388
                        if parent_noeols[version_id]:
 
389
                            theline = theline + '\n'
 
390
                    elif parent_noeols[version_id]:
 
391
                        # parent has no eol, we do:
 
392
                        # our line is new, report as such..
 
393
                        new_line = True
 
394
                    elif noeols[version_id]:
 
395
                        # append a eol so that it looks like
 
396
                        # a normalised delta
 
397
                        theline = theline + '\n'
 
398
                        if parents[version_id] is not None:
 
399
                        #if last_parent_lines[version_id] is not None:
 
400
                            parent_should_go = True
 
401
                        if last_parent_lines[version_id] != theline:
 
402
                            # but changed anyway
 
403
                            new_line = True
 
404
                            #parent_should_go = False
 
405
                    if new_line:
 
406
                        current_hunks[version_id][2] += 1
 
407
                        current_hunks[version_id][3].append((inserted, theline))
 
408
                    if parent_should_go:
 
409
                        # last hunk last parent line is not eaten
 
410
                        current_hunks[version_id][1] -= 1
 
411
                    if current_hunks[version_id][1] < 0:
 
412
                        current_hunks[version_id][1] = 0
 
413
                        # import pdb;pdb.set_trace()
 
414
                    # assert current_hunks[version_id][1] >= 0
 
415
 
 
416
        # flush last hunk
 
417
        for i in range(nv):
 
418
            version = self._idx_to_name(i)
 
419
            if current_hunks[version] != [0, 0, 0, []]:
 
420
                diff_hunks[version].append(tuple(current_hunks[version]))
 
421
        result = {}
 
422
        for version_id in version_ids:
 
423
            result[version_id] = (
 
424
                                  parents[version_id],
 
425
                                  sha1s[version_id],
 
426
                                  noeols[version_id],
 
427
                                  diff_hunks[version_id],
 
428
                                  )
344
429
        return result
345
430
 
346
 
    def get_parents_with_ghosts(self, version_id):
347
 
        raise NotImplementedError(self.get_parents_with_ghosts)
348
 
 
349
 
    def insert_record_stream(self, stream):
350
 
        """Insert a record stream into this versioned file.
351
 
 
352
 
        :param stream: A stream of records to insert. 
353
 
        :return: None
354
 
        :seealso VersionedFile.get_record_stream:
355
 
        """
356
 
        adapters = {}
357
 
        for record in stream:
358
 
            # Raise an error when a record is missing.
359
 
            if record.storage_kind == 'absent':
360
 
                raise RevisionNotPresent([record.key[0]], self)
361
 
            # adapt to non-tuple interface
362
 
            parents = [parent[0] for parent in record.parents]
363
 
            if (record.storage_kind == 'fulltext'
364
 
                or record.storage_kind == 'chunked'):
365
 
                self.add_lines(record.key[0], parents,
366
 
                    osutils.chunks_to_lines(record.get_bytes_as('chunked')))
367
 
            else:
368
 
                adapter_key = record.storage_kind, 'fulltext'
369
 
                try:
370
 
                    adapter = adapters[adapter_key]
371
 
                except KeyError:
372
 
                    adapter_factory = adapter_registry.get(adapter_key)
373
 
                    adapter = adapter_factory(self)
374
 
                    adapters[adapter_key] = adapter
375
 
                lines = split_lines(adapter.get_bytes(
376
 
                    record, record.get_bytes_as(record.storage_kind)))
377
 
                try:
378
 
                    self.add_lines(record.key[0], parents, lines)
379
 
                except RevisionAlreadyPresent:
380
 
                    pass
 
431
    def get_parents(self, version_id):
 
432
        """See VersionedFile.get_parent."""
 
433
        return map(self._idx_to_name, self._parents[self._lookup(version_id)])
381
434
 
382
435
    def _check_repeated_add(self, name, parents, text, sha1):
383
436
        """Check that a duplicated add is OK.
390
443
            raise RevisionAlreadyPresent(name, self._weave_name)
391
444
        return idx
392
445
 
393
 
    def _add_lines(self, version_id, parents, lines, parent_texts,
394
 
       left_matching_blocks, nostore_sha, random_id, check_content):
 
446
    @deprecated_method(zero_eight)
 
447
    def add_identical(self, old_rev_id, new_rev_id, parents):
 
448
        """Please use Weave.clone_text now."""
 
449
        return self.clone_text(new_rev_id, old_rev_id, parents)
 
450
 
 
451
    def _add_lines(self, version_id, parents, lines, parent_texts):
395
452
        """See VersionedFile.add_lines."""
396
 
        idx = self._add(version_id, lines, map(self._lookup, parents),
397
 
            nostore_sha=nostore_sha)
398
 
        return sha_strings(lines), sum(map(len, lines)), idx
399
 
 
400
 
    def _add(self, version_id, lines, parents, sha1=None, nostore_sha=None):
 
453
        return self._add(version_id, lines, map(self._lookup, parents))
 
454
 
 
455
    @deprecated_method(zero_eight)
 
456
    def add(self, name, parents, text, sha1=None):
 
457
        """See VersionedFile.add_lines for the non deprecated api."""
 
458
        return self._add(name, text, map(self._maybe_lookup, parents), sha1)
 
459
 
 
460
    def _add(self, version_id, lines, parents, sha1=None):
401
461
        """Add a single text on top of the weave.
402
462
  
403
463
        Returns the index number of the newly added version.
411
471
            
412
472
        lines
413
473
            Sequence of lines to be added in the new version.
 
474
        """
414
475
 
415
 
        :param nostore_sha: See VersionedFile.add_lines.
416
 
        """
 
476
        assert isinstance(version_id, basestring)
417
477
        self._check_lines_not_unicode(lines)
418
478
        self._check_lines_are_lines(lines)
419
479
        if not sha1:
420
480
            sha1 = sha_strings(lines)
421
 
        if sha1 == nostore_sha:
422
 
            raise errors.ExistingContent
423
481
        if version_id in self._name_map:
424
482
            return self._check_repeated_add(version_id, parents, lines, sha1)
425
483
 
469
527
        # another small special case: a merge, producing the same text
470
528
        # as auto-merge
471
529
        if lines == basis_lines:
472
 
            return new_version
 
530
            return new_version            
473
531
 
474
532
        # add a sentinel, because we can also match against the final line
475
533
        basis_lineno.append(len(self._weave))
494
552
            #print 'raw match', tag, i1, i2, j1, j2
495
553
            if tag == 'equal':
496
554
                continue
 
555
 
497
556
            i1 = basis_lineno[i1]
498
557
            i2 = basis_lineno[i2]
 
558
 
 
559
            assert 0 <= j1 <= j2 <= len(lines)
 
560
 
 
561
            #print tag, i1, i2, j1, j2
 
562
 
499
563
            # the deletion and insertion are handled separately.
500
564
            # first delete the region.
501
565
            if i1 != i2:
514
578
                offset += 2 + (j2 - j1)
515
579
        return new_version
516
580
 
 
581
    def _clone_text(self, new_version_id, old_version_id, parents):
 
582
        """See VersionedFile.clone_text."""
 
583
        old_lines = self.get_text(old_version_id)
 
584
        self.add_lines(new_version_id, parents, old_lines)
 
585
 
517
586
    def _inclusions(self, versions):
518
587
        """Return set of all ancestors of given version(s)."""
519
588
        if not len(versions):
527
596
        ## except IndexError:
528
597
        ##     raise ValueError("version %d not present in weave" % v)
529
598
 
530
 
    def get_ancestry(self, version_ids, topo_sorted=True):
 
599
    @deprecated_method(zero_eight)
 
600
    def inclusions(self, version_ids):
 
601
        """Deprecated - see VersionedFile.get_ancestry for the replacement."""
 
602
        if not version_ids:
 
603
            return []
 
604
        if isinstance(version_ids[0], int):
 
605
            return [self._idx_to_name(v) for v in self._inclusions(version_ids)]
 
606
        else:
 
607
            return self.get_ancestry(version_ids)
 
608
 
 
609
    def get_ancestry(self, version_ids):
531
610
        """See VersionedFile.get_ancestry."""
532
611
        if isinstance(version_ids, basestring):
533
612
            version_ids = [version_ids]
562
641
        return len(other_parents.difference(my_parents)) == 0
563
642
 
564
643
    def annotate(self, version_id):
565
 
        """Return a list of (version-id, line) tuples for version_id.
 
644
        if isinstance(version_id, int):
 
645
            warnings.warn('Weave.annotate(int) is deprecated. Please use version names'
 
646
                 ' in all circumstances as of 0.8',
 
647
                 DeprecationWarning,
 
648
                 stacklevel=2
 
649
                 )
 
650
            result = []
 
651
            for origin, lineno, text in self._extract([version_id]):
 
652
                result.append((origin, text))
 
653
            return result
 
654
        else:
 
655
            return super(Weave, self).annotate(version_id)
 
656
    
 
657
    def annotate_iter(self, version_id):
 
658
        """Yield list of (version-id, line) pairs for the specified version.
566
659
 
567
660
        The index indicates when the line originated in the weave."""
568
661
        incls = [self._lookup(version_id)]
569
 
        return [(self._idx_to_name(origin), text) for origin, lineno, text in
570
 
            self._extract(incls)]
 
662
        for origin, lineno, text in self._extract(incls):
 
663
            yield self._idx_to_name(origin), text
 
664
 
 
665
    @deprecated_method(zero_eight)
 
666
    def _walk(self):
 
667
        """_walk has become visit, a supported api."""
 
668
        return self._walk_internal()
571
669
 
572
670
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
573
671
                                                pb=None):
581
679
            # properly, we do not filter down to that
582
680
            # if inserted not in version_ids: continue
583
681
            if line[-1] != '\n':
584
 
                yield line + '\n', inserted
 
682
                yield line + '\n'
585
683
            else:
586
 
                yield line, inserted
 
684
                yield line
 
685
 
 
686
    #@deprecated_method(zero_eight)
 
687
    def walk(self, version_ids=None):
 
688
        """See VersionedFile.walk."""
 
689
        return self._walk_internal(version_ids)
587
690
 
588
691
    def _walk_internal(self, version_ids=None):
589
692
        """Helper method for weave actions."""
602
705
                elif c == '}':
603
706
                    istack.pop()
604
707
                elif c == '[':
 
708
                    assert self._names[v] not in dset
605
709
                    dset.add(self._names[v])
606
710
                elif c == ']':
607
711
                    dset.remove(self._names[v])
608
712
                else:
609
713
                    raise WeaveFormatError('unexpected instruction %r' % v)
610
714
            else:
 
715
                assert l.__class__ in (str, unicode)
 
716
                assert istack
611
717
                yield lineno, istack[-1], frozenset(dset), l
612
718
            lineno += 1
613
719
 
630
736
        inc_b = set(self.get_ancestry([ver_b]))
631
737
        inc_c = inc_a & inc_b
632
738
 
633
 
        for lineno, insert, deleteset, line in self._walk_internal([ver_a, ver_b]):
 
739
        for lineno, insert, deleteset, line in\
 
740
            self.walk([ver_a, ver_b]):
634
741
            if deleteset & inc_c:
635
742
                # killed in parent; can't be in either a or b
636
743
                # not relevant to our work
662
769
                # not in either revision
663
770
                yield 'irrelevant', line
664
771
 
 
772
        yield 'unchanged', ''           # terminator
 
773
 
665
774
    def _extract(self, versions):
666
775
        """Yield annotation of lines in included set.
667
776
 
721
830
                c, v = l
722
831
                isactive = None
723
832
                if c == '{':
 
833
                    assert v not in iset
724
834
                    istack.append(v)
725
835
                    iset.add(v)
726
836
                elif c == '}':
727
837
                    iset.remove(istack.pop())
728
838
                elif c == '[':
729
839
                    if v in included:
 
840
                        assert v not in dset
730
841
                        dset.add(v)
731
 
                elif c == ']':
 
842
                else:
 
843
                    assert c == ']'
732
844
                    if v in included:
 
845
                        assert v in dset
733
846
                        dset.remove(v)
734
 
                else:
735
 
                    raise AssertionError()
736
847
            else:
 
848
                assert l.__class__ in (str, unicode)
737
849
                if isactive is None:
738
850
                    isactive = (not dset) and istack and (istack[-1] in included)
739
851
                if isactive:
747
859
                                   % dset)
748
860
        return result
749
861
 
 
862
    @deprecated_method(zero_eight)
 
863
    def get_iter(self, name_or_index):
 
864
        """Deprecated, please do not use. Lookups are not not needed.
 
865
        
 
866
        Please use get_lines now.
 
867
        """
 
868
        return iter(self.get_lines(self._maybe_lookup(name_or_index)))
 
869
 
 
870
    @deprecated_method(zero_eight)
 
871
    def maybe_lookup(self, name_or_index):
 
872
        """Deprecated, please do not use. Lookups are not not needed."""
 
873
        return self._maybe_lookup(name_or_index)
 
874
 
750
875
    def _maybe_lookup(self, name_or_index):
751
876
        """Convert possible symbolic name to index, or pass through indexes.
752
877
        
757
882
        else:
758
883
            return self._lookup(name_or_index)
759
884
 
 
885
    @deprecated_method(zero_eight)
 
886
    def get(self, version_id):
 
887
        """Please use either Weave.get_text or Weave.get_lines as desired."""
 
888
        return self.get_lines(version_id)
 
889
 
760
890
    def get_lines(self, version_id):
761
891
        """See VersionedFile.get_lines()."""
762
892
        int_index = self._maybe_lookup(version_id)
770
900
                       expected_sha1, measured_sha1))
771
901
        return result
772
902
 
773
 
    def get_sha1s(self, version_ids):
774
 
        """See VersionedFile.get_sha1s()."""
775
 
        result = {}
776
 
        for v in version_ids:
777
 
            result[v] = self._sha1s[self._lookup(v)]
778
 
        return result
 
903
    def get_sha1(self, version_id):
 
904
        """See VersionedFile.get_sha1()."""
 
905
        return self._sha1s[self._lookup(version_id)]
 
906
 
 
907
    @deprecated_method(zero_eight)
 
908
    def numversions(self):
 
909
        """How many versions are in this weave?
 
910
 
 
911
        Deprecated in favour of num_versions.
 
912
        """
 
913
        return self.num_versions()
779
914
 
780
915
    def num_versions(self):
781
916
        """How many versions are in this weave?"""
782
917
        l = len(self._parents)
 
918
        assert l == len(self._sha1s)
783
919
        return l
784
920
 
785
921
    __len__ = num_versions
805
941
            # For creating the ancestry, IntSet is much faster (3.7s vs 0.17s)
806
942
            # The problem is that set membership is much more expensive
807
943
            name = self._idx_to_name(i)
808
 
            sha1s[name] = sha()
 
944
            sha1s[name] = sha.new()
809
945
            texts[name] = []
810
946
            new_inc = set([name])
811
947
            for p in self._parents[i]:
812
948
                new_inc.update(inclusions[self._idx_to_name(p)])
813
949
 
814
 
            if set(new_inc) != set(self.get_ancestry(name)):
815
 
                raise AssertionError(
816
 
                    'failed %s != %s' 
817
 
                    % (set(new_inc), set(self.get_ancestry(name))))
 
950
            assert set(new_inc) == set(self.get_ancestry(name)), \
 
951
                'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
818
952
            inclusions[name] = new_inc
819
953
 
820
954
        nlines = len(self._weave)
850
984
        # no lines outside of insertion blocks, that deletions are
851
985
        # properly paired, etc.
852
986
 
 
987
    def _join(self, other, pb, msg, version_ids, ignore_missing):
 
988
        """Worker routine for join()."""
 
989
        if not other.versions():
 
990
            return          # nothing to update, easy
 
991
 
 
992
        if not version_ids:
 
993
            # versions is never none, InterWeave checks this.
 
994
            return 0
 
995
 
 
996
        # two loops so that we do not change ourselves before verifying it
 
997
        # will be ok
 
998
        # work through in index order to make sure we get all dependencies
 
999
        names_to_join = []
 
1000
        processed = 0
 
1001
        # get the selected versions only that are in other.versions.
 
1002
        version_ids = set(other.versions()).intersection(set(version_ids))
 
1003
        # pull in the referenced graph.
 
1004
        version_ids = other.get_ancestry(version_ids)
 
1005
        pending_graph = [(version, other.get_parents(version)) for
 
1006
                         version in version_ids]
 
1007
        for name in topo_sort(pending_graph):
 
1008
            other_idx = other._name_map[name]
 
1009
            # returns True if we have it, False if we need it.
 
1010
            if not self._check_version_consistent(other, other_idx, name):
 
1011
                names_to_join.append((other_idx, name))
 
1012
            processed += 1
 
1013
 
 
1014
 
 
1015
        if pb and not msg:
 
1016
            msg = 'weave join'
 
1017
 
 
1018
        merged = 0
 
1019
        time0 = time.time()
 
1020
        for other_idx, name in names_to_join:
 
1021
            # TODO: If all the parents of the other version are already
 
1022
            # present then we can avoid some work by just taking the delta
 
1023
            # and adjusting the offsets.
 
1024
            new_parents = self._imported_parents(other, other_idx)
 
1025
            sha1 = other._sha1s[other_idx]
 
1026
 
 
1027
            merged += 1
 
1028
 
 
1029
            if pb:
 
1030
                pb.update(msg, merged, len(names_to_join))
 
1031
           
 
1032
            lines = other.get_lines(other_idx)
 
1033
            self._add(name, lines, new_parents, sha1)
 
1034
 
 
1035
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
 
1036
                merged, processed, self._weave_name, time.time()-time0))
 
1037
 
853
1038
    def _imported_parents(self, other, other_idx):
854
1039
        """Return list of parents in self corresponding to indexes in other."""
855
1040
        new_parents = []
890
1075
        else:
891
1076
            return False
892
1077
 
 
1078
    @deprecated_method(zero_eight)
 
1079
    def reweave(self, other, pb=None, msg=None):
 
1080
        """reweave has been superseded by plain use of join."""
 
1081
        return self.join(other, pb, msg)
 
1082
 
893
1083
    def _reweave(self, other, pb, msg):
894
1084
        """Reweave self with other - internal helper for join().
895
1085
 
912
1102
 
913
1103
    WEAVE_SUFFIX = '.weave'
914
1104
    
915
 
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w', get_scope=None):
 
1105
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w'):
916
1106
        """Create a WeaveFile.
917
1107
        
918
1108
        :param create: If not True, only open an existing knit.
919
1109
        """
920
 
        super(WeaveFile, self).__init__(name, access_mode, get_scope=get_scope,
921
 
            allow_reserved=False)
 
1110
        super(WeaveFile, self).__init__(name, access_mode)
922
1111
        self._transport = transport
923
1112
        self._filemode = filemode
924
1113
        try:
929
1118
            # new file, save it
930
1119
            self._save()
931
1120
 
932
 
    def _add_lines(self, version_id, parents, lines, parent_texts,
933
 
        left_matching_blocks, nostore_sha, random_id, check_content):
 
1121
    def _add_lines(self, version_id, parents, lines, parent_texts):
934
1122
        """Add a version and save the weave."""
935
 
        self.check_not_reserved_id(version_id)
936
1123
        result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
937
 
            parent_texts, left_matching_blocks, nostore_sha, random_id,
938
 
            check_content)
 
1124
                                                   parent_texts)
939
1125
        self._save()
940
1126
        return result
941
1127
 
 
1128
    def _clone_text(self, new_version_id, old_version_id, parents):
 
1129
        """See VersionedFile.clone_text."""
 
1130
        super(WeaveFile, self)._clone_text(new_version_id, old_version_id, parents)
 
1131
        self._save
 
1132
 
942
1133
    def copy_to(self, name, transport):
943
1134
        """See VersionedFile.copy_to()."""
944
1135
        # as we are all in memory always, just serialise to the new place.
947
1138
        sio.seek(0)
948
1139
        transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
949
1140
 
 
1141
    def create_empty(self, name, transport, filemode=None):
 
1142
        return WeaveFile(name, transport, filemode, create=True)
 
1143
 
950
1144
    def _save(self):
951
1145
        """Save the weave."""
952
1146
        self._check_write_ok()
953
1147
        sio = StringIO()
954
1148
        write_weave_v5(self, sio)
955
1149
        sio.seek(0)
956
 
        bytes = sio.getvalue()
957
 
        path = self._weave_name + WeaveFile.WEAVE_SUFFIX
958
 
        try:
959
 
            self._transport.put_bytes(path, bytes, self._filemode)
960
 
        except errors.NoSuchFile:
961
 
            self._transport.mkdir(dirname(path))
962
 
            self._transport.put_bytes(path, bytes, self._filemode)
 
1150
        self._transport.put_file(self._weave_name + WeaveFile.WEAVE_SUFFIX,
 
1151
                                 sio,
 
1152
                                 self._filemode)
963
1153
 
964
1154
    @staticmethod
965
1155
    def get_suffixes():
966
1156
        """See VersionedFile.get_suffixes()."""
967
1157
        return [WeaveFile.WEAVE_SUFFIX]
968
1158
 
969
 
    def insert_record_stream(self, stream):
970
 
        super(WeaveFile, self).insert_record_stream(stream)
971
 
        self._save()
972
 
 
973
 
    @deprecated_method(one_five)
974
1159
    def join(self, other, pb=None, msg=None, version_ids=None,
975
1160
             ignore_missing=False):
976
1161
        """Join other into self and save."""
978
1163
        self._save()
979
1164
 
980
1165
 
 
1166
@deprecated_function(zero_eight)
 
1167
def reweave(wa, wb, pb=None, msg=None):
 
1168
    """reweaving is deprecation, please just use weave.join()."""
 
1169
    _reweave(wa, wb, pb, msg)
 
1170
 
981
1171
def _reweave(wa, wb, pb=None, msg=None):
982
1172
    """Combine two weaves and return the result.
983
1173
 
1001
1191
    # map from version name -> all parent names
1002
1192
    combined_parents = _reweave_parent_graphs(wa, wb)
1003
1193
    mutter("combined parents: %r", combined_parents)
1004
 
    order = tsort.topo_sort(combined_parents.iteritems())
 
1194
    order = topo_sort(combined_parents.iteritems())
1005
1195
    mutter("order to reweave: %r", order)
1006
1196
 
1007
1197
    if pb and not msg:
1240
1430
if __name__ == '__main__':
1241
1431
    import sys
1242
1432
    sys.exit(main(sys.argv))
 
1433
 
 
1434
 
 
1435
class InterWeave(InterVersionedFile):
 
1436
    """Optimised code paths for weave to weave operations."""
 
1437
    
 
1438
    _matching_file_from_factory = staticmethod(WeaveFile)
 
1439
    _matching_file_to_factory = staticmethod(WeaveFile)
 
1440
    
 
1441
    @staticmethod
 
1442
    def is_compatible(source, target):
 
1443
        """Be compatible with weaves."""
 
1444
        try:
 
1445
            return (isinstance(source, Weave) and
 
1446
                    isinstance(target, Weave))
 
1447
        except AttributeError:
 
1448
            return False
 
1449
 
 
1450
    def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
 
1451
        """See InterVersionedFile.join."""
 
1452
        version_ids = self._get_source_version_ids(version_ids, ignore_missing)
 
1453
        if self.target.versions() == [] and version_ids is None:
 
1454
            self.target._copy_weave_content(self.source)
 
1455
            return
 
1456
        try:
 
1457
            self.target._join(self.source, pb, msg, version_ids, ignore_missing)
 
1458
        except errors.WeaveParentMismatch:
 
1459
            self.target._reweave(self.source, pb, msg)
 
1460
 
 
1461
 
 
1462
InterVersionedFile.register_optimiser(InterWeave)