~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2006-03-09 07:14:10 UTC
  • mfrom: (1600 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1602.
  • Revision ID: mbp@sourcefrog.net-20060309071410-4ab7d54905541c75
[merge] from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
67
67
# the possible relationships.
68
68
 
69
69
 
 
70
from cStringIO import StringIO
 
71
from difflib import SequenceMatcher
70
72
import os
71
73
import sha
72
 
from difflib import SequenceMatcher
 
74
import time
73
75
 
74
76
from bzrlib.trace import mutter
75
77
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
76
 
        WeaveRevisionNotPresent, WeaveRevisionAlreadyPresent)
 
78
        RevisionAlreadyPresent,
 
79
        RevisionNotPresent,
 
80
        WeaveRevisionAlreadyPresent,
 
81
        WeaveRevisionNotPresent,
 
82
        )
77
83
import bzrlib.errors as errors
 
84
from bzrlib.osutils import sha_strings
 
85
from bzrlib.symbol_versioning import *
78
86
from bzrlib.tsort import topo_sort
79
 
 
80
 
 
81
 
class Weave(object):
 
87
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
 
88
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
 
89
 
 
90
 
 
91
class Weave(VersionedFile):
82
92
    """weave - versioned text file storage.
83
93
    
84
94
    A Weave manages versions of line-based text files, keeping track
181
191
    def __repr__(self):
182
192
        return "Weave(%r)" % self._weave_name
183
193
 
184
 
 
185
194
    def copy(self):
186
195
        """Return a deep copy of self.
187
196
        
201
210
        return self._parents == other._parents \
202
211
               and self._weave == other._weave \
203
212
               and self._sha1s == other._sha1s 
204
 
 
205
213
    
206
214
    def __ne__(self, other):
207
215
        return not self.__eq__(other)
208
216
 
209
 
    def __contains__(self, name):
210
 
        return self._name_map.has_key(name)
211
 
 
212
 
    def maybe_lookup(self, name_or_index):
213
 
        """Convert possible symbolic name to index, or pass through indexes."""
214
 
        if isinstance(name_or_index, (int, long)):
215
 
            return name_or_index
216
 
        else:
217
 
            return self.lookup(name_or_index)
218
 
 
219
 
        
 
217
    @deprecated_method(zero_eight)
 
218
    def idx_to_name(self, index):
 
219
        """Old public interface, the public interface is all names now."""
 
220
        return index
 
221
 
 
222
    def _idx_to_name(self, version):
 
223
        return self._names[version]
 
224
 
 
225
    @deprecated_method(zero_eight)
220
226
    def lookup(self, name):
 
227
        """Backwards compatability thunk:
 
228
 
 
229
        Return name, as name is valid in the api now, and spew deprecation
 
230
        warnings everywhere.
 
231
        """
 
232
        return name
 
233
 
 
234
    def _lookup(self, name):
221
235
        """Convert symbolic version name to index."""
222
236
        try:
223
237
            return self._name_map[name]
224
238
        except KeyError:
225
 
            raise WeaveRevisionNotPresent(name, self)
226
 
 
 
239
            raise RevisionNotPresent(name, self._weave_name)
 
240
 
 
241
    @deprecated_method(zero_eight)
 
242
    def iter_names(self):
 
243
        """Deprecated convenience function, please see VersionedFile.names()."""
 
244
        return iter(self.names())
 
245
 
 
246
    @deprecated_method(zero_eight)
227
247
    def names(self):
 
248
        """See Weave.versions for the current api."""
 
249
        return self.versions()
 
250
 
 
251
    def versions(self):
 
252
        """See VersionedFile.versions."""
228
253
        return self._names[:]
229
254
 
230
 
    def iter_names(self):
231
 
        """Yield a list of all names in this weave."""
232
 
        return iter(self._names)
233
 
 
234
 
    def idx_to_name(self, version):
235
 
        return self._names[version]
 
255
    def has_version(self, version_id):
 
256
        """See VersionedFile.has_version."""
 
257
        return self._name_map.has_key(version_id)
 
258
 
 
259
    __contains__ = has_version
 
260
 
 
261
    def get_parents(self, version_id):
 
262
        """See VersionedFile.get_parent."""
 
263
        return map(self._idx_to_name, self._parents[self._lookup(version_id)])
236
264
 
237
265
    def _check_repeated_add(self, name, parents, text, sha1):
238
266
        """Check that a duplicated add is OK.
239
267
 
240
268
        If it is, return the (old) index; otherwise raise an exception.
241
269
        """
242
 
        idx = self.lookup(name)
 
270
        idx = self._lookup(name)
243
271
        if sorted(self._parents[idx]) != sorted(parents) \
244
272
            or sha1 != self._sha1s[idx]:
245
 
            raise WeaveRevisionAlreadyPresent(name, self)
 
273
            raise RevisionAlreadyPresent(name, self._weave_name)
246
274
        return idx
247
 
        
 
275
 
 
276
    @deprecated_method(zero_eight)
 
277
    def add_identical(self, old_rev_id, new_rev_id, parents):
 
278
        """Please use Weave.clone_text now."""
 
279
        return self.clone_text(new_rev_id, old_rev_id, parents)
 
280
 
 
281
    def add_lines(self, version_id, parents, lines):
 
