~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Robert Collins
  • Date: 2006-02-22 10:35:05 UTC
  • mto: (1594.2.4 integration)
  • mto: This revision was merged to the branch mainline in revision 1596.
  • Revision ID: robertc@robertcollins.net-20060222103505-bddb211d353f2543
Merge in a variation of the versionedfile api from versioned-file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
69
69
 
70
70
import os
71
71
import sha
 
72
import time
72
73
from difflib import SequenceMatcher
73
74
 
74
75
from bzrlib.trace import mutter
75
76
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
76
 
        WeaveRevisionNotPresent, WeaveRevisionAlreadyPresent)
 
77
        RevisionAlreadyPresent,
 
78
        RevisionNotPresent,
 
79
        WeaveRevisionAlreadyPresent,
 
80
        WeaveRevisionNotPresent,
 
81
        )
77
82
import bzrlib.errors as errors
 
83
from bzrlib.symbol_versioning import *
78
84
from bzrlib.tsort import topo_sort
79
 
 
80
 
 
81
 
class Weave(object):
 
85
from bzrlib.versionedfile import VersionedFile
 
86
 
 
87
 
 
88
class Weave(VersionedFile):
82
89
    """weave - versioned text file storage.
83
90
    
84
91
    A Weave manages versions of line-based text files, keeping track
206
213
    def __ne__(self, other):
207
214
        return not self.__eq__(other)
208
215
 
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
 
        
 
216
    @deprecated_method(zero_eight)
 
217
    def idx_to_name(self, index):
 
218
        """Old public interface, the public interface is all names now."""
 
219
        return index
 
220
 
 
221
    def _idx_to_name(self, version):
 
222
        return self._names[version]
 
223
 
 
224
    @deprecated_method(zero_eight)
220
225
    def lookup(self, name):
 
226
        """Backwards compatability thunk:
 
227
 
 
228
        Return name, as name is valid in the api now, and spew deprecation
 
229
        warnings everywhere.
 
230
        """
 
231
        return name
 
232
 
 
233
    def _lookup(self, name):
221
234
        """Convert symbolic version name to index."""
222
235
        try:
223
236
            return self._name_map[name]
224
237
        except KeyError:
225
 
            raise WeaveRevisionNotPresent(name, self)
226
 
 
 
238
            raise RevisionNotPresent(name, self._weave_name)
 
239
 
 
240
    @deprecated_method(zero_eight)
 
241
    def iter_names(self):
 
242
        """Deprecated convenience function, please see VersionedFile.names()."""
 
243
        return iter(self.names())
 
244
 
 
245
    @deprecated_method(zero_eight)
227
246
    def names(self):
 
247
        """See Weave.versions for the current api."""
 
248
        return self.versions()
 
249
 
 
250
    def versions(self):
 
251
        """See VersionedFile.versions."""
228
252
        return self._names[:]
229
253
 
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]
 
254
    def has_version(self, version_id):
 
255
        """See VersionedFile.has_version."""
 
256
        return self._name_map.has_key(version_id)
 
257
 
 
258
    __contains__ = has_version
 
259
 
 
260
    @deprecated_method(zero_eight)
 
261
    def parent_names(self, version):
 
262
        """Return version names for parents of a version.
 
263
        
 
264
        See get_parents for the current api.
 
265
        """
 
266
        return self.get_parents(version)
 
267
 
 
268
    def get_parents(self, version_id):
 
269
        """See VersionedFile.get_parent."""
 
270
        return map(self._idx_to_name, self._parents[self._lookup(version_id)])
236
271
 
237
272
    def _check_repeated_add(self, name, parents, text, sha1):
238
273
        """Check that a duplicated add is OK.
239
274
 
240
275
        If it is, return the (old) index; otherwise raise an exception.
