~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Vincent Ladeuil
  • Date: 2007-07-15 11:24:18 UTC
  • mfrom: (2617 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2646.
  • Revision ID: v.ladeuil+lp@free.fr-20070715112418-9nn4n6esxv60ny4b
merge bzr.dev@1617

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
 
70
71
from copy import copy
71
72
from cStringIO import StringIO
72
 
from difflib import SequenceMatcher
73
73
import os
74
74
import sha
75
75
import time
 
76
import warnings
76
77
 
 
78
from bzrlib import (
 
79
    progress,
 
80
    )
77
81
from bzrlib.trace import mutter
78
82
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
79
83
        RevisionAlreadyPresent,
83
87
        )
84
88
import bzrlib.errors as errors
85
89
from bzrlib.osutils import sha_strings
86
 
from bzrlib.symbol_versioning import *
 
90
import bzrlib.patiencediff
 
91
from bzrlib.symbol_versioning import (deprecated_method,
 
92
        deprecated_function,
 
93
        zero_eight,
 
94
        )
87
95
from bzrlib.tsort import topo_sort
88
96
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
89
97
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
179
187
    """
180
188
 
181
189
    __slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
182
 
                 '_weave_name']
 
190
                 '_weave_name', '_matcher']
183
191
    
184
 
    def __init__(self, weave_name=None, access_mode='w'):
 
192
    def __init__(self, weave_name=None, access_mode='w', matcher=None):
185
193
        super(Weave, self).__init__(access_mode)
186
194
        self._weave = []
187
195
        self._parents = []
189
197
        self._names = []
190
198
        self._name_map = {}
191
199
        self._weave_name = weave_name
 
200
        if matcher is None:
 
201
            self._matcher = bzrlib.patiencediff.PatienceSequenceMatcher
 
202
        else:
 
203
            self._matcher = matcher
192
204
 
193
205
    def __repr__(self):
194
206
        return "Weave(%r)" % self._weave_name
226
238
 
227
239
    @deprecated_method(zero_eight)
228
240
    def lookup(self, name):
229
 
        """Backwards compatability thunk:
 
241
        """Backwards compatibility thunk:
230
242
 
231
243
        Return name, as name is valid in the api now, and spew deprecation
232
244
        warnings everywhere.
235
247
 
236
248
    def _lookup(self, name):
237
249
        """Convert symbolic version name to index."""
 
250
        self.check_not_reserved_id(name)
238
251
        try:
239
252
            return self._name_map[name]
240
253
        except KeyError:
256
269
 
257
270
    def has_version(self, version_id):
258
271
        """See VersionedFile.has_version."""
259
 
        return self._name_map.has_key(version_id)
 
272
        return (version_id in self._name_map)
260
273
 
261
274
    __contains__ = has_version
262
275
 
462
475
        """
463
476
 
464
477
        assert isinstance(version_id, basestring)
 
478
        self._check_lines_not_unicode(lines)
 
479
        self._check_lines_are_lines(lines)
465
480
        if not sha1:
466
481
            sha1 = sha_strings(lines)
467
482
        if version_id in self._name_map:
515
530
        if lines == basis_lines:
516
531
            return new_version            
517
532
 
518
 
        # add a sentinal, because we can also match against the final line
 
533
        # add a sentinel, because we can also match against the final line
519
534
        basis_lineno.append(len(self._weave))
520
535
 
521
536
        # XXX: which line of the weave should we really consider
525
540
        #print 'basis_lines:', basis_lines
526
541
        #print 'new_lines:  ', lines
527
542
 
528
 
        s = SequenceMatcher(None, basis_lines, lines)
 
543
        s = self._matcher(None, basis_lines, lines)
529
544
 
530
545
        # offset gives the number of lines that have been inserted
531
546
        # into the weave up to the current point; if the original edit instruction
592
607
        else:
593
608
            return self.get_ancestry(version_ids)
594
609
 
595
 
    def get_ancestry(self, version_ids):
 
610
    def get_ancestry(self, version_ids, topo_sorted=True):
596
611
        """See VersionedFile.get_ancestry."""
597
612
        if isinstance(version_ids, basestring):
598
613
            version_ids = [version_ids]
626
641
        """
627
642
        return len(other_parents.difference(my_parents)) == 0
628
643
 
629
 
    def annotate(self, version_id):
630
 
        if isinstance(version_id, int):
631
 
            warn('Weave.annotate(int) is deprecated. Please use version names'
632
 
                 ' in all circumstances as of 0.8',
633
 
                 DeprecationWarning,
634
 
                 stacklevel=2
635
 
                 )
636
 
            result = []
637
 
            for origin, lineno, text in self._extract([version_id]):
638
 
                result.append((origin, text))
639
 
            return result
640
 
        else:
641
 
            return super(Weave, self).annotate(version_id)
642
 
    
643
644
    def annotate_iter(self, version_id):
644
645
        """Yield list of (version-id, line) pairs for the specified version.