282
        """See VersionedFile.add_lines."""
 
283
        return self._add(version_id, lines, map(self._lookup, parents))
 
284
 
 
285
    @deprecated_method(zero_eight)
248
286
    def add(self, name, parents, text, sha1=None):
 
287
        """See VersionedFile.add_lines for the non deprecated api."""
 
288
        return self._add(name, text, map(self._maybe_lookup, parents), sha1)
 
289
 
 
290
    def _add(self, version_id, lines, parents, sha1=None):
249
291
        """Add a single text on top of the weave.
250
292
  
251
293
        Returns the index number of the newly added version.
252
294
 
253
 
        name
 
295
        version_id
254
296
            Symbolic name for this version.
255
297
            (Typically the revision-id of the revision that added it.)
256
298
 
257
299
        parents
258
300
            List or set of direct parent version numbers.
259
301
            
260
 
        text
 
302
        lines
261
303
            Sequence of lines to be added in the new version.
262
 
 
263
 
        sha -- SHA-1 of the file, if known.  This is trusted to be
264
 
            correct if supplied.
265
304
        """
266
 
        from bzrlib.osutils import sha_strings
267
 
 
268
 
        assert isinstance(name, basestring)
269
 
        if sha1 is None:
270
 
            sha1 = sha_strings(text)
271
 
        if name in self._name_map:
272
 
            return self._check_repeated_add(name, parents, text, sha1)
273
 
 
274
 
        parents = map(self.maybe_lookup, parents)
 
305
 
 
306
        assert isinstance(version_id, basestring)
 
307
        if not sha1:
 
308
            sha1 = sha_strings(lines)
 
309
        if version_id in self._name_map:
 
310
            return self._check_repeated_add(version_id, parents, lines, sha1)
 
311
 
275
312
        self._check_versions(parents)
276
 
        ## self._check_lines(text)
 
313
        ## self._check_lines(lines)
277
314
        new_version = len(self._parents)
278
315
 
279
 
 
280
316
        # if we abort after here the (in-memory) weave will be corrupt because only
281
317
        # some fields are updated
282
318
        # XXX: FIXME implement a succeed-or-fail of the rest of this routine.
283
319
        #      - Robert Collins 20060226
284
320
        self._parents.append(parents[:])
285
321
        self._sha1s.append(sha1)
286
 
        self._names.append(name)
287
 
        self._name_map[name] = new_version
 
322
        self._names.append(version_id)
 
323
        self._name_map[version_id] = new_version
288
324
 
289
325
            
290
326
        if not parents:
292
328
            # this more quickly by just appending unconditionally.
293
329
            # even more specially, if we're adding an empty text we
294
330
            # need do nothing at all.
295
 
            if text:
 
331
            if lines:
296
332
                self._weave.append(('{', new_version))
297
 
                self._weave.extend(text)
 
333
                self._weave.extend(lines)
298
334
                self._weave.append(('}', None))
299
 
        
300
335
            return new_version
301
336
 
302
337
        if len(parents) == 1:
306
341
                return new_version
307
342
            
308
343
 
309
 
        ancestors = self.inclusions(parents)
 
344
        ancestors = self._inclusions(parents)
310
345
 
311
346
        l = self._weave
312
347
 
319
354
 
320
355
        # another small special case: a merge, producing the same text
321
356
        # as auto-merge
322
 
        if text == basis_lines:
323
 
            return new_version
 
357
        if lines == basis_lines:
 
358
            return new_version            
324
359
 
325
360
        # add a sentinal, because we can also match against the final line
326
361
        basis_lineno.append(len(self._weave))
332
367
        #print 'basis_lines:', basis_lines
333
368
        #print 'new_lines:  ', lines
334
369
 
335
 
        s = SequenceMatcher(None, basis_lines, text)
 
370
        s = SequenceMatcher(None, basis_lines, lines)
336
371
 
337
372
        # offset gives the number of lines that have been inserted
338
373
        # into the weave up to the current point; if the original edit instruction
349
384
            i1 = basis_lineno[i1]
350
385
            i2 = basis_lineno[i2]
351
386
 
352
 
            assert 0 <= j1 <= j2 <= len(text)
 
387
            assert 0 <= j1 <= j2 <= len(lines)
353
388
 
354
389
            #print tag, i1, i2, j1, j2
355
390
 
366
401
                # we don't destroy ourselves
367
402
                i = i2 + offset
368
403
                self._weave[i:i] = ([('{', new_version)] 
369
 
                                    + text[j1:j2] 
 
404
                                    + lines[j1:j2] 
370
405
                                    + [('}', None)])
371
406
                offset += 2 + (j2 - j1)
372
 
 
373
407
        return new_version
374
408
 
375
 
    def add_identical(self, old_rev_id, new_rev_id, parents):
376
 
        """Add an identical text to old_rev_id as new_rev_id."""
377
 
        old_lines = self.get(self.lookup(old_rev_id))
378
 
        self.add(new_rev_id, parents, old_lines)
 
409
    def clone_text(self, new_version_id, old_version_id, parents):
 
410
        """See VersionedFile.clone_text."""
 
411
        old_lines = self.get_text(old_version_id)
 
412
        self.add_lines(new_version_id, parents, old_lines)
379
413
 
380
 
    def inclusions(self, versions):
 