241
276
        """
242
 
        idx = self.lookup(name)
 
277
        idx = self._lookup(name)
243
278
        if sorted(self._parents[idx]) != sorted(parents) \
244
279
            or sha1 != self._sha1s[idx]:
245
 
            raise WeaveRevisionAlreadyPresent(name, self)
 
280
            raise RevisionAlreadyPresent(name, self._weave_name)
246
281
        return idx
247
 
        
 
282
 
 
283
    @deprecated_method(zero_eight)
 
284
    def add_identical(self, old_rev_id, new_rev_id, parents):
 
285
        """Please use Weave.clone_text now."""
 
286
        return self.clone_text(new_rev_id, old_rev_id, parents, None)
 
287
 
 
288
    def add_lines(self, version_id, parents, lines):
 
289
        """See VersionedFile.add_lines."""
 
290
        return self._add(version_id, lines, map(self._lookup, parents))
 
291
 
 
292
    @deprecated_method(zero_eight)
248
293
    def add(self, name, parents, text, sha1=None):
 
294
        """See VersionedFile.add_lines for the non deprecated api."""
 
295
        return self._add(name, text, map(self._maybe_lookup, parents), sha1)
 
296
 
 
297
    def _add(self, version_id, lines, parents, sha1=None):
249
298
        """Add a single text on top of the weave.
250
299
  
251
300
        Returns the index number of the newly added version.
252
301
 
253
 
        name
 
302
        version_id
254
303
            Symbolic name for this version.
255
304
            (Typically the revision-id of the revision that added it.)
256
305
 
257
306
        parents
258
307
            List or set of direct parent version numbers.
259
308
            
260
 
        text
 
309
        lines
261
310
            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
311
        """
266
312
        from bzrlib.osutils import sha_strings
267
313
 
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)
 
314
        assert isinstance(version_id, basestring)
 
315
        if not sha1:
 
316
            sha1 = sha_strings(lines)
 
317
        if version_id in self._name_map:
 
318
            return self._check_repeated_add(version_id, parents, lines, sha1)
273
319
 
274
 
        parents = map(self.maybe_lookup, parents)
275
320
        self._check_versions(parents)
276
 
        ## self._check_lines(text)
 
321
        ## self._check_lines(lines)
277
322
        new_version = len(self._parents)
278
323
 
279
 
 
280
324
        # if we abort after here the (in-memory) weave will be corrupt because only
281
325
        # some fields are updated
282
326
        self._parents.append(parents[:])
283
327
        self._sha1s.append(sha1)
284
 
        self._names.append(name)
285
 
        self._name_map[name] = new_version
 
328
        self._names.append(version_id)
 
329
        self._name_map[version_id] = new_version
286
330
 
287
331
            
288
332
        if not parents:
290
334
            # this more quickly by just appending unconditionally.
291
335
            # even more specially, if we're adding an empty text we
292
336
            # need do nothing at all.
293
 
            if text:
 
337
            if lines:
294
338
                self._weave.append(('{', new_version))
295
 
                self._weave.extend(text)
 
339
                self._weave.extend(lines)
296
340
                self._weave.append(('}', None))
297
 
        
298
341
            return new_version
299
342
 
300
343
        if len(parents) == 1:
304
347
                return new_version
305
348
            
306
349
 
307
 
        ancestors = self.inclusions(parents)
 
350
        ancestors = self._inclusions(parents)
308
351
 
309
352
        l = self._weave
310
353
 
317
360
 
318
361
        # another small special case: a merge, producing the same text
319
362
        # as auto-merge
320
 
        if text == basis_lines:
 
363
        if lines == basis_lines:
321
364
            return new_version            
322
365
 
323
366
        # add a sentinal, because we can also match against the final line
330
373
        #print 'basis_lines:', basis_lines
331
374
        #print 'new_lines:  ', lines
332
375
 
333
 
        s = SequenceMatcher(None, basis_lines, text)
 
376
        s = SequenceMatcher(None, basis_lines, lines)
334
377
 
335
378
        # offset gives the number of lines that have been inserted
336
379
        # into the weave up to the current point; if the original edit instruction
347
390
            i1 = basis_lineno[i1]
348
391
            i2 = basis_lineno[i2]
349
392
 
350
 
            assert 0 <= j1 <= j2 <= len(text)
 
393
            assert 0 <= j1 <= j2 <= len(lines)
351
394
 
352
395
            #print tag, i1, i2, j1, j2
353
396
 
364
407
                # we don't destroy ourselves
365
408
                i = i2 + offset
366
409
                self._weave[i:i] = ([('{', new_version)] 
367
 
                                    + text[j1:j2] 
 
410
                                    + lines[j1:j2] 
368
411
                                    + [('}', None)])
369
412
                offset += 2 + (j2 - j1)
370
 
 
371
413
        return new_version
372
414
 
373
 
    def add_identical(self, old_rev_id, new_rev_id, parents):
374
 
        """Add an identical text to old_rev_id as new_rev_id."""
375
 
        old_lines = self.get(self.lookup(old_rev_id))
376
 
        self.add(new_rev_id, parents, old_lines)
 
415
    def clone_text(self, new_version_id, old_version_id, parents,
 
416
                   transaction):
 
417
        """See VersionedFile.clone_text."""
 
418
        old_lines = self.get_text(old_version_id)
 
419
        self.add_lines(new_version_id, parents, old_lines)
377
420
 
378
 
    def inclusions(self, versions):
 
421
    def _inclusions(self, versions):
379
422
        """Return set of all ancestors of given version(s)."""
380
423
        i = set(versions)
381
424
        for v in xrange(max(versions), 0, -1):
386
429
        ## except IndexError:
387
430
        ##     raise ValueError("version %d not present in weave" % v)
388
431
 
389
 
 
390
 
    def parents(self, version):
391
 
        return self._parents[version]
392
 
 
393
 
 
394
 
    def parent_names(self, version):
395
 
        """Return version names for parents of a version."""
396
 
        return map(self.idx_to_name, self._parents[self.lookup(version)])
397
 
 
398
 
 
399
 
    def minimal_parents(self, version):
400
 
        """Find the minimal set of parents for the version."""
401
 
        included = self._parents[version]
402
 
        if not included:
 
432
    @deprecated_method(zero_eight)
 
433
    def inclusions(self, version_ids):
 
434
        """Deprecated - see VersionedFile.get_ancestry for the replacement."""
 
435
        if not version_ids:
403
436
            return []
404
 
        
405
 
        li = list(included)
406
 
        li.sort(reverse=True)
407
 
 
408
 
        mininc = []
409
 
        gotit = set()
410
 
 
411
 
        for pv in li:
412
 
            if pv not in gotit:
413
 
                mininc.append(pv)
414
 
                gotit.update(self.inclusions(pv))
415
 
 
416
 
        assert mininc[0] >= 0
417
 
        assert mininc[-1] < version
418
 
        return mininc
419
 
 
420
 
 
 
437
        if isinstance(version_ids[0], int):
 
438
            return [self._idx_to_name(v) for v in self._inclusions(version_ids)]
 
439
        else:
 
440
            return self.get_ancestry(version_ids)
 
441
 
 
442
    def get_ancestry(self, version_ids):
 
443
        """See VersionedFile.get_ancestry."""
 
444
        if isinstance(version_ids, basestring):
 
445
            version_ids = [version_ids]
 
446
        i = self._inclusions([self._lookup(v) for v in version_ids])
 
447
        return [self._idx_to_name(v) for v in i]
421
448
 
422
449
    def _check_lines(self, text):
423
450
        if not isinstance(text, list):
438
465
            except IndexError:
439
466
                raise IndexError("invalid version number %r" % i)
440
467
 
 
468
    def annotate(self, version_id):
 
469
        if isinstance(version_id, int):
 
470
            warn('Weave.annotate(int) is deprecated. Please use version names'
 
471
                 ' in all circumstances as of 0.8',
 
472
                 DeprecationWarning,
 
473
                 stacklevel=2
 
474
                 )
 
475
            result = []
 
476
            for origin, lineno, text in self._extract([version_id]):
 
477
                result.append((origin, text))
 
478
            return result
 
479
        else:
 
480
            return super(Weave, self).annotate(version_id)
441
481
    
442
 
    def annotate(self, name_or_index):
443
 
        return list(self.annotate_iter(name_or_index))
444
 
 
445
 
 
446
 
    def annotate_iter(self, name_or_index):
447
 
        """Yield list of (index-id, line) pairs for the specified version.
 