645
646
 
653
654
        """_walk has become visit, a supported api."""
654
655
        return self._walk_internal()
655
656
 
656
 
    def iter_lines_added_or_present_in_versions(self, version_ids=None):
 
657
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
 
658
                                                pb=None):
657
659
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
658
660
        if version_ids is None:
659
661
            version_ids = self.versions()
709
711
            raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
710
712
                                   % dset)
711
713
 
 
714
    def plan_merge(self, ver_a, ver_b):
 
715
        """Return pseudo-annotation indicating how the two versions merge.
 
716
 
 
717
        This is computed between versions a and b and their common
 
718
        base.
 
719
 
 
720
        Weave lines present in none of them are skipped entirely.
 
721
        """
 
722
        inc_a = set(self.get_ancestry([ver_a]))
 
723
        inc_b = set(self.get_ancestry([ver_b]))
 
724
        inc_c = inc_a & inc_b
 
725
 
 
726
        for lineno, insert, deleteset, line in\
 
727
            self.walk([ver_a, ver_b]):
 
728
            if deleteset & inc_c:
 
729
                # killed in parent; can't be in either a or b
 
730
                # not relevant to our work
 
731
                yield 'killed-base', line
 
732
            elif insert in inc_c:
 
733
                # was inserted in base
 
734
                killed_a = bool(deleteset & inc_a)
 
735
                killed_b = bool(deleteset & inc_b)
 
736
                if killed_a and killed_b:
 
737
                    yield 'killed-both', line
 
738
                elif killed_a:
 
739
                    yield 'killed-a', line
 
740
                elif killed_b:
 
741
                    yield 'killed-b', line
 
742
                else:
 
743
                    yield 'unchanged', line
 
744
            elif insert in inc_a:
 
745
                if deleteset & inc_a:
 
746
                    yield 'ghost-a', line
 
747
                else:
 
748
                    # new in A; not in B
 
749
                    yield 'new-a', line
 
750
            elif insert in inc_b:
 
751
                if deleteset & inc_b:
 
752
                    yield 'ghost-b', line
 
753
                else:
 
754
                    yield 'new-b', line
 
755
            else:
 
756
                # not in either revision
 
757
                yield 'irrelevant', line
 
758
 
 
759
        yield 'unchanged', ''           # terminator
 
760
 
712
761
    def _extract(self, versions):
713
762
        """Yield annotation of lines in included set.
714
763
 
838
887
                       expected_sha1, measured_sha1))
839
888
        return result
840
889
 
841
 
    def get_sha1(self, name):
842
 
        """Get the stored sha1 sum for the given revision.
843
 
        
844
 
        :param name: The name of the version to lookup
