~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-06-16 14:33:42 UTC
  • mfrom: (1770.2.1 config)
  • Revision ID: pqm@pqm.ubuntu.com-20060616143342-8f7f4a4f77c1e4c8
Use create_signature for signing policy, deprecate check_signatures for this

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
73
73
import os
74
74
import sha
75
75
import time
76
 
import warnings
77
76
 
78
 
from bzrlib import (
79
 
    progress,
80
 
    )
 
77
from bzrlib.trace import mutter
81
78
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
82
79
        RevisionAlreadyPresent,
83
80
        RevisionNotPresent,
84
 
        UnavailableRepresentation,
85
81
        WeaveRevisionAlreadyPresent,
86
82
        WeaveRevisionNotPresent,
87
83
        )
88
84
import bzrlib.errors as errors
89
 
from bzrlib.osutils import dirname, sha_strings, split_lines
 
85
from bzrlib.osutils import sha_strings
90
86
import bzrlib.patiencediff
91
 
from bzrlib.revision import NULL_REVISION
92
87
from bzrlib.symbol_versioning import *
93
 
from bzrlib.trace import mutter
94
88
from bzrlib.tsort import topo_sort
95
 
from bzrlib.versionedfile import (
96
 
    AbsentContentFactory,
97
 
    adapter_registry,
98
 
    ContentFactory,
99
 
    VersionedFile,
100
 
    )
 
89
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
101
90
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
102
91
 
103
92
 
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
93
class Weave(VersionedFile):
128
94
    """weave - versioned text file storage.
129
95
    
214
180
    """
215
181
 
216
182
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
217
 
                 '_weave_name', '_matcher', '_allow_reserved']
 
183
                 '_weave_name', '_matcher']
218
184
    
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
 
        """
 
185
    def __init__(self, weave_name=None, access_mode='w', matcher=None):
227
186
        super(Weave, self).__init__(access_mode)
228
187
        self._weave = []
229
188
        self._parents = []
235
194
            self._matcher = bzrlib.patiencediff.PatienceSequenceMatcher
236
195
        else:
237
196
            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
197
 
245
198
    def __repr__(self):
246
199
        return "Weave(%r)" % self._weave_name
247
200
 
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
201
    def copy(self):
256
202
        """Return a deep copy of self.
257
203
        
275
221
    def __ne__(self, other):
276
222
        return not self.__eq__(other)
277
223
 
 
224
    @deprecated_method(zero_eight)
 
225
    def idx_to_name(self, index):
 
226
        """Old public interface, the public interface is all names now."""
 
227
        return index
 
228
 
278
229
    def _idx_to_name(self, version):
279
230
        return self._names[version]
280
231
 
 
232
    @deprecated_method(zero_eight)
 
233
    def lookup(self, name):
 
234
        """Backwards compatibility thunk:
 
235
 
 
236
        Return name, as name is valid in the api now, and spew deprecation
 
237
        warnings everywhere.
 
238
        """
 
239
        return name
 
240
 
281
241
    def _lookup(self, name):
282
242
        """Convert symbolic version name to index."""
283
 
        if not self._allow_reserved:
284
 
            self.check_not_reserved_id(name)
285
243
        try:
286
244
            return self._name_map[name]
287
245
        except KeyError:
288
246
            raise RevisionNotPresent(name, self._weave_name)
289
247
 
 
248
    @deprecated_method(zero_eight)
 
249
    def iter_names(self):
 
250
        """Deprecated convenience function, please see VersionedFile.names()."""
 
251
        return iter(self.names())
 
252
 
 
253
    @deprecated_method(zero_eight)
 
254
    def names(self):
 
255
        """See Weave.versions for the current api."""
 
256
        return self.versions()
 
257
 
290
258
    def versions(self):
291
259
        """See VersionedFile.versions."""
292
260
        return self._names[:]
293
261
 
294
262
    def has_version(self, version_id):
295
263
        """See VersionedFile.has_version."""
296
 
        return (version_id in self._name_map)
 
264
        return self._name_map.has_key(version_id)
297
265
 
298
266
    __contains__ = has_version
299
267
 
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 = {}
 
268
    def get_delta(self, version_id):
 
269
        """See VersionedFile.get_delta."""
 
270
        return self.get_deltas([version_id])[version_id]
 
271
 
 
272
    def get_deltas(self, version_ids):
 
273
        """See VersionedFile.get_deltas."""
 
274
        version_ids = self.get_ancestry(version_ids)
328
275
        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:
 
276
            if not self.has_version(version_id):
 
277
                raise RevisionNotPresent(version_id, self)
 
278
        # try extracting all versions; parallel extraction is used
 
279
        nv = self.num_versions()
 
280
        sha1s = {}
 
281
        deltas = {}
 
282
        texts = {}
 
283
        inclusions = {}
 
284
        noeols = {}
 
285
        last_parent_lines = {}
 
286
        parents = {}
 
287
        parent_inclusions = {}
 
288
        parent_linenums = {}
 
289
        parent_noeols = {}
 
290
        current_hunks = {}
 
291
        diff_hunks = {}
 
292
        # its simplest to generate a full set of prepared variables.
 
293
        for i in range(nv):
 
294
            name = self._names[i]
 
295
            sha1s[name] = self.get_sha1(name)
 
296
            parents_list = self.get_parents(name)
 
297
            try:
 
298
                parent = parents_list[0]
 
299
                parents[name] = parent
 
300
                parent_inclusions[name] = inclusions[parent]
 
301
            except IndexError:
 
302
                parents[name] = None
 
303
                parent_inclusions[name] = set()
 
304
            # we want to emit start, finish, replacement_length, replacement_lines tuples.
 
305
            diff_hunks[name] = []
 
306
            current_hunks[name] = [0, 0, 0, []] # #start, finish, repl_length, repl_tuples
 
307
            parent_linenums[name] = 0
 
308
            noeols[name] = False
 
309
            parent_noeols[name] = False
 
310
            last_parent_lines[name] = None
 
311
            new_inc = set([name])
 
312
            for p in self._parents[i]:
 
313
                new_inc.update(inclusions[self._idx_to_name(p)])
 
314
            # debug only, known good so far.
 
315
            #assert set(new_inc) == set(self.get_ancestry(name)), \
 
316
            #    'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
 
317
            inclusions[name] = new_inc
 
318
 
 
319
        nlines = len(self._weave)
 
320
 
 
321
        for lineno, inserted, deletes, line in self._walk_internal():
 
322
            # a line is active in a version if:
 
323
            # insert is in the versions inclusions
 
324
            # and
 
325
            # deleteset & the versions inclusions is an empty set.
 
326
            # so - if we have a included by mapping - version is included by
 
327
            # children, we get a list of children to examine for deletes affect
 
328
            # ing them, which is less than the entire set of children.
 
329
            for version_id in version_ids:  
 
330
                # The active inclusion must be an ancestor,
 
331
                # and no ancestors must have deleted this line,
 
332
                # because we don't support resurrection.
 
333
                parent_inclusion = parent_inclusions[version_id]
 
334
                inclusion = inclusions[version_id]
 
335
                parent_active = inserted in parent_inclusion and not (deletes & parent_inclusion)
 
336
                version_active = inserted in inclusion and not (deletes & inclusion)
 
337
                if not parent_active and not version_active:
 
338
                    # unrelated line of ancestry
337
339
                    continue
338
 
            result[version_id] = parents
 
340
                elif parent_active and version_active:
 
341
                    # shared line
 
342
                    parent_linenum = parent_linenums[version_id]
 
343
                    if current_hunks[version_id] != [parent_linenum, parent_linenum, 0, []]:
 
344
                        diff_hunks[version_id].append(tuple(current_hunks[version_id]))
 
345
                    parent_linenum += 1
 
346
                    current_hunks[version_id] = [parent_linenum, parent_linenum, 0, []]
 
347
                    parent_linenums[version_id] = parent_linenum
 