482
    def annotate_iter(self, version_id):
 
483
        """Yield list of (version-id, line) pairs for the specified version.
448
484
 
449
485
        The index indicates when the line originated in the weave."""
450
 
        incls = [self.maybe_lookup(name_or_index)]
 
486
        incls = [self._lookup(version_id)]
451
487
        for origin, lineno, text in self._extract(incls):
452
 
            yield origin, text
 
488
            yield self._idx_to_name(origin), text
453
489
 
 
490
    @deprecated_method(zero_eight)
454
491
    def _walk(self):
455
 
        """Walk the weave.
 
492
        """_walk has become walk, a supported api."""
 
493
        return self.walk()
456
494
 
457
 
        Yields sequence of
458
 
        (lineno, insert, deletes, text)
459
 
        for each literal line.
460
 
        """
 
495
    def walk(self, version_ids=None):
 
496
        """See VersionedFile.walk."""
461
497
        
462
498
        istack = []
463
499
        dset = set()
469
505
                c, v = l
470
506
                isactive = None
471
507
                if c == '{':
472
 
                    istack.append(v)
 
508
                    istack.append(self._idx_to_name(v))
473
509
                elif c == '}':
474
510
                    istack.pop()
475
511
                elif c == '[':
476
 
                    assert v not in dset
477
 
                    dset.add(v)
 
512
                    assert self._idx_to_name(v) not in dset
 
513
                    dset.add(self._idx_to_name(v))
478
514
                elif c == ']':
479
 
                    dset.remove(v)
 
515
                    dset.remove(self._idx_to_name(v))
480
516
                else:
481
517
                    raise WeaveFormatError('unexpected instruction %r' % v)
482
518
            else:
483
519
                assert isinstance(l, basestring)
484
520
                assert istack
485
 
                yield lineno, istack[-1], dset, l
 
521
                yield lineno, istack[-1], dset.copy(), l
486
522
            lineno += 1
487
523
 
488
524
        if istack:
505
541
            if not isinstance(i, int):
506
542
                raise ValueError(i)
507
543
            
508
 
        included = self.inclusions(versions)
 
544
        included = self._inclusions(versions)
509
545
 
510
546
        istack = []
511
547
        dset = set()
551
587
                                   % dset)
552
588
        return result
553
589
 
554
 
 
 
590
    @deprecated_method(zero_eight)
555
591
    def get_iter(self, name_or_index):
 
592
        """Deprecated, please do not use. Lookups are not not needed."""
 
593
        return self._get_iter(self._maybe_lookup(name_or_index))
 
594
 
 
595
    @deprecated_method(zero_eight)
 
596
    def maybe_lookup(self, name_or_index):
 
597
        """Deprecated, please do not use. Lookups are not not needed."""
 
598
        return self._maybe_lookup(name_or_index)
 
599
 
 
600
    def _maybe_lookup(self, name_or_index):
 
601
        """Convert possible symbolic name to index, or pass through indexes.
 
602
        
 
603
        NOT FOR PUBLIC USE.
 
604
        """
 
605
        if isinstance(name_or_index, (int, long)):
 
606
            return name_or_index
 
607
        else:
 
608
            return self._lookup(name_or_index)
 
609
 
 
610
    def _get_iter(self, version_id):
556
611
        """Yield lines for the specified version."""
557
 
        incls = [self.maybe_lookup(name_or_index)]
 
612
        incls = [self._maybe_lookup(version_id)]