414
    def _inclusions(self, versions):
381
415
        """Return set of all ancestors of given version(s)."""
 
416
        if not len(versions):
 
417
            return []
382
418
        i = set(versions)
383
419
        for v in xrange(max(versions), 0, -1):
384
420
            if v in i:
388
424
        ## except IndexError:
389
425
        ##     raise ValueError("version %d not present in weave" % v)
390
426
 
391
 
 
392
 
    def parents(self, version):
393
 
        return self._parents[version]
394
 
 
395
 
 
396
 
    def parent_names(self, version):
397
 
        """Return version names for parents of a version."""
398
 
        return map(self.idx_to_name, self._parents[self.lookup(version)])
399
 
 
400
 
 
401
 
    def minimal_parents(self, version):
402
 
        """Find the minimal set of parents for the version."""
403
 
        included = self._parents[version]
404
 
        if not included:
 
427
    @deprecated_method(zero_eight)
 
428
    def inclusions(self, version_ids):
 
429
        """Deprecated - see VersionedFile.get_ancestry for the replacement."""
 
430
        if not version_ids:
405
431
            return []
406
 
        
407
 
        li = list(included)
408
 
        li.sort(reverse=True)
409
 
 
410
 
        mininc = []
411
 
        gotit = set()
412
 
 
413
 
        for pv in li:
414
 
            if pv not in gotit:
415
 
                mininc.append(pv)
416
 
                gotit.update(self.inclusions(pv))
417
 
 
418
 
        assert mininc[0] >= 0
419
 
        assert mininc[-1] < version
420
 
        return mininc
421
 
 
422
 
 
 
432
        if isinstance(version_ids[0], int):
 
433
            return [self._idx_to_name(v) for v in self._inclusions(version_ids)]
 
434
        else:
 
435
            return self.get_ancestry(version_ids)
 
436
 
 
437
    def get_ancestry(self, version_ids):
 
438
        """See VersionedFile.get_ancestry."""
 
439
        if isinstance(version_ids, basestring):
 
440
            version_ids = [version_ids]
 
441
        i = self._inclusions([self._lookup(v) for v in version_ids])
 
442
        return [self._idx_to_name(v) for v in i]
423
443
 
424
444
    def _check_lines(self, text):
425
445
        if not isinstance(text, list):
440
460
            except IndexError:
441
461
                raise IndexError("invalid version number %r" % i)
442
462
 
 
463
    def _compatible_parents(self, my_parents, other_parents):
 
464
        """During join check that other_parents are joinable with my_parents.
 
465
 
 
466
        Joinable is defined as 'is a subset of' - supersets may require 
 
467
        regeneration of diffs, but subsets do not.
 
468
        """
 
469
        return len(other_parents.difference(my_parents)) == 0
 
470
 
 
471
    def annotate(self, version_id):
 
472
        if isinstance(version_id, int):
 
473
            warn('Weave.annotate(int) is deprecated. Please use version names'
 
474
                 ' in all circumstances as of 0.8',
 
475
                 DeprecationWarning,
 
476
                 stacklevel=2
 
477
                 )
 
478
            result = []
 
479
            for origin, lineno, text in self._extract([version_id]):
 
480
                result.append((origin, text))
 
481
            return result
 
482
        else:
 
483
            return super(Weave, self).annotate(version_id)
443
484
    
444
 
    def annotate(self, name_or_index):
445
 
        return list(self.annotate_iter(name_or_index))
446
 
 
447
 
 
448
 
    def annotate_iter(self, name_or_index):
449
 
        """Yield list of (index-id, line) pairs for the specified version.
 
485
    def annotate_iter(self, version_id):
 
486
        """Yield list of (version-id, line) pairs for the specified version.
450
487
 
451
488
        The index indicates when the line originated in the weave."""
452
 
        incls = [self.maybe_lookup(name_or_index)]
 
489
        incls = [self._lookup(version_id)]
453
490
        for origin, lineno, text in self._extract(incls):
454
 
            yield origin, text
 
491
            yield self._idx_to_name(origin), text
455
492
 
 
493
    @deprecated_method(zero_eight)
456
494
    def _walk(self):
457
 
        """Walk the weave.
 
495
        """_walk has become walk, a supported api."""
 
496
        return self.walk()
458
497
 
459
 
        Yields sequence of
460
 
        (lineno, insert, deletes, text)
461
 
        for each literal line.
462
 
        """
 
498
    def walk(self, version_ids=None):
 
499
        """See VersionedFile.walk."""
463
500
        
464
501
        istack = []
465
502
        dset = set()
471
508
                c, v = l
472
509
                isactive = None
473
510
                if c == '{':
474
 
                    istack.append(v)
 
511
                    istack.append(self._idx_to_name(v))
475
512
                elif c == '}':
476
513
                    istack.pop()
477
514
                elif c == '[':
478
 
                    assert v not in dset
479
 
                    dset.add(v)
 
515
                    assert self._idx_to_name(v) not in dset
 
516
                    dset.add(self._idx_to_name(v))
480
517
                elif c == ']':
481
 
                    dset.remove(v)
 
518
                    dset.remove(self._idx_to_name(v))
482
519
                else:
483
520
                    raise WeaveFormatError('unexpected instruction %r' % v)
484
521
            else:
485
522
                assert isinstance(l, basestring)
486
523
                assert istack
487
 
                yield lineno, istack[-1], dset, l
 
524
                yield lineno, istack[-1], dset.copy(), l
488
525
            lineno += 1
489
526
 
490
527
        if istack:
507
544
            if not isinstance(i, int):
508
545
                raise ValueError(i)
509
546
            
510
 
        included = self.inclusions(versions)
 
547
        included = self._inclusions(versions)
511
548
 
512
549
        istack = []
513
550
        dset = set()
553
590
                                   % dset)
554
591
        return result
555
592
 
556
 
 
 
593
    @deprecated_method(zero_eight)
557
594
    def get_iter(self, name_or_index):
 
595
        """Deprecated, please do not use. Lookups are not not needed.
 
596
        
 
597
        Please use get_lines now.
 
598
        """
 
599
        return self._get_iter(self._maybe_lookup(name_or_index))
 
600
 
 
601
    @deprecated_method(zero_eight)
 
602
    def maybe_lookup(self, name_or_index):
 
603
        """Deprecated, please do not use. Lookups are not not needed."""
 
604
        return self._maybe_lookup(name_or_index)
 
605
 
 
606
    def _maybe_lookup(self, name_or_index):
 
607
        """Convert possible symbolic name to index, or pass through indexes.
 
608
        
 
609
        NOT FOR PUBLIC USE.
 
610
        """
 
611
        if isinstance(name_or_index, (int, long)):
 
612
            return name_or_index
 
613
        else:
 
614
            return self._lookup(name_or_index)
 
615
 
 
616
    def _get_iter(self, version_id):
558
617
        """Yield lines for the specified version."""
559
 
        incls = [self.maybe_lookup(name_or_index)]
 
618
        incls = [self._maybe_lookup(version_id)]
560
619
        if len(incls) == 1:
561
620
            index = incls[0]
562
621
            cur_sha = sha.new()
576
635
                        % (self._weave_name, self._names[index],
577
636
                           expected_sha1, measured_sha1))
578
637
 
579
 
 
580
 
    def get_text(self, name_or_index):
581
 
        return ''.join(self.get_iter(name_or_index))
582
 
        assert isinstance(version, int)
583
 
 
584
 
 
585
 
    def get_lines(self, name_or_index):
586
 
        return list(self.get_iter(name_or_index))
587
 
 
588
 
 
589
 
    get = get_lines
590
 
 
 
638
    @deprecated_method(zero_eight)
 
639
    def get(self, version_id):
 
640
        """Please use either Weave.get_text or Weave.get_lines as desired."""
 
641
        return self.get_lines(version_id)
 
642
 
 
643
    def get_lines(self, version_id):
 
644
        """See VersionedFile.get_lines()."""
 
645
        return list(self._get_iter(version_id))
591
646
 
592
647
    def get_sha1(self, name):
593
648
        """Get the stored sha1 sum for the given revision.
594
649
        
595
650
        :param name: The name of the version to lookup
596
651
        """
597
 
        return self._sha1s[self.lookup(name)]
598
 
 
599
 
    def mash_iter(self, included):
600
 
        """Return composed version of multiple included versions."""
601
 
        included = map(self.maybe_lookup, included)
602
 
        for origin, lineno, text in self._extract(included):
603
 
            yield text
604
 
 
605
 
 
606
 
    def dump(self, to_file):
607
 
        from pprint import pprint
608
 
        print >>to_file, "Weave._weave = ",
609
 
        pprint(self._weave, to_file)
610
 
        print >>to_file, "Weave._parents = ",
611
 
        pprint(self._parents, to_file)
612
 
 
613
 
 
614
 
 
 
652
        return self._sha1s[self._lookup(name)]
 
653
 
 
654
    @deprecated_method(zero_eight)
615
655
    def numversions(self):
 
656
        """How many versions are in this weave?
 
657
 
 
658
        Deprecated in favour of num_versions.
 
659
        """
 
660
        return self.num_versions()
 
661
 
 
662
    def num_versions(self):
 
663
        """How many versions are in this weave?"""
616
664
        l = len(self._parents)
617
665
        assert l == len(self._sha1s)
618
666
        return l
619
667
 
620
 
 
621
 
    def __len__(self):
622
 
        return self.numversions()
 
668
    __len__ = num_versions
623
669
 
624
670
    def check(self, progress_bar=None):
 
671
        # TODO evaluate performance hit of using string sets in this routine.
625
672
        # check no circular inclusions
626
 
        for version in range(self.numversions()):
 
673
        for version in range(self.num_versions()):
627
674
            inclusions = list(self._parents[version])
628
675
            if inclusions:
629
676
                inclusions.sort()
632
679
                                           % (inclusions[-1], version))
633
680
 
634
681
        # try extracting all versions; parallel extraction is used
635
 
        nv = self.numversions()
636
 
        sha1s = [sha.new() for i in range(nv)]
637
 
        texts = [[] for i in range(nv)]
638
 
        inclusions = []
 
682
        nv = self.num_versions()
 
683
        sha1s = {}
 
684
        texts = {}
 
685
        inclusions = {}
639
686
        for i in range(nv):
640
687
            # For creating the ancestry, IntSet is much faster (3.7s vs 0.17s)
641
688
            # The problem is that set membership is much more expensive
642
 
            new_inc = set([i])
 
689
            name = self._idx_to_name(i)
 
690
            sha1s[name] = sha.new()
 
691
            texts[name] = []
 
692
            new_inc = set([name])
643
693
            for p in self._parents[i]:
644
 
                new_inc.update(inclusions[p])
 
694
                new_inc.update(inclusions[self._idx_to_name(p)])
645
695
 
646
 
            #assert set(new_inc) == self.inclusions([i]), 'failed %s != %s' % (new_inc, self.inclusions([i]))
647
 
            inclusions.append(new_inc)
 
696
            assert set(new_inc) == set(self.get_ancestry(name)), \
 
697
                'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
 
698
            inclusions[name] = new_inc
648
699
 
649
700
        nlines = len(self._weave)
650
701
 
654
705
            update_text = 'checking %s' % (short_name,)
655
706
            update_text = update_text[:25]
656
707
 
657
 
        for lineno, insert, deleteset, line in self._walk():
 
708
        for lineno, insert, deleteset, line in self.walk():
658
709
            if progress_bar:
659
710
                progress_bar.update(update_text, lineno, nlines)
660
711
 
661
 
            for j, j_inc in enumerate(inclusions):
 
712
            for name, name_inclusions in inclusions.items():
662
713
                # The active inclusion must be an ancestor,
663
714
                # and no ancestors must have deleted this line,
664
715
                # because we don't support resurrection.
665
 
                if (insert in j_inc) and not (deleteset & j_inc):
666
 
                    sha1s[j].update(line)
 
716
                if (insert in name_inclusions) and not (deleteset & name_inclusions):
 
717
                    sha1s[name].update(line)
667
718
 
668
 
        for version in range(nv):
 
719
        for i in range(nv):
 
720
            version = self._idx_to_name(i)
669
721
            hd = sha1s[version].hexdigest()
670
 
            expected = self._sha1s[version]
 
722
            expected = self._sha1s[i]
671
723
            if hd != expected:
672
724
                raise errors.WeaveInvalidChecksum(
673
725
                        "mismatched sha1 for version %s: "
674
726
                        "got %s, expected %s"
675
 
                        % (self._names[version], hd, expected))
 
727
                        % (version, hd, expected))
676
728
 
677
729
        # TODO: check insertions are properly nested, that there are
678
730
        # no lines outside of insertion blocks, that deletions are
679
731
        # properly paired, etc.
680
732
 
681
 
    def _delta(self, included, lines):
682
 
        """Return changes from basis to new revision.
683
 
 
684
 
        The old text for comparison is the union of included revisions.
685
 
 
686
 
        This is used in inserting a new text.
687
 
 
688
 
        Delta is returned as a sequence of
689
 
        (weave1, weave2, newlines).
690
 
 
691
 
        This indicates that weave1:weave2 of the old weave should be
692
 
        replaced by the sequence of lines in newlines.  Note that
693
 
        these line numbers are positions in the total weave and don't
694
 
        correspond to the lines in any extracted version, or even the
695
 
        extracted union of included versions.
696
 
 
697
 
        If line1=line2, this is a pure insert; if newlines=[] this is a
698
 
        pure delete.  (Similar to difflib.)
699
 
        """
700
 
        raise NotImplementedError()
701
 
 
702
 
            
703
 
    def plan_merge(self, ver_a, ver_b):
704
 
        """Return pseudo-annotation indicating how the two versions merge.
705
 
 
706
 
        This is computed between versions a and b and their common
707
 
        base.
708
 
 
709
 
        Weave lines present in none of them are skipped entirely.
710
 
        """
711
 
        inc_a = self.inclusions([ver_a])
712
 
        inc_b = self.inclusions([ver_b])
713
 
        inc_c = inc_a & inc_b
714
 
 
715
 
        for lineno, insert, deleteset, line in self._walk():
716
 
            if deleteset & inc_c:
717
 
                # killed in parent; can't be in either a or b
718
 
                # not relevant to our work
719
 
                yield 'killed-base', line
720
 
            elif insert in inc_c:
721
 
                # was inserted in base
722
 
                killed_a = bool(deleteset & inc_a)
723
 
                killed_b = bool(deleteset & inc_b)
724
 
                if killed_a and killed_b:
725
 
                    yield 'killed-both', line
726
 
                elif killed_a:
727
 
                    yield 'killed-a', line
728
 
                elif killed_b:
729
 
                    yield 'killed-b', line
730
 
                else:
731
 
                    yield 'unchanged', line
732
 
            elif insert in inc_a:
733
 
                if deleteset & inc_a:
734
 
                    yield 'ghost-a', line
735
 
                else:
736
 
                    # new in A; not in B
737
 
                    yield 'new-a', line
738
 
            elif insert in inc_b:
739
 
                if deleteset & inc_b:
740
 
                    yield 'ghost-b', line
741
 
                else:
742
 
                    yield 'new-b', line
743
 
            else:
744
 
                # not in either revision
745
 
                yield 'irrelevant', line
746
 
 
747
 
        yield 'unchanged', ''           # terminator
748
 
 
749
 
 
750
 
 
751
 
    def weave_merge(self, plan, a_marker='<<<<<<< \n', b_marker='>>>>>>> \n'):
752
 
        lines_a = []
753
 
        lines_b = []
754
 
        ch_a = ch_b = False
755
 
        # TODO: Return a structured form of the conflicts (e.g. 2-tuples for
756
 
        # conflicted regions), rather than just inserting the markers.
757
 
        # 
758
 
        # TODO: Show some version information (e.g. author, date) on 
759
 
        # conflicted regions.
760
 
        for state, line in plan:
761
 
            if state == 'unchanged' or state == 'killed-both':
762
 
                # resync and flush queued conflicts changes if any
763
 
                if not lines_a and not lines_b:
764
 
                    pass
765
 
                elif ch_a and not ch_b:
766
 
                    # one-sided change:                    
767
 
                    for l in lines_a: yield l
768
 
                elif ch_b and not ch_a:
769
 
                    for l in lines_b: yield l
770
 
                elif lines_a == lines_b:
771
 
                    for l in lines_a: yield l
772
 
                else:
773
 
                    yield a_marker
774
 
                    for l in lines_a: yield l
775
 
                    yield '=======\n'
776
 
                    for l in lines_b: yield l
777
 
                    yield b_marker
778
 
 
779
 
                del lines_a[:]
780
 
                del lines_b[:]
781
 
                ch_a = ch_b = False
782
 
                
783
 
            if state == 'unchanged':
784
 
                if line:
785
 
                    yield line
786
 
            elif state == 'killed-a':
787
 
                ch_a = True
788
 
                lines_b.append(line)
789
 
            elif state == 'killed-b':
790
 
                ch_b = True
791
 
                lines_a.append(line)
792
 
            elif state == 'new-a':
793
 
                ch_a = True
794
 
                lines_a.append(line)
795
 
            elif state == 'new-b':
796
 
                ch_b = True
797
 
                lines_b.append(line)
798
 
            else:
799
 
                assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
800
 
                                 'killed-both'), \
801
 
                       state
802
 
 
803
 
 
804
 
    def join(self, other, pb=None, msg=None):
805
 
        import sys, time
806
 
        """Integrate versions from other into this weave.
807
 
 
808
 
        The resulting weave contains all the history of both weaves; 
809
 
        any version you could retrieve from either self or other can be 
810
 
        retrieved from self after this call.
811
 
 
812
 
        It is illegal for the two weaves to contain different values 
813
 
        or different parents for any version.  See also reweave().
814
 
 
815
 
        :param other: The other weave to pull into this one
816
 
        :param pb: An optional progress bar
817
 
        :param msg: An optional message to display for progress
818
 
        """
819
 
        if other.numversions() == 0:
 
733
    def _join(self, other, pb, msg, version_ids, ignore_missing):
 
734
        """Worker routine for join()."""
 
735
        if not other.versions():
820
736
            return          # nothing to update, easy
 
737
 
 
738
        if version_ids:
 
739
            for version_id in version_ids:
 
740
                if not other.has_version(version_id) and not ignore_missing:
 
741
                    raise RevisionNotPresent(version_id, self._weave_name)
 
742
        else:
 
743
            version_ids = other.versions()
 
744
 
821
745
        # two loops so that we do not change ourselves before verifying it
822
746
        # will be ok
823
747
        # work through in index order to make sure we get all dependencies
824
748
        names_to_join = []
825
749
        processed = 0
826
 
        for other_idx, name in enumerate(other._names):
 
750
        # get the selected versions only that are in other.versions.
 
751
        version_ids = set(other.versions()).intersection(set(version_ids))
 
752
        # pull in the referenced graph.
 
753
        version_ids = other.get_ancestry(version_ids)
 
754
        pending_graph = [(version, other.get_parents(version)) for
 
755
                         version in version_ids]
 
756
        for name in topo_sort(pending_graph):
 
757
            other_idx = other._name_map[name]
827
758
            self._check_version_consistent(other, other_idx, name)
828
759
            sha1 = other._sha1s[other_idx]
829
760
 
830
761
            processed += 1
831
762
 
832
763
            if name in self._name_map:
833
 
                idx = self.lookup(name)
834
 
                n1 = set(map(other.idx_to_name, other._parents[other_idx]))
835
 
                n2 = set(map(self.idx_to_name, self._parents[idx]))
 
764
                idx = self._lookup(name)
 
765
                n1 = set(map(other._idx_to_name, other._parents[other_idx]))
 
766
                n2 = set(map(self._idx_to_name, self._parents[idx]))
836
767
                if sha1 ==  self._sha1s[idx] and n1 == n2:
837
768
                        continue
838
769
 
842
773
            msg = 'weave join'
843
774
 
844
775
        merged = 0
845
 
        time0 = time.time( )
 
776
        time0 = time.time()
846
777
        for other_idx, name in names_to_join:
847
778
            # TODO: If all the parents of the other version are already
848
779
            # present then we can avoid some work by just taking the delta
856
787
                pb.update(msg, merged, len(names_to_join))
857
788
           
858
789
            lines = other.get_lines(other_idx)
859
 
            self.add(name, new_parents, lines, sha1)
 
790
            self._add(name, lines, new_parents, sha1)
860
791
 
861
792
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
862
 
                merged, processed, self._weave_name, time.time( )-time0))
 
793
                merged, processed, self._weave_name, time.time()-time0))
863
794
 
864
795
    def _imported_parents(self, other, other_idx):
865
796
        """Return list of parents in self corresponding to indexes in other."""
888
819
        this_idx = self._name_map.get(name, -1)
889
820
        if this_idx != -1:
890
821
            if self._sha1s[this_idx] != other._sha1s[other_idx]:
891
 
                raise WeaveError("inconsistent texts for version {%s} "
892
 
                                 "when joining weaves"
893
 
                                 % (name))
 
822
                raise errors.WeaveTextDiffers(name, self, other)
894
823
            self_parents = self._parents[this_idx]
895
824
            other_parents = other._parents[other_idx]
896
825
            n1 = set([self._names[i] for i in self_parents])
897
826
            n2 = set([other._names[i] for i in other_parents])
898
 
            if n1 != n2:
 
827
            if not self._compatible_parents(n1, n2):
899
828
                raise WeaveParentMismatch("inconsistent parents "
900
829
                    "for version {%s}: %s vs %s" % (name, n1, n2))
901
830
            else:
903
832
        else:
904
833
            return False
905
834
 
 
835
    @deprecated_method(zero_eight)
906
836
    def reweave(self, other, pb=None, msg=None):
907
 
        """Reweave self with other.
 