348
                    try:
 
349
                        if line[-1] != '\n':
 
350
                            noeols[version_id] = True
 
351
                    except IndexError:
 
352
                        pass
 
353
                elif parent_active and not version_active:
 
354
                    # deleted line
 
355
                    current_hunks[version_id][1] += 1
 
356
                    parent_linenums[version_id] += 1
 
357
                    last_parent_lines[version_id] = line
 
358
                elif not parent_active and version_active:
 
359
                    # replacement line
 
360
                    # noeol only occurs at the end of a file because we 
 
361
                    # diff linewise. We want to show noeol changes as a
 
362
                    # empty diff unless the actual eol-less content changed.
 
363
                    theline = line
 
364
                    try:
 
365
                        if last_parent_lines[version_id][-1] != '\n':
 
366
                            parent_noeols[version_id] = True
 
367
                    except (TypeError, IndexError):
 
368
                        pass
 
369
                    try:
 
370
                        if theline[-1] != '\n':
 
371
                            noeols[version_id] = True
 
372
                    except IndexError:
 
373
                        pass
 
374
                    new_line = False
 
375
                    parent_should_go = False
 
376
 
 
377
                    if parent_noeols[version_id] == noeols[version_id]:
 
378
                        # no noeol toggle, so trust the weaves statement
 
379
                        # that this line is changed.
 
380
                        new_line = True
 
381
                        if parent_noeols[version_id]:
 
382
                            theline = theline + '\n'
 
383
                    elif parent_noeols[version_id]:
 
384
                        # parent has no eol, we do:
 
385
                        # our line is new, report as such..
 
386
                        new_line = True
 
387
                    elif noeols[version_id]:
 
388
                        # append a eol so that it looks like
 
389
                        # a normalised delta
 
390
                        theline = theline + '\n'
 
391
                        if parents[version_id] is not None:
 
392
                        #if last_parent_lines[version_id] is not None:
 
393
                            parent_should_go = True
 
394
                        if last_parent_lines[version_id] != theline:
 
395
                            # but changed anyway
 
396
                            new_line = True
 
397
                            #parent_should_go = False
 
398
                    if new_line:
 
399
                        current_hunks[version_id][2] += 1
 
400
                        current_hunks[version_id][3].append((inserted, theline))
 
401
                    if parent_should_go:
 
402
                        # last hunk last parent line is not eaten
 
403
                        current_hunks[version_id][1] -= 1
 
404
                    if current_hunks[version_id][1] < 0:
 
405
                        current_hunks[version_id][1] = 0
 
406
                        # import pdb;pdb.set_trace()
 
407
                    # assert current_hunks[version_id][1] >= 0
 
408
 
 
409
        # flush last hunk
 
410
        for i in range(nv):
 
411
            version = self._idx_to_name(i)
 
412
            if current_hunks[version] != [0, 0, 0, []]:
 
413
                diff_hunks[version].append(tuple(current_hunks[version]))
 
414
        result = {}
 
415
        for version_id in version_ids:
 
416
            result[version_id] = (
 
417
                                  parents[version_id],
 
418
                                  sha1s[version_id],
 
419
                                  noeols[version_id],
 
420
                                  diff_hunks[version_id],
 
421
                                  )
339
422
        return result
340
423
 
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
 
424
    def get_parents(self, version_id):
 
425
        """See VersionedFile.get_parent."""
 
426
        return map(self._idx_to_name, self._parents[self._lookup(version_id)])
375
427
 
376
428
    def _check_repeated_add(self, name, parents, text, sha1):
377
429
        """Check that a duplicated add is OK.
384
436
            raise RevisionAlreadyPresent(name, self._weave_name)
385
437
        return idx
386
438
 
387
 
    def _add_lines(self, version_id, parents, lines, parent_texts,
388
 
       left_matching_blocks, nostore_sha, random_id, check_content):
 
439
    @deprecated_method(zero_eight)
 
440
    def add_identical(self, old_rev_id, new_rev_id, parents):
 
441
        """Please use Weave.clone_text now."""
 
442
        return self.clone_text(new_rev_id, old_rev_id, parents)
 
443
 
 
444
    def _add_lines(self, version_id, parents, lines, parent_texts):
389
445
        """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):
 
446
        return self._add(version_id, lines, map(self._lookup, parents))
 
447
 
 
448
    @deprecated_method(zero_eight)
 
449
    def add(self, name, parents, text, sha1=None):
 
450
        """See VersionedFile.add_lines for the non deprecated api."""
 
451
        return self._add(name, text, map(self._maybe_lookup, parents), sha1)
 
452
 
 
453
    def _add(self, version_id, lines, parents, sha1=None):
395
454
        """Add a single text on top of the weave.
396
455
  
397
456
        Returns the index number of the newly added version.
405
464
            
406
465
        lines
407
466
            Sequence of lines to be added in the new version.
 
467
        """
408
468
 
409
 
        :param nostore_sha: See VersionedFile.add_lines.
410
 
        """
 
469
        assert isinstance(version_id, basestring)
411
470
        self._check_lines_not_unicode(lines)
412
471
        self._check_lines_are_lines(lines)
413
472
        if not sha1:
414
473
            sha1 = sha_strings(lines)
415
 
        if sha1 == nostore_sha:
416
 
            raise errors.ExistingContent
417
474
        if version_id in self._name_map:
418
475
            return self._check_repeated_add(version_id, parents, lines, sha1)
419
476
 
463
520
        # another small special case: a merge, producing the same text
464
521
        # as auto-merge
465
522
        if lines == basis_lines:
466
 
            return new_version
 
523
            return new_version            
467
524
 
468
525
        # add a sentinel, because we can also match against the final line
469
526
        basis_lineno.append(len(self._weave))
488
545
            #print 'raw match', tag, i1, i2, j1, j2
489
546
            if tag == 'equal':
490
547
                continue
 
548
 
491
549
            i1 = basis_lineno[i1]
492
550
            i2 = basis_lineno[i2]
 
551
 
 
552
            assert 0 <= j1 <= j2 <= len(lines)
 
553
 
 
554
            #print tag, i1, i2, j1, j2
 
555
 
493
556
            # the deletion and insertion are handled separately.
494
557
            # first delete the region.
495
558
            if i1 != i2:
508
571
                offset += 2 + (j2 - j1)
509
572
        return new_version
510
573
 
 
574
    def _clone_text(self, new_version_id, old_version_id, parents):
 
575
        """See VersionedFile.clone_text."""
 
576
        old_lines = self.get_text(old_version_id)
 
577
        self.add_lines(new_version_id, parents, old_lines)
 
578
 
511
579
    def _inclusions(self, versions):
512
580
        """Return set of all ancestors of given version(s)."""
513
581
        if not len(versions):
521
589
        ## except IndexError:
522
590
        ##     raise ValueError("version %d not present in weave" % v)
523
591
 
524
 
    def get_ancestry(self, version_ids, topo_sorted=True):
 
592
    @deprecated_method(zero_eight)
 
593
    def inclusions(self, version_ids):
 
594
        """Deprecated - see VersionedFile.get_ancestry for the replacement."""
 
595
        if not version_ids:
 
596
            return []
 
597
        if isinstance(version_ids[0], int):
 
598
            return [self._idx_to_name(v) for v in self._inclusions(version_ids)]
 
599
        else:
 
600
            return self.get_ancestry(version_ids)
 
601
 
 
602
    def get_ancestry(self, version_ids):
525
603
        """See VersionedFile.get_ancestry."""
526
604
        if isinstance(version_ids, basestring):
527
605
            version_ids = [version_ids]
556
634
        return len(other_parents.difference(my_parents)) == 0
557
635
 
558
636
    def annotate(self, version_id):
559
 
        """Return a list of (version-id, line) tuples for version_id.
 
637
        if isinstance(version_id, int):
 
638
            warn('Weave.annotate(int) is deprecated. Please use version names'
 
639
                 ' in all circumstances as of 0.8',
 
640
                 DeprecationWarning,
 
641
                 stacklevel=2
 
642
                 )
 
643
            result = []
 
644
            for origin, lineno, text in self._extract([version_id]):
 
645
                result.append((origin, text))
 
646
            return result
 
647
        else:
 
648
            return super(Weave, self).annotate(version_id)
 
649
    
 
650
    def annotate_iter(self, version_id):
 
651
        """Yield list of (version-id, line) pairs for the specified version.
560
652
 
561
653
        The index indicates when the line originated in the weave."""
562
654
        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):
 
655
        for origin, lineno, text in self._extract(incls):
 
656
            yield self._idx_to_name(origin), text
 
657
 
 
658
    @deprecated_method(zero_eight)
 
659
    def _walk(self):
 
660
        """_walk has become visit, a supported api."""
 
661
        return self._walk_internal()
 
662
 
 
663
    def iter_lines_added_or_present_in_versions(self, version_ids=None):
568
664
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
569
665
        if version_ids is None:
570
666
            version_ids = self.versions()
575
671
            # properly, we do not filter down to that
576
672
            # if inserted not in version_ids: continue
577
673
            if line[-1] != '\n':
578
 
                yield line + '\n', inserted
 
674
                yield line + '\n'
579
675
            else:
580
 
                yield line, inserted
 
676
                yield line
 
677
 
 
678
    #@deprecated_method(zero_eight)
 
679
    def walk(self, version_ids=None):
 
680
        """See VersionedFile.walk."""
 
681
        return self._walk_internal(version_ids)
581
682
 
582
683
    def _walk_internal(self, version_ids=None):
583
684
        """Helper method for weave actions."""
596
697
                elif c == '}':
597
698
                    istack.pop()
598
699
                elif c == '[':
 
700
                    assert self._names[v] not in dset
599
701
                    dset.add(self._names[v])
600
702
                elif c == ']':
601
703
                    dset.remove(self._names[v])
602
704
                else:
603
705
                    raise WeaveFormatError('unexpected instruction %r' % v)
604
706
            else:
 
707
                assert l.__class__ in (str, unicode)
 
708
                assert istack
605
709
                yield lineno, istack[-1], frozenset(dset), l
606
710
            lineno += 1
607
711
 
624
728
        inc_b = set(self.get_ancestry([ver_b]))
625
729
        inc_c = inc_a & inc_b
626
730
 
627
 
        for lineno, insert, deleteset, line in self._walk_internal([ver_a, ver_b]):
 
731
        for lineno, insert, deleteset, line in\
 
732
            self.walk([ver_a, ver_b]):
628
733
            if deleteset & inc_c:
629
734
                # killed in parent; can't be in either a or b
630
735
                # not relevant to our work
656
761
                # not in either revision
657
762
                yield 'irrelevant', line
658
763
 
 
764
        yield 'unchanged', ''           # terminator
 
765
 
659
766
    def _extract(self, versions):
660
767
        """Yield annotation of lines in included set.
661
768
 
715
822
                c, v = l
716
823
                isactive = None
717
824
                if c == '{':
 
825
                    assert v not in iset
718
826
                    istack.append(v)
719
827
                    iset.add(v)
720
828
                elif c == '}':
721
829
                    iset.remove(istack.pop())
722
830
                elif c == '[':
723
831
                    if v in included:
 
832
                        assert v not in dset
724
833
                        dset.add(v)
725
 
                elif c == ']':
 
834
                else:
 
835
                    assert c == ']'
726
836
                    if v in included:
 
837
                        assert v in dset
727
838
                        dset.remove(v)
728
 
                else:
729
 
                    raise AssertionError()
730
839
            else:
 
840
                assert l.__class__ in (str, unicode)
731
841
                if isactive is None:
732
842
                    isactive = (not dset) and istack and (istack[-1] in included)
733
843
                if isactive:
741
851
                                   % dset)
742
852
        return result
743
853
 
 
854
    @deprecated_method(zero_eight)
 
855
    def get_iter(self, name_or_index):
 
856
        """Deprecated, please do not use. Lookups are not not needed.
 