558
613
        if len(incls) == 1:
559
614
            index = incls[0]
560
615
            cur_sha = sha.new()
574
629
                        % (self._weave_name, self._names[index],
575
630
                           expected_sha1, measured_sha1))
576
631
 
577
 
 
578
 
    def get_text(self, name_or_index):
579
 
        return ''.join(self.get_iter(name_or_index))
580
 
        assert isinstance(version, int)
581
 
 
582
 
 
583
 
    def get_lines(self, name_or_index):
584
 
        return list(self.get_iter(name_or_index))
585
 
 
586
 
 
587
 
    get = get_lines
588
 
 
 
632
    @deprecated_method(zero_eight)
 
633
    def get(self, version_id):
 
634
        """Please use either Weave.get_text or Weave.get_lines as desired."""
 
635
        return self.get_lines(version_id)
 
636
 
 
637
    def get_lines(self, version_id):
 
638
        """See VersionedFile.get_lines()."""
 
639
        return list(self._get_iter(version_id))
589
640
 
590
641
    def get_sha1(self, name):
591
642
        """Get the stored sha1 sum for the given revision.
592
643
        
593
644
        :param name: The name of the version to lookup
594
645
        """
595
 
        return self._sha1s[self.lookup(name)]
596
 
 
597
 
    def mash_iter(self, included):
598
 
        """Return composed version of multiple included versions."""
599
 
        included = map(self.maybe_lookup, included)
600
 
        for origin, lineno, text in self._extract(included):
601
 
            yield text
602
 
 
603
 
 
604
 
    def dump(self, to_file):
605
 
        from pprint import pprint
606
 
        print >>to_file, "Weave._weave = ",
607
 
        pprint(self._weave, to_file)
608
 
        print >>to_file, "Weave._parents = ",
609
 
        pprint(self._parents, to_file)
610
 
 
611
 
 
 
646
        return self._sha1s[self._lookup(name)]
612
647
 
613
648
    def numversions(self):
614
649
        l = len(self._parents)
615
650
        assert l == len(self._sha1s)
616
651
        return l
617
652
 
618
 
 
619
 
    def __len__(self):
620
 
        return self.numversions()
 
653
    __len__ = numversions
621
654
 
622
655
    def check(self, progress_bar=None):
 
656
        # TODO evaluate performance hit of using string sets in this routine.
623
657
        # check no circular inclusions
624
658
        for version in range(self.numversions()):
625
659
            inclusions = list(self._parents[version])
631
665
 
632
666
        # try extracting all versions; parallel extraction is used
633
667
        nv = self.numversions()
634
 
        sha1s = [sha.new() for i in range(nv)]
635
 
        texts = [[] for i in range(nv)]
636
 
        inclusions = []
 
668
        sha1s = {}
 
669
        texts = {}
 
670
        inclusions = {}
637
671
        for i in range(nv):
638
672
            # For creating the ancestry, IntSet is much faster (3.7s vs 0.17s)
639
673
            # The problem is that set membership is much more expensive
640
 
            new_inc = set([i])
 
674
            name = self._idx_to_name(i)
 
675
            sha1s[name] = sha.new()
 
676
            texts[name] = []
 
677
            new_inc = set([name])
641
678
            for p in self._parents[i]:
642
 
                new_inc.update(inclusions[p])
 
679
                new_inc.update(inclusions[self._idx_to_name(p)])
643
680
 
644
 
            #assert set(new_inc) == self.inclusions([i]), 'failed %s != %s' % (new_inc, self.inclusions([i]))
645
 
            inclusions.append(new_inc)
 
681
            assert set(new_inc) == set(self.get_ancestry(name)), \
 
682
                'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
 
683
            inclusions[name] = new_inc
646
684
 
647
685
        nlines = len(self._weave)
648
686
 
652
690
            update_text = 'checking %s' % (short_name,)
653
691
            update_text = update_text[:25]
654
692
 
