~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2006-04-27 05:25:18 UTC
  • mto: This revision was merged to the branch mainline in revision 1688.
  • Revision ID: mbp@sourcefrog.net-20060427052518-07705dc5b3ce02cf
(HACKING) some notes on handling unicode & urls for transports

Show diffs side-by-side

added added

removed removed

Lines of Context:
66
66
# be done fairly efficiently because the sequence numbers constrain
67
67
# the possible relationships.
68
68
 
 
69
# FIXME: the conflict markers should be *7* characters
69
70
 
 
71
from copy import copy
 
72
from cStringIO import StringIO
 
73
from difflib import SequenceMatcher
 
74
import os
70
75
import sha
71
 
from difflib import SequenceMatcher
 
76
import time
72
77
 
73
78
from bzrlib.trace import mutter
74
 
 
75
 
 
76
 
class WeaveError(Exception):
77
 
    """Exception in processing weave"""
78
 
 
79
 
 
80
 
class WeaveFormatError(WeaveError):
81
 
    """Weave invariant violated"""
82
 
 
83
 
 
84
 
class WeaveParentMismatch(WeaveError):
85
 
    """Parents are mismatched between two revisions."""
86
 
    
87
 
 
88
 
class Weave(object):
 
79
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
 
80
        RevisionAlreadyPresent,
 
81
        RevisionNotPresent,
 
82
        WeaveRevisionAlreadyPresent,
 
83
        WeaveRevisionNotPresent,
 
84
        )
 
85
import bzrlib.errors as errors
 
86
from bzrlib.osutils import sha_strings
 
87
from bzrlib.symbol_versioning import *
 
88
from bzrlib.tsort import topo_sort
 
89
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
 
90
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
 
91
 
 
92
 
 
93
class Weave(VersionedFile):
89
94
    """weave - versioned text file storage.
90
95
    
91
96
    A Weave manages versions of line-based text files, keeping track
177
182
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
178
183
                 '_weave_name']
179
184
    
180
 
    def __init__(self, weave_name=None):
 
185
    def __init__(self, weave_name=None, access_mode='w'):
 
186
        super(Weave, self).__init__(access_mode)
181
187
        self._weave = []
182
188
        self._parents = []
183
189
        self._sha1s = []
185
191
        self._name_map = {}
186
192
        self._weave_name = weave_name
187
193
 
 
194
    def __repr__(self):
 
195
        return "Weave(%r)" % self._weave_name
188
196
 
189
197
    def copy(self):
190
198
        """Return a deep copy of self.
205
213
        return self._parents == other._parents \
206
214
               and self._weave == other._weave \
207
215
               and self._sha1s == other._sha1s 
208
 
 
209
216
    
210
217
    def __ne__(self, other):
211
218
        return not self.__eq__(other)
212
219
 
213
 
 
214
 
    def maybe_lookup(self, name_or_index):
215
 
        """Convert possible symbolic name to index, or pass through indexes."""
216
 
        if isinstance(name_or_index, (int, long)):
217
 
            return name_or_index
218
 
        else:
219
 
            return self.lookup(name_or_index)
220
 
 
221
 
        
 
220
    @deprecated_method(zero_eight)
 
221
    def idx_to_name(self, index):
 
222
        """Old public interface, the public interface is all names now."""
 
223
        return index
 
224
 
 
225
    def _idx_to_name(self, version):
 
226
        return self._names[version]
 
227
 
 
228
    @deprecated_method(zero_eight)
222
229
    def lookup(self, name):
 
230
        """Backwards compatability thunk:
 
231
 
 
232
        Return name, as name is valid in the api now, and spew deprecation
 
233
        warnings everywhere.
 
234
        """
 
235
        return name
 
236
 
 
237
    def _lookup(self, name):
223
238
        """Convert symbolic version name to index."""
224
239
        try:
225
240
            return self._name_map[name]
226
241
        except KeyError:
227
 
            raise WeaveError("name %r not present in weave %r" %
228
 
                             (name, self._weave_name))
229
 
 
230
 
 
 
242
            raise RevisionNotPresent(name, self._weave_name)
 
243
 
 
244
    @deprecated_method(zero_eight)
231
245
    def iter_names(self):
232
 
        """Yield a list of all names in this weave."""
233
 
        return iter(self._names)
234
 
 
235
 
    def idx_to_name(self, version):
236
 
        return self._names[version]
237
 
 
 
246
        """Deprecated convenience function, please see VersionedFile.names()."""
 
247
        return iter(self.names())
 
248
 
 
249
    @deprecated_method(zero_eight)
 
250
    def names(self):
 
251
        """See Weave.versions for the current api."""
 
252
        return self.versions()
 
253
 
 
254
    def versions(self):
 
255
        """See VersionedFile.versions."""
 
256
        return self._names[:]
 
257
 
 
258
    def has_version(self, version_id):
 
259
        """See VersionedFile.has_version."""
 
260
        return self._name_map.has_key(version_id)
 
261
 
 
262
    __contains__ = has_version
 
263
 
 
264
    def get_delta(self, version_id):
 
265
        """See VersionedFile.get_delta."""
 
266
        return self.get_deltas([version_id])[version_id]
 
267
 
 
268
    def get_deltas(self, version_ids):
 
269
        """See VersionedFile.get_deltas."""
 
270
        version_ids = self.get_ancestry(version_ids)
 
271
        for version_id in version_ids:
 
272
            if not self.has_version(version_id):
 
273
                raise RevisionNotPresent(version_id, self)
 
274
        # try extracting all versions; parallel extraction is used
 
275
        nv = self.num_versions()
 
276
        sha1s = {}
 
277
        deltas = {}
 
278
        texts = {}
 
279
        inclusions = {}
 
280
        noeols = {}
 
281
        last_parent_lines = {}
 
282
        parents = {}
 
283
        parent_inclusions = {}
 
284
        parent_linenums = {}
 
285
        parent_noeols = {}
 
286
        current_hunks = {}
 
287
        diff_hunks = {}
 
288
        # its simplest to generate a full set of prepared variables.
 
289
        for i in range(nv):
 
290
            name = self._names[i]
 
291
            sha1s[name] = self.get_sha1(name)
 
292
            parents_list = self.get_parents(name)
 
293
            try:
 
294
                parent = parents_list[0]
 
295
                parents[name] = parent
 
296
                parent_inclusions[name] = inclusions[parent]
 
297
            except IndexError:
 
298
                parents[name] = None
 
299
                parent_inclusions[name] = set()
 
300
            # we want to emit start, finish, replacement_length, replacement_lines tuples.
 
301
            diff_hunks[name] = []
 
302
            current_hunks[name] = [0, 0, 0, []] # #start, finish, repl_length, repl_tuples
 
303
            parent_linenums[name] = 0
 
304
            noeols[name] = False
 
305
            parent_noeols[name] = False
 
306
            last_parent_lines[name] = None
 
307
            new_inc = set([name])
 
308
            for p in self._parents[i]:
 
309
                new_inc.update(inclusions[self._idx_to_name(p)])
 
310
            # debug only, known good so far.
 
311
            #assert set(new_inc) == set(self.get_ancestry(name)), \
 
312
            #    'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
 
313
            inclusions[name] = new_inc
 
314
 
 
315
        nlines = len(self._weave)
 
316
 
 
317
        for lineno, inserted, deletes, line in self._walk_internal():
 
318
            # a line is active in a version if:
 
319
            # insert is in the versions inclusions
 
320
            # and
 
321
            # deleteset & the versions inclusions is an empty set.
 
322
            # so - if we have a included by mapping - version is included by
 
323
            # children, we get a list of children to examine for deletes affect
 
324
            # ing them, which is less than the entire set of children.
 
325
            for version_id in version_ids:  
 
326
                # The active inclusion must be an ancestor,
 