845
 
        """
846
 
        return self._sha1s[self._lookup(name)]
 
890
    def get_sha1(self, version_id):
 
891
        """See VersionedFile.get_sha1()."""
 
892
        return self._sha1s[self._lookup(version_id)]
847
893
 
848
894
    @deprecated_method(zero_eight)
849
895
    def numversions(self):
930
976
        if not other.versions():
931
977
            return          # nothing to update, easy
932
978
 
933
 
        if version_ids:
934
 
            for version_id in version_ids:
935
 
                if not other.has_version(version_id) and not ignore_missing:
936
 
                    raise RevisionNotPresent(version_id, self._weave_name)
937
 
        else:
938
 
            version_ids = other.versions()
 
979
        if not version_ids:
 
980
            # versions is never none, InterWeave checks this.
 
981
            return 0
939
982
 
940
983
        # two loops so that we do not change ourselves before verifying it
941
984
        # will be ok
1021
1064
 
1022
1065
    @deprecated_method(zero_eight)
1023
1066
    def reweave(self, other, pb=None, msg=None):
1024
 
        """reweave has been superceded by plain use of join."""
 
1067
        """reweave has been superseded by plain use of join."""
1025
1068
        return self.join(other, pb, msg)
1026
1069
 
1027
1070
    def _reweave(self, other, pb, msg):
1064
1107
 
1065
1108
    def _add_lines(self, version_id, parents, lines, parent_texts):
1066
1109
        """Add a version and save the weave."""
 
1110
        self.check_not_reserved_id(version_id)
1067
1111
        result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
1068
1112
                                                   parent_texts)
1069
1113
        self._save()
1080
1124
        sio = StringIO()
1081
1125
        write_weave_v5(self, sio)
1082
1126
        sio.seek(0)
1083
 
        transport.put(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
 
1127
        transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
1084
1128
 
1085
1129
    def create_empty(self, name, transport, filemode=None):
1086
1130
        return WeaveFile(name, transport, filemode, create=True)
1091
1135
        sio = StringIO()
1092
1136
        write_weave_v5(self, sio)
1093
1137
        sio.seek(0)
1094
 
        self._transport.put(self._weave_name + WeaveFile.WEAVE_SUFFIX,
1095
 
                            sio,
1096
 
                            self._filemode)
 
1138
        self._transport.put_file(self._weave_name + WeaveFile.WEAVE_SUFFIX,
 
1139
                                 sio,
 
1140
                                 self._filemode)
1097
1141
 
1098
1142
    @staticmethod
1099
1143
    def get_suffixes():
1191
1235
    from bzrlib.weavefile import read_weave
1192
1236
 
1193
1237
    wf = file(weave_file, 'rb')
1194
 
    w = read_weave(wf, WeaveVersionedFile)
 
1238
    w = read_weave(wf)
1195
1239
    # FIXME: doesn't work on pipes
1196
1240
    weave_size = wf.tell()
1197
1241
 
1312
1356
        sys.stdout.writelines(w.get_iter(int(argv[3])))
1313
1357
        
1314
1358
    elif cmd == 'diff':
1315
 
        from difflib import unified_diff
1316
1359
        w = readit()
1317
1360
        fn = argv[2]
1318
1361
        v1, v2 = map(int, argv[3:5])
1319
1362
        lines1 = w.get(v1)
1320
1363
        lines2 = w.get(v2)
1321
 
        diff_gen = unified_diff(lines1, lines2,
 
1364
        diff_gen = bzrlib.patiencediff.unified_diff(lines1, lines2,
1322
1365
                                '%s version %d' % (fn, v1),
1323
1366
                                '%s version %d' % (fn, v2))
1324
1367
        sys.stdout.writelines(diff_gen)
1358
1401
        print ' '.join(map(str, w._parents[int(argv[3])]))
1359
1402
 
1360
1403
    elif cmd == 'plan-merge':
 
1404
        # replaced by 'bzr weave-plan-merge'
1361
1405
        w = readit()
1362
1406
        for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1363
1407
            if line:
1364
1408
                print '%14s | %s' % (state, line),
1365
 
 
1366
1409
    elif cmd == 'merge':
 
1410
        # replaced by 'bzr weave-merge-text'
1367
1411
        w = readit()
1368
1412
        p = w.plan_merge(int(argv[3]), int(argv[4]))
1369
1413
        sys.stdout.writelines(w.weave_merge(p))
1370
 
            
1371
1414
    else:
1372
1415
        raise ValueError('unknown command %r' % cmd)
1373
1416
    
1374
1417
 
1375
 
 
1376
 
def profile_main(argv): 
1377
 
    import tempfile, hotshot, hotshot.stats
1378
 
 
1379
 
    prof_f = tempfile.NamedTemporaryFile()
1380
 
 
1381
 
    prof = hotshot.Profile(prof_f.name)
1382
 
 
1383
 
    ret = prof.runcall(main, argv)
1384
 
    prof.close()
1385
 
 
1386
 
    stats = hotshot.stats.load(prof_f.name)
1387
 
    #stats.strip_dirs()
1388
 
    stats.sort_stats('cumulative')
1389
 
    ## XXX: Might like to write to stderr or the trace file instead but
1390
 
    ## print_stats seems hardcoded to stdout
1391
 
    stats.print_stats(20)
1392
 
            
1393
 
    return ret
1394
 
 
1395
 
 
1396
 
def lsprofile_main(argv): 
1397
 
    from bzrlib.lsprof import profile
1398
 
    ret,stats = profile(main, argv)
1399
 
    stats.sort()
1400
 
    stats.pprint()
1401
 
    return ret
1402
 
 
1403
 
 
1404
1418
if __name__ == '__main__':
1405
1419
    import sys
1406
 
    if '--profile' in sys.argv:
1407
 
        args = sys.argv[:]
1408
 
        args.remove('--profile')
1409
 
        sys.exit(profile_main(args))
1410
 
    elif '--lsprof' in sys.argv:
1411
 
        args = sys.argv[:]
1412
 
        args.remove('--lsprof')
1413
 
        sys.exit(lsprofile_main(args))
1414
 
    else:
1415
 
        sys.exit(main(sys.argv))
 
1420
    sys.exit(main(sys.argv))
1416
1421
 
1417
1422
 
1418
1423
class InterWeave(InterVersionedFile):
1419
1424
    """Optimised code paths for weave to weave operations."""
1420
1425
    
1421
 
    _matching_file_factory = staticmethod(WeaveFile)
 
1426
    _matching_file_from_factory = staticmethod(WeaveFile)
 
1427
    _matching_file_to_factory = staticmethod(WeaveFile)
1422
1428
    
1423
1429
    @staticmethod
1424
1430
    def is_compatible(source, target):
1431
1437
 
1432
1438
    def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
1433
1439
        """See InterVersionedFile.join."""
1434
 
        if self.target.versions() == []:
1435
 
            # optimised copy
 
1440
        version_ids = self._get_source_version_ids(version_ids, ignore_missing)
 
1441
        if self.target.versions() == [] and version_ids is None:
1436
1442
            self.target._copy_weave_content(self.source)
1437
1443
            return
1438
1444
        try: