~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Aaron Bentley
  • Date: 2007-02-06 14:52:16 UTC
  • mfrom: (2266 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2268.
  • Revision ID: abentley@panoramicfeedback.com-20070206145216-fcpi8o3ufvuzwbp9
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
78
78
from bzrlib import (
79
79
    progress,
80
80
    )
 
81
from bzrlib.trace import mutter
81
82
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
82
83
        RevisionAlreadyPresent,
83
84
        RevisionNotPresent,
84
 
        UnavailableRepresentation,
85
85
        WeaveRevisionAlreadyPresent,
86
86
        WeaveRevisionNotPresent,
87
87
        )
88
88
import bzrlib.errors as errors
89
 
from bzrlib.osutils import dirname, sha_strings, split_lines
 
89
from bzrlib.osutils import sha_strings
90
90
import bzrlib.patiencediff
91
 
from bzrlib.revision import NULL_REVISION
92
 
from bzrlib.symbol_versioning import *
93
 
from bzrlib.trace import mutter
 
91
from bzrlib.symbol_versioning import (deprecated_method,
 
92
        deprecated_function,
 
93
        zero_eight,
 
94
        )
94
95
from bzrlib.tsort import topo_sort
95
 
from bzrlib.versionedfile import (
96
 
    AbsentContentFactory,
97
 
    adapter_registry,
98
 
    ContentFactory,
99
 
    VersionedFile,
100
 
    )
 
96
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
101
97
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
102
98
 
103
99
 
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
100
class Weave(VersionedFile):
128
101
    """weave - versioned text file storage.
129
102
    
214
187
    """
215
188
 
216
189
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
217
 
                 '_weave_name', '_matcher', '_allow_reserved']
 
190
                 '_weave_name', '_matcher']
218
191
    
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
 
        """
 
192
    def __init__(self, weave_name=None, access_mode='w', matcher=None):
227
193
        super(Weave, self).__init__(access_mode)
228
194
        self._weave = []
229
195
        self._parents = []
235
201
            self._matcher = bzrlib.patiencediff.PatienceSequenceMatcher
236
202
        else:
237
203
            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
204
 
245
205
    def __repr__(self):
246
206
        return "Weave(%r)" % self._weave_name
247
207
 
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
208
    def copy(self):
256
209
        """Return a deep copy of self.
257
210
        
275
228
    def __ne__(self, other):
276
229
        return not self.__eq__(other)
277
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
 
278
236
    def _idx_to_name(self, version):
279
237
        return self._names[version]
280
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
 
281
248
    def _lookup(self, name):
282
249
        """Convert symbolic version name to index."""
283
 
        if not self._allow_reserved:
284
 
            self.check_not_reserved_id(name)
 
250
        self.check_not_reserved_id(name)
285
251
        try:
286
252
            return self._name_map[name]
287
253
        except KeyError:
288
254
            raise RevisionNotPresent(name, self._weave_name)
289
255
 
 
256
    @deprecated_method(zero_eight)
 
257
    def iter_names(self):
 
258
        """Deprecated convenience function, please see VersionedFile.names()."""
 
259
        return iter(self.names())
 
260
 
 
261
    @deprecated_method(zero_eight)
 
262
    def names(self):
 
263
        """See Weave.versions for the current api."""
 
264
        return self.versions()
 
265
 
290
266
    def versions(self):
291
267
        """See VersionedFile.versions."""
292
268
        return self._names[:]
297
273
 
298
274
    __contains__ = has_version
299
275
 
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 = {}
 
276
    def get_delta(self, version_id):
 
277
        """See VersionedFile.get_delta."""
 
278
        return self.get_deltas([version_id])[version_id]
 
279
 
 
280
    def get_deltas(self, version_ids):
 
281
        """See VersionedFile.get_deltas."""
 
282
        version_ids = self.get_ancestry(version_ids)
328
283
        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:
 
284
            if not self.has_version(version_id):
 
285
                raise RevisionNotPresent(version_id, self)
 
286
        # try extracting all versions; parallel extraction is used
 
287
        nv = self.num_versions()
 
288
        sha1s = {}
 
289
        deltas = {}
 
290
        texts = {}
 
291
        inclusions = {}
 
292
        noeols = {}
 
293
        last_parent_lines = {}
 
294
        parents = {}
 
295
        parent_inclusions = {}
 
296
        parent_linenums = {}
 
297
        parent_noeols = {}
 
298
        current_hunks = {}
 
299
        diff_hunks = {}
 
300
        # its simplest to generate a full set of prepared variables.
 
301
        for i in range(nv):
 
302
            name = self._names[i]
 
303
            sha1s[name] = self.get_sha1(name)
 
304
            parents_list = self.get_parents(name)
 
305
            try:
 
306
                parent = parents_list[0]
 
307
                parents[name] = parent
 
308
                parent_inclusions[name] = inclusions[parent]
 
309
            except IndexError:
 
310
                parents[name] = None
 
311
                parent_inclusions[name] = set()
 
312
            # we want to emit start, finish, replacement_length, replacement_lines tuples.
 
313
            diff_hunks[name] = []
 
314
            current_hunks[name] = [0, 0, 0, []] # #start, finish, repl_length, repl_tuples
 
315
            parent_linenums[name] = 0
 
316
            noeols[name] = False
 
317
            parent_noeols[name] = False
 
318
            last_parent_lines[name] = None
 
319
            new_inc = set([name])
 
320
            for p in self._parents[i]:
 
321
                new_inc.update(inclusions[self._idx_to_name(p)])
 
322
            # debug only, known good so far.
 
323
            #assert set(new_inc) == set(self.get_ancestry(name)), \
 
324
            #    'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
 
325
            inclusions[name] = new_inc
 
326
 
 
327
        nlines = len(self._weave)
 
328
 
 
329
        for lineno, inserted, deletes, line in self._walk_internal():
 
330
            # a line is active in a version if:
 
331
            # insert is in the versions inclusions
 
332
            # and
 
333
            # deleteset & the versions inclusions is an empty set.
 
334
            # so - if we have a included by mapping - version is included by
 
335
            # children, we get a list of children to examine for deletes affect
 
336
            # ing them, which is less than the entire set of children.
 
337
            for version_id in version_ids:  
 
338
                # The active inclusion must be an ancestor,
 
339
                # and no ancestors must have deleted this line,
 
340
                # because we don't support resurrection.
 
341
                parent_inclusion = parent_inclusions[version_id]
 
342
                inclusion = inclusions[version_id]
 
343
                parent_active = inserted in parent_inclusion and not (deletes & parent_inclusion)
 
344
                version_active = inserted in inclusion and not (deletes & inclusion)
 
345
                if not parent_active and not version_active:
 
346
                    # unrelated line of ancestry
337
347
                    continue
338
 
            result[version_id] = parents
 
348
                elif parent_active and version_active:
 
349
                    # shared line
 
350
                    parent_linenum = parent_linenums[version_id]
 
351
                    if current_hunks[version_id] != [parent_linenum, parent_linenum, 0, []]:
 
352
                        diff_hunks[version_id].append(tuple(current_hunks[version_id]))
 
353
                    parent_linenum += 1
 
354
                    current_hunks[version_id] = [parent_linenum, parent_linenum, 0, []]
 
355
                    parent_linenums[version_id] = parent_linenum
 
356
                    try:
 
357
                        if line[-1] != '\n':
 
358
                            noeols[version_id] = True
 
359
                    except IndexError:
 
360
                        pass
 
361
                elif parent_active and not version_active:
 
362
                    # deleted line
 
363
                    current_hunks[version_id][1] += 1
 
364
                    parent_linenums[version_id] += 1
 
365
                    last_parent_lines[version_id] = line
 
366
                elif not parent_active and version_active:
 
367
                    # replacement line
 
368
                    # noeol only occurs at the end of a file because we 
 
369
                    # diff linewise. We want to show noeol changes as a
 
370
                    # empty diff unless the actual eol-less content changed.
 
371
                    theline = line
 
372
                    try:
 
373
                        if last_parent_lines[version_id][-1] != '\n':
 
374
                            parent_noeols[version_id] = True
 
375
                    except (TypeError, IndexError):
 
376
                        pass
 
377
                    try:
 
378
                        if theline[-1] != '\n':
 
379
                            noeols[version_id] = True
 
380
                    except IndexError:
 
381
                        pass
 
382
                    new_line = False
 
383
                    parent_should_go = False
 
384
 
 
385
                    if parent_noeols[version_id] == noeols[version_id]:
 
386
                        # no noeol toggle, so trust the weaves statement
 
387
                        # that this line is changed.
 
388
                        new_line = True
 
389
                        if parent_noeols[version_id]:
 
390
                            theline = theline + '\n'
 
391
                    elif parent_noeols[version_id]:
 
392
                        # parent has no eol, we do:
 
393
                        # our line is new, report as such..
 
394
                        new_line = True
 
395
                    elif noeols[version_id]:
 
396
                        # append a eol so that it looks like
 
397
                        # a normalised delta
 
398
                        theline = theline + '\n'
 
399
                        if parents[version_id] is not None:
 
400
                        #if last_parent_lines[version_id] is not None:
 
401
                            parent_should_go = True
 
402
                        if last_parent_lines[version_id] != theline:
 
403
                            # but changed anyway
 
404
                            new_line = True
 
405
                            #parent_should_go = False
 
406
                    if new_line:
 
407
                        current_hunks[version_id][2] += 1
 
408
                        current_hunks[version_id][3].append((inserted, theline))
 
409
                    if parent_should_go:
 
410
                        # last hunk last parent line is not eaten
 
411
                        current_hunks[version_id][1] -= 1
 
412
                    if current_hunks[version_id][1] < 0:
 
413
                        current_hunks[version_id][1] = 0
 
414
                        # import pdb;pdb.set_trace()
 
415
                    # assert current_hunks[version_id][1] >= 0
 
416
 
 
417
        # flush last hunk
 
418
        for i in range(nv):
 
419
            version = self._idx_to_name(i)
 
420
            if current_hunks[version] != [0, 0, 0, []]:
 
421
                diff_hunks[version].append(tuple(current_hunks[version]))
 
422
        result = {}
 
423
        for version_id in version_ids:
 
424
            result[version_id] = (
 
425
                                  parents[version_id],
 
426
                                  sha1s[version_id],
 
427
                                  noeols[version_id],
 
428
                                  diff_hunks[version_id],
 
429
                                  )
339
430
        return result
340
431
 
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
 
432
    def get_parents(self, version_id):
 
433
        """See VersionedFile.get_parent."""
 
434
        return map(self._idx_to_name, self._parents[self._lookup(version_id)])
375
435
 
376
436
    def _check_repeated_add(self, name, parents, text, sha1):
377
437
        """Check that a duplicated add is OK.
384
444
            raise RevisionAlreadyPresent(name, self._weave_name)
385
445
        return idx
386
446
 
387
 
    def _add_lines(self, version_id, parents, lines, parent_texts,
388
 
       left_matching_blocks, nostore_sha, random_id, check_content):
 
447
    @deprecated_method(zero_eight)
 
448
    def add_identical(self, old_rev_id, new_rev_id, parents):
 
449
        """Please use Weave.clone_text now."""
 
450
        return self.clone_text(new_rev_id, old_rev_id, parents)
 
451
 
 
452
    def _add_lines(self, version_id, parents, lines, parent_texts):
389
453
        """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):
 
454
        return self._add(version_id, lines, map(self._lookup, parents))
 
455
 
 
456
    @deprecated_method(zero_eight)
 
457
    def add(self, name, parents, text, sha1=None):
 
458
        """See VersionedFile.add_lines for the non deprecated api."""
 
459
        return self._add(name, text, map(self._maybe_lookup, parents), sha1)
 
460
 
 
461
    def _add(self, version_id, lines, parents, sha1=None):
395
462
        """Add a single text on top of the weave.
396
463
  
397
464
        Returns the index number of the newly added version.
405
472
            
406
473
        lines
407
474
            Sequence of lines to be added in the new version.
 
475
        """
408
476
 
409
 
        :param nostore_sha: See VersionedFile.add_lines.
