~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2008-02-13 03:45:58 UTC
  • mto: (3221.1.9 prepare-1.2rc1)
  • mto: This revision was merged to the branch mainline in revision 3236.
  • Revision ID: mbp@sourcefrog.net-20080213034558-anhm9j1l3mbbmnyo
Add in thumpers tests for selection of the right Launchpad instance

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#! /usr/bin/python
2
2
 
3
3
# Copyright (C) 2005 Canonical Ltd
4
 
 
 
4
#
5
5
# This program is free software; you can redistribute it and/or modify
6
6
# it under the terms of the GNU General Public License as published by
7
7
# the Free Software Foundation; either version 2 of the License, or
8
8
# (at your option) any later version.
9
 
 
 
9
#
10
10
# This program is distributed in the hope that it will be useful,
11
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
13
# GNU General Public License for more details.
14
 
 
 
14
#
15
15
# You should have received a copy of the GNU General Public License
16
16
# along with this program; if not, write to the Free Software
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27
27
# property.
28
28
 
29
29
# TODO: Nothing here so far assumes the lines are really \n newlines,
30
 
# rather than being split up in some other way.  We could accomodate
 
30
# rather than being split up in some other way.  We could accommodate
31
31
# binaries, perhaps by naively splitting on \n or perhaps using
32
32
# something like a rolling checksum.
33
33
 
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
import os
70
74
import sha
71
 
from difflib import SequenceMatcher
 
75
import time
 
76
import warnings
72
77
 
 
78
from bzrlib import (
 
79
    progress,
 
80
    )
73
81
from bzrlib.trace import mutter
74
 
from bzrlib.errors import WeaveError, WeaveFormatError, WeaveParentMismatch, \
75
 
        WeaveRevisionNotPresent, WeaveRevisionAlreadyPresent
 
82
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
 
83
        RevisionAlreadyPresent,
 
84
        RevisionNotPresent,
 
85
        WeaveRevisionAlreadyPresent,
 
86
        WeaveRevisionNotPresent,
 
87
        )
 
88
import bzrlib.errors as errors
 
89
from bzrlib.osutils import sha_strings
 
90
import bzrlib.patiencediff
76
91
from bzrlib.tsort import topo_sort
77
 
 
78
 
 
79
 
class Weave(object):
 
92
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
 
93
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
 
94
 
 
95
 
 
96
class Weave(VersionedFile):
80
97
    """weave - versioned text file storage.
81
98
    
82
99
    A Weave manages versions of line-based text files, keeping track
166
183
    """
167
184
 
168
185
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
169
 
                 '_weave_name']
 
186
                 '_weave_name', '_matcher']
170
187
    
171
 
    def __init__(self, weave_name=None):
 
188
    def __init__(self, weave_name=None, access_mode='w', matcher=None):
 
189
        super(Weave, self).__init__(access_mode)
172
190
        self._weave = []
173
191
        self._parents = []
174
192
        self._sha1s = []
175
193
        self._names = []
176
194
        self._name_map = {}
177
195
        self._weave_name = weave_name
 
196
        if matcher is None:
 
197
            self._matcher = bzrlib.patiencediff.PatienceSequenceMatcher
 
198
        else:
 
199
            self._matcher = matcher
178
200
 
179
201
    def __repr__(self):
180
202
        return "Weave(%r)" % self._weave_name
181
203
 
182
 
 
183
204
    def copy(self):
184
205
        """Return a deep copy of self.
185
206
        
199
220
        return self._parents == other._parents \
200
221
               and self._weave == other._weave \
201
222
               and self._sha1s == other._sha1s 
202
 
 
203
223
    
204
224
    def __ne__(self, other):
205
225
        return not self.__eq__(other)
206
226
 
207
 
    def __contains__(self, name):
208
 
        return self._name_map.has_key(name)
209
 
 
210
 
    def maybe_lookup(self, name_or_index):
211
 
        """Convert possible symbolic name to index, or pass through indexes."""
212
 
        if isinstance(name_or_index, (int, long)):
213
 
            return name_or_index
214
 
        else:
215
 
            return self.lookup(name_or_index)
216
 
 
217
 
        
218
 
    def lookup(self, name):
 
227
    def _idx_to_name(self, version):
 
228
        return self._names[version]
 
229
 
 
230
    def _lookup(self, name):
219
231
        """Convert symbolic version name to index."""
 
232
        self.check_not_reserved_id(name)
220
233
        try:
221
234
            return self._name_map[name]
222
235
        except KeyError:
223
 
            raise WeaveRevisionNotPresent(name, self)
 
236
            raise RevisionNotPresent(name, self._weave_name)
224
237
 
225
 
    def names(self):
 
238
    def versions(self):
 
239
        """See VersionedFile.versions."""
226
240
        return self._names[:]
227
241
 
228
 
    def iter_names(self):
229
 
        """Yield a list of all names in this weave."""
230
 
        return iter(self._names)
231
 
 
232
 
    def idx_to_name(self, version):
233
 
        return self._names[version]
 
242
    def has_version(self, version_id):
 
243
        """See VersionedFile.has_version."""
 
244
        return (version_id in self._name_map)
 
245
 
 
246
    __contains__ = has_version
 
247
 
 
248
    def get_parents(self, version_id):
 
249
        """See VersionedFile.get_parent."""
 
250
        return map(self._idx_to_name, self._parents[self._lookup(version_id)])
234
251
 
235
252
    def _check_repeated_add(self, name, parents, text, sha1):
236
253
        """Check that a duplicated add is OK.
237
254
 
238
255
        If it is, return the (old) index; otherwise raise an exception.
239
256
        """
240
 
        idx = self.lookup(name)
 
257
        idx = self._lookup(name)
241
258
        if sorted(self._parents[idx]) != sorted(parents) \
242
259
            or sha1 != self._sha1s[idx]:
243
 
            raise WeaveRevisionAlreadyPresent(name, self)
 
260
            raise RevisionAlreadyPresent(name, self._weave_name)
244
261
        return idx
245
 
        
246
 
    def add(self, name, parents, text, sha1=None):
 
262
 
 
263
    def _add_lines(self, version_id, parents, lines, parent_texts,
 
264
       left_matching_blocks, nostore_sha, random_id, check_content):
 
265
        """See VersionedFile.add_lines."""
 
266
        idx = self._add(version_id, lines, map(self._lookup, parents),
 
267
            nostore_sha=nostore_sha)
 
268
        return sha_strings(lines), sum(map(len, lines)), idx
 
269
 
 
270
    def _add(self, version_id, lines, parents, sha1=None, nostore_sha=None):
247
271
        """Add a single text on top of the weave.
248
272
  
249
273
        Returns the index number of the newly added version.
250
274
 
251
 
        name
 
275
        version_id
252
276
            Symbolic name for this version.
253
277
            (Typically the revision-id of the revision that added it.)
254
278
 
255
279
        parents
256
280
            List or set of direct parent version numbers.
257
281
            
258
 
        text
 
282
        lines
259
283
            Sequence of lines to be added in the new version.
260
284
 
261
 
        sha -- SHA-1 of the file, if known.  This is trusted to be
262
 
            correct if supplied.
 
285
        :param nostore_sha: See VersionedFile.add_lines.
263
286
        """
264
 
        from bzrlib.osutils import sha_strings
265
 
 
266
 
        assert isinstance(name, basestring)
267
 
        if sha1 is None:
268
 
            sha1 = sha_strings(text)
269
 
        if name in self._name_map:
270
 
            return self._check_repeated_add(name, parents, text, sha1)
271
 
 
272
 
        parents = map(self.maybe_lookup, parents)
 
287
        assert isinstance(version_id, basestring)
 
288
        self._check_lines_not_unicode(lines)
 
289
        self._check_lines_are_lines(lines)
 
290
        if not sha1:
 
291
            sha1 = sha_strings(lines)
 
292
        if sha1 == nostore_sha:
 
293
            raise errors.ExistingContent
 
294
        if version_id in self._name_map:
 
295
            return self._check_repeated_add(version_id, parents, lines, sha1)
 
296
 
273
297
        self._check_versions(parents)
274
 
        ## self._check_lines(text)
 
298
        ## self._check_lines(lines)
275
299
        new_version = len(self._parents)
276
300
 
277
 
 
278
301
        # if we abort after here the (in-memory) weave will be corrupt because only
279
302
        # some fields are updated
 
303
        # XXX: FIXME implement a succeed-or-fail of the rest of this routine.
 
304
        #      - Robert Collins 20060226
280
305
        self._parents.append(parents[:])
281
306
        self._sha1s.append(sha1)
282
 
        self._names.append(name)
283
 
        self._name_map[name] = new_version
 
307
        self._names.append(version_id)
 
308
        self._name_map[version_id] = new_version
284
309
 
285
310
            
286
311
        if not parents:
288
313
            # this more quickly by just appending unconditionally.
289
314
            # even more specially, if we're adding an empty text we
290
315
            # need do nothing at all.
291
 
            if text:
 
316
            if lines:
292
317
                self._weave.append(('{', new_version))
293
 
                self._weave.extend(text)
 
318
                self._weave.extend(lines)
294
319
                self._weave.append(('}', None))
295
 
        
296
320
            return new_version
297
321
 
298
322
        if len(parents) == 1:
302
326
                return new_version
303
327
            
304
328
 
305
 
        ancestors = self.inclusions(parents)
 
329
        ancestors = self._inclusions(parents)
306
330
 
307
331
        l = self._weave
308
332
 
315
339
 
316
340
        # another small special case: a merge, producing the same text
317
341
        # as auto-merge
318
 
        if text == basis_lines:
319
 
            return new_version            
 
342
        if lines == basis_lines:
 
343
            return new_version
320
344
 
321
 
        # add a sentinal, because we can also match against the final line
 
345
        # add a sentinel, because we can also match against the final line
322
346
        basis_lineno.append(len(self._weave))
323
347
 
324
348
        # XXX: which line of the weave should we really consider
328
352
        #print 'basis_lines:', basis_lines
329
353
        #print 'new_lines:  ', lines
330
354
 
331
 
        s = SequenceMatcher(None, basis_lines, text)
 
355
        s = self._matcher(None, basis_lines, lines)
332
356
 
333
357
        # offset gives the number of lines that have been inserted
334
358
        # into the weave up to the current point; if the original edit instruction
345
369
            i1 = basis_lineno[i1]
346
370
            i2 = basis_lineno[i2]
347
371
 
348
 
            assert 0 <= j1 <= j2 <= len(text)
 
372
            assert 0 <= j1 <= j2 <= len(lines)
349
373
 
350
374
            #print tag, i1, i2, j1, j2
351
375
 
362
386
                # we don't destroy ourselves
363
387
                i = i2 + offset
364
388
                self._weave[i:i] = ([('{', new_version)] 
365
 
                                    + text[j1:j2] 
 
389
                                    + lines[j1:j2] 
366
390
                                    + [('}', None)])
367
391
                offset += 2 + (j2 - j1)
368
 
 
369
392
        return new_version
370
393
 
371
 
    def add_identical(self, old_rev_id, new_rev_id, parents):
372
 
        """Add an identical text to old_rev_id as new_rev_id."""
373
 
        old_lines = self.get(self.lookup(old_rev_id))
374
 
        self.add(new_rev_id, parents, old_lines)
 
394
    def _clone_text(self, new_version_id, old_version_id, parents):
 
395
        """See VersionedFile.clone_text."""
 
396
        old_lines = self.get_text(old_version_id)
 
397
        self.add_lines(new_version_id, parents, old_lines)
375
398
 
376
 
    def inclusions(self, versions):
 
399
    def _inclusions(self, versions):
377
400
        """Return set of all ancestors of given version(s)."""
 
401
        if not len(versions):
 
402
            return []
378
403
        i = set(versions)
379
404
        for v in xrange(max(versions), 0, -1):
380
405
            if v in i:
384
409
        ## except IndexError:
385
410
        ##     raise ValueError("version %d not present in weave" % v)
386
411
 
387
 
 
388
 
    def parents(self, version):
389
 
        return self._parents[version]
390
 
 
391
 
 
392
 
    def parent_names(self, version):
393
 
        """Return version names for parents of a version."""
394
 
        return map(self.idx_to_name, self._parents[self.lookup(version)])
395
 
 
396
 
 
397
 
    def minimal_parents(self, version):
398
 
        """Find the minimal set of parents for the version."""
399
 
        included = self._parents[version]
400
 
        if not included:
401
 
            return []
402
 
        
403
 
        li = list(included)
404
 
        li.sort(reverse=True)
405
 
 
406
 
        mininc = []
407
 
        gotit = set()
408
 
 
409
 
        for pv in li:
410
 
            if pv not in gotit:
411
 
                mininc.append(pv)
412
 
                gotit.update(self.inclusions(pv))
413
 
 
414
 
        assert mininc[0] >= 0
415
 
        assert mininc[-1] < version
416
 
        return mininc
417
 
 
418
 
 
 
412
    def get_ancestry(self, version_ids, topo_sorted=True):
 
413
        """See VersionedFile.get_ancestry."""
 
414
        if isinstance(version_ids, basestring):
 
415
            version_ids = [version_ids]
 
416
        i = self._inclusions([self._lookup(v) for v in version_ids])
 
417
        return [self._idx_to_name(v) for v in i]
419
418
 
420
419
    def _check_lines(self, text):
421
420
        if not isinstance(text, list):
436
435
            except IndexError:
437
436
                raise IndexError("invalid version number %r" % i)
438
437
 
439
 
    
440
 
    def annotate(self, name_or_index):
441
 
        return list(self.annotate_iter(name_or_index))
442
 
 
443
 
 
444
 
    def annotate_iter(self, name_or_index):
445
 
        """Yield list of (index-id, line) pairs for the specified version.
 