837
        """reweave has been superceded by plain use of join."""
 
838
        return self.join(other, pb, msg)
 
839
 
 
840
    def _reweave(self, other, pb, msg):
 
841
        """Reweave self with other - internal helper for join().
908
842
 
909
843
        :param other: The other weave to merge
910
844
        :param pb: An optional progress bar, indicating how far done we are
911
845
        :param msg: An optional message for the progress
912
846
        """
913
 
        new_weave = reweave(self, other, pb=pb, msg=msg)
 
847
        new_weave = _reweave(self, other, pb=pb, msg=msg)
914
848
        for attr in self.__slots__:
915
 
            setattr(self, attr, getattr(new_weave, attr))
916
 
 
917
 
 
 
849
            if attr != '_weave_name':
 
850
                setattr(self, attr, getattr(new_weave, attr))
 
851
 
 
852
 
 
853
class WeaveFile(Weave):
 
854
    """A WeaveFile represents a Weave on disk and writes on change."""
 
855
 
 
856
    WEAVE_SUFFIX = '.weave'
 
857
    
 
858
    def __init__(self, name, transport, mode=None, create=False):
 
859
        """Create a WeaveFile.
 
860
        
 
861
        :param create: If not True, only open an existing knit.
 
862
        """
 
863
        super(WeaveFile, self).__init__(name)
 
864
        self._transport = transport
 
865
        self._mode = mode
 
866
        try:
 
867
            _read_weave_v5(self._transport.get(name + WeaveFile.WEAVE_SUFFIX), self)
 
868
        except errors.NoSuchFile:
 
869
            if not create:
 
870
                raise
 
871
            # new file, save it
 
872
            self._save()
 
873
 
 
874
    def add_lines(self, version_id, parents, lines):
 
875
        """Add a version and save the weave."""
 
876
        super(WeaveFile, self).add_lines(version_id, parents, lines)
 
877
        self._save()
 
878
 
 
879
    def copy_to(self, name, transport):
 
880
        """See VersionedFile.copy_to()."""
 
881
        # as we are all in memory always, just serialise to the new place.
 
882
        sio = StringIO()
 
883
        write_weave_v5(self, sio)
 
884
        sio.seek(0)
 
885
        transport.put(name + WeaveFile.WEAVE_SUFFIX, sio, self._mode)
 
886
 
 
887
    def create_empty(self, name, transport, mode=None):
 
888
        return WeaveFile(name, transport, mode, create=True)
 
889
 
 
890
    def _save(self):
 
891
        """Save the weave."""
 
892
        sio = StringIO()
 
893
        write_weave_v5(self, sio)
 
894
        sio.seek(0)
 
895
        self._transport.put(self._weave_name + WeaveFile.WEAVE_SUFFIX,
 
896
                            sio,
 
897
                            self._mode)
 
898
 
 
899
    @staticmethod
 
900
    def get_suffixes():
 
901
        """See VersionedFile.get_suffixes()."""
 
902
        return [WeaveFile.WEAVE_SUFFIX]
 
903
 
 
904
    def join(self, other, pb=None, msg=None, version_ids=None,
 
905
             ignore_missing=False):
 
906
        """Join other into self and save."""
 
907
        super(WeaveFile, self).join(other, pb, msg, version_ids, ignore_missing)
 
908
        self._save()
 
909
 
 
910
 
 
911
@deprecated_function(zero_eight)
918
912
def reweave(wa, wb, pb=None, msg=None):
 
913
    """reweaving is deprecation, please just use weave.join()."""
 
914
    _reweave(wa, wb, pb, msg)
 
915
 
 
916
def _reweave(wa, wb, pb=None, msg=None):
919
917
    """Combine two weaves and return the result.
920
918
 
921
919
    This works even if a revision R has different parents in 
932
930
    """
933
931
    wr = Weave()
934
932
    ia = ib = 0
935
 
    queue_a = range(wa.numversions())
936
 
    queue_b = range(wb.numversions())
 