857
        
 
858
        Please use get_lines now.
 
859
        """
 
860
        return iter(self.get_lines(self._maybe_lookup(name_or_index)))
 
861
 
 
862
    @deprecated_method(zero_eight)
 
863
    def maybe_lookup(self, name_or_index):
 
864
        """Deprecated, please do not use. Lookups are not not needed."""
 
865
        return self._maybe_lookup(name_or_index)
 
866
 
744
867
    def _maybe_lookup(self, name_or_index):
745
868
        """Convert possible symbolic name to index, or pass through indexes.
746
869
        
751
874
        else:
752
875
            return self._lookup(name_or_index)
753
876
 
 
877
    @deprecated_method(zero_eight)
 
878
    def get(self, version_id):
 
879
        """Please use either Weave.get_text or Weave.get_lines as desired."""
 
880
        return self.get_lines(version_id)
 
881
 
754
882
    def get_lines(self, version_id):
755
883
        """See VersionedFile.get_lines()."""
756
884
        int_index = self._maybe_lookup(version_id)
764
892
                       expected_sha1, measured_sha1))
765
893
        return result
766
894
 
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
 
895
    def get_sha1(self, version_id):
 
896
        """See VersionedFile.get_sha1()."""
 
897
        return self._sha1s[self._lookup(version_id)]
 
898
 
 
899
    @deprecated_method(zero_eight)
 
900
    def numversions(self):
 
901
        """How many versions are in this weave?
 
902
 
 
903
        Deprecated in favour of num_versions.
 
904
        """
 
905
        return self.num_versions()
773
906
 
774
907
    def num_versions(self):
775
908
        """How many versions are in this weave?"""
776
909
        l = len(self._parents)
 
910
        assert l == len(self._sha1s)
777
911
        return l
778
912
 
779
913
    __len__ = num_versions
805
939
            for p in self._parents[i]:
806
940
                new_inc.update(inclusions[self._idx_to_name(p)])
807
941
 
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))))
 
942
            assert set(new_inc) == set(self.get_ancestry(name)), \
 
943
                'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
812
944
            inclusions[name] = new_inc
813
945
 
814
946
        nlines = len(self._weave)
844
976
        # no lines outside of insertion blocks, that deletions are
845
977
        # properly paired, etc.
846
978
 
 
979
    def _join(self, other, pb, msg, version_ids, ignore_missing):
 
980
        """Worker routine for join()."""
 
981
        if not other.versions():
 
982
            return          # nothing to update, easy
 
983
 
 
984
        if not version_ids:
 
985
            # versions is never none, InterWeave checks this.
 
986
            return 0
 
987
 
 
988
        # two loops so that we do not change ourselves before verifying it
 
989
        # will be ok
 
990
        # work through in index order to make sure we get all dependencies
 
991
        names_to_join = []
 
992
        processed = 0
 
993
        # get the selected versions only that are in other.versions.
 
994
        version_ids = set(other.versions()).intersection(set(version_ids))
 
995
        # pull in the referenced graph.
 
996
        version_ids = other.get_ancestry(version_ids)
 
997
        pending_graph = [(version, other.get_parents(version)) for
 
998
                         version in version_ids]
 
999
        for name in topo_sort(pending_graph):
 
1000
            other_idx = other._name_map[name]
 
1001
            # returns True if we have it, False if we need it.
 
1002
            if not self._check_version_consistent(other, other_idx, name):
 
1003
                names_to_join.append((other_idx, name))
 
1004
            processed += 1
 
1005
 
 
1006
 
 
1007
        if pb and not msg:
 
1008
            msg = 'weave join'
 
1009
 
 
1010
        merged = 0
 
1011
        time0 = time.time()
 
1012
        for other_idx, name in names_to_join:
 
1013
            # TODO: If all the parents of the other version are already
 
1014
            # present then we can avoid some work by just taking the delta
 
1015
            # and adjusting the offsets.
 
1016
            new_parents = self._imported_parents(other, other_idx)
 
1017
            sha1 = other._sha1s[other_idx]
 
1018
 
 
1019
            merged += 1
 
1020
 
 
1021
            if pb:
 
1022
                pb.update(msg, merged, len(names_to_join))
 
1023
           
 
1024
            lines = other.get_lines(other_idx)
 
1025
            self._add(name, lines, new_parents, sha1)
 
1026
 
 
1027
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
 
1028
                merged, processed, self._weave_name, time.time()-time0))
 
1029
 
847
1030
    def _imported_parents(self, other, other_idx):
848
1031
        """Return list of parents in self corresponding to indexes in other."""
849
1032
        new_parents = []
884
1067
        else:
885
1068
            return False
886
1069
 
 
1070
    @deprecated_method(zero_eight)
 
1071
    def reweave(self, other, pb=None, msg=None):
 
1072
        """reweave has been superseded by plain use of join."""
 
1073
        return self.join(other, pb, msg)
 
1074
 
887
1075
    def _reweave(self, other, pb, msg):
888
1076
        """Reweave self with other - internal helper for join().
889
1077
 
906
1094
 
907
1095
    WEAVE_SUFFIX = '.weave'
908
1096
    
909
 
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w', get_scope=None):
 
1097
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w'):
910
1098
        """Create a WeaveFile.
911
1099
        
912
1100
        :param create: If not True, only open an existing knit.
913
1101
        """
914
 
        super(WeaveFile, self).__init__(name, access_mode, get_scope=get_scope,
915
 
            allow_reserved=False)
 
1102
        super(WeaveFile, self).__init__(name, access_mode)
916
1103
        self._transport = transport
917
1104
        self._filemode = filemode
918
1105
        try:
923
1110
            # new file, save it
924
1111
            self._save()
925
1112
 
926
 
    def _add_lines(self, version_id, parents, lines, parent_texts,
927
 
        left_matching_blocks, nostore_sha, random_id, check_content):
 
1113
    def _add_lines(self, version_id, parents, lines, parent_texts):
928
1114
        """Add a version and save the weave."""
929
 
        self.check_not_reserved_id(version_id)
930
1115
        result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
931
 
            parent_texts, left_matching_blocks, nostore_sha, random_id,
932
 
            check_content)
 
1116
                                                   parent_texts)
933
1117
        self._save()
934
1118
        return result
935
1119
 
 
1120
    def _clone_text(self, new_version_id, old_version_id, parents):
 
1121
        """See VersionedFile.clone_text."""
 
1122
        super(WeaveFile, self)._clone_text(new_version_id, old_version_id, parents)
 
1123
        self._save
 
1124
 
936
1125
    def copy_to(self, name, transport):
937
1126
        """See VersionedFile.copy_to()."""
938
1127
        # as we are all in memory always, just serialise to the new place.
939
1128
        sio = StringIO()
940
1129
        write_weave_v5(self, sio)
941
1130
        sio.seek(0)
942
 
        transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
 
1131
        transport.put(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
 
1132
 
 
1133
    def create_empty(self, name, transport, filemode=None):
 
1134
        return WeaveFile(name, transport, filemode, create=True)
943
1135
 
944
1136
    def _save(self):
945
1137
        """Save the weave."""
947
1139
        sio = StringIO()
948
1140
        write_weave_v5(self, sio)
949
1141
        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)
 
1142
        self._transport.put(self._weave_name + WeaveFile.WEAVE_SUFFIX,
 
1143
                            sio,
 
1144
                            self._filemode)
957
1145
 
958
1146
    @staticmethod
959
1147
    def get_suffixes():
960
1148
        """See VersionedFile.get_suffixes()."""
961
1149
        return [WeaveFile.WEAVE_SUFFIX]
962
1150
 
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
1151
    def join(self, other, pb=None, msg=None, version_ids=None,
969
1152
             ignore_missing=False):
970
1153
        """Join other into self and save."""
972
1155
        self._save()
973
1156
 
974
1157
 
 
1158
@deprecated_function(zero_eight)
 
1159
def reweave(wa, wb, pb=None, msg=None):
 
1160
    """reweaving is deprecation, please just use weave.join()."""
 
1161
    _reweave(wa, wb, pb, msg)
 
1162
 
975
1163
def _reweave(wa, wb, pb=None, msg=None):
976
1164
    """Combine two weaves and return the result.