438
    def _compatible_parents(self, my_parents, other_parents):
 
439
        """During join check that other_parents are joinable with my_parents.
 
440
 
 
441
        Joinable is defined as 'is a subset of' - supersets may require 
 
442
        regeneration of diffs, but subsets do not.
 
443
        """
 
444
        return len(other_parents.difference(my_parents)) == 0
 
445
 
 
446
    def annotate_iter(self, version_id):
 
447
        """Yield list of (version-id, line) pairs for the specified version.
446
448
 
447
449
        The index indicates when the line originated in the weave."""
448
 
        incls = [self.maybe_lookup(name_or_index)]
 
450
        incls = [self._lookup(version_id)]
449
451
        for origin, lineno, text in self._extract(incls):
450
 
            yield origin, text
451
 
 
452
 
    def _walk(self):
453
 
        """Walk the weave.
454
 
 
455
 
        Yields sequence of
456
 
        (lineno, insert, deletes, text)
457
 
        for each literal line.
458
 
        """
 
452
            yield self._idx_to_name(origin), text
 
453
 
 
454
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
 
455
                                                pb=None):
 
456
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
 
457
        if version_ids is None:
 
458
            version_ids = self.versions()
 
459
        version_ids = set(version_ids)
 
460
        for lineno, inserted, deletes, line in self._walk_internal(version_ids):
 
461
            # if inserted not in version_ids then it was inserted before the
 
462
            # versions we care about, but because weaves cannot represent ghosts
 
463
            # properly, we do not filter down to that
 
464
            # if inserted not in version_ids: continue
 
465
            if line[-1] != '\n':
 
466
                yield line + '\n', inserted
 
467
            else:
 
468
                yield line, inserted
 
469
 
 
470
    def _walk_internal(self, version_ids=None):
 
471
        """Helper method for weave actions."""
459
472
        
460
473
        istack = []
461
474
        dset = set()
463
476
        lineno = 0         # line of weave, 0-based
464
477
 
465
478
        for l in self._weave:
466
 
            if isinstance(l, tuple):
 
479
            if l.__class__ == tuple:
467
480
                c, v = l
468
481
                isactive = None
469
482
                if c == '{':
470
 
                    istack.append(v)
 
483
                    istack.append(self._names[v])
471
484
                elif c == '}':
472
485
                    istack.pop()
473
486
                elif c == '[':
474
 
                    assert v not in dset
475
 
                    dset.add(v)
 
487
                    assert self._names[v] not in dset
 
488
                    dset.add(self._names[v])
476
489
                elif c == ']':
477
 
                    dset.remove(v)
 
490
                    dset.remove(self._names[v])
478
491
                else:
479
492
                    raise WeaveFormatError('unexpected instruction %r' % v)
480
493
            else:
481
 
                assert isinstance(l, basestring)
 
494
                assert l.__class__ in (str, unicode)
482
495
                assert istack
483
 
                yield lineno, istack[-1], dset, l
 
496
                yield lineno, istack[-1], frozenset(dset), l
484
497
            lineno += 1
485
498
 
486
 
 
 
499
        if istack:
 
500
            raise WeaveFormatError("unclosed insertion blocks "
 
501
                    "at end of weave: %s" % istack)
 
502
        if dset:
 
503
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
 
504
                                   % dset)
 
505
 
 
506
    def plan_merge(self, ver_a, ver_b):
 
507
        """Return pseudo-annotation indicating how the two versions merge.
 
508
 
 
509
        This is computed between versions a and b and their common
 
510
        base.
 
511
 
 
512
        Weave lines present in none of them are skipped entirely.
 
513
        """
 
514
        inc_a = set(self.get_ancestry([ver_a]))
 
515
        inc_b = set(self.get_ancestry([ver_b]))
 
516
        inc_c = inc_a & inc_b
 
517
 
 
518
        for lineno, insert, deleteset, line in self._walk_internal([ver_a, ver_b]):
 
519
            if deleteset & inc_c:
 
520
                # killed in parent; can't be in either a or b
 
521
                # not relevant to our work
 
522
                yield 'killed-base', line
 
523
            elif insert in inc_c:
 
524
                # was inserted in base
 
525
                killed_a = bool(deleteset & inc_a)
 
526
                killed_b = bool(deleteset & inc_b)
 
527
                if killed_a and killed_b:
 
528
                    yield 'killed-both', line
 
529
                elif killed_a:
 
530
                    yield 'killed-a', line
 
531
                elif killed_b:
 
532
                    yield 'killed-b', line
 
533
                else:
 
534
                    yield 'unchanged', line
 
535
            elif insert in inc_a:
 
536
                if deleteset & inc_a:
 
537
                    yield 'ghost-a', line
 
538
                else:
 
539
                    # new in A; not in B
 
540
                    yield 'new-a', line
 
541
            elif insert in inc_b:
 
542
                if deleteset & inc_b:
 
543
                    yield 'ghost-b', line
 
544
                else:
 
545
                    yield 'new-b', line
 
546
            else:
 
547
                # not in either revision
 
548
                yield 'irrelevant', line
 
549
 
 
550
        yield 'unchanged', ''           # terminator
