~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weave.py

  • Committer: Martin Pool
  • Date: 2006-03-21 12:26:54 UTC
  • mto: This revision was merged to the branch mainline in revision 1621.
  • Revision ID: mbp@sourcefrog.net-20060321122654-514047ed65795a17
New developer commands 'weave-list' and 'weave-join'.

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