655
 
        for lineno, insert, deleteset, line in self._walk():
 
693
        for lineno, insert, deleteset, line in self.walk():
656
694
            if progress_bar:
657
695
                progress_bar.update(update_text, lineno, nlines)
658
696
 
659
 
            for j, j_inc in enumerate(inclusions):
 
697
            for name, name_inclusions in inclusions.items():
660
698
                # The active inclusion must be an ancestor,
661
699
                # and no ancestors must have deleted this line,
662
700
                # because we don't support resurrection.
663
 
                if (insert in j_inc) and not (deleteset & j_inc):
664
 
                    sha1s[j].update(line)
 
701
                if (insert in name_inclusions) and not (deleteset & name_inclusions):
 
702
                    sha1s[name].update(line)
665
703
 
666
 
        for version in range(nv):
 
704
        for i in range(nv):
 
705
            version = self._idx_to_name(i)
667
706
            hd = sha1s[version].hexdigest()
668
 
            expected = self._sha1s[version]
 
707
            expected = self._sha1s[i]
669
708
            if hd != expected:
670
709
                raise errors.WeaveInvalidChecksum(
671
710
                        "mismatched sha1 for version %s: "
672
711
                        "got %s, expected %s"
673
 
                        % (self._names[version], hd, expected))
 
712
                        % (version, hd, expected))
674
713
 
675
714
        # TODO: check insertions are properly nested, that there are
676
715
        # no lines outside of insertion blocks, that deletions are
677
716
        # properly paired, etc.
678
717
 
679
 
    def _delta(self, included, lines):
680
 
        """Return changes from basis to new revision.
681
 
 
682
 
        The old text for comparison is the union of included revisions.
683
 
 
684
 
        This is used in inserting a new text.
685
 
 
686
 
        Delta is returned as a sequence of
687
 
        (weave1, weave2, newlines).
688
 
 
689
 
        This indicates that weave1:weave2 of the old weave should be
690
 
        replaced by the sequence of lines in newlines.  Note that
691
 
        these line numbers are positions in the total weave and don't
692
 
        correspond to the lines in any extracted version, or even the
693
 
        extracted union of included versions.
694
 
 
695
 
        If line1=line2, this is a pure insert; if newlines=[] this is a
696
 
        pure delete.  (Similar to difflib.)
697
 
        """
698
 
        raise NotImplementedError()
699
 
 
700
 
            
701
 
    def plan_merge(self, ver_a, ver_b):
702
 
        """Return pseudo-annotation indicating how the two versions merge.
703
 
 
704
 
        This is computed between versions a and b and their common
705
 
        base.
706
 
 
707
 
        Weave lines present in none of them are skipped entirely.
708
 
        """
709
 
        inc_a = self.inclusions([ver_a])
710
 
        inc_b = self.inclusions([ver_b])
711
 
        inc_c = inc_a & inc_b
712
 
 
713
 
        for lineno, insert, deleteset, line in self._walk():
714
 
            if deleteset & inc_c:
715
 
                # killed in parent; can't be in either a or b
716
 
                # not relevant to our work
717
 
                yield 'killed-base', line
718
 
            elif insert in inc_c:
719
 
                # was inserted in base
720
 
                killed_a = bool(deleteset & inc_a)
721
 
                killed_b = bool(deleteset & inc_b)
722
 
                if killed_a and killed_b:
723
 
                    yield 'killed-both', line
724
 
                elif killed_a:
725
 
                    yield 'killed-a', line
726
 
                elif killed_b:
727
 
                    yield 'killed-b', line
728
 
                else:
729
 
                    yield 'unchanged', line
730
 
            elif insert in inc_a:
731
 
                if deleteset & inc_a:
732
 
                    yield 'ghost-a', line
733
 
                else:
734
 
                    # new in A; not in B
735
 
                    yield 'new-a', line
736
 
            elif insert in inc_b:
737
 
                if deleteset & inc_b:
738
 
                    yield 'ghost-b', line
739
 
                else:
740
 
                    yield 'new-b', line
741
 
            else:
742
 
                # not in either revision
743
 
                yield 'irrelevant', line
744
 
 
745
 
        yield 'unchanged', ''           # terminator
746
 
 
747
 
 
748
 
 
749
 
    def weave_merge(self, plan):
750
 
        lines_a = []
751
 
        lines_b = []
752
 
        ch_a = ch_b = False
753
 
        # TODO: Return a structured form of the conflicts (e.g. 2-tuples for
754
 
        # conflicted regions), rather than just inserting the markers.
755
 
        # 
756
 
        # TODO: Show some version information (e.g. author, date) on 
757
 
        # conflicted regions.
758
 
        for state, line in plan:
759
 
            if state == 'unchanged' or state == 'killed-both':
760
 
                # resync and flush queued conflicts changes if any
761
 
                if not lines_a and not lines_b:
762
 
                    pass
763
 
                elif ch_a and not ch_b:
764
 
                    # one-sided change:                    
765
 
                    for l in lines_a: yield l
766
 
                elif ch_b and not ch_a:
767
 
                    for l in lines_b: yield l
768
 
                elif lines_a == lines_b:
769
 
                    for l in lines_a: yield l
770
 
                else:
771
 
                    yield '<<<<<<<\n'
772
 
                    for l in lines_a: yield l
773
 
                    yield '=======\n'
774
 
                    for l in lines_b: yield l
775
 
                    yield '>>>>>>>\n'
776
 
 
777
 
                del lines_a[:]
778
 
                del lines_b[:]
779
 
                ch_a = ch_b = False
780
 
                
781
 
            if state == 'unchanged':
782
 
                if line:
783
 
                    yield line
784
 
            elif state == 'killed-a':
785
 
                ch_a = True
786
 
                lines_b.append(line)
787
 
            elif state == 'killed-b':
788
 
                ch_b = True
789
 
                lines_a.append(line)
790
 
            elif state == 'new-a':
791
 
                ch_a = True
792
 
                lines_a.append(line)
793
 
            elif state == 'new-b':
794
 
                ch_b = True
795
 
                lines_b.append(line)
796
 
            else:
797
 
                assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
798
 
                                 'killed-both'), \
799
 
                       state
800
 
 
801
 
 
802
 
    def join(self, other, pb=None, msg=None):
 
718
 
 
719
    def join(self, other, pb=None, msg=None, version_ids=None):
803
720
        import sys, time
804
721
        """Integrate versions from other into this weave.
805
722
 
814
731
        :param pb: An optional progress bar
815
732
        :param msg: An optional message to display for progress
816
733
        """
817
 
        if other.numversions() == 0:
 
734
        if not other.versions():
818
735
            return          # nothing to update, easy
 
736
 
 
737
        if version_ids:
 
738
            for version_id in version_ids:
 
739
                if not self.has_version(version_id):
 
740
                    raise RevisionNotPresent(version_id, self._weave_name)
 
741
        assert version_ids == None
 
742
 
819
743
        # two loops so that we do not change ourselves before verifying it
820
744
        # will be ok
821
745
        # work through in index order to make sure we get all dependencies
828
752
            processed += 1
829
753
 
830
754
            if name in self._name_map:
831
 
                idx = self.lookup(name)
832
 
                n1 = set(map(other.idx_to_name, other._parents[other_idx]))
833
 
                n2 = set(map(self.idx_to_name, self._parents[idx]))
 
755
                idx = self._lookup(name)
 
756
                n1 = set(map(other._idx_to_name, other._parents[other_idx]))
 
757
                n2 = set(map(self._idx_to_name, self._parents[idx]))
834
758
                if sha1 ==  self._sha1s[idx] and n1 == n2:
835
759
                        continue
836
760
 
840
764
            msg = 'weave join'
841
765
 
842
766
        merged = 0
843
 
        time0 = time.time( )
 
767
        time0 = time.time()
