~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Ian Clatworthy
  • Date: 2007-03-22 01:02:07 UTC
  • mto: (2370.2.1 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 2371.
  • Revision ID: ian.clatworthy@internode.on.net-20070322010207-r8xcwjshipoahmcx
Minor corrections to HACKING

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2009 Canonical Ltd
 
1
#! /usr/bin/python
 
2
 
 
3
# Copyright (C) 2005 Canonical Ltd
2
4
#
3
5
# This program is free software; you can redistribute it and/or modify
4
6
# it under the terms of the GNU General Public License as published by
12
14
#
13
15
# You should have received a copy of the GNU General Public License
14
16
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
18
 
17
19
# Author: Martin Pool <mbp@canonical.com>
18
20
 
 
21
 
19
22
"""Weave - storage of related text file versions"""
20
23
 
21
 
from __future__ import absolute_import
22
24
 
23
25
# XXX: If we do weaves this way, will a merge still behave the same
24
26
# way if it's done in a different order?  That's a pretty desirable
59
61
# where the basis and destination are unchanged.
60
62
 
61
63
# FIXME: Sometimes we will be given a parents list for a revision
62
 
# that includes some redundant parents (i.e. already a parent of
63
 
# something in the list.)  We should eliminate them.  This can
 
64
# that includes some redundant parents (i.e. already a parent of 
 
65
# something in the list.)  We should eliminate them.  This can 
64
66
# be done fairly efficiently because the sequence numbers constrain
65
67
# the possible relationships.
66
68
 
69
71
from copy import copy
70
72
from cStringIO import StringIO
71
73
import os
 
74
import sha
 
75
import time
 
76
import warnings
72
77
 
73
 
from bzrlib.lazy_import import lazy_import
74
 
lazy_import(globals(), """
75
 
from bzrlib import tsort
76
 
""")
77
78
from bzrlib import (
78
 
    errors,
79
 
    osutils,
 
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
        WeaveRevisionAlreadyPresent,
 
86
        WeaveRevisionNotPresent,
85
87
        )
86
 
from bzrlib.osutils import dirname, sha, sha_strings, split_lines
 
88
import bzrlib.errors as errors
 
89
from bzrlib.osutils import sha_strings
87
90
import bzrlib.patiencediff
88
 
from bzrlib.revision import NULL_REVISION
89
 
from bzrlib.symbol_versioning import *
90
 
from bzrlib.trace import mutter
91
 
from bzrlib.versionedfile import (
92
 
    AbsentContentFactory,
93
 
    adapter_registry,
94
 
    ContentFactory,
95
 
    sort_groupcompress,
96
 
    VersionedFile,
97
 
    )
 
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
98
97
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
99
98
 
100
99
 
101
 
class WeaveContentFactory(ContentFactory):
102
 
    """Content factory for streaming from weaves.
103
 
 
104
 
    :seealso ContentFactory:
105
 
    """
106
 
 
107
 
    def __init__(self, version, weave):
108
 
        """Create a WeaveContentFactory for version from weave."""
109
 
        ContentFactory.__init__(self)
110
 
        self.sha1 = weave.get_sha1s([version])[version]
111
 
        self.key = (version,)
112
 
        parents = weave.get_parent_map([version])[version]
113
 
        self.parents = tuple((parent,) for parent in parents)
114
 
        self.storage_kind = 'fulltext'
115
 
        self._weave = weave
116
 
 
117
 
    def get_bytes_as(self, storage_kind):
118
 
        if storage_kind == 'fulltext':
119
 
            return self._weave.get_text(self.key[-1])
120
 
        elif storage_kind == 'chunked':
121
 
            return self._weave.get_lines(self.key[-1])
122
 
        else:
123
 
            raise UnavailableRepresentation(self.key, storage_kind, 'fulltext')
124
 
 
125
 
 
126
100
class Weave(VersionedFile):
127
101
    """weave - versioned text file storage.
128
 
 
 
102
    
129
103
    A Weave manages versions of line-based text files, keeping track
130
104
    of the originating version for each line.
131
105
 
177
151
 
178
152
    * It doesn't seem very useful to have an active insertion
179
153
      inside an inactive insertion, but it might happen.
180
 
 
 
154
      
181
155
    * Therefore, all instructions are always"considered"; that
182
156
      is passed onto and off the stack.  An outer inactive block
183
157
      doesn't disable an inner block.
213
187
    """
214
188
 
215
189
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
216
 
                 '_weave_name', '_matcher', '_allow_reserved']
217
 
 
218
 
    def __init__(self, weave_name=None, access_mode='w', matcher=None,
219
 
                 get_scope=None, allow_reserved=False):
220
 
        """Create a weave.
221
 
 
222
 
        :param get_scope: A callable that returns an opaque object to be used
223
 
            for detecting when this weave goes out of scope (should stop
224
 
            answering requests or allowing mutation).
225
 
        """
226
 
        super(Weave, self).__init__()
 
190
                 '_weave_name', '_matcher']
 
191
    
 
192
    def __init__(self, weave_name=None, access_mode='w', matcher=None):
 
193
        super(Weave, self).__init__(access_mode)
227
194
        self._weave = []
228
195
        self._parents = []
229
196
        self._sha1s = []
234
201
            self._matcher = bzrlib.patiencediff.PatienceSequenceMatcher
235
202
        else:
236
203
            self._matcher = matcher
237
 
        if get_scope is None:
238
 
            get_scope = lambda:None
239
 
        self._get_scope = get_scope
240
 
        self._scope = get_scope()
241
 
        self._access_mode = access_mode
242
 
        self._allow_reserved = allow_reserved
243
204
 
244
205
    def __repr__(self):
245
206
        return "Weave(%r)" % self._weave_name
246
207
 
247
 
    def _check_write_ok(self):
248
 
        """Is the versioned file marked as 'finished' ? Raise if it is."""
249
 
        if self._get_scope() != self._scope:
250
 
            raise errors.OutSideTransaction()
251
 
        if self._access_mode != 'w':
252
 
            raise errors.ReadOnlyObjectDirtiedError(self)
253
 
 
254
208
    def copy(self):
255
209
        """Return a deep copy of self.
256
 
 
 
210
        
257
211
        The copy can be modified without affecting the original weave."""
258
212
        other = Weave()
259
213
        other._weave = self._weave[:]
269
223
            return False
270
224
        return self._parents == other._parents \
271
225
               and self._weave == other._weave \
272
 
               and self._sha1s == other._sha1s
273
 
 
 
226
               and self._sha1s == other._sha1s 
 
227
    
274
228
    def __ne__(self, other):
275
229
        return not self.__eq__(other)
276
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
 
277
236
    def _idx_to_name(self, version):
278
237
        return self._names[version]
279
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
 
280
248
    def _lookup(self, name):
281
249
        """Convert symbolic version name to index."""
282
 
        if not self._allow_reserved:
283
 
            self.check_not_reserved_id(name)
 
250
        self.check_not_reserved_id(name)
284
251
        try:
285
252
            return self._name_map[name]
286
253
        except KeyError:
287
254
            raise RevisionNotPresent(name, self._weave_name)
288
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
 
289
266
    def versions(self):
290
267
        """See VersionedFile.versions."""
291
268
        return self._names[:]
296
273
 
297
274
    __contains__ = has_version
298
275
 
299
 
    def get_record_stream(self, versions, ordering, include_delta_closure):
300
 
        """Get a stream of records for versions.
301
 
 
302
 
        :param versions: The versions to include. Each version is a tuple
303
 
            (version,).
304
 
        :param ordering: Either 'unordered' or 'topological'. A topologically
305
 
            sorted stream has compression parents strictly before their
306
 
            children.
307
 
        :param include_delta_closure: If True then the closure across any
308
 
            compression parents will be included (in the opaque data).
309
 
        :return: An iterator of ContentFactory objects, each of which is only
310
 
            valid until the iterator is advanced.
311
 
        """
312
 
        versions = [version[-1] for version in versions]
313
 
        if ordering == 'topological':
314
 
            parents = self.get_parent_map(versions)
315
 
            new_versions = tsort.topo_sort(parents)
316
 
            new_versions.extend(set(versions).difference(set(parents)))
317
 
            versions = new_versions
318
 
        elif ordering == 'groupcompress':
319
 
            parents = self.get_parent_map(versions)
320
 
            new_versions = sort_groupcompress(parents)
321
 
            new_versions.extend(set(versions).difference(set(parents)))
322
 
            versions = new_versions
323
 
        for version in versions:
324
 
            if version in self:
325
 
                yield WeaveContentFactory(version, self)
326
 
            else:
327
 
                yield AbsentContentFactory((version,))
328
 
 
329
 
    def get_parent_map(self, version_ids):
330
 
        """See VersionedFile.get_parent_map."""
331
 
        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)
332
283
        for version_id in version_ids:
333
 
            if version_id == NULL_REVISION:
334
 
                parents = ()
335
 
            else:
336
 
                try:
337
 
                    parents = tuple(
338
 
                        map(self._idx_to_name,
339
 
                            self._parents[self._lookup(version_id)]))
340
 
                except RevisionNotPresent:
 
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
341
347
                    continue
342
 
            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
                                  )
343
430
        return result
344
431
 
345
 
    def get_parents_with_ghosts(self, version_id):
346
 
        raise NotImplementedError(self.get_parents_with_ghosts)
347
 
 
348
 
    def insert_record_stream(self, stream):
349
 
        """Insert a record stream into this versioned file.
350
 
 
351
 
        :param stream: A stream of records to insert.
352
 
        :return: None
353
 
        :seealso VersionedFile.get_record_stream:
354
 
        """
355
 
        adapters = {}
356
 
        for record in stream:
357
 
            # Raise an error when a record is missing.
358
 
            if record.storage_kind == 'absent':
359
 
                raise RevisionNotPresent([record.key[0]], self)
360
 
            # adapt to non-tuple interface
361
 
            parents = [parent[0] for parent in record.parents]
362
 
            if (record.storage_kind == 'fulltext'
363
 
                or record.storage_kind == 'chunked'):
364
 
                self.add_lines(record.key[0], parents,
365
 
                    osutils.chunks_to_lines(record.get_bytes_as('chunked')))
366
 
            else:
367
 
                adapter_key = record.storage_kind, 'fulltext'
368
 
                try:
369
 
                    adapter = adapters[adapter_key]
370
 
                except KeyError:
371
 
                    adapter_factory = adapter_registry.get(adapter_key)
372
 
                    adapter = adapter_factory(self)
373
 
                    adapters[adapter_key] = adapter
374
 
                lines = split_lines(adapter.get_bytes(record))
375
 
                try:
376
 
                    self.add_lines(record.key[0], parents, lines)
377
 
                except RevisionAlreadyPresent:
378
 
                    pass
 
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)])
379
435
 
380
436
    def _check_repeated_add(self, name, parents, text, sha1):
381
437
        """Check that a duplicated add is OK.
388
444
            raise RevisionAlreadyPresent(name, self._weave_name)
389
445
        return idx
390
446
 
391
 
    def _add_lines(self, version_id, parents, lines, parent_texts,
392
 
       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):
393
453
        """See VersionedFile.add_lines."""
394
 
        idx = self._add(version_id, lines, map(self._lookup, parents),
395
 
            nostore_sha=nostore_sha)
396
 
        return sha_strings(lines), sum(map(len, lines)), idx
397
 
 
398
 
    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):
399
462
        """Add a single text on top of the weave.
400
 
 
 
463
  
401
464
        Returns the index number of the newly added version.
402
465
 
403
466
        version_id
404
467
            Symbolic name for this version.
405
468
            (Typically the revision-id of the revision that added it.)
406
 
            If None, a name will be allocated based on the hash. (sha1:SHAHASH)
407
469
 
408
470
        parents
409
471
            List or set of direct parent version numbers.
410
 
 
 
472
            
411
473
        lines
412
474
            Sequence of lines to be added in the new version.
 
475
        """
413
476
 
414
 
        :param nostore_sha: See VersionedFile.add_lines.
415
 
        """
 
477
        assert isinstance(version_id, basestring)
416
478
        self._check_lines_not_unicode(lines)
417
479
        self._check_lines_are_lines(lines)
418
480
        if not sha1:
419
481
            sha1 = sha_strings(lines)
420
 
        if sha1 == nostore_sha:
421
 
            raise errors.ExistingContent
422
 
        if version_id is None:
423
 
            version_id = "sha1:" + sha1
424
482
        if version_id in self._name_map:
425
483
            return self._check_repeated_add(version_id, parents, lines, sha1)
426
484
 
437
495
        self._names.append(version_id)
438
496
        self._name_map[version_id] = new_version
439
497
 
440
 
 
 
498
            
441
499
        if not parents:
442
500
            # special case; adding with no parents revision; can do
443
501
            # this more quickly by just appending unconditionally.
454
512
            if sha1 == self._sha1s[pv]:
455
513
                # special case: same as the single parent
456
514
                return new_version
457
 
 
 
515
            
458
516
 
459
517
        ancestors = self._inclusions(parents)
460
518
 
470
528
        # another small special case: a merge, producing the same text
471
529
        # as auto-merge
472
530
        if lines == basis_lines:
473
 
            return new_version
 
531
            return new_version            
474
532
 
475
533
        # add a sentinel, because we can also match against the final line
476
534
        basis_lineno.append(len(self._weave))
495
553
            #print 'raw match', tag, i1, i2, j1, j2
496
554
            if tag == 'equal':
497
555
                continue
 
556
 
498
557
            i1 = basis_lineno[i1]
499
558
            i2 = basis_lineno[i2]
 
559
 
 
560
            assert 0 <= j1 <= j2 <= len(lines)
 
561
 
 
562
            #print tag, i1, i2, j1, j2
 
563
 
500
564
            # the deletion and insertion are handled separately.
501
565
            # first delete the region.
502
566
            if i1 != i2:
509
573
                # i2; we want to insert after this region to make sure
510
574
                # we don't destroy ourselves
511
575
                i = i2 + offset
512
 
                self._weave[i:i] = ([('{', new_version)]
513
 
                                    + lines[j1:j2]
 
576
                self._weave[i:i] = ([('{', new_version)] 
 
577
                                    + lines[j1:j2] 
514
578
                                    + [('}', None)])
515
579
                offset += 2 + (j2 - j1)
516
580
        return new_version
517
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
 
518
587
    def _inclusions(self, versions):
519
588
        """Return set of all ancestors of given version(s)."""
520
589
        if not len(versions):
528
597
        ## except IndexError:
529
598
        ##     raise ValueError("version %d not present in weave" % v)
530
599
 
531
 
    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):
532
611
        """See VersionedFile.get_ancestry."""
533
612
        if isinstance(version_ids, basestring):
534
613
            version_ids = [version_ids]
543
622
            if not isinstance(l, basestring):
544
623
                raise ValueError("text line should be a string or unicode, not %s"
545
624
                                 % type(l))
546
 
 
 
625
        
547
626
 
548
627
 
549
628
    def _check_versions(self, indexes):
557
636
    def _compatible_parents(self, my_parents, other_parents):
558
637
        """During join check that other_parents are joinable with my_parents.
559
638
 
560
 
        Joinable is defined as 'is a subset of' - supersets may require
 
639
        Joinable is defined as 'is a subset of' - supersets may require 
561
640
        regeneration of diffs, but subsets do not.
562
641
        """
563
642
        return len(other_parents.difference(my_parents)) == 0
564
643
 
565
644
    def annotate(self, version_id):
566
 
        """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.
567
660
 
568
661
        The index indicates when the line originated in the weave."""
569
662
        incls = [self._lookup(version_id)]
570
 
        return [(self._idx_to_name(origin), text) for origin, lineno, text in
571
 
            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()
572
670
 
573
671
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
574
672
                                                pb=None):
577
675
            version_ids = self.versions()
578
676
        version_ids = set(version_ids)
579
677
        for lineno, inserted, deletes, line in self._walk_internal(version_ids):
580
 
            if inserted not in version_ids: continue
 
678
            # if inserted not in version_ids then it was inserted before the
 
679
            # versions we care about, but because weaves cannot represent ghosts
 
680
            # properly, we do not filter down to that
 
681
            # if inserted not in version_ids: continue
581
682
            if line[-1] != '\n':
582
 
                yield line + '\n', inserted
 
683
                yield line + '\n'
583
684
            else:
584
 
                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)
585
691
 
586
692
    def _walk_internal(self, version_ids=None):
587
693
        """Helper method for weave actions."""
588
 
 
 
694
        
589
695
        istack = []
590
696
        dset = set()
591
697
 
600
706
                elif c == '}':
601
707
                    istack.pop()
602
708
                elif c == '[':
 
709
                    assert self._names[v] not in dset
603
710
                    dset.add(self._names[v])
604
711
                elif c == ']':
605
712
                    dset.remove(self._names[v])
606
713
                else:
607
714
                    raise WeaveFormatError('unexpected instruction %r' % v)
608
715
            else:
 
716
                assert l.__class__ in (str, unicode)
 
717
                assert istack
609
718
                yield lineno, istack[-1], frozenset(dset), l
610
719
            lineno += 1
611
720
 
628
737
        inc_b = set(self.get_ancestry([ver_b]))
629
738
        inc_c = inc_a & inc_b
630
739
 
631
 
        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]):
632
742
            if deleteset & inc_c:
633
743
                # killed in parent; can't be in either a or b
634
744
                # not relevant to our work
660
770
                # not in either revision
661
771
                yield 'irrelevant', line
662
772
 
 
773
        yield 'unchanged', ''           # terminator
 
774
 
663
775
    def _extract(self, versions):
664
776
        """Yield annotation of lines in included set.
665
777
 
672
784
        for i in versions:
673
785
            if not isinstance(i, int):
674
786
                raise ValueError(i)
675
 
 
 
787
            
676
788
        included = self._inclusions(versions)
677
789
 
678
790
        istack = []
687
799
 
688
800
        WFE = WeaveFormatError
689
801
 
690
 
        # wow.
 
802
        # wow. 
691
803
        #  449       0   4474.6820   2356.5590   bzrlib.weave:556(_extract)
692
804
        #  +285282   0   1676.8040   1676.8040   +<isinstance>
693
805
        # 1.6 seconds in 'isinstance'.
699
811
        # we're still spending ~1/4 of the method in isinstance though.
700
812
        # so lets hard code the acceptable string classes we expect:
701
813
        #  449       0   1202.9420    786.2930   bzrlib.weave:556(_extract)
702
 
        # +71352     0    377.5560    377.5560   +<method 'append' of 'list'
 
814
        # +71352     0    377.5560    377.5560   +<method 'append' of 'list' 
703
815
        #                                          objects>
704
816
        # yay, down to ~1/4 the initial extract time, and our inline time
705
817
        # has shrunk again, with isinstance no longer dominating.
706
818
        # tweaking the stack inclusion test to use a set gives:
707
819
        #  449       0   1122.8030    713.0080   bzrlib.weave:556(_extract)
708
 
        # +71352     0    354.9980    354.9980   +<method 'append' of 'list'
 
820
        # +71352     0    354.9980    354.9980   +<method 'append' of 'list' 
709
821
        #                                          objects>
710
822
        # - a 5% win, or possibly just noise. However with large istacks that
711
823
        # 'in' test could dominate, so I'm leaving this change in place -
712
824
        # when its fast enough to consider profiling big datasets we can review.
713
825
 
714
 
 
715
 
 
 
826
              
 
827
             
716
828
 
717
829
        for l in self._weave:
718
830
            if l.__class__ == tuple:
719
831
                c, v = l
720
832
                isactive = None
721
833
                if c == '{':
 
834
                    assert v not in iset
722
835
                    istack.append(v)
723
836
                    iset.add(v)
724
837
                elif c == '}':
725
838
                    iset.remove(istack.pop())
726
839
                elif c == '[':
727
840
                    if v in included:
 
841
                        assert v not in dset
728
842
                        dset.add(v)
729
 
                elif c == ']':
 
843
                else:
 
844
                    assert c == ']'
730
845
                    if v in included:
 
846
                        assert v in dset
731
847
                        dset.remove(v)
732
 
                else:
733
 
                    raise AssertionError()
734
848
            else:
 
849
                assert l.__class__ in (str, unicode)
735
850
                if isactive is None:
736
851
                    isactive = (not dset) and istack and (istack[-1] in included)
737
852
                if isactive:
745
860
                                   % dset)
746
861
        return result
747
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
 
748
876
    def _maybe_lookup(self, name_or_index):
749
877
        """Convert possible symbolic name to index, or pass through indexes.
750
 
 
 
878
        
751
879
        NOT FOR PUBLIC USE.
752
880
        """
753
881
        if isinstance(name_or_index, (int, long)):
755
883
        else:
756
884
            return self._lookup(name_or_index)
757
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
 
758
891
    def get_lines(self, version_id):
759
892
        """See VersionedFile.get_lines()."""
760
893
        int_index = self._maybe_lookup(version_id)
763
896
        measured_sha1 = sha_strings(result)
764
897
        if measured_sha1 != expected_sha1:
765
898
            raise errors.WeaveInvalidChecksum(
766
 
                    'file %s, revision %s, expected: %s, measured %s'
 
899
                    'file %s, revision %s, expected: %s, measured %s' 
767
900
                    % (self._weave_name, version_id,
768
901
                       expected_sha1, measured_sha1))
769
902
        return result
770
903
 
771
 
    def get_sha1s(self, version_ids):
772
 
        """See VersionedFile.get_sha1s()."""
773
 
        result = {}
774
 
        for v in version_ids:
775
 
            result[v] = self._sha1s[self._lookup(v)]
776
 
        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()
777
915
 
778
916
    def num_versions(self):
779
917
        """How many versions are in this weave?"""
780
918
        l = len(self._parents)
 
919
        assert l == len(self._sha1s)
781
920
        return l
782
921
 
783
922
    __len__ = num_versions
803
942
            # For creating the ancestry, IntSet is much faster (3.7s vs 0.17s)
804
943
            # The problem is that set membership is much more expensive
805
944
            name = self._idx_to_name(i)
806
 
            sha1s[name] = sha()
 
945
            sha1s[name] = sha.new()
807
946
            texts[name] = []
808
947
            new_inc = set([name])
809
948
            for p in self._parents[i]:
810
949
                new_inc.update(inclusions[self._idx_to_name(p)])
811
950
 
812
 
            if set(new_inc) != set(self.get_ancestry(name)):
813
 
                raise AssertionError(
814
 
                    'failed %s != %s'
815
 
                    % (set(new_inc), set(self.get_ancestry(name))))
 
951
            assert set(new_inc) == set(self.get_ancestry(name)), \
 
952
                'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
816
953
            inclusions[name] = new_inc
817
954
 
818
955
        nlines = len(self._weave)
848
985
        # no lines outside of insertion blocks, that deletions are
849
986
        # properly paired, etc.
850
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
 
851
1039
    def _imported_parents(self, other, other_idx):
852
1040
        """Return list of parents in self corresponding to indexes in other."""
853
1041
        new_parents = []
855
1043
            parent_name = other._names[parent_idx]
856
1044
            if parent_name not in self._name_map:
857
1045
                # should not be possible
858
 
                raise WeaveError("missing parent {%s} of {%s} in %r"
 
1046
                raise WeaveError("missing parent {%s} of {%s} in %r" 
859
1047
                                 % (parent_name, other._name_map[other_idx], self))
860
1048
            new_parents.append(self._name_map[parent_name])
861
1049
        return new_parents
868
1056
         * the same text
869
1057
         * the same direct parents (by name, not index, and disregarding
870
1058
           order)
871
 
 
 
1059
        
872
1060
        If present & correct return True;
873
 
        if not present in self return False;
 
1061
        if not present in self return False; 
874
1062
        if inconsistent raise error."""
875
1063
        this_idx = self._name_map.get(name, -1)
876
1064
        if this_idx != -1:
888
1076
        else:
889
1077
            return False
890
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
 
891
1084
    def _reweave(self, other, pb, msg):
892
1085
        """Reweave self with other - internal helper for join().
893
1086
 
909
1102
    """A WeaveFile represents a Weave on disk and writes on change."""
910
1103
 
911
1104
    WEAVE_SUFFIX = '.weave'
912
 
 
913
 
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w', get_scope=None):
 
1105
    
 
1106
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w'):
914
1107
        """Create a WeaveFile.
915
 
 
 
1108
        
916
1109
        :param create: If not True, only open an existing knit.
917
1110
        """
918
 
        super(WeaveFile, self).__init__(name, access_mode, get_scope=get_scope,
919
 
            allow_reserved=False)
 
1111
        super(WeaveFile, self).__init__(name, access_mode)
920
1112
        self._transport = transport
921
1113
        self._filemode = filemode
922
1114
        try:
923
 
            f = self._transport.get(name + WeaveFile.WEAVE_SUFFIX)
924
 
            _read_weave_v5(StringIO(f.read()), self)
 
1115
            _read_weave_v5(self._transport.get(name + WeaveFile.WEAVE_SUFFIX), self)
925
1116
        except errors.NoSuchFile:
926
1117
            if not create:
927
1118
                raise
928
1119
            # new file, save it
929
1120
            self._save()
930
1121
 
931
 
    def _add_lines(self, version_id, parents, lines, parent_texts,
932
 
        left_matching_blocks, nostore_sha, random_id, check_content):
 
1122
    def _add_lines(self, version_id, parents, lines, parent_texts):
933
1123
        """Add a version and save the weave."""
934
1124
        self.check_not_reserved_id(version_id)
935
1125
        result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
936
 
            parent_texts, left_matching_blocks, nostore_sha, random_id,
937
 
            check_content)
 
1126
                                                   parent_texts)
938
1127
        self._save()
939
1128
        return result
940
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
 
941
1135
    def copy_to(self, name, transport):
942
1136
        """See VersionedFile.copy_to()."""
943
1137
        # as we are all in memory always, just serialise to the new place.
946
1140
        sio.seek(0)
947
1141
        transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
948
1142
 
 
1143
    def create_empty(self, name, transport, filemode=None):
 
1144
        return WeaveFile(name, transport, filemode, create=True)
 
1145
 
949
1146
    def _save(self):
950
1147
        """Save the weave."""
951
1148
        self._check_write_ok()
952
1149
        sio = StringIO()
953
1150
        write_weave_v5(self, sio)
954
1151
        sio.seek(0)
955
 
        bytes = sio.getvalue()
956
 
        path = self._weave_name + WeaveFile.WEAVE_SUFFIX
957
 
        try:
958
 
            self._transport.put_bytes(path, bytes, self._filemode)
959
 
        except errors.NoSuchFile:
960
 
            self._transport.mkdir(dirname(path))
961
 
            self._transport.put_bytes(path, bytes, self._filemode)
 
1152
        self._transport.put_file(self._weave_name + WeaveFile.WEAVE_SUFFIX,
 
1153
                                 sio,
 
1154
                                 self._filemode)
962
1155
 
963
1156
    @staticmethod
964
1157
    def get_suffixes():
965
1158
        """See VersionedFile.get_suffixes()."""
966
1159
        return [WeaveFile.WEAVE_SUFFIX]
967
1160
 
968
 
    def insert_record_stream(self, stream):
969
 
        super(WeaveFile, self).insert_record_stream(stream)
 
1161
    def join(self, other, pb=None, msg=None, version_ids=None,
 
1162
             ignore_missing=False):
 
1163
        """Join other into self and save."""
 
1164
        super(WeaveFile, self).join(other, pb, msg, version_ids, ignore_missing)
970
1165
        self._save()
971
1166
 
972
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
 
973
1173
def _reweave(wa, wb, pb=None, msg=None):
974
1174
    """Combine two weaves and return the result.
975
1175
 
976
 
    This works even if a revision R has different parents in
 
1176
    This works even if a revision R has different parents in 
977
1177
    wa and wb.  In the resulting weave all the parents are given.
978
1178
 
979
 
    This is done by just building up a new weave, maintaining ordering
 
1179
    This is done by just building up a new weave, maintaining ordering 
980
1180
    of the versions in the two inputs.  More efficient approaches
981
 
    might be possible but it should only be necessary to do
982
 
    this operation rarely, when a new previously ghost version is
 
1181
    might be possible but it should only be necessary to do 
 
1182
    this operation rarely, when a new previously ghost version is 
983
1183
    inserted.
984
1184
 
985
1185
    :param pb: An optional progress bar, indicating how far done we are
993
1193
    # map from version name -> all parent names
994
1194
    combined_parents = _reweave_parent_graphs(wa, wb)
995
1195
    mutter("combined parents: %r", combined_parents)
996
 
    order = tsort.topo_sort(combined_parents.iteritems())
 
1196
    order = topo_sort(combined_parents.iteritems())
997
1197
    mutter("order to reweave: %r", order)
998
1198
 
999
1199
    if pb and not msg:
1019
1219
        wr._add(name, lines, [wr._lookup(i) for i in combined_parents[name]])
1020
1220
    return wr
1021
1221
 
1022
 
 
1023
1222
def _reweave_parent_graphs(wa, wb):
1024
1223
    """Return combined parent ancestry for two weaves.
1025
 
 
 
1224
    
1026
1225
    Returned as a list of (version_name, set(parent_names))"""
1027
1226
    combined = {}
1028
1227
    for weave in [wa, wb]:
1030
1229
            p = combined.setdefault(name, set())
1031
1230
            p.update(map(weave._idx_to_name, weave._parents[idx]))
1032
1231
    return combined
 
1232
 
 
1233
 
 
1234
def weave_toc(w):
 
1235
    """Show the weave's table-of-contents"""
 
1236
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
 
1237
    for i in (6, 50, 10, 10):
 
1238
        print '-' * i,
 
1239
    print
 
1240
    for i in range(w.num_versions()):
 
1241
        sha1 = w._sha1s[i]
 
1242
        name = w._names[i]
 
1243
        parent_str = ' '.join(map(str, w._parents[i]))
 
1244
        print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
 
1245
 
 
1246
 
 
1247
 
 
1248
def weave_stats(weave_file, pb):
 
1249
    from bzrlib.weavefile import read_weave
 
1250
 
 
1251
    wf = file(weave_file, 'rb')
 
1252
    w = read_weave(wf)
 
1253
    # FIXME: doesn't work on pipes
 
1254
    weave_size = wf.tell()
 
1255
 
 
1256
    total = 0
 
1257
    vers = len(w)
 
1258
    for i in range(vers):
 
1259
        pb.update('checking sizes', i, vers)
 
1260
        for origin, lineno, line in w._extract([i]):
 
1261
            total += len(line)
 
1262
 
 
1263
    pb.clear()
 
1264
 
 
1265
    print 'versions          %9d' % vers
 
1266
    print 'weave file        %9d bytes' % weave_size
 
1267
    print 'total contents    %9d bytes' % total
 
1268
    print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
 
1269
    if vers:
 
1270
        avg = total/vers
 
1271
        print 'average size      %9d bytes' % avg
 
1272
        print 'relative size     %9.2fx' % (float(weave_size) / float(avg))
 
1273
 
 
1274
 
 
1275
def usage():
 
1276
    print """bzr weave tool
 
1277
 
 
1278
Experimental tool for weave algorithm.
 
1279
 
 
1280
usage:
 
1281
    weave init WEAVEFILE
 
1282
        Create an empty weave file
 
1283
    weave get WEAVEFILE VERSION
 
1284
        Write out specified version.
 
1285
    weave check WEAVEFILE
 
1286
        Check consistency of all versions.
 
1287
    weave toc WEAVEFILE
 
1288
        Display table of contents.
 
1289
    weave add WEAVEFILE NAME [BASE...] < NEWTEXT
 
1290
        Add NEWTEXT, with specified parent versions.
 
1291
    weave annotate WEAVEFILE VERSION
 
1292
        Display origin of each line.
 
1293
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
 
1294
        Auto-merge two versions and display conflicts.
 
1295
    weave diff WEAVEFILE VERSION1 VERSION2 
 
1296
        Show differences between two versions.
 
1297
 
 
1298
example:
 
1299
 
 
1300
    % weave init foo.weave
 
1301
    % vi foo.txt
 
1302
    % weave add foo.weave ver0 < foo.txt
 
1303
    added version 0
 
1304
 
 
1305
    (create updated version)
 
1306
    % vi foo.txt
 
1307
    % weave get foo.weave 0 | diff -u - foo.txt
 
1308
    % weave add foo.weave ver1 0 < foo.txt
 
1309
    added version 1
 
1310
 
 
1311
    % weave get foo.weave 0 > foo.txt       (create forked version)
 
1312
    % vi foo.txt
 
1313
    % weave add foo.weave ver2 0 < foo.txt
 
1314
    added version 2
 
1315
 
 
1316
    % weave merge foo.weave 1 2 > foo.txt   (merge them)
 
1317
    % vi foo.txt                            (resolve conflicts)
 
1318
    % weave add foo.weave merged 1 2 < foo.txt     (commit merged version)     
 
1319
    
 
1320
"""
 
1321
    
 
1322
 
 
1323
 
 
1324
def main(argv):
 
1325
    import sys
 
1326
    import os
 
1327
    try:
 
1328
        import bzrlib
 
1329
    except ImportError:
 
1330
        # in case we're run directly from the subdirectory
 
1331
        sys.path.append('..')
 
1332
        import bzrlib
 
1333
    from bzrlib.weavefile import write_weave, read_weave
 
1334
    from bzrlib.progress import ProgressBar
 
1335
 
 
1336
    try:
 
1337
        import psyco
 
1338
        psyco.full()
 
1339
    except ImportError:
 
1340
        pass
 
1341
 
 
1342
    if len(argv) < 2:
 
1343
        usage()
 
1344
        return 0
 
1345
 
 
1346
    cmd = argv[1]
 
1347
 
 
1348
    def readit():
 
1349
        return read_weave(file(argv[2], 'rb'))
 
1350
    
 
1351
    if cmd == 'help':
 
1352
        usage()
 
1353
    elif cmd == 'add':
 
1354
        w = readit()
 
1355
        # at the moment, based on everything in the file
 
1356
        name = argv[3]
 
1357
        parents = map(int, argv[4:])
 
1358
        lines = sys.stdin.readlines()
 
1359
        ver = w.add(name, parents, lines)
 
1360
        write_weave(w, file(argv[2], 'wb'))
 
1361
        print 'added version %r %d' % (name, ver)
 
1362
    elif cmd == 'init':
 
1363
        fn = argv[2]
 
1364
        if os.path.exists(fn):
 
1365
            raise IOError("file exists")
 
1366
        w = Weave()
 
1367
        write_weave(w, file(fn, 'wb'))
 
1368
    elif cmd == 'get': # get one version
 
1369
        w = readit()
 
1370
        sys.stdout.writelines(w.get_iter(int(argv[3])))
 
1371
        
 
1372
    elif cmd == 'diff':
 
1373
        w = readit()
 
1374
        fn = argv[2]
 
1375
        v1, v2 = map(int, argv[3:5])
 
1376
        lines1 = w.get(v1)
 
1377
        lines2 = w.get(v2)
 
1378
        diff_gen = bzrlib.patiencediff.unified_diff(lines1, lines2,
 
1379
                                '%s version %d' % (fn, v1),
 
1380
                                '%s version %d' % (fn, v2))
 
1381
        sys.stdout.writelines(diff_gen)
 
1382
            
 
1383
    elif cmd == 'annotate':
 
1384
        w = readit()
 
1385
        # newline is added to all lines regardless; too hard to get
 
1386
        # reasonable formatting otherwise
 
1387
        lasto = None
 
1388
        for origin, text in w.annotate(int(argv[3])):
 
1389
            text = text.rstrip('\r\n')
 
1390
            if origin == lasto:
 
1391
                print '      | %s' % (text)
 
1392
            else:
 
1393
                print '%5d | %s' % (origin, text)
 
1394
                lasto = origin
 
1395
                
 
1396
    elif cmd == 'toc':
 
1397
        weave_toc(readit())
 
1398
 
 
1399
    elif cmd == 'stats':
 
1400
        weave_stats(argv[2], ProgressBar())
 
1401
        
 
1402
    elif cmd == 'check':
 
1403
        w = readit()
 
1404
        pb = ProgressBar()
 
1405
        w.check(pb)
 
1406
        pb.clear()
 
1407
        print '%d versions ok' % w.num_versions()
 
1408
 
 
1409
    elif cmd == 'inclusions':
 
1410
        w = readit()
 
1411
        print ' '.join(map(str, w.inclusions([int(argv[3])])))
 
1412
 
 
1413
    elif cmd == 'parents':
 
1414
        w = readit()
 
1415
        print ' '.join(map(str, w._parents[int(argv[3])]))
 
1416
 
 
1417
    elif cmd == 'plan-merge':
 
1418
        # replaced by 'bzr weave-plan-merge'
 
1419
        w = readit()
 
1420
        for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
 
1421
            if line:
 
1422
                print '%14s | %s' % (state, line),
 
1423
    elif cmd == 'merge':
 
1424
        # replaced by 'bzr weave-merge-text'
 
1425
        w = readit()
 
1426
        p = w.plan_merge(int(argv[3]), int(argv[4]))
 
1427
        sys.stdout.writelines(w.weave_merge(p))
 
1428
    else:
 
1429
        raise ValueError('unknown command %r' % cmd)
 
1430
    
 
1431
 
 
1432
if __name__ == '__main__':
 
1433
    import sys
 
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)