977
1165
 
1051
1239
    from bzrlib.weavefile import read_weave
1052
1240
 
1053
1241
    wf = file(weave_file, 'rb')
1054
 
    w = read_weave(wf)
 
1242
    w = read_weave(wf, WeaveVersionedFile)
1055
1243
    # FIXME: doesn't work on pipes
1056
1244
    weave_size = wf.tell()
1057
1245
 
1231
1419
        raise ValueError('unknown command %r' % cmd)
1232
1420
    
1233
1421
 
 
1422
 
 
1423
def profile_main(argv):
 
1424
    import tempfile, hotshot, hotshot.stats
 
1425
 
 
1426
    prof_f = tempfile.NamedTemporaryFile()
 
1427
 
 
1428
    prof = hotshot.Profile(prof_f.name)
 
1429
 
 
1430
    ret = prof.runcall(main, argv)
 
1431
    prof.close()
 
1432
 
 
1433
    stats = hotshot.stats.load(prof_f.name)
 
1434
    #stats.strip_dirs()
 
1435
    stats.sort_stats('cumulative')
 
1436
    ## XXX: Might like to write to stderr or the trace file instead but
 
1437
    ## print_stats seems hardcoded to stdout
 
1438
    stats.print_stats(20)
 
1439
            
 
1440
    return ret
 
1441
 
 
1442
 
 
1443
def lsprofile_main(argv): 
 
1444
    from bzrlib.lsprof import profile
 
1445
    ret,stats = profile(main, argv)
 
1446
    stats.sort()
 
1447
    stats.pprint()
 
1448
    return ret
 
1449
 
 
1450
 
1234
1451
if __name__ == '__main__':
1235
1452
    import sys
1236
 
    sys.exit(main(sys.argv))
 
1453
    if '--profile' in sys.argv:
 
1454
        args = sys.argv[:]
 
1455
        args.remove('--profile')
 
1456
        sys.exit(profile_main(args))
 
1457
    elif '--lsprof' in sys.argv:
 
1458
        args = sys.argv[:]
 
1459
        args.remove('--lsprof')
 
1460
        sys.exit(lsprofile_main(args))
 
1461
    else:
 
1462
        sys.exit(main(sys.argv))
 
1463
 
 
1464
 
 
1465
class InterWeave(InterVersionedFile):
 
1466
    """Optimised code paths for weave to weave operations."""
 
1467
    
 
1468
    _matching_file_from_factory = staticmethod(WeaveFile)
 
1469
    _matching_file_to_factory = staticmethod(WeaveFile)
 
1470
    
 
1471
    @staticmethod
 
1472
    def is_compatible(source, target):
 
1473
        """Be compatible with weaves."""
 
1474
        try:
 
1475
            return (isinstance(source, Weave) and
 
1476
                    isinstance(target, Weave))
 
1477
        except AttributeError:
 
1478
            return False
 
1479
 
 
1480
    def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
 
1481
        """See InterVersionedFile.join."""
 
1482
        version_ids = self._get_source_version_ids(version_ids, ignore_missing)
 
1483
        if self.target.versions() == [] and version_ids is None:
 
1484
            self.target._copy_weave_content(self.source)
 
1485
            return
 
1486
        try:
 
1487
            self.target._join(self.source, pb, msg, version_ids, ignore_missing)
 
1488
        except errors.WeaveParentMismatch:
 
1489
            self.target._reweave(self.source, pb, msg)
 
1490
 
 
1491
 
 
1492
InterVersionedFile.register_optimiser(InterWeave)