844
768
        for other_idx, name in names_to_join:
845
769
            # TODO: If all the parents of the other version are already
846
770
            # present then we can avoid some work by just taking the delta
854
778
                pb.update(msg, merged, len(names_to_join))
855
779
           
856
780
            lines = other.get_lines(other_idx)
857
 
            self.add(name, new_parents, lines, sha1)
 
781
            self._add(name, lines, new_parents, sha1)
858
782
 
859
783
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
860
 
                merged, processed, self._weave_name, time.time( )-time0))
 
784
                merged, processed, self._weave_name, time.time()-time0))
861
785
 
862
786
    def _imported_parents(self, other, other_idx):
863
787
        """Return list of parents in self corresponding to indexes in other."""
959
883
                    raise errors.WeaveTextDiffers(name, wa, wb)
960
884
        else:
961
885
            lines = wb.get_lines(name)
962
 
        wr.add(name, combined_parents[name], lines)
 
886
        wr._add(name, lines, [wr._lookup(i) for i in combined_parents[name]])
963
887
    return wr
964
888
 
965
 
 
966
889
def _reweave_parent_graphs(wa, wb):
967
890
    """Return combined parent ancestry for two weaves.
968
891
    
971
894
    for weave in [wa, wb]:
972
895
        for idx, name in enumerate(weave._names):
973
896
            p = combined.setdefault(name, set())
974
 
            p.update(map(weave.idx_to_name, weave._parents[idx]))
 
897
            p.update(map(weave._idx_to_name, weave._parents[idx]))
975
898
    return combined
976
899
 
977
900
 
993
916
    from bzrlib.weavefile import read_weave
994
917
 
995
918
    wf = file(weave_file, 'rb')
996
 
    w = read_weave(wf)
 
919
    w = read_weave(wf, WeaveVersionedFile)
997
920
    # FIXME: doesn't work on pipes
998
921
    weave_size = wf.tell()
999
922
 
1034
957
        Add NEWTEXT, with specified parent versions.
1035
958
    weave annotate WEAVEFILE VERSION
1036
959
        Display origin of each line.
1037
 
    weave mash WEAVEFILE VERSION...
1038
 
        Display composite of all selected versions.
1039
960
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
1040
961
        Auto-merge two versions and display conflicts.
1041
962
    weave diff WEAVEFILE VERSION1 VERSION2 
1115
1036
        w = readit()
1116
1037
        sys.stdout.writelines(w.get_iter(int(argv[3])))
1117
1038
        
1118
 
    elif cmd == 'mash': # get composite
1119
 
        w = readit()
1120
 
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
1121
 
 
1122
1039
    elif cmd == 'diff':
1123
1040
        from difflib import unified_diff
1124
1041
        w = readit()
1176
1093
        p = w.plan_merge(int(argv[3]), int(argv[4]))
1177
1094
        sys.stdout.writelines(w.weave_merge(p))
1178
1095
            
1179
 
    elif cmd == 'mash-merge':
1180
 
        if len(argv) != 5:
1181
 
            usage()
1182
 
            return 1
1183
 
 
1184
 
        w = readit()
1185
 
        v1, v2 = map(int, argv[3:5])
1186
 
 
1187
 
        basis = w.inclusions([v1]).intersection(w.inclusions([v2]))
1188
 
 
1189
 
        base_lines = list(w.mash_iter(basis))
1190
 
        a_lines = list(w.get(v1))
1191
 
        b_lines = list(w.get(v2))
1192
 
 
1193
 
        from bzrlib.merge3 import Merge3
1194
 
        m3 = Merge3(base_lines, a_lines, b_lines)
1195
 
 
1196
 
        name_a = 'version %d' % v1
1197
 
        name_b = 'version %d' % v2
1198
 
        sys.stdout.writelines(m3.merge_lines(name_a=name_a, name_b=name_b))
1199
1096
    else:
1200
1097
        raise ValueError('unknown command %r' % cmd)
1201
1098