487
551
 
488
552
    def _extract(self, versions):
489
553
        """Yield annotation of lines in included set.
498
562
            if not isinstance(i, int):
499
563
                raise ValueError(i)
500
564
            
501
 
        included = self.inclusions(versions)
 
565
        included = self._inclusions(versions)
502
566
 
503
567
        istack = []
 
568
        iset = set()
504
569
        dset = set()
505
570
 
506
571
        lineno = 0         # line of weave, 0-based
511
576
 
512
577
        WFE = WeaveFormatError
513
578
 
 
579
        # wow. 
 
580
        #  449       0   4474.6820   2356.5590   bzrlib.weave:556(_extract)
 
581
        #  +285282   0   1676.8040   1676.8040   +<isinstance>
 
582
        # 1.6 seconds in 'isinstance'.
 
583
        # changing the first isinstance:
 
584
        #  449       0   2814.2660   1577.1760   bzrlib.weave:556(_extract)
 
585
        #  +140414   0    762.8050    762.8050   +<isinstance>
 
586
        # note that the inline time actually dropped (less function calls)
 
587
        # and total processing time was halved.
 
588
        # we're still spending ~1/4 of the method in isinstance though.
 
589
        # so lets hard code the acceptable string classes we expect:
 
590
        #  449       0   1202.9420    786.2930   bzrlib.weave:556(_extract)
 
591
        # +71352     0    377.5560    377.5560   +<method 'append' of 'list' 
 
592
        #                                          objects>
 
593
        # yay, down to ~1/4 the initial extract time, and our inline time
 
594
        # has shrunk again, with isinstance no longer dominating.
 
595
        # tweaking the stack inclusion test to use a set gives:
 
596
        #  449       0   1122.8030    713.0080   bzrlib.weave:556(_extract)
 
597
        # +71352     0    354.9980    354.9980   +<method 'append' of 'list' 
 
598
        #                                          objects>
 
599
        # - a 5% win, or possibly just noise. However with large istacks that
 
600
        # 'in' test could dominate, so I'm leaving this change in place -
 
601
        # when its fast enough to consider profiling big datasets we can review.
 
602
 
 
603
              
 
604
             
 
605
 
514
606
        for l in self._weave:
515
 
            if isinstance(l, tuple):
 
607
            if l.__class__ == tuple:
516
608
                c, v = l
517
609
                isactive = None
518
610
                if c == '{':
519
 
                    assert v not in istack
 
611
                    assert v not in iset
520
612
                    istack.append(v)
 
613
                    iset.add(v)
521
614
                elif c == '}':
522
 
                    istack.pop()
 
615
                    iset.remove(istack.pop())
523
616
                elif c == '[':
524
617
                    if v in included:
525
618
                        assert v not in dset
530
623
                        assert v in dset
531
624
                        dset.remove(v)
532
625
            else:
533
 
                assert isinstance(l, basestring)
 
626
                assert l.__class__ in (str, unicode)
534
627
                if isactive is None:
535
628
                    isactive = (not dset) and istack and (istack[-1] in included)
536
629
                if isactive:
544
637
                                   % dset)
545
638
        return result
546
639
 
547
 
 
548
 
    def get_iter(self, name_or_index):
549
 
        """Yield lines for the specified version."""
550
 
        incls = [self.maybe_lookup(name_or_index)]
551
 
        for origin, lineno, line in self._extract(incls):
552
 
            yield line
553
 
 
554
 
 
555
 
    def get_text(self, name_or_index):
556
 
        return ''.join(self.get_iter(name_or_index))
557
 
        assert isinstance(version, int)
558
 
 
559
 
 
560
 
    def get_lines(self, name_or_index):
561
 
        return list(self.get_iter(name_or_index))
562
 
 
563
 
 
564
 
    get = get_lines
565
 
 
566
 
 
567
 
    def mash_iter(self, included):
568
 
        """Return composed version of multiple included versions."""
569
 
        included = map(self.maybe_lookup, included)
570
 
        for origin, lineno, text in self._extract(included):
571
 
            yield text
572
 
 
573
 
 
574
 
    def dump(self, to_file):
575
 
        from pprint import pprint
576
 
        print >>to_file, "Weave._weave = ",
577
 
        pprint(self._weave, to_file)
578
 
        print >>to_file, "Weave._parents = ",
579
 
        pprint(self._parents, to_file)
580
 
 
581
 
 
582
 
 
583
 
    def numversions(self):
 
640
    def _maybe_lookup(self, name_or_index):
 
641
        """Convert possible symbolic name to index, or pass through indexes.
 
642
        
 
643
        NOT FOR PUBLIC USE.
 