327
                # and no ancestors must have deleted this line,
 
328
                # because we don't support resurrection.
 
329
                parent_inclusion = parent_inclusions[version_id]
 
330
                inclusion = inclusions[version_id]
 
331
                parent_active = inserted in parent_inclusion and not (deletes & parent_inclusion)
 
332
                version_active = inserted in inclusion and not (deletes & inclusion)
 
333
                if not parent_active and not version_active:
 
334
                    # unrelated line of ancestry
 
335
                    continue
 
336
                elif parent_active and version_active:
 
337
                    # shared line
 
338
                    parent_linenum = parent_linenums[version_id]
 
339
                    if current_hunks[version_id] != [parent_linenum, parent_linenum, 0, []]:
 
340
                        diff_hunks[version_id].append(tuple(current_hunks[version_id]))
 
341
                    parent_linenum += 1
 
342
                    current_hunks[version_id] = [parent_linenum, parent_linenum, 0, []]
 
343
                    parent_linenums[version_id] = parent_linenum
 
344
                    try:
 
345
                        if line[-1] != '\n':
 
346
                            noeols[version_id] = True
 
347
                    except IndexError:
 
348
                        pass
 
349
                elif parent_active and not version_active:
 
350
                    # deleted line
 
351
                    current_hunks[version_id][1] += 1
 
352
                    parent_linenums[version_id] += 1
 
353
                    last_parent_lines[version_id] = line
 
354
                elif not parent_active and version_active:
 
355
                    # replacement line
 
356
                    # noeol only occurs at the end of a file because we 
 
357
                    # diff linewise. We want to show noeol changes as a
 
358
                    # empty diff unless the actual eol-less content changed.
 
359
                    theline = line
 
360
                    try:
 
361
                        if last_parent_lines[version_id][-1] != '\n':
 
362
                            parent_noeols[version_id] = True
 
363
                    except (TypeError, IndexError):
 
364
                        pass
 
365
                    try:
 
366
                        if theline[-1] != '\n':
 
367
                            noeols[version_id] = True
 
368
                    except IndexError:
 
369
                        pass
 
370
                    new_line = False
 
371
                    parent_should_go = False
 
372
 
 
373
                    if parent_noeols[version_id] == noeols[version_id]:
 
374
                        # no noeol toggle, so trust the weaves statement
 
375
                        # that this line is changed.
 
376
                        new_line = True
 
377
                        if parent_noeols[version_id]:
 
378
                            theline = theline + '\n'
 
379
                    elif parent_noeols[version_id]:
 
380
                        # parent has no eol, we do:
 
381
                        # our line is new, report as such..
 
382
                        new_line = True
 
383
                    elif noeols[version_id]:
 
384
                        # append a eol so that it looks like
 
385
                        # a normalised delta
 
386
                        theline = theline + '\n'
 
387
                        if parents[version_id] is not None:
 
388
                        #if last_parent_lines[version_id] is not None:
 
389
                            parent_should_go = True
 
390
                        if last_parent_lines[version_id] != theline:
 
391
                            # but changed anyway
 
392
                            new_line = True
 
393
                            #parent_should_go = False
 
394
                    if new_line:
 
395
                        current_hunks[version_id][2] += 1
 
396
                        current_hunks[version_id][3].append((inserted, theline))
 
397
                    if parent_should_go:
 
398
                        # last hunk last parent line is not eaten
 
399
                        current_hunks[version_id][1] -= 1
 
400
                    if current_hunks[version_id][1] < 0:
 
401
                        current_hunks[version_id][1] = 0
 
402
                        # import pdb;pdb.set_trace()
 
403
                    # assert current_hunks[version_id][1] >= 0
 
404
 
 
405
        # flush last hunk
 
406
        for i in range(nv):
 
407
            version = self._idx_to_name(i)
 
408
            if current_hunks[version] != [0, 0, 0, []]:
 
409
                diff_hunks[version].append(tuple(current_hunks[version]))
 
410
        result = {}
 
411
        for version_id in version_ids:
 
412
            result[version_id] = (
 
413
                                  parents[version_id],
 
414
                                  sha1s[version_id],
 
415
                                  noeols[version_id],
 
416
                                  diff_hunks[version_id],
 
417
                                  )
 
418
        return result
 
419
 
 
420
    def get_parents(self, version_id):
 
421
        """See VersionedFile.get_parent."""
 
422
        return map(self._idx_to_name, self._parents[self._lookup(version_id)])
238
423
 
239
424
    def _check_repeated_add(self, name, parents, text, sha1):
240
425
        """Check that a duplicated add is OK.
241
426
 
242
427
        If it is, return the (old) index; otherwise raise an exception.
243
428
        """
244
 
        idx = self.lookup(name)
245
 
        if sorted(self._parents[idx]) != sorted(parents):
246
 
            raise WeaveError("name \"%s\" already present in weave "
247
 
                             "with different parents" % name)
248
 
        if sha1 != self._sha1s[idx]:
249
 
            raise WeaveError("name \"%s\" already present in weave "
250
 
                             "with different text" % name)            
 
429
        idx = self._lookup(name)
 
430
        if sorted(self._parents[idx]) != sorted(parents) \
 
431
            or sha1 != self._sha1s[idx]:
 
432
            raise RevisionAlreadyPresent(name, self._weave_name)
251
433
        return idx
252
 
        
253
 
 
254
 
        
 
434
 
 
435
    @deprecated_method(zero_eight)
 
436
    def add_identical(self, old_rev_id, new_rev_id, parents):
 
437
        """Please use Weave.clone_text now."""
 
438
        return self.clone_text(new_rev_id, old_rev_id, parents)
 
439
 
 
440
    def _add_lines(self, version_id, parents, lines, parent_texts):
 
441
        """See VersionedFile.add_lines."""
 
442
        return self._add(version_id, lines, map(self._lookup, parents))
 
443
 
 
444
    @deprecated_method(zero_eight)
255
445
    def add(self, name, parents, text, sha1=None):
 
446
        """See VersionedFile.add_lines for the non deprecated api."""
 
447
        return self._add(name, text, map(self._maybe_lookup, parents), sha1)
 
448
 
 
449
    def _add(self, version_id, lines, parents, sha1=None):
256
450
        """Add a single text on top of the weave.
257
451
  
258
452
        Returns the index number of the newly added version.
259
453
 
260
 
        name
 
454
        version_id
261
455
            Symbolic name for this version.
262
456
            (Typically the revision-id of the revision that added it.)
263
457
 
264
458
        parents
265
459
            List or set of direct parent version numbers.
266
460
            
267
 
        text
 
461
        lines
268
462
            Sequence of lines to be added in the new version.
269
 
 
270
 
        sha -- SHA-1 of the file, if known.  This is trusted to be
271
 
            correct if supplied.
272
463
        """
273
 
        from bzrlib.osutils import sha_strings
274
 
 
275
 
        assert isinstance(name, basestring)
276
 
        if sha1 is None:
277
 
            sha1 = sha_strings(text)
278
 
        if name in self._name_map:
279
 
            return self._check_repeated_add(name, parents, text, sha1)
280
 
 
281
 
        parents = map(self.maybe_lookup, parents)
 
464
 
 
465
        assert isinstance(version_id, basestring)
 
466
        self._check_lines_not_unicode(lines)
 
467
        self._check_lines_are_lines(lines)
 
468
        if not sha1:
 
469
            sha1 = sha_strings(lines)
 
470
        if version_id in self._name_map:
 
471
            return self._check_repeated_add(version_id, parents, lines, sha1)
 
472
 
282
473
        self._check_versions(parents)
283
 
        ## self._check_lines(text)
 
474
        ## self._check_lines(lines)
284
475
        new_version = len(self._parents)
285
476
 
286
 
 
287
477
        # if we abort after here the (in-memory) weave will be corrupt because only
288
478
        # some fields are updated
 
479
        # XXX: FIXME implement a succeed-or-fail of the rest of this routine.
 
480
        #      - Robert Collins 20060226
289
481
        self._parents.append(parents[:])
290
482
        self._sha1s.append(sha1)
291
 
        self._names.append(name)
292
 
        self._name_map[name] = new_version
 
483
        self._names.append(version_id)
 
484
        self._name_map[version_id] = new_version
293
485
 
294
486
            
295
487
        if not parents:
297
489
            # this more quickly by just appending unconditionally.
298
490
            # even more specially, if we're adding an empty text we
299
491
            # need do nothing at all.
300
 
            if text:
 
492
            if lines:
301
493
                self._weave.append(('{', new_version))
302
 
                self._weave.extend(text)
 
494
                self._weave.extend(lines)
303
495
                self._weave.append(('}', None))
304
 
        
305
496
            return new_version
306
497
 
307
498
        if len(parents) == 1:
311
502
                return new_version
312
503
            
313
504
 
314
 
        ancestors = self.inclusions(parents)
 
505
        ancestors = self._inclusions(parents)
315
506
 
316
507
        l = self._weave
317
508
 
324
515
 
325
516
        # another small special case: a merge, producing the same text
326
517
        # as auto-merge
327
 
        if text == basis_lines:
 
518
        if lines == basis_lines:
328
519
            return new_version            
329
520
 
330
521
        # add a sentinal, because we can also match against the final line
337
528
        #print 'basis_lines:', basis_lines
338
529
        #print 'new_lines:  ', lines
339
530
 
340
 
        s = SequenceMatcher(None, basis_lines, text)
 
531
        s = SequenceMatcher(None, basis_lines, lines)
341
532
 
342
533
        # offset gives the number of lines that have been inserted
343
534
        # into the weave up to the current point; if the original edit instruction
354
545
            i1 = basis_lineno[i1]
355
546
            i2 = basis_lineno[i2]
356
547
 
357
 
            assert 0 <= j1 <= j2 <= len(text)
 
548
            assert 0 <= j1 <= j2 <= len(lines)
358
549
 
359
550
            #print tag, i1, i2, j1, j2
360
551
 
371
562
                # we don't destroy ourselves
372
563
                i = i2 + offset
373
564
                self._weave[i:i] = ([('{', new_version)] 
374
 
                                    + text[j1:j2] 
 
565
                                    + lines[j1:j2] 
375
566
                                    + [('}', None)])
376
567
                offset += 2 + (j2 - j1)
377
 
 
378
568
        return new_version
379
569
 
380
 
    def add_identical(self, old_rev_id, new_rev_id, parents):
381
 
        """Add an identical text to old_rev_id as new_rev_id."""
382
 
        old_lines = self.get(self.lookup(old_rev_id))
383
 
        self.add(new_rev_id, parents, old_lines)
 
570
    def _clone_text(self, new_version_id, old_version_id, parents):
 
571
        """See VersionedFile.clone_text."""
 
572
        old_lines = self.get_text(old_version_id)
 
573
        self.add_lines(new_version_id, parents, old_lines)
384
574
 
385
 
    def inclusions(self, versions):
 
575
    def _inclusions(self, versions):
386
576
        """Return set of all ancestors of given version(s)."""
 
577
        if not len(versions):
 
578
            return []
387
579
        i = set(versions)
388
580
        for v in xrange(max(versions), 0, -1):
389
581
            if v in i:
393
585
        ## except IndexError:
394
586
        ##     raise ValueError("version %d not present in weave" % v)
395
587
 
396
 
 
397
 
    def parents(self, version):
398
 
        return self._parents[version]
399
 
 
400
 
 
401
 
    def parent_names(self, version):
402
 
        """Return version names for parents of a version."""
403
 
        return map(self.idx_to_name, self._parents[self.lookup(version)])
404
 
 
405
 
 
406
 
    def minimal_parents(self, version):
407
 
        """Find the minimal set of parents for the version."""
408
 
        included = self._parents[version]
409
 
        if not included:
 
588
    @deprecated_method(zero_eight)
 
589
    def inclusions(self, version_ids):
 
590
        """Deprecated - see VersionedFile.get_ancestry for the replacement."""
 
591
        if not version_ids:
410
592
            return []
411
 
        
412
 
        li = list(included)
413
 
        li.sort(reverse=True)
414
 
 
415
 
        mininc = []
416
 
        gotit = set()
417
 
 
418
 
        for pv in li:
419
 
            if pv not in gotit:
420
 
                mininc.append(pv)
421
 
                gotit.update(self.inclusions(pv))
422
 
 
423
 
        assert mininc[0] >= 0
424
 
        assert mininc[-1] < version
425
 
        return mininc
426
 
 
427
 
 
 
593
        if isinstance(version_ids[0], int):
 
594
            return [self._idx_to_name(v) for v in self._inclusions(version_ids)]
 
595
        else:
 
596
            return self.get_ancestry(version_ids)
 
597
 
 
598
    def get_ancestry(self, version_ids):
 
599
        """See VersionedFile.get_ancestry."""
 
600
        if isinstance(version_ids, basestring):
 
601
            version_ids = [version_ids]
 
602
        i = self._inclusions([self._lookup(v) for v in version_ids])
 
603
        return [self._idx_to_name(v) for v in i]
428
604
 
429
605
    def _check_lines(self, text):
430
606
        if not isinstance(text, list):
445
621
            except IndexError:
446
622
                raise IndexError("invalid version number %r" % i)
447
623
 
 
624
    def _compatible_parents(self, my_parents, other_parents):
 
625
        """During join check that other_parents are joinable with my_parents.
 
626
 
 
627
        Joinable is defined as 'is a subset of' - supersets may require 
 
628
        regeneration of diffs, but subsets do not.
 
629
        """
 
630
        return len(other_parents.difference(my_parents)) == 0
 
631
 
 
632
    def annotate(self, version_id):
 
633
        if isinstance(version_id, int):
 
634
            warn('Weave.annotate(int) is deprecated. Please use version names'
 
635
                 ' in all circumstances as of 0.8',
 
636
                 DeprecationWarning,
 
637
                 stacklevel=2
 
638
                 )
 
639
            result = []
 
640
            for origin, lineno, text in self._extract([version_id]):
 
641
                result.append((origin, text))
 
642
            return result
 
643
        else:
 
644
            return super(Weave, self).annotate(version_id)
448
645
    
449
 
    def annotate(self, name_or_index):
450
 
        return list(self.annotate_iter(name_or_index))
451
 
 
452
 
 
453
 
    def annotate_iter(self, name_or_index):
454
 
        """Yield list of (index-id, line) pairs for the specified version.
 
646
    def annotate_iter(self, version_id):
 
647
        """Yield list of (version-id, line) pairs for the specified version.
455
648
 
456
649
        The index indicates when the line originated in the weave."""
457
 
        incls = [self.maybe_lookup(name_or_index)]
 
650
        incls = [self._lookup(version_id)]
458
651
        for origin, lineno, text in self._extract(incls):
459
 
            yield origin, text
460
 
 
461
 
 
 
652
            yield self._idx_to_name(origin), text
 
653
 
 
654
    @deprecated_method(zero_eight)
462
655
    def _walk(self):
463
 
        """Walk the weave.
464
 
 
465
 
        Yields sequence of
466
 
        (lineno, insert, deletes, text)
467
 
        for each literal line.
468
 
        """
 
656
        """_walk has become visit, a supported api."""
 
657
        return self._walk_internal()
 
658
 
 
659
    def iter_lines_added_or_present_in_versions(self, version_ids=None):
 
660
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
 
661
        if version_ids is None:
 
662
            version_ids = self.versions()
 
663
        version_ids = set(version_ids)
 
664
        for lineno, inserted, deletes, line in self._walk_internal(version_ids):
 
665
            # if inserted not in version_ids then it was inserted before the
 
666
            # versions we care about, but because weaves cannot represent ghosts
 
667
            # properly, we do not filter down to that
 
668
            # if inserted not in version_ids: continue
 
669
            if line[-1] != '\n':
 
670
                yield line + '\n'
 
671
            else:
 
672
                yield line
 
673
 
 
674
    #@deprecated_method(zero_eight)
 
675
    def walk(self, version_ids=None):
 
676
        """See VersionedFile.walk."""
 
677
        return self._walk_internal(version_ids)
 
678
 
 
679
    def _walk_internal(self, version_ids=None):
 
680
        """Helper method for weave actions."""
469
681
        
470
682
        istack = []
471
683
        dset = set()
473
685
        lineno = 0         # line of weave, 0-based
474
686
 
475
687
        for l in self._weave:
476
 
            if isinstance(l, tuple):
 
688
            if l.__class__ == tuple:
477
689
                c, v = l
478
690
                isactive = None
479
691
                if c == '{':
480
 
                    istack.append(v)
 
692
                    istack.append(self._names[v])
481
693
                elif c == '}':
482
694
                    istack.pop()
483
695
                elif c == '[':
484
 
                    assert v not in dset
485
 
                    dset.add(v)
 
696
                    assert self._names[v] not in dset
 
697
                    dset.add(self._names[v])
486
698
                elif c == ']':
487
 
                    dset.remove(v)
 
699
                    dset.remove(self._names[v])
488
700
                else:
489
 
                    raise WeaveFormatError('unexpected instruction %r'
490
 
                                           % v)
 
701
                    raise WeaveFormatError('unexpected instruction %r' % v)
491
702
            else:
492
 
                assert isinstance(l, basestring)
 
703
                assert l.__class__ in (str, unicode)
493
704
                assert istack
494
 
                yield lineno, istack[-1], dset, l
 
705
                yield lineno, istack[-1], frozenset(dset), l
495
706
            lineno += 1
496
707
 
497
 
 
 
708
        if istack:
 
709
            raise WeaveFormatError("unclosed insertion blocks "
 
710
                    "at end of weave: %s" % istack)
 
711
        if dset:
 
712
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
 
713
                                   % dset)
 
714
 
 
715
    def plan_merge(self, ver_a, ver_b):
 
716
        """Return pseudo-annotation indicating how the two versions merge.
 
717
 
 
718
        This is computed between versions a and b and their common
 
719
        base.
 
720
 
 
721
        Weave lines present in none of them are skipped entirely.
 
722
        """
 
723
        inc_a = set(self.get_ancestry([ver_a]))
 
724
        inc_b = set(self.get_ancestry([ver_b]))
 
725
        inc_c = inc_a & inc_b
 
726
 
 
727
        for lineno, insert, deleteset, line in\
 
728
            self.walk([ver_a, ver_b]):
 
729
            if deleteset & inc_c:
 
730
                # killed in parent; can't be in either a or b
 
731
                # not relevant to our work
 
732
                yield 'killed-base', line
 
733
            elif insert in inc_c:
 
734
                # was inserted in base
 
735
                killed_a = bool(deleteset & inc_a)
 
736
                killed_b = bool(deleteset & inc_b)
 
737
                if killed_a and killed_b:
 
738
                    yield 'killed-both', line
 
739
                elif killed_a:
 
740
                    yield 'killed-a', line
 
741
                elif killed_b:
 
742
                    yield 'killed-b', line
 
743
                else:
 
744
                    yield 'unchanged', line
 
745
            elif insert in inc_a:
 
746
                if deleteset & inc_a:
 
747
                    yield 'ghost-a', line
 
748
                else:
 
749
                    # new in A; not in B
 
750
                    yield 'new-a', line
 
751
            elif insert in inc_b:
 
752
                if deleteset & inc_b:
 
753
                    yield 'ghost-b', line
 
754
                else:
 
755
                    yield 'new-b', line
 
756
            else:
 
757
                # not in either revision
 
758
                yield 'irrelevant', line
 
759
 
 
760
        yield 'unchanged', ''           # terminator
498
761
 
499
762
    def _extract(self, versions):
500
763
        """Yield annotation of lines in included set.
509
772
            if not isinstance(i, int):
510
773
                raise ValueError(i)
511
774
            
512
 
        included = self.inclusions(versions)
 
775
        included = self._inclusions(versions)
513
776
 
514
777
        istack = []
 
778
        iset = set()
515
779
        dset = set()
516
780
 
517
781
        lineno = 0         # line of weave, 0-based
522
786
 
523
787
        WFE = WeaveFormatError
524
788
 
 
789
        # wow. 
 
790
        #  449       0   4474.6820   2356.5590   bzrlib.weave:556(_extract)
 
791
        #  +285282   0   1676.8040   1676.8040   +<isinstance>
 
792
        # 1.6 seconds in 'isinstance'.
 
793
        # changing the first isinstance:
 
794
        #  449       0   2814.2660   1577.1760   bzrlib.weave:556(_extract)
 
795
        #  +140414   0    762.8050    762.8050   +<isinstance>
 
796
        # note that the inline time actually dropped (less function calls)
 
797
        # and total processing time was halved.
 
798
        # we're still spending ~1/4 of the method in isinstance though.
 
799
        # so lets hard code the acceptable string classes we expect:
 
800
        #  449       0   1202.9420    786.2930   bzrlib.weave:556(_extract)
 
801
        # +71352     0    377.5560    377.5560   +<method 'append' of 'list' 
 
802
        #                                          objects>
 
803
        # yay, down to ~1/4 the initial extract time, and our inline time
 
804
        # has shrunk again, with isinstance no longer dominating.
 
805
        # tweaking the stack inclusion test to use a set gives:
 
806
        #  449       0   1122.8030    713.0080   bzrlib.weave:556(_extract)
 
807
        # +71352     0    354.9980    354.9980   +<method 'append' of 'list' 
 
808
        #                                          objects>
 
809
        # - a 5% win, or possibly just noise. However with large istacks that
 
810
        # 'in' test could dominate, so I'm leaving this change in place -
 
811
        # when its fast enough to consider profiling big datasets we can review.
 
812
 
 
813
              
 
814
             
 
815
 
525
816
        for l in self._weave:
526
 
            if isinstance(l, tuple):
 
817
            if l.__class__ == tuple:
527
818
                c, v = l
528
819
                isactive = None
529
820
                if c == '{':
530
 
                    assert v not in istack
 
821
                    assert v not in iset
531
822
                    istack.append(v)
 
823
                    iset.add(v)
532
824
                elif c == '}':
533
 
                    istack.pop()
 
825
                    iset.remove(istack.pop())
534
826
                elif c == '[':
535
827
                    if v in included:
536
828
                        assert v not in dset
541
833
                        assert v in dset
542
834
                        dset.remove(v)
543
835
            else:
544
 
                assert isinstance(l, basestring)
 
836
                assert l.__class__ in (str, unicode)
545
837
                if isactive is None:
546
838
                    isactive = (not dset) and istack and (istack[-1] in included)
547
839
                if isactive:
548
840
                    result.append((istack[-1], lineno, l))
549
841
            lineno += 1
550
 
 
551
842
        if istack:
552
 
            raise WFE("unclosed insertion blocks at end of weave",
553
 
                                   istack)
 
843
            raise WeaveFormatError("unclosed insertion blocks "
 
844
                    "at end of weave: %s" % istack)
554
845
        if dset:
555
 
            raise WFE("unclosed deletion blocks at end of weave",
556
 
                                   dset)
557
 
 
 
846
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
 
847
                                   % dset)
558
848
        return result
559
 
    
560
 
 
561
 
 
 
849
 
 
850
    @deprecated_method(zero_eight)
562
851
    def get_iter(self, name_or_index):
563
 
        """Yield lines for the specified version."""
564
 
        incls = [self.maybe_lookup(name_or_index)]
565
 
        for origin, lineno, line in self._extract(incls):
566
 
            yield line
567
 
 
568
 
 
569
 
    def get_text(self, name_or_index):
570
 
        return ''.join(self.get_iter(name_or_index))
571
 
        assert isinstance(version, int)
572
 
 
573
 
 
574
 
    def get_lines(self, name_or_index):
575
 
        return list(self.get_iter(name_or_index))
576
 
 
577
 
 
578
 
    get = get_lines
579
 
 
580
 
 
581
 
    def mash_iter(self, included):
582
 
        """Return composed version of multiple included versions."""
583
 
        included = map(self.maybe_lookup, included)
584
 
        for origin, lineno, text in self._extract(included):
585
 
            yield text
586
 
 
587
 
 
588
 
    def dump(self, to_file):
589
 
        from pprint import pprint
590
 
        print >>to_file, "Weave._weave = ",
591
 
        pprint(self._weave, to_file)
592
 
        print >>to_file, "Weave._parents = ",
593
 
        pprint(self._parents, to_file)
594
 
 
595
 
 
596
 
 
 
852
        """Deprecated, please do not use. Lookups are not not needed.
 
853
        
 
854
        Please use get_lines now.
 
855
        """
 
856
        return iter(self.get_lines(self._maybe_lookup(name_or_index)))
 
857
 
 
858
    @deprecated_method(zero_eight)
 
859
    def maybe_lookup(self, name_or_index):
 
860
        """Deprecated, please do not use. Lookups are not not needed."""
 
861
        return self._maybe_lookup(name_or_index)
 
862
 
 
863
    def _maybe_lookup(self, name_or_index):
 
864
        """Convert possible symbolic name to index, or pass through indexes.
 
865
        
 
866
        NOT FOR PUBLIC USE.
 
867
        """
 
868
        if isinstance(name_or_index, (int, long)):
 
869
            return name_or_index
 
870
        else:
 
871
            return self._lookup(name_or_index)
 
872
 
 
873
    @deprecated_method(zero_eight)
 
874
    def get(self, version_id):
 
875
        """Please use either Weave.get_text or Weave.get_lines as desired."""
 
876
        return self.get_lines(version_id)
 
877
 
 
878
    def get_lines(self, version_id):
 
879
        """See VersionedFile.get_lines()."""
 
880
        int_index = self._maybe_lookup(version_id)
 
881
        result = [line for (origin, lineno, line) in self._extract([int_index])]
 
882
        expected_sha1 = self._sha1s[int_index]
 
883
        measured_sha1 = sha_strings(result)
 
884
        if measured_sha1 != expected_sha1:
 
885
            raise errors.WeaveInvalidChecksum(
 
886
                    'file %s, revision %s, expected: %s, measured %s' 
 
887
                    % (self._weave_name, version_id,
 
888
                       expected_sha1, measured_sha1))
 
889
        return result
 
890
 
 
891
    def get_sha1(self, version_id):
 
892
        """See VersionedFile.get_sha1()."""
 
893
        return self._sha1s[self._lookup(version_id)]
 
894
 
 
895
    @deprecated_method(zero_eight)
597
896
    def numversions(self):
 
897
        """How many versions are in this weave?
 
898
 
 
899
        Deprecated in favour of num_versions.
 
900
        """
 
901
        return self.num_versions()
 
902
 
 
903
    def num_versions(self):
 
904
        """How many versions are in this weave?"""
598
905
        l = len(self._parents)
599
906
        assert l == len(self._sha1s)
600
907
        return l
601
908
 
602
 
 
603
 
    def __len__(self):
604
 
        return self.numversions()
605
 
 
 
909
    __len__ = num_versions
606
910
 
607
911
    def check(self, progress_bar=None):
608
 
        # check no circular inclusions
609
 
        for version in range(self.numversions()):
 
912
        # TODO evaluate performance hit of using string sets in this routine.
 
913
        # TODO: check no circular inclusions
 
914
        # TODO: create a nested progress bar
 
915
        for version in range(self.num_versions()):
610
916
            inclusions = list(self._parents[version])
611
917
            if inclusions:
612
918
                inclusions.sort()
614
920
                    raise WeaveFormatError("invalid included version %d for index %d"
615
921
                                           % (inclusions[-1], version))
616
922
 
617
 
        # try extracting all versions; this is a bit slow and parallel
618
 
        # extraction could be used
619
 
        nv = self.numversions()
620
 
        for version in range(nv):
 
923
        # try extracting all versions; parallel extraction is used
 
924
        nv = self.num_versions()
 
925
        sha1s = {}
 
926
        texts = {}
 
927
        inclusions = {}
 
928
        for i in range(nv):
 
929
            # For creating the ancestry, IntSet is much faster (3.7s vs 0.17s)
 
930
            # The problem is that set membership is much more expensive
 
931
            name = self._idx_to_name(i)
 
932
            sha1s[name] = sha.new()
 
933
            texts[name] = []
 
934
            new_inc = set([name])
 
935
            for p in self._parents[i]:
 
936
                new_inc.update(inclusions[self._idx_to_name(p)])
 
937
 
 
938
            assert set(new_inc) == set(self.get_ancestry(name)), \
 
939
                'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
 
940
            inclusions[name] = new_inc
 
941
 
 
942
        nlines = len(self._weave)
 
943
 
 
944
        update_text = 'checking weave'
 
945
        if self._weave_name:
 
946
            short_name = os.path.basename(self._weave_name)
 
947
            update_text = 'checking %s' % (short_name,)
 
948
            update_text = update_text[:25]
 
949
 
 
950
        for lineno, insert, deleteset, line in self._walk_internal():
621
951
            if progress_bar:
622
 
                progress_bar.update('checking text', version, nv)
623
 
            s = sha.new()
624
 
            for l in self.get_iter(version):
625
 
                s.update(l)
626
 
            hd = s.hexdigest()
627
 
            expected = self._sha1s[version]
 
952
                progress_bar.update(update_text, lineno, nlines)
 
953
 
 
954
            for name, name_inclusions in inclusions.items():
 
955
                # The active inclusion must be an ancestor,
 
956
                # and no ancestors must have deleted this line,
 
957
                # because we don't support resurrection.
 
958
                if (insert in name_inclusions) and not (deleteset & name_inclusions):
 
959
                    sha1s[name].update(line)
 
960
 
 
961
        for i in range(nv):
 
962
            version = self._idx_to_name(i)
 
963
            hd = sha1s[version].hexdigest()
 
964
            expected = self._sha1s[i]
628
965
            if hd != expected:
629
 
                raise WeaveError("mismatched sha1 for version %d; "
630
 
                                 "got %s, expected %s"
631
 
                                 % (version, hd, expected))
 
966
                raise errors.WeaveInvalidChecksum(
 
967
                        "mismatched sha1 for version %s: "
 
968
                        "got %s, expected %s"
 
969
                        % (version, hd, expected))
632
970
 
633
971
        # TODO: check insertions are properly nested, that there are
634
972
        # no lines outside of insertion blocks, that deletions are
635
973
        # properly paired, etc.
636
974
 
637
 
 
638
 
 
639
 
    def merge(self, merge_versions):
640
 
        """Automerge and mark conflicts between versions.
641
 
 
642
 
        This returns a sequence, each entry describing alternatives
643
 
        for a chunk of the file.  Each of the alternatives is given as
644
 
        a list of lines.
645
 
 
646
 
        If there is a chunk of the file where there's no diagreement,
647
 
        only one alternative is given.
648
 
        """
649
 
        # approach: find the included versions common to all the
650
 
        # merged versions
651
 
        raise NotImplementedError()
652
 
 
653
 
 
654
 
 
655
 
    def _delta(self, included, lines):
656
 
        """Return changes from basis to new revision.
657
 
 
658
 
        The old text for comparison is the union of included revisions.
659
 
 
660
 
        This is used in inserting a new text.
661
 
 
662
 
        Delta is returned as a sequence of
663
 
        (weave1, weave2, newlines).
664
 
 
665
 
        This indicates that weave1:weave2 of the old weave should be
666
 
        replaced by the sequence of lines in newlines.  Note that
667
 
        these line numbers are positions in the total weave and don't
668
 
        correspond to the lines in any extracted version, or even the
669
 
        extracted union of included versions.
670
 
 
671
 
        If line1=line2, this is a pure insert; if newlines=[] this is a
672
 
        pure delete.  (Similar to difflib.)
673
 
        """
674
 
        raise NotImplementedError()
675
 
 
676
 
            
677
 
    def plan_merge(self, ver_a, ver_b):
678
 
        """Return pseudo-annotation indicating how the two versions merge.
679
 
 
680
 
        This is computed between versions a and b and their common
681
 
        base.
682
 
 
683
 
        Weave lines present in none of them are skipped entirely.
684
 
        """
685
 
        inc_a = self.inclusions([ver_a])
686
 
        inc_b = self.inclusions([ver_b])
687
 
        inc_c = inc_a & inc_b
688
 
 
689
 
        for lineno, insert, deleteset, line in self._walk():
690
 
            if deleteset & inc_c:
691
 
                # killed in parent; can't be in either a or b
692
 
                # not relevant to our work
693
 
                yield 'killed-base', line
694
 
            elif insert in inc_c:
695
 
                # was inserted in base
696
 
                killed_a = bool(deleteset & inc_a)
697
 
                killed_b = bool(deleteset & inc_b)
698
 
                if killed_a and killed_b:
699
 
                    yield 'killed-both', line
700
 
                elif killed_a:
701
 
                    yield 'killed-a', line
702
 
                elif killed_b:
703
 
                    yield 'killed-b', line
704
 
                else:
705
 
                    yield 'unchanged', line
706
 
            elif insert in inc_a:
707
 
                if deleteset & inc_a:
708
 
                    yield 'ghost-a', line
709
 
                else:
710
 
                    # new in A; not in B
711
 
                    yield 'new-a', line
712
 
            elif insert in inc_b:
713
 
                if deleteset & inc_b:
714
 
                    yield 'ghost-b', line
715
 
                else:
716
 
                    yield 'new-b', line
717
 
            else:
718
 
                # not in either revision
719
 
                yield 'irrelevant', line
720
 
 
721
 
        yield 'unchanged', ''           # terminator
722
 
 
723
 
 
724
 
 
725
 
    def weave_merge(self, plan):
726
 
        lines_a = []
727
 
        lines_b = []
728
 
        ch_a = ch_b = False
729
 
 
730
 
        for state, line in plan:
731
 
            if state == 'unchanged' or state == 'killed-both':
732
 
                # resync and flush queued conflicts changes if any
733
 
                if not lines_a and not lines_b:
734
 
                    pass
735
 
                elif ch_a and not ch_b:
736
 
                    # one-sided change:                    
737
 
                    for l in lines_a: yield l
738
 
                elif ch_b and not ch_a:
739
 
                    for l in lines_b: yield l
740
 
                elif lines_a == lines_b:
741
 
                    for l in lines_a: yield l
742
 
                else:
743
 
                    yield '<<<<\n'
744
 
                    for l in lines_a: yield l
745
 
                    yield '====\n'
746
 
                    for l in lines_b: yield l
747
 
                    yield '>>>>\n'
748
 
 
749
 
                del lines_a[:]
750
 
                del lines_b[:]
751
 
                ch_a = ch_b = False
752
 
                
753
 
            if state == 'unchanged':
754
 
                if line:
755
 
                    yield line
756
 
            elif state == 'killed-a':
757
 
                ch_a = True
758
 
                lines_b.append(line)
759
 
            elif state == 'killed-b':
760
 
                ch_b = True
761
 
                lines_a.append(line)
762
 
            elif state == 'new-a':
763
 
                ch_a = True
764
 
                lines_a.append(line)
765
 
            elif state == 'new-b':
766
 
                ch_b = True
767
 
                lines_b.append(line)
768
 
            else:
769
 
                assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
770
 
                                 'killed-both'), \
771
 
                       state
772
 
 
773
 
                
774
 
    def join(self, other):
775
 
        """Integrate versions from other into this weave.
776
 
 
777
 
        The resulting weave contains all the history of both weaves; 
778
 
        any version you could retrieve from either self or other can be 
779
 
        retrieved from self after this call.
780
 
 
781
 
        It is illegal for the two weaves to contain different values 
782
 
        or different parents for any version.  See also reweave().
783
 
        """
784
 
        if other.numversions() == 0:
 
975
    def _join(self, other, pb, msg, version_ids, ignore_missing):
 
976
        """Worker routine for join()."""
 
977
        if not other.versions():
785
978
            return          # nothing to update, easy
 
979
 
 
980
        if version_ids:
 
981
            for version_id in version_ids:
 
982
                if not other.has_version(version_id) and not ignore_missing:
 
983
                    raise RevisionNotPresent(version_id, self._weave_name)
 
984
        else:
 
985
            version_ids = other.versions()
 
986
 
786
987
        # two loops so that we do not change ourselves before verifying it
787
988
        # will be ok
788
989
        # work through in index order to make sure we get all dependencies
789
 
        for other_idx, name in enumerate(other._names):
790
 
            if self._check_version_consistent(other, other_idx, name):
791
 
                continue
792
 
        for other_idx, name in enumerate(other._names):
793
 
            # TODO: If all the parents of the other version are already 
 
990
        names_to_join = []
 
991
        processed = 0
 
992
        # get the selected versions only that are in other.versions.
 
993
        version_ids = set(other.versions()).intersection(set(version_ids))
 
994
        # pull in the referenced graph.
 
995
        version_ids = other.get_ancestry(version_ids)
 
996
        pending_graph = [(version, other.get_parents(version)) for
 
997
                         version in version_ids]
 
998
        for name in topo_sort(pending_graph):
 
999
            other_idx = other._name_map[name]
 
1000
            # returns True if we have it, False if we need it.
 
1001
            if not self._check_version_consistent(other, other_idx, name):
 
1002
                names_to_join.append((other_idx, name))
 
1003
            processed += 1
 
1004
 
 
1005
 
 
1006
        if pb and not msg:
 
1007
            msg = 'weave join'
 
1008
 
 
1009
        merged = 0
 
1010
        time0 = time.time()
 
1011
        for other_idx, name in names_to_join:
 
1012
            # TODO: If all the parents of the other version are already
794
1013
            # present then we can avoid some work by just taking the delta
795
1014
            # and adjusting the offsets.
796
1015
            new_parents = self._imported_parents(other, other_idx)
 
1016
            sha1 = other._sha1s[other_idx]
 
1017
 
 
1018
            merged += 1
 
1019
 
 
1020
            if pb:
 
1021
                pb.update(msg, merged, len(names_to_join))
 
1022
           
797
1023
            lines = other.get_lines(other_idx)
798
 
            sha1 = other._sha1s[other_idx]
799
 
            self.add(name, new_parents, lines, sha1)
800
 
 
801
 
 
 
1024
            self._add(name, lines, new_parents, sha1)
 
1025
 
 
1026
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
 
1027
                merged, processed, self._weave_name, time.time()-time0))
 
1028
 
802
1029
    def _imported_parents(self, other, other_idx):
803
1030
        """Return list of parents in self corresponding to indexes in other."""
804
1031
        new_parents = []
805
1032
        for parent_idx in other._parents[other_idx]:
806
1033
            parent_name = other._names[parent_idx]
807
 
            if parent_name not in self._names:
 
1034
            if parent_name not in self._name_map:
808
1035
                # should not be possible
809
1036
                raise WeaveError("missing parent {%s} of {%s} in %r" 
810
1037
                                 % (parent_name, other._name_map[other_idx], self))
826
1053
        this_idx = self._name_map.get(name, -1)
827
1054
        if this_idx != -1:
828
1055
            if self._sha1s[this_idx] != other._sha1s[other_idx]:
829
 
                raise WeaveError("inconsistent texts for version {%s} "
830
 
                                 "when joining weaves"
831
 
                                 % (name))
 
1056
                raise errors.WeaveTextDiffers(name, self, other)
832
1057
            self_parents = self._parents[this_idx]
833
1058
            other_parents = other._parents[other_idx]
834
 
            n1 = [self._names[i] for i in self_parents]
835
 
            n2 = [other._names[i] for i in other_parents]
836
 
            n1.sort()
837
 
            n2.sort()
838
 
            if n1 != n2:
 
1059
            n1 = set([self._names[i] for i in self_parents])
 
1060
            n2 = set([other._names[i] for i in other_parents])
 
1061
            if not self._compatible_parents(n1, n2):
839
1062
                raise WeaveParentMismatch("inconsistent parents "
840
1063
                    "for version {%s}: %s vs %s" % (name, n1, n2))
841
1064
            else:
843
1066
        else:
844
1067
            return False
845
1068
 
846
 
    def reweave(self, other):
847
 
        """Reweave self with other."""
848
 
        new_weave = reweave(self, other)
 
1069
    @deprecated_method(zero_eight)
 
1070
    def reweave(self, other, pb=None, msg=None):
 
1071
        """reweave has been superceded by plain use of join."""
 
1072
        return self.join(other, pb, msg)
 
1073
 
 
1074
    def _reweave(self, other, pb, msg):
 
1075
        """Reweave self with other - internal helper for join().
 
1076
 
 
1077
        :param other: The other weave to merge
 
1078
        :param pb: An optional progress bar, indicating how far done we are
 
1079
        :param msg: An optional message for the progress
 
1080
        """
 
1081
        new_weave = _reweave(self, other, pb=pb, msg=msg)
 
1082
        self._copy_weave_content(new_weave)
 
1083
 
 
1084
    def _copy_weave_content(self, otherweave):
 
1085
        """adsorb the content from otherweave."""
849
1086
        for attr in self.__slots__:
850
 
            setattr(self, attr, getattr(new_weave, attr))
851
 
 
852
 
 
853
 
def reweave(wa, wb):
 
1087
            if attr != '_weave_name':
 
1088
                setattr(self, attr, copy(getattr(otherweave, attr)))
 
1089
 
 
1090
 
 
1091
class WeaveFile(Weave):
 
1092
    """A WeaveFile represents a Weave on disk and writes on change."""
 
1093
 
 
1094
    WEAVE_SUFFIX = '.weave'
 
1095
    
 
1096
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w'):
 
1097
        """Create a WeaveFile.
 
1098
        
 
1099
        :param create: If not True, only open an existing knit.
 
1100
        """
 
1101
        super(WeaveFile, self).__init__(name, access_mode)
 
1102
        self._transport = transport
 
1103
        self._filemode = filemode
 
1104
        try:
 
1105
            _read_weave_v5(self._transport.get(name + WeaveFile.WEAVE_SUFFIX), self)
 
1106
        except errors.NoSuchFile:
 
1107
            if not create:
 
1108
                raise
 
1109
            # new file, save it
 
1110
            self._save()
 
1111
 
 
1112
    def _add_lines(self, version_id, parents, lines, parent_texts):
 
1113
        """Add a version and save the weave."""
 
1114
        result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
 
1115
                                                   parent_texts)
 
1116
        self._save()
 
1117
        return result
 
1118
 
 
1119
    def _clone_text(self, new_version_id, old_version_id, parents):
 
1120
        """See VersionedFile.clone_text."""
 
1121
        super(WeaveFile, self)._clone_text(new_version_id, old_version_id, parents)
 
1122
        self._save
 
1123
 
 
1124
    def copy_to(self, name, transport):
 
1125
        """See VersionedFile.copy_to()."""
 
1126
        # as we are all in memory always, just serialise to the new place.
 
1127
        sio = StringIO()
 
1128
        write_weave_v5(self, sio)
 
1129
        sio.seek(0)
 