410
 
        """
 
477
        assert isinstance(version_id, basestring)
411
478
        self._check_lines_not_unicode(lines)
412
479
        self._check_lines_are_lines(lines)
413
480
        if not sha1:
414
481
            sha1 = sha_strings(lines)
415
 
        if sha1 == nostore_sha:
416
 
            raise errors.ExistingContent
417
482
        if version_id in self._name_map:
418
483
            return self._check_repeated_add(version_id, parents, lines, sha1)
419
484
 
463
528
        # another small special case: a merge, producing the same text
464
529
        # as auto-merge
465
530
        if lines == basis_lines:
466
 
            return new_version
 
531
            return new_version            
467
532
 
468
533
        # add a sentinel, because we can also match against the final line
469
534
        basis_lineno.append(len(self._weave))
488
553
            #print 'raw match', tag, i1, i2, j1, j2
489
554
            if tag == 'equal':
490
555
                continue
 
556
 
491
557
            i1 = basis_lineno[i1]
492
558
            i2 = basis_lineno[i2]
 
559
 
 
560
            assert 0 <= j1 <= j2 <= len(lines)
 
561
 
 
562
            #print tag, i1, i2, j1, j2
 
563
 
493
564
            # the deletion and insertion are handled separately.
494
565
            # first delete the region.
495
566
            if i1 != i2:
508
579
                offset += 2 + (j2 - j1)
509
580
        return new_version
510
581
 
 
582
    def _clone_text(self, new_version_id, old_version_id, parents):
 
583
        """See VersionedFile.clone_text."""
 
584
        old_lines = self.get_text(old_version_id)
 
585
        self.add_lines(new_version_id, parents, old_lines)
 
586
 
511
587
    def _inclusions(self, versions):
512
588
        """Return set of all ancestors of given version(s)."""
513
589
        if not len(versions):
521
597
        ## except IndexError:
522
598
        ##     raise ValueError("version %d not present in weave" % v)
523
599
 
524
 
    def get_ancestry(self, version_ids, topo_sorted=True):
 
600
    @deprecated_method(zero_eight)
 
601
    def inclusions(self, version_ids):
 
602
        """Deprecated - see VersionedFile.get_ancestry for the replacement."""
 
603
        if not version_ids:
 
604
            return []
 
605
        if isinstance(version_ids[0], int):
 
606
            return [self._idx_to_name(v) for v in self._inclusions(version_ids)]
 
607
        else:
 
608
            return self.get_ancestry(version_ids)
 
609
 
 
610
    def get_ancestry(self, version_ids):
525
611
        """See VersionedFile.get_ancestry."""
526
612
        if isinstance(version_ids, basestring):
527
613
            version_ids = [version_ids]
556
642
        return len(other_parents.difference(my_parents)) == 0
557
643
 
558
644
    def annotate(self, version_id):
559
 
        """Return a list of (version-id, line) tuples for version_id.
 
645
        if isinstance(version_id, int):
 
646
            warnings.warn('Weave.annotate(int) is deprecated. Please use version names'
 
647
                 ' in all circumstances as of 0.8',
 
648
                 DeprecationWarning,
 
649
                 stacklevel=2
 
650
                 )
 
651
            result = []
 
652
            for origin, lineno, text in self._extract([version_id]):
 
653
                result.append((origin, text))
 
654
            return result
 
655
        else:
 
656
            return super(Weave, self).annotate(version_id)
 
657
    
 
658
    def annotate_iter(self, version_id):
 
659
        """Yield list of (version-id, line) pairs for the specified version.
560
660
 
561
661
        The index indicates when the line originated in the weave."""
562
662
        incls = [self._lookup(version_id)]
563
 
        return [(self._idx_to_name(origin), text) for origin, lineno, text in
564
 
            self._extract(incls)]
 
663
        for origin, lineno, text in self._extract(incls):
 
664
            yield self._idx_to_name(origin), text
 
665
 
 
666
    @deprecated_method(zero_eight)
 
667
    def _walk(self):
 
668
        """_walk has become visit, a supported api."""
 
669
        return self._walk_internal()
565
670
 
566
671
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
567
672
                                                pb=None):
575
680
            # properly, we do not filter down to that
576
681
            # if inserted not in version_ids: continue
577
682
            if line[-1] != '\n':
578
 
                yield line + '\n', inserted
 
683
                yield line + '\n'
579
684
            else:
580
 
                yield line, inserted
 
685
                yield line
 
686
 
 
687
    #@deprecated_method(zero_eight)
 
688
    def walk(self, version_ids=None):
 
689
        """See VersionedFile.walk."""
 
690
        return self._walk_internal(version_ids)
581
691
 
582
692
    def _walk_internal(self, version_ids=None):
583
693
        """Helper method for weave actions."""
596
706
                elif c == '}':
597
707
                    istack.pop()
598
708
                elif c == '[':
 
709
                    assert self._names[v] not in dset
599
710
                    dset.add(self._names[v])
600
711
                elif c == ']':
601
712
                    dset.remove(self._names[v])
602
713
                else:
603
714
                    raise WeaveFormatError('unexpected instruction %r' % v)
604
715
            else:
 
716
                assert l.__class__ in (str, unicode)
 
717
                assert istack
605
718
                yield lineno, istack[-1], frozenset(dset), l
606
719
            lineno += 1
607
720
 
624
737
        inc_b = set(self.get_ancestry([ver_b]))
625
738
        inc_c = inc_a & inc_b
626
739
 
627
 
        for lineno, insert, deleteset, line in self._walk_internal([ver_a, ver_b]):
 
740
        for lineno, insert, deleteset, line in\
 
741
            self.walk([ver_a, ver_b]):
628
742
            if deleteset & inc_c:
629
743
                # killed in parent; can't be in either a or b
630
744
                # not relevant to our work
656
770
                # not in either revision
657
771
                yield 'irrelevant', line
658
772
 
 
773
        yield 'unchanged', ''           # terminator
 
774
 
659
775
    def _extract(self, versions):
660
776
        """Yield annotation of lines in included set.
661
777
 
715
831
                c, v = l
716
832
                isactive = None
717
833
                if c == '{':
 
834
                    assert v not in iset
718
835
                    istack.append(v)
719
836
                    iset.add(v)
720
837
                elif c == '}':
721
838
                    iset.remove(istack.pop())
722
839
                elif c == '[':
723
840
                    if v in included:
 
841
                        assert v not in dset
724
842
                        dset.add(v)
725
 
                elif c == ']':
 
843
                else:
 
844
                    assert c == ']'
726
845
                    if v in included:
 
846
                        assert v in dset
727
847
                        dset.remove(v)
728
 
                else:
729
 
                    raise AssertionError()
730
848
            else:
 
849
                assert l.__class__ in (str, unicode)
731
850
                if isactive is None:
732
851
                    isactive = (not dset) and istack and (istack[-1] in included)
733
852
                if isactive:
741
860
                                   % dset)
742
861
        return result
743
862
 
 
863
    @deprecated_method(zero_eight)
 
864
    def get_iter(self, name_or_index):
 
865
        """Deprecated, please do not use. Lookups are not not needed.
 
866
        
 
867
        Please use get_lines now.
 
868
        """
 
869
        return iter(self.get_lines(self._maybe_lookup(name_or_index)))
 
870
 
 
871
    @deprecated_method(zero_eight)
 
872
    def maybe_lookup(self, name_or_index):
 
873
        """Deprecated, please do not use. Lookups are not not needed."""
 
874
        return self._maybe_lookup(name_or_index)
 
875
 
744
876
    def _maybe_lookup(self, name_or_index):
745
877
        """Convert possible symbolic name to index, or pass through indexes.
746
878
        
751
883
        else:
752
884
            return self._lookup(name_or_index)
753
885
 
 
886
    @deprecated_method(zero_eight)
 
887
    def get(self, version_id):
 
888
        """Please use either Weave.get_text or Weave.get_lines as desired."""
 
889
        return self.get_lines(version_id)
 
890
 
754
891
    def get_lines(self, version_id):
755
892
        """See VersionedFile.get_lines()."""
756
893
        int_index = self._maybe_lookup(version_id)
764
901
                       expected_sha1, measured_sha1))
765
902
        return result
766
903
 
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
 
904
    def get_sha1(self, version_id):
 
905
        """See VersionedFile.get_sha1()."""
 
906
        return self._sha1s[self._lookup(version_id)]
 
907
 
 
908
    @deprecated_method(zero_eight)
 