644
        """
 
645
        if isinstance(name_or_index, (int, long)):
 
646
            return name_or_index
 
647
        else:
 
648
            return self._lookup(name_or_index)
 
649
 
 
650
    def get_lines(self, version_id):
 
651
        """See VersionedFile.get_lines()."""
 
652
        int_index = self._maybe_lookup(version_id)
 
653
        result = [line for (origin, lineno, line) in self._extract([int_index])]
 
654
        expected_sha1 = self._sha1s[int_index]
 
655
        measured_sha1 = sha_strings(result)
 
656
        if measured_sha1 != expected_sha1:
 
657
            raise errors.WeaveInvalidChecksum(
 
658
                    'file %s, revision %s, expected: %s, measured %s' 
 
659
                    % (self._weave_name, version_id,
 
660
                       expected_sha1, measured_sha1))
 
661
        return result
 
662
 
 
663
    def get_sha1(self, version_id):
 
664
        """See VersionedFile.get_sha1()."""
 
665
        return self._sha1s[self._lookup(version_id)]
 
666
 
 
667
    def get_sha1s(self, version_ids):
 
668
        """See VersionedFile.get_sha1s()."""
 
669
        return [self._sha1s[self._lookup(v)] for v in version_ids]
 
670
 
 
671
    def num_versions(self):
 
672
        """How many versions are in this weave?"""
584
673
        l = len(self._parents)
585
674
        assert l == len(self._sha1s)
586
675
        return l
587
676
 
588
 
 
589
 
    def __len__(self):
590
 
        return self.numversions()
591
 
 
 
677
    __len__ = num_versions
592
678
 
593
679
    def check(self, progress_bar=None):
594
 
        # check no circular inclusions
595
 
        for version in range(self.numversions()):
 
680
        # TODO evaluate performance hit of using string sets in this routine.
 
681
        # TODO: check no circular inclusions
 
682
        # TODO: create a nested progress bar
 
683
        for version in range(self.num_versions()):
596
684
            inclusions = list(self._parents[version])
597
685
            if inclusions:
598
686
                inclusions.sort()
600
688
                    raise WeaveFormatError("invalid included version %d for index %d"
601
689
                                           % (inclusions[-1], version))
602
690
 
603
 
        # try extracting all versions; this is a bit slow and parallel
604
 
        # extraction could be used
605
 
        nv = self.numversions()
606
 
        for version in range(nv):
 
691
        # try extracting all versions; parallel extraction is used
 
692
        nv = self.num_versions()
 
693
        sha1s = {}
 
694
        texts = {}
 
695
        inclusions = {}
 
696
        for i in range(nv):
 
697
            # For creating the ancestry, IntSet is much faster (3.7s vs 0.17s)
 
698
            # The problem is that set membership is much more expensive
 
699
            name = self._idx_to_name(i)
 
700
            sha1s[name] = sha.new()
 
701
            texts[name] = []
 
702
            new_inc = set([name])
 
703
            for p in self._parents[i]:
 
704
                new_inc.update(inclusions[self._idx_to_name(p)])
 
705
 
 
706
            assert set(new_inc) == set(self.get_ancestry(name)), \
 
707
                'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
 
708
            inclusions[name] = new_inc
 
709
 
 
710
        nlines = len(self._weave)
 
711
 
 
712
        update_text = 'checking weave'
 
713
        if self._weave_name:
 
714
            short_name = os.path.basename(self._weave_name)
 
715
            update_text = 'checking %s' % (short_name,)
 
716
            update_text = update_text[:25]
 
717
 
 
718
        for lineno, insert, deleteset, line in self._walk_internal():
607
719
            if progress_bar:
608
 
                progress_bar.update('checking text', version, nv)
609
 
            s = sha.new()
610
 
            for l in self.get_iter(version):
611
 
                s.update(l)
612
 
            hd = s.hexdigest()
613
 
            expected = self._sha1s[version]
 
720
                progress_bar.update(update_text, lineno, nlines)
 
721
 
 
722
            for name, name_inclusions in inclusions.items():
 
723
                # The active inclusion must be an ancestor,
 
724
                # and no ancestors must have deleted this line,
 
725
                # because we don't support resurrection.
 
726
                if (insert in name_inclusions) and not (deleteset & name_inclusions):
 
727
                    sha1s[name].update(line)
 
728
 
 
729
        for i in range(nv):
 
730
            version = self._idx_to_name(i)
 
731
            hd = sha1s[version].hexdigest()
 
732
            expected = self._sha1s[i]
614
733
            if hd != expected:
615
 
                raise WeaveError("mismatched sha1 for version %d; "
616
 
                                 "got %s, expected %s"
617
 
                                 % (version, hd, expected))
 
734
                raise errors.WeaveInvalidChecksum(
 
735
                        "mismatched sha1 for version %s: "
 
736
                        "got %s, expected %s"
 
737
                        % (version, hd, expected))
618
738
 
619
739
        # TODO: check insertions are properly nested, that there are
620
740
        # no lines outside of insertion blocks, that deletions are
621
741
        # properly paired, etc.
622
742
 
623
 
 
624
 
    def _delta(self, included, lines):
625
 
        """Return changes from basis to new revision.
626
 
 
627
 
        The old text for comparison is the union of included revisions.
628
 
 
629
 
        This is used in inserting a new text.
630
 
 
631
 
        Delta is returned as a sequence of
632
 
        (weave1, weave2, newlines).
633
 
 
634
 
        This indicates that weave1:weave2 of the old weave should be
635
 
        replaced by the sequence of lines in newlines.  Note that
636
 
        these line numbers are positions in the total weave and don't
637
 
        correspond to the lines in any extracted version, or even the
638
 
        extracted union of included versions.
639
 
 
640
 
        If line1=line2, this is a pure insert; if newlines=[] this is a
641
 
        pure delete.  (Similar to difflib.)
642
 
        """
643
 
        raise NotImplementedError()
644
 
 
645
 
            
646
 
    def plan_merge(self, ver_a, ver_b):
647
 
        """Return pseudo-annotation indicating how the two versions merge.
648
 
 
649
 
        This is computed between versions a and b and their common
650
 
        base.
651
 
 
652
 
        Weave lines present in none of them are skipped entirely.
653
 
        """
654
 
        inc_a = self.inclusions([ver_a])
655
 
        inc_b = self.inclusions([ver_b])
656
 
        inc_c = inc_a & inc_b
657
 
 
658
 
        for lineno, insert, deleteset, line in self._walk():
659
 
            if deleteset & inc_c:
660
 
                # killed in parent; can't be in either a or b
661
 
                # not relevant to our work
662
 
                yield 'killed-base', line
663
 
            elif insert in inc_c:
664
 
                # was inserted in base
665
 
                killed_a = bool(deleteset & inc_a)
666
 
                killed_b = bool(deleteset & inc_b)
667
 
                if killed_a and killed_b:
668
 
                    yield 'killed-both', line
669
 
                elif killed_a:
670
 
                    yield 'killed-a', line
671
 
                elif killed_b:
672
 
                    yield 'killed-b', line
673
 
                else:
674
 
                    yield 'unchanged', line
675
 
            elif insert in inc_a:
676
 
                if deleteset & inc_a:
677
 
                    yield 'ghost-a', line
678
 
                else:
679
 
                    # new in A; not in B
680
 
                    yield 'new-a', line
681
 
            elif insert in inc_b:
682
 
                if deleteset & inc_b:
683
 
                    yield 'ghost-b', line
684
 
                else:
685
 
                    yield 'new-b', line
686
 
            else:
687
 
                # not in either revision
688
 
                yield 'irrelevant', line
689
 
 
690
 
        yield 'unchanged', ''           # terminator
691
 
 
692
 
 
693
 
 
694
 
    def weave_merge(self, plan):
695
 
        lines_a = []
696
 
        lines_b = []
697
 
        ch_a = ch_b = False
698
 
 
699
 
        for state, line in plan:
700
 
            if state == 'unchanged' or state == 'killed-both':
701
 
                # resync and flush queued conflicts changes if any
702
 
                if not lines_a and not lines_b:
703
 
                    pass
704
 
                elif ch_a and not ch_b:
705
 
                    # one-sided change:                    
706
 
                    for l in lines_a: yield l
707
 
                elif ch_b and not ch_a:
708
 
                    for l in lines_b: yield l
709
 
                elif lines_a == lines_b:
710
 
                    for l in lines_a: yield l
711
 
                else:
712
 
                    yield '<<<<<<<\n'
713
 
                    for l in lines_a: yield l
714
 
                    yield '=======\n'
715
 
                    for l in lines_b: yield l
716
 
                    yield '>>>>>>>\n'
717
 
 
718
 
                del lines_a[:]
719
 
                del lines_b[:]
720
 
                ch_a = ch_b = False
721
 
                
722
 
            if state == 'unchanged':
723
 
                if line:
724
 
                    yield line
725
 
            elif state == 'killed-a':
726
 
                ch_a = True
727
 
                lines_b.append(line)
728
 
            elif state == 'killed-b':
729
 
                ch_b = True
730
 
                lines_a.append(line)
731
 
            elif state == 'new-a':
732
 
                ch_a = True
733
 
                lines_a.append(line)
734
 
            elif state == 'new-b':
735
 
                ch_b = True
736
 
                lines_b.append(line)
737
 
            else:
738
 
                assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
739
 
                                 'killed-both'), \
740
 
                       state
741
 
 
742
 
                
743
 
    def join(self, other):
744
 
        """Integrate versions from other into this weave.