1130
        transport.put(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
 
1131
 
 
1132
    def create_empty(self, name, transport, filemode=None):
 
1133
        return WeaveFile(name, transport, filemode, create=True)
 
1134
 
 
1135
    def _save(self):
 
1136
        """Save the weave."""
 
1137
        self._check_write_ok()
 
1138
        sio = StringIO()
 
1139
        write_weave_v5(self, sio)
 
1140
        sio.seek(0)
 
1141
        self._transport.put(self._weave_name + WeaveFile.WEAVE_SUFFIX,
 
1142
                            sio,
 
1143
                            self._filemode)
 
1144
 
 
1145
    @staticmethod
 
1146
    def get_suffixes():
 
1147
        """See VersionedFile.get_suffixes()."""
 
1148
        return [WeaveFile.WEAVE_SUFFIX]
 
1149
 
 
1150
    def join(self, other, pb=None, msg=None, version_ids=None,
 
1151
             ignore_missing=False):
 
1152
        """Join other into self and save."""
 
1153
        super(WeaveFile, self).join(other, pb, msg, version_ids, ignore_missing)
 
1154
        self._save()
 
1155
 
 
1156
 
 
1157
@deprecated_function(zero_eight)
 
1158
def reweave(wa, wb, pb=None, msg=None):
 
1159
    """reweaving is deprecation, please just use weave.join()."""
 
1160
    _reweave(wa, wb, pb, msg)
 
1161
 
 
1162
def _reweave(wa, wb, pb=None, msg=None):
854
1163
    """Combine two weaves and return the result.
855
1164
 
856
1165
    This works even if a revision R has different parents in 
861
1170
    might be possible but it should only be necessary to do 
862
1171
    this operation rarely, when a new previously ghost version is 
863
1172
    inserted.
 
1173
 
 
1174
    :param pb: An optional progress bar, indicating how far done we are
 
1175
    :param msg: An optional message for the progress
864
1176
    """
865
1177
    wr = Weave()
866
1178
    ia = ib = 0
867
 
    queue_a = range(wa.numversions())
868
 
    queue_b = range(wb.numversions())
 
1179
    queue_a = range(wa.num_versions())
 
1180
    queue_b = range(wb.num_versions())
869
1181
    # first determine combined parents of all versions
870
1182
    # map from version name -> all parent names
871
1183
    combined_parents = _reweave_parent_graphs(wa, wb)
872
1184
    mutter("combined parents: %r", combined_parents)
873
 
    order = _make_reweave_order(wa._names, wb._names, combined_parents)
 
1185
    order = topo_sort(combined_parents.iteritems())
874
1186
    mutter("order to reweave: %r", order)
875
 
    for name in order:
 
1187
 
 
1188
    if pb and not msg:
 
1189
        msg = 'reweave'
 
1190
 
 
1191
    for idx, name in enumerate(order):
 
1192
        if pb:
 
1193
            pb.update(msg, idx, len(order))
876
1194
        if name in wa._name_map:
877
1195
            lines = wa.get_lines(name)
878
1196
            if name in wb._name_map:
879
 
                assert lines == wb.get_lines(name)
 
1197
                lines_b = wb.get_lines(name)
 
1198
                if lines != lines_b:
 
1199
                    mutter('Weaves differ on content. rev_id {%s}', name)
 
1200
                    mutter('weaves: %s, %s', wa._weave_name, wb._weave_name)
 
1201
                    import difflib
 
1202
                    lines = list(difflib.unified_diff(lines, lines_b,
 
1203
                            wa._weave_name, wb._weave_name))
 
1204
                    mutter('lines:\n%s', ''.join(lines))
 
1205
                    raise errors.WeaveTextDiffers(name, wa, wb)
880
1206
        else:
881
1207
            lines = wb.get_lines(name)
882
 
        wr.add(name, combined_parents[name], lines)
 
1208
        wr._add(name, lines, [wr._lookup(i) for i in combined_parents[name]])
883
1209
    return wr
884
1210
 
885
 
 
886
1211
def _reweave_parent_graphs(wa, wb):
887
1212
    """Return combined parent ancestry for two weaves.
888
1213
    
891
1216
    for weave in [wa, wb]:
892
1217
        for idx, name in enumerate(weave._names):
893
1218
            p = combined.setdefault(name, set())
894
 
            p.update(map(weave.idx_to_name, weave._parents[idx]))
 
1219
            p.update(map(weave._idx_to_name, weave._parents[idx]))
895
1220
    return combined
896
1221
 
897
1222
 
898
 
def _make_reweave_order(wa_order, wb_order, combined_parents):
899
 
    """Return an order to reweave versions respecting parents."""
900
 
    done = set()
901
 
    result = []
902
 
    ia = ib = 0
903
 
    next_a = next_b = None
904
 
    len_a = len(wa_order)
905
 
    len_b = len(wb_order)
906
 
    while ia < len(wa_order) or ib < len(wb_order):
907
 
        if ia < len_a:
908
 
            next_a = wa_order[ia]
909
 
            if next_a in done:
910
 
                ia += 1
911
 
                continue
912
 
            if combined_parents[next_a].issubset(done):
913
 
                ia += 1
914
 
                result.append(next_a)
915
 
                done.add(next_a)
916
 
                continue
917
 
        if ib < len_b:
918
 
            next_b = wb_order[ib]
919
 
            if next_b in done:
920
 
                ib += 1
921
 
                continue
922
 
            elif combined_parents[next_b].issubset(done):
923
 
                ib += 1
924
 
                result.append(next_b)
925
 
                done.add(next_b)
926
 
                continue
927
 
        raise WeaveError("don't know how to reweave at {%s} and {%s}"
928
 
                         % (next_a, next_b))
929
 
    return result
930
 
 
931
 
 
932
1223
def weave_toc(w):
933
1224
    """Show the weave's table-of-contents"""
934
1225
    print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
935
1226
    for i in (6, 50, 10, 10):
936
1227
        print '-' * i,
937
1228
    print
938
 
    for i in range(w.numversions()):
 
1229
    for i in range(w.num_versions()):
939
1230
        sha1 = w._sha1s[i]
940
1231
        name = w._names[i]
941
1232
        parent_str = ' '.join(map(str, w._parents[i]))
947
1238
    from bzrlib.weavefile import read_weave
948
1239
 
949
1240
    wf = file(weave_file, 'rb')
950
 
    w = read_weave(wf)
 
1241
    w = read_weave(wf, WeaveVersionedFile)
951
1242
    # FIXME: doesn't work on pipes
952
1243
    weave_size = wf.tell()
953
1244
 
988
1279
        Add NEWTEXT, with specified parent versions.
989
1280
    weave annotate WEAVEFILE VERSION
990
1281
        Display origin of each line.
991
 
    weave mash WEAVEFILE VERSION...
992
 
        Display composite of all selected versions.
993
1282
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
994
1283
        Auto-merge two versions and display conflicts.
995
1284
    weave diff WEAVEFILE VERSION1 VERSION2 
1069
1358
        w = readit()
1070
1359
        sys.stdout.writelines(w.get_iter(int(argv[3])))
1071
1360
        
1072
 
    elif cmd == 'mash': # get composite
1073
 
        w = readit()
1074
 
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
1075
 
 
1076
1361
    elif cmd == 'diff':
1077
1362
        from difflib import unified_diff
1078
1363
        w = readit()
1109
1394
        pb = ProgressBar()
1110
1395
        w.check(pb)
1111
1396
        pb.clear()
1112
 
        print '%d versions ok' % w.numversions()
 
1397
        print '%d versions ok' % w.num_versions()
1113
1398
 
1114
1399
    elif cmd == 'inclusions':
1115
1400
        w = readit()
1120
1405
        print ' '.join(map(str, w._parents[int(argv[3])]))
1121
1406
 
1122
1407
    elif cmd == 'plan-merge':
 
1408
        # replaced by 'bzr weave-plan-merge'
1123
1409
        w = readit()
1124
1410
        for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1125
1411
            if line:
1126
1412
                print '%14s | %s' % (state, line),
1127
 
 
1128
1413
    elif cmd == 'merge':
 
1414
        # replaced by 'bzr weave-merge-text'
1129
1415
        w = readit()
1130
1416
        p = w.plan_merge(int(argv[3]), int(argv[4]))
1131
1417
        sys.stdout.writelines(w.weave_merge(p))
1132
 
            
1133
 
    elif cmd == 'mash-merge':
1134
 
        if len(argv) != 5:
1135
 
            usage()
1136
 
            return 1
1137
 
 
1138
 
        w = readit()
1139
 
        v1, v2 = map(int, argv[3:5])
1140
 
 
1141
 
        basis = w.inclusions([v1]).intersection(w.inclusions([v2]))
1142
 
 
1143
 
        base_lines = list(w.mash_iter(basis))
1144
 
        a_lines = list(w.get(v1))
1145
 
        b_lines = list(w.get(v2))
1146
 
 
1147
 
        from bzrlib.merge3 import Merge3
1148
 
        m3 = Merge3(base_lines, a_lines, b_lines)
1149
 
 
1150
 
        name_a = 'version %d' % v1
1151
 
        name_b = 'version %d' % v2
1152
 
        sys.stdout.writelines(m3.merge_lines(name_a=name_a, name_b=name_b))
1153
1418
    else:
1154
1419
        raise ValueError('unknown command %r' % cmd)
1155
1420
    
1156
1421
 
1157
1422
 
1158
 
def profile_main(argv): 
 
1423
def profile_main(argv):
1159
1424
    import tempfile, hotshot, hotshot.stats
1160
1425
 
1161
1426
    prof_f = tempfile.NamedTemporaryFile()
1175
1440
    return ret
1176
1441
 
1177
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
 
1178
1451
if __name__ == '__main__':
1179
1452
    import sys
1180
1453
    if '--profile' in sys.argv:
1181
1454
        args = sys.argv[:]
1182
1455
        args.remove('--profile')
1183
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))
1184
1461
    else:
1185
1462
        sys.exit(main(sys.argv))
1186
1463
 
 
1464
 
 
1465
class InterWeave(InterVersionedFile):
 
1466
    """Optimised code paths for weave to weave operations."""
 
1467
    
 
1468
    _matching_file_factory = staticmethod(WeaveFile)
 
1469
    
 
1470
    @staticmethod
 
1471
    def is_compatible(source, target):
 
1472
        """Be compatible with weaves."""
 
1473
        try:
 
1474
            return (isinstance(source, Weave) and
 
1475
                    isinstance(target, Weave))
 
1476
        except AttributeError:
 
1477
            return False
 
1478
 
 
1479
    def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
 
1480
        """See InterVersionedFile.join."""
 
1481
        if self.target.versions() == []:
 
1482
            # optimised copy
 
1483
            self.target._copy_weave_content(self.source)
 
1484
            return
 
1485
        try:
 
1486
            self.target._join(self.source, pb, msg, version_ids, ignore_missing)
 
1487
        except errors.WeaveParentMismatch:
 
1488
            self.target._reweave(self.source, pb, msg)
 
1489
 
 
1490
 
 
1491
InterVersionedFile.register_optimiser(InterWeave)