933
    queue_a = range(wa.num_versions())
 
934
    queue_b = range(wb.num_versions())
937
935
    # first determine combined parents of all versions
938
936
    # map from version name -> all parent names
939
937
    combined_parents = _reweave_parent_graphs(wa, wb)
961
959
                    raise errors.WeaveTextDiffers(name, wa, wb)
962
960
        else:
963
961
            lines = wb.get_lines(name)
964
 
        wr.add(name, combined_parents[name], lines)
 
962
        wr._add(name, lines, [wr._lookup(i) for i in combined_parents[name]])
965
963
    return wr
966
964
 
967
 
 
968
965
def _reweave_parent_graphs(wa, wb):
969
966
    """Return combined parent ancestry for two weaves.
970
967
    
973
970
    for weave in [wa, wb]:
974
971
        for idx, name in enumerate(weave._names):
975
972
            p = combined.setdefault(name, set())
976
 
            p.update(map(weave.idx_to_name, weave._parents[idx]))
 
973
            p.update(map(weave._idx_to_name, weave._parents[idx]))
977
974
    return combined
978
975
 
979
976
 
983
980
    for i in (6, 50, 10, 10):
984
981
        print '-' * i,
985
982
    print
986
 
    for i in range(w.numversions()):
 
983
    for i in range(w.num_versions()):
987
984
        sha1 = w._sha1s[i]
988
985
        name = w._names[i]
989
986
        parent_str = ' '.join(map(str, w._parents[i]))
995
992
    from bzrlib.weavefile import read_weave
996
993
 
997
994
    wf = file(weave_file, 'rb')
998
 
    w = read_weave(wf)
 
995
    w = read_weave(wf, WeaveVersionedFile)
999
996
    # FIXME: doesn't work on pipes
1000
997
    weave_size = wf.tell()
1001
998
 
1036
1033
        Add NEWTEXT, with specified parent versions.
1037
1034
    weave annotate WEAVEFILE VERSION
1038
1035
        Display origin of each line.
1039
 
    weave mash WEAVEFILE VERSION...
1040
 
        Display composite of all selected versions.
1041
1036
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
1042
1037
        Auto-merge two versions and display conflicts.
1043
1038
    weave diff WEAVEFILE VERSION1 VERSION2 
1117
1112
        w = readit()
1118
1113
        sys.stdout.writelines(w.get_iter(int(argv[3])))
1119
1114
        
1120
 
    elif cmd == 'mash': # get composite
1121
 
        w = readit()
1122
 
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
1123
 
 
1124
1115
    elif cmd == 'diff':
1125
1116
        from difflib import unified_diff
1126
1117
        w = readit()
1157
1148
        pb = ProgressBar()
1158
1149
        w.check(pb)
1159
1150
        pb.clear()
1160
 
        print '%d versions ok' % w.numversions()
 
1151
        print '%d versions ok' % w.num_versions()
1161
1152
 
1162
1153
    elif cmd == 'inclusions':
1163
1154
        w = readit()
1178
1169
        p = w.plan_merge(int(argv[3]), int(argv[4]))
1179
1170
        sys.stdout.writelines(w.weave_merge(p))
1180
1171
            
1181
 
    elif cmd == 'mash-merge':
1182
 
        if len(argv) != 5:
1183
 
            usage()
1184
 
            return 1
1185
 
 
1186
 
        w = readit()
1187
 
        v1, v2 = map(int, argv[3:5])
1188
 
 
1189
 
        basis = w.inclusions([v1]).intersection(w.inclusions([v2]))
1190
 
 
1191
 
        base_lines = list(w.mash_iter(basis))
1192
 
        a_lines = list(w.get(v1))
1193
 
        b_lines = list(w.get(v2))
1194
 
 
1195
 
        from bzrlib.merge3 import Merge3
1196
 
        m3 = Merge3(base_lines, a_lines, b_lines)
1197
 
 
1198
 
        name_a = 'version %d' % v1
1199
 
        name_b = 'version %d' % v2
1200
 
        sys.stdout.writelines(m3.merge_lines(name_a=name_a, name_b=name_b))
1201
1172
    else:
1202
1173
        raise ValueError('unknown command %r' % cmd)
1203
1174
    
1244
1215
    else:
1245
1216
        sys.exit(main(sys.argv))
1246
1217
 
 
1218
 
 
1219
class InterWeave(InterVersionedFile):
 
1220
    """Optimised code paths for weave to weave operations."""
 
1221
    
 
1222
    _matching_file_factory = staticmethod(WeaveFile)
 
1223
    
 
1224
    @staticmethod
 
1225
    def is_compatible(source, target):
 
1226
        """Be compatible with weaves."""
 
1227
        try:
 
1228
            return (isinstance(source, Weave) and
 
1229
                    isinstance(target, Weave))
 
1230
        except AttributeError:
 
1231
            return False
 
1232
 
 
1233
    def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
 
1234
        """See InterVersionedFile.join."""
 
1235
        try:
 
1236
            self.target._join(self.source, pb, msg, version_ids, ignore_missing)
 
1237
        except errors.WeaveParentMismatch:
 
1238
            self.target._reweave(self.source, pb, msg)
 
1239
 
 
1240
 
 
1241
InterVersionedFile.register_optimiser(InterWeave)