745
 
 
746
 
        The resulting weave contains all the history of both weaves; 
747
 
        any version you could retrieve from either self or other can be 
748
 
        retrieved from self after this call.
749
 
 
750
 
        It is illegal for the two weaves to contain different values 
751
 
        or different parents for any version.  See also reweave().
752
 
        """
753
 
        if other.numversions() == 0:
 
743
    def _join(self, other, pb, msg, version_ids, ignore_missing):
 
744
        """Worker routine for join()."""
 
745
        if not other.versions():
754
746
            return          # nothing to update, easy
 
747
 
 
748
        if not version_ids:
 
749
            # versions is never none, InterWeave checks this.
 
750
            return 0
 
751
 
755
752
        # two loops so that we do not change ourselves before verifying it
756
753
        # will be ok
757
754
        # work through in index order to make sure we get all dependencies
758
 
        for other_idx, name in enumerate(other._names):
759
 
            if self._check_version_consistent(other, other_idx, name):
760
 
                continue
761
 
        for other_idx, name in enumerate(other._names):
762
 
            # TODO: If all the parents of the other version are already 
 
755
        names_to_join = []
 
756
        processed = 0
 
757
        # get the selected versions only that are in other.versions.
 
758
        version_ids = set(other.versions()).intersection(set(version_ids))
 
759
        # pull in the referenced graph.
 
760
        version_ids = other.get_ancestry(version_ids)
 
761
        pending_graph = [(version, other.get_parents(version)) for
 
762
                         version in version_ids]
 
763
        for name in topo_sort(pending_graph):
 
764
            other_idx = other._name_map[name]
 
765
            # returns True if we have it, False if we need it.
 
766
            if not self._check_version_consistent(other, other_idx, name):
 
767
                names_to_join.append((other_idx, name))
 
768
            processed += 1
 
769
 
 
770
 
 
771
        if pb and not msg:
 
772
            msg = 'weave join'
 
773
 
 
774
        merged = 0
 
775
        time0 = time.time()
 
776
        for other_idx, name in names_to_join:
 
777
            # TODO: If all the parents of the other version are already
763
778
            # present then we can avoid some work by just taking the delta
764
779
            # and adjusting the offsets.
765
780
            new_parents = self._imported_parents(other, other_idx)
 
781
            sha1 = other._sha1s[other_idx]
 
782
 
 
783
            merged += 1
 
784
 
 
785
            if pb:
 
786
                pb.update(msg, merged, len(names_to_join))
 
787
           
766
788
            lines = other.get_lines(other_idx)
767
 
            sha1 = other._sha1s[other_idx]
768
 
            self.add(name, new_parents, lines, sha1)
769
 
 
770
 
 
 
789
            self._add(name, lines, new_parents, sha1)
 
790
 
 
791
        mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
 
792
                merged, processed, self._weave_name, time.time()-time0))
 
793
 
771
794
    def _imported_parents(self, other, other_idx):
772
795
        """Return list of parents in self corresponding to indexes in other."""
773
796
        new_parents = []
774
797
        for parent_idx in other._parents[other_idx]:
775
798
            parent_name = other._names[parent_idx]
776
 
            if parent_name not in self._names:
 
799
            if parent_name not in self._name_map:
777
800
                # should not be possible
778
801
                raise WeaveError("missing parent {%s} of {%s} in %r" 
779
802
                                 % (parent_name, other._name_map[other_idx], self))
795
818
        this_idx = self._name_map.get(name, -1)
796
819
        if this_idx != -1:
797
820
            if self._sha1s[this_idx] != other._sha1s[other_idx]:
798
 
                raise WeaveError("inconsistent texts for version {%s} "
799
 
                                 "when joining weaves"
800
 
                                 % (name))
 
821
                raise errors.WeaveTextDiffers(name, self, other)
801
822
            self_parents = self._parents[this_idx]
802
823
            other_parents = other._parents[other_idx]
803
 
            n1 = [self._names[i] for i in self_parents]
804
 
            n2 = [other._names[i] for i in other_parents]
805
 
            n1.sort()
806
 
            n2.sort()
807
 
            if n1 != n2:
 
824
            n1 = set([self._names[i] for i in self_parents])
 
825
            n2 = set([other._names[i] for i in other_parents])
 
826
            if not self._compatible_parents(n1, n2):
808
827
                raise WeaveParentMismatch("inconsistent parents "
809
828
                    "for version {%s}: %s vs %s" % (name, n1, n2))
810
829
            else:
812
831
        else:
813
832
            return False
814
833
 
815
 
    def reweave(self, other):
816
 
        """Reweave self with other."""
817
 
        new_weave = reweave(self, other)
 
834
    def _reweave(self, other, pb, msg):
 
835
        """Reweave self with other - internal helper for join().
 
836
 
 
837
        :param other: The other weave to merge
 
838
        :param pb: An optional progress bar, indicating how far done we are
 
839
        :param msg: An optional message for the progress
 
840
        """
 
841
        new_weave = _reweave(self, other, pb=pb, msg=msg)
 
842
        self._copy_weave_content(new_weave)
 
843
 
 
844
    def _copy_weave_content(self, otherweave):
 
845
        """adsorb the content from otherweave."""
818
846
        for attr in self.__slots__:
819
 
            setattr(self, attr, getattr(new_weave, attr))
820
 
 
821
 
 
822
 
def reweave(wa, wb):
 
847
            if attr != '_weave_name':
 
848
                setattr(self, attr, copy(getattr(otherweave, attr)))
 
849
 
 
850
 
 
851
class WeaveFile(Weave):
 
852
    """A WeaveFile represents a Weave on disk and writes on change."""
 
853
 
 
854
    WEAVE_SUFFIX = '.weave'
 
855
    
 
856
    def __init__(self, name, transport, filemode=None, create=False, access_mode='w'):
 
857
        """Create a WeaveFile.
 
858
        
 
859
        :param create: If not True, only open an existing knit.
 
860
        """
 
861
        super(WeaveFile, self).__init__(name, access_mode)
 
862
        self._transport = transport
 
863
        self._filemode = filemode
 
864
        try:
 
865
            _read_weave_v5(self._transport.get(name + WeaveFile.WEAVE_SUFFIX), self)
 
866
        except errors.NoSuchFile:
 
867
            if not create:
 
868
                raise
 
869
            # new file, save it
 
870
            self._save()
 
871
 
 
872
    def _add_lines(self, version_id, parents, lines, parent_texts,
 
873
        left_matching_blocks, nostore_sha, random_id, check_content):
 
874
        """Add a version and save the weave."""
 
875
        self.check_not_reserved_id(version_id)
 
876
        result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
 
877
            parent_texts, left_matching_blocks, nostore_sha, random_id,
 
878
            check_content)
 
879
        self._save()
 
880
        return result
 
881
 
 
882
    def _clone_text(self, new_version_id, old_version_id, parents):
 
883
        """See VersionedFile.clone_text."""
 
884
        super(WeaveFile, self)._clone_text(new_version_id, old_version_id, parents)
 
885
        self._save
 
886
 
 
887
    def copy_to(self, name, transport):
 
888
        """See VersionedFile.copy_to()."""
 
889
        # as we are all in memory always, just serialise to the new place.
 
890
        sio = StringIO()
 
891
        write_weave_v5(self, sio)
 
892
        sio.seek(0)
 
893
        transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
 
894
 
 
895
    def create_empty(self, name, transport, filemode=None):
 
896
        return WeaveFile(name, transport, filemode, create=True)
 
897
 
 
898
    def _save(self):
 
899
        """Save the weave."""
 
900
        self._check_write_ok()
 
901
        sio = StringIO()
 
902
        write_weave_v5(self, sio)
 
903
        sio.seek(0)
 
904
        self._transport.put_file(self._weave_name + WeaveFile.WEAVE_SUFFIX,
 
905
                                 sio,
 
906
                                 self._filemode)
 
907
 
 
908
    @staticmethod
 
909
    def get_suffixes():
 
910
        """See VersionedFile.get_suffixes()."""
 
911
        return [WeaveFile.WEAVE_SUFFIX]
 
912
 
 
913
    def join(self, other, pb=None, msg=None, version_ids=None,
 
914
             ignore_missing=False):
 
915
        """Join other into self and save."""
 
916
        super(WeaveFile, self).join(other, pb, msg, version_ids, ignore_missing)
 
917
        self._save()
 
918
 
 
919
 
 
920
def _reweave(wa, wb, pb=None, msg=None):
823
921
    """Combine two weaves and return the result.
824
922
 
825
923
    This works even if a revision R has different parents in 
830
928
    might be possible but it should only be necessary to do 
831
929
    this operation rarely, when a new previously ghost version is 
832
930
    inserted.
 
931
 
 
932
    :param pb: An optional progress bar, indicating how far done we are
 
933
    :param msg: An optional message for the progress
833
934
    """
834
935
    wr = Weave()
835
936
    ia = ib = 0
836
 
    queue_a = range(wa.numversions())
837
 
    queue_b = range(wb.numversions())
 
937
    queue_a = range(wa.num_versions())
 
938
    queue_b = range(wb.num_versions())
838
939
    # first determine combined parents of all versions
839
940
    # map from version name -> all parent names
840
941
    combined_parents = _reweave_parent_graphs(wa, wb)
841
942
    mutter("combined parents: %r", combined_parents)
842
943
    order = topo_sort(combined_parents.iteritems())
843
944
    mutter("order to reweave: %r", order)
844
 
    for name in order:
 
945
 
 
946
    if pb and not msg:
 
947
        msg = 'reweave'
 
948
 
 
949
    for idx, name in enumerate(order):
 
950
        if pb:
 
951
            pb.update(msg, idx, len(order))
845
952
        if name in wa._name_map:
846
953
            lines = wa.get_lines(name)
847
954
            if name in wb._name_map:
848
 
                assert lines == wb.get_lines(name)
 
955
                lines_b = wb.get_lines(name)
 
956
                if lines != lines_b:
 
957
                    mutter('Weaves differ on content. rev_id {%s}', name)
 
958
                    mutter('weaves: %s, %s', wa._weave_name, wb._weave_name)
 
959
                    import difflib
 
960
                    lines = list(difflib.unified_diff(lines, lines_b,
 
961
                            wa._weave_name, wb._weave_name))
 
962
                    mutter('lines:\n%s', ''.join(lines))
 
963
                    raise errors.WeaveTextDiffers(name, wa, wb)
849
964
        else:
850
965
            lines = wb.get_lines(name)
851
 
        wr.add(name, combined_parents[name], lines)
 
966
        wr._add(name, lines, [wr._lookup(i) for i in combined_parents[name]])
852
967
    return wr
853
968
 
854
 
 
855
969
def _reweave_parent_graphs(wa, wb):
856
970
    """Return combined parent ancestry for two weaves.
857
971
    
860
974
    for weave in [wa, wb]:
861
975
        for idx, name in enumerate(weave._names):
862
976
            p = combined.setdefault(name, set())
863
 
            p.update(map(weave.idx_to_name, weave._parents[idx]))
 
977
            p.update(map(weave._idx_to_name, weave._parents[idx]))
864
978
    return combined