909
    def numversions(self):
 
910
        """How many versions are in this weave?
 
911
 
 
912
        Deprecated in favour of num_versions.
 
913
        """
 
914
        return self.num_versions()
773
915
 
774
916
    def num_versions(self):
775
917
        """How many versions are in this weave?"""
776
918
        l = len(self._parents)
 
919
        assert l == len(self._sha1s)
777
920
        return l
778
921
 
779
922
    __len__ = num_versions
805
948
            for p in self._parents[i]:
806
949
                new_inc.update(inclusions[self._idx_to_name(p)])
807
950
 
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))))
 
951
            assert set(new_inc) == set(self.get_ancestry(name)), \
 
952
                'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
812
953
            inclusions[name] = new_inc
813
954
 
814
955
        nlines = len(self._weave)
844
985
        # no lines outside of insertion blocks, that deletions are
845
986
        # properly paired, etc.
846
987
 
 
988
    def _join(self, other, pb, msg, version_ids, ignore_missing):
 
989
        """Worker routine for join()."""
 
990
        if not other.versions():
 
991
            return          # nothing to update, easy
 
992
 
 
993
        if not version_ids:
 
994
            # versions is never none, InterWeave checks this.
 
995
            return 0
 
996
 
 
997
        # two loops so that we do not change ourselves before verifying it
 
998
        # will be ok
 
999
        # work through in index order to make sure we get all dependencies
 
1000
        names_to_join = []
 
1001
        processed = 0
 
1002
        # get the selected versions only that are in other.versions.
 
1003
        version_ids = set(other.versions()).intersection(set(version_ids))
 
1004
        # pull in the referenced graph.
 
1005
        version_ids = other.get_ancestry(version_ids)
 
1006
        pending_graph = [(version, other.get_parents(version)) for
 
1007
                         version in version_ids]
 
1008
        for name in topo_sort(pending_graph):
 
1009
            other_idx = other._name_map[name]
 
1010
            # returns True if we have it, False if we need it.
 
1011
            if not self._check_version_consistent(other, other_idx, name):
 
1012
                names_to_join.append((other_idx, name))
 
1013
            processed += 1
 
1014
 
 
1015
 
 
1016
        if pb and not msg:
 
1017
            msg = 'weave join'
 
1018
 
 
1019
        merged = 0
 
1020
        time0 = time.time()
 
1021
        for other_idx, name in names_to_join:
 
1022
            # TODO: If all the parents of the other version are already
 
1023
            # present then we can avoid some work by just taking the delta
 
1024
            # and adjusting the offsets.
 
1025
            new_parents = self._imported_parents(other, other_idx)
 
1026
            sha1 = other._sha1s[other_idx]
 
1027
 
 
1028
            merged += 1
 
1029
 
 
1030
            if pb:
 
1031
                pb.update(msg, merged, len(names_to_join))
 
1032
           
 
1033
            lines = other.get_lines(other_idx)
 
1034
            self._add(name, lines, new_parents, sha1)
 
1035
 
 
1036
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
 
1037
                merged, processed, self._weave_name, time.time()-time0))
 
1038
 
847
1039
    def _imported_parents(self, other, other_idx):
848
1040
        """Return list of parents in self corresponding to indexes in other."""
849
1041
        new_parents = []
884
1076
        else:
885
1077
            return False
886
1078
 
 
1079
    @deprecated_method(zero_eight)
 
1080
    def reweave(self, other, pb=None, msg=None):
 
1081
        """reweave has been superseded by plain use of join."""
 
1082
        return self.join(other, pb, msg)
 
1083
 
887
1084
    def _reweave(self, other, pb, msg):
888
1085
        """Reweave self with other - internal helper for join().
889
1086
 
906
1103
 
907
1104
    WEAVE_SUFFIX = '.weave'
908
1105
    
909
 
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w', get_scope=None):
 
1106
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w'):
910
1107
        """Create a WeaveFile.
911
1108
        
912
1109
        :param create: If not True, only open an existing knit.
913
1110
        """
914
 
        super(WeaveFile, self).__init__(name, access_mode, get_scope=get_scope,
915
 
            allow_reserved=False)
 
1111
        super(WeaveFile, self).__init__(name, access_mode)
916
1112
        self._transport = transport
917
1113
        self._filemode = filemode
918
1114
        try:
923
1119
            # new file, save it
924
1120
            self._save()
925
1121
 
926
 
    def _add_lines(self, version_id, parents, lines, parent_texts,
927
 
        left_matching_blocks, nostore_sha, random_id, check_content):
 
1122
    def _add_lines(self, version_id, parents, lines, parent_texts):
928
1123
        """Add a version and save the weave."""
929
1124
        self.check_not_reserved_id(version_id)
930
1125
        result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
931
 
            parent_texts, left_matching_blocks, nostore_sha, random_id,
932
 
            check_content)
 
1126
                                                   parent_texts)
933
1127
        self._save()
934
1128
        return result
935
1129
 
 
1130
    def _clone_text(self, new_version_id, old_version_id, parents):
 
1131
        """See VersionedFile.clone_text."""
 
1132
        super(WeaveFile, self)._clone_text(new_version_id, old_version_id, parents)
 
1133
        self._save
 
1134
 
936
1135
    def copy_to(self, name, transport):
937
1136
        """See VersionedFile.copy_to()."""
938
1137
        # as we are all in memory always, just serialise to the new place.
941
1140
        sio.seek(0)
942
1141
        transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
943
1142
 
 
1143
    def create_empty(self, name, transport, filemode=None):
 
1144
        return WeaveFile(name, transport, filemode, create=True)
 
1145
 
944
1146
    def _save(self):
945
1147
        """Save the weave."""
946
1148
        self._check_write_ok()
947
1149
        sio = StringIO()
948
1150
        write_weave_v5(self, sio)
949
1151
        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)
 
1152
        self._transport.put_file(self._weave_name + WeaveFile.WEAVE_SUFFIX,
 
1153
                                 sio,
 
1154
                                 self._filemode)
957
1155
 
958
1156
    @staticmethod
959
1157
    def get_suffixes():
960
1158
        """See VersionedFile.get_suffixes()."""
961
1159
        return [WeaveFile.WEAVE_SUFFIX]
962
1160
 
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
1161
    def join(self, other, pb=None, msg=None, version_ids=None,
969
1162
             ignore_missing=False):
970
1163
        """Join other into self and save."""
972
1165
        self._save()
973
1166
 
974
1167
 
 
1168
@deprecated_function(zero_eight)
 
1169
def reweave(wa, wb, pb=None, msg=None):
 
1170
    """reweaving is deprecation, please just use weave.join()."""
 
1171
    _reweave(wa, wb, pb, msg)
 
1172
 
975
1173
def _reweave(wa, wb, pb=None, msg=None):
976
1174
    """Combine two weaves and return the result.
977
1175
 
1234
1432
if __name__ == '__main__':
1235
1433
    import sys
1236
1434
    sys.exit(main(sys.argv))
 
1435
 
 
1436
 
 
1437
class InterWeave(InterVersionedFile):
 
1438
    """Optimised code paths for weave to weave operations."""
 
1439
    
 
1440
    _matching_file_from_factory = staticmethod(WeaveFile)
 
1441
    _matching_file_to_factory = staticmethod(WeaveFile)
 
1442
    
 
1443
    @staticmethod
 
1444
    def is_compatible(source, target):
 
1445
        """Be compatible with weaves."""
 
1446
        try:
 
1447
            return (isinstance(source, Weave) and
 
1448
                    isinstance(target, Weave))
 
1449
        except AttributeError:
 
1450
            return False
 
1451
 
 
1452
    def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
 
1453
        """See InterVersionedFile.join."""
 
1454
        version_ids = self._get_source_version_ids(version_ids, ignore_missing)
 
1455
        if self.target.versions() == [] and version_ids is None:
 
1456
            self.target._copy_weave_content(self.source)
 
1457
            return
 
1458
        try:
 
1459
            self.target._join(self.source, pb, msg, version_ids, ignore_missing)
 
1460
        except errors.WeaveParentMismatch:
 
1461
            self.target._reweave(self.source, pb, msg)
 
1462
 
 
1463
 
 
1464
InterVersionedFile.register_optimiser(InterWeave)