865
979
 
866
980
 
870
984
    for i in (6, 50, 10, 10):
871
985
        print '-' * i,
872
986
    print
873
 
    for i in range(w.numversions()):
 
987
    for i in range(w.num_versions()):
874
988
        sha1 = w._sha1s[i]
875
989
        name = w._names[i]
876
990
        parent_str = ' '.join(map(str, w._parents[i]))
923
1037
        Add NEWTEXT, with specified parent versions.
924
1038
    weave annotate WEAVEFILE VERSION
925
1039
        Display origin of each line.
926
 
    weave mash WEAVEFILE VERSION...
927
 
        Display composite of all selected versions.
928
1040
    weave merge WEAVEFILE VERSION1 VERSION2 > OUT
929
1041
        Auto-merge two versions and display conflicts.
930
1042
    weave diff WEAVEFILE VERSION1 VERSION2 
1004
1116
        w = readit()
1005
1117
        sys.stdout.writelines(w.get_iter(int(argv[3])))
1006
1118
        
1007
 
    elif cmd == 'mash': # get composite
1008
 
        w = readit()
1009
 
        sys.stdout.writelines(w.mash_iter(map(int, argv[3:])))
1010
 
 
1011
1119
    elif cmd == 'diff':
1012
 
        from difflib import unified_diff
1013
1120
        w = readit()
1014
1121
        fn = argv[2]
1015
1122
        v1, v2 = map(int, argv[3:5])
1016
1123
        lines1 = w.get(v1)
1017
1124
        lines2 = w.get(v2)
1018
 
        diff_gen = unified_diff(lines1, lines2,
 
1125
        diff_gen = bzrlib.patiencediff.unified_diff(lines1, lines2,
1019
1126
                                '%s version %d' % (fn, v1),
1020
1127
                                '%s version %d' % (fn, v2))
1021
1128
        sys.stdout.writelines(diff_gen)
1044
1151
        pb = ProgressBar()
1045
1152
        w.check(pb)
1046
1153
        pb.clear()
1047
 
        print '%d versions ok' % w.numversions()
 
1154
        print '%d versions ok' % w.num_versions()
1048
1155
 
1049
1156
    elif cmd == 'inclusions':
1050
1157
        w = readit()
1055
1162
        print ' '.join(map(str, w._parents[int(argv[3])]))
1056
1163
 
1057
1164
    elif cmd == 'plan-merge':
 
1165
        # replaced by 'bzr weave-plan-merge'
1058
1166
        w = readit()
1059
1167
        for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1060
1168
            if line:
1061
1169
                print '%14s | %s' % (state, line),
1062
 
 
1063
1170
    elif cmd == 'merge':
 
1171
        # replaced by 'bzr weave-merge-text'
1064
1172
        w = readit()
1065
1173
        p = w.plan_merge(int(argv[3]), int(argv[4]))
1066
1174
        sys.stdout.writelines(w.weave_merge(p))
1067
 
            
1068
 
    elif cmd == 'mash-merge':
1069
 
        if len(argv) != 5:
1070
 
            usage()
1071
 
            return 1
1072
 
 
1073
 
        w = readit()
1074
 
        v1, v2 = map(int, argv[3:5])
1075
 
 
1076
 
        basis = w.inclusions([v1]).intersection(w.inclusions([v2]))
1077
 
 
1078
 
        base_lines = list(w.mash_iter(basis))
1079
 
        a_lines = list(w.get(v1))
1080
 
        b_lines = list(w.get(v2))
1081
 
 
1082
 
        from bzrlib.merge3 import Merge3
1083
 
        m3 = Merge3(base_lines, a_lines, b_lines)
1084
 
 
1085
 
        name_a = 'version %d' % v1
1086
 
        name_b = 'version %d' % v2
1087
 
        sys.stdout.writelines(m3.merge_lines(name_a=name_a, name_b=name_b))
1088
1175
    else:
1089
1176
        raise ValueError('unknown command %r' % cmd)
1090
1177
    
1091
1178
 
1092
 
 
1093
 
def profile_main(argv): 
1094
 
    import tempfile, hotshot, hotshot.stats
1095
 
 
1096
 
    prof_f = tempfile.NamedTemporaryFile()
1097
 
 
1098
 
    prof = hotshot.Profile(prof_f.name)
1099
 
 
1100
 
    ret = prof.runcall(main, argv)
1101
 
    prof.close()
1102
 
 
1103
 
    stats = hotshot.stats.load(prof_f.name)
1104
 
    #stats.strip_dirs()
1105
 
    stats.sort_stats('cumulative')
1106
 
    ## XXX: Might like to write to stderr or the trace file instead but
1107
 
    ## print_stats seems hardcoded to stdout
1108
 
    stats.print_stats(20)
1109
 
            
1110
 
    return ret
1111
 
 
1112
 
 
1113
1179
if __name__ == '__main__':
1114
1180
    import sys
1115
 
    if '--profile' in sys.argv:
1116
 
        args = sys.argv[:]
1117
 
        args.remove('--profile')
1118
 
        sys.exit(profile_main(args))
1119
 
    else:
1120
 
        sys.exit(main(sys.argv))
1121
 
 
 
1181
    sys.exit(main(sys.argv))
 
1182
 
 
1183
 
 
1184
class InterWeave(InterVersionedFile):
 
1185
    """Optimised code paths for weave to weave operations."""
 
1186
    
 
1187
    _matching_file_from_factory = staticmethod(WeaveFile)
 
1188
    _matching_file_to_factory = staticmethod(WeaveFile)
 
1189
    
 
1190
    @staticmethod
 
1191
    def is_compatible(source, target):
 
1192
        """Be compatible with weaves."""
 
1193
        try:
 
1194
            return (isinstance(source, Weave) and
 
1195
                    isinstance(target, Weave))
 
1196
        except AttributeError:
 
1197
            return False
 
1198
 
 
1199
    def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
 
1200
        """See InterVersionedFile.join."""
 
1201
        version_ids = self._get_source_version_ids(version_ids, ignore_missing)
 
1202
        if self.target.versions() == [] and version_ids is None:
 
1203
            self.target._copy_weave_content(self.source)
 
1204
            return
 
1205
        self.target._join(self.source, pb, msg, version_ids, ignore_missing)
 
1206
 
 
1207
 
 
1208
InterVersionedFile.register_optimiser(InterWeave)