~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Vincent Ladeuil
  • Date: 2009-07-02 13:07:14 UTC
  • mto: (4524.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 4525.
  • Revision ID: v.ladeuil+lp@free.fr-20090702130714-hsyqfusi8vn3a11m
Use tree.has_changes() where appropriate (the test suite caught a
bug in has_changes() (not filtering out the root) in an impressive
number of tests)

* bzrlib/send.py:
(send): Use tree.has_changes() instead of tree.changes_from().

* bzrlib/reconfigure.py:
(Reconfigure._check): Use tree.has_changes() instead of
tree.changes_from().

* bzrlib/merge.py:
(Merger.ensure_revision_trees, Merger.compare_basis): Use
tree.has_changes() instead of tree.changes_from().

* bzrlib/builtins.py:
(cmd_remove_tree.run, cmd_push.run, cmd_merge.run): Use
tree.has_changes() instead of tree.changes_from().

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
#
16
16
# You should have received a copy of the GNU General Public License
17
17
# along with this program; if not, write to the Free Software
18
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
19
 
20
20
"""Versioned text file storage api."""
21
21
 
22
22
from copy import copy
23
23
from cStringIO import StringIO
24
24
import os
 
25
import struct
25
26
from zlib import adler32
26
27
 
27
28
from bzrlib.lazy_import import lazy_import
30
31
 
31
32
from bzrlib import (
32
33
    errors,
 
34
    groupcompress,
33
35
    index,
 
36
    knit,
34
37
    osutils,
35
38
    multiparent,
36
39
    tsort,
37
40
    revision,
38
41
    ui,
39
42
    )
40
 
from bzrlib.graph import DictParentsProvider, Graph, _StackedParentsProvider
 
43
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
41
44
from bzrlib.transport.memory import MemoryTransport
42
45
""")
43
46
from bzrlib.inter import InterObject
44
47
from bzrlib.registry import Registry
45
48
from bzrlib.symbol_versioning import *
46
49
from bzrlib.textmerge import TextMerge
 
50
from bzrlib import bencode
47
51
 
48
52
 
49
53
adapter_registry = Registry()
65
69
 
66
70
class ContentFactory(object):
67
71
    """Abstract interface for insertion and retrieval from a VersionedFile.
68
 
    
 
72
 
69
73
    :ivar sha1: None, or the sha1 of the content fulltext.
70
74
    :ivar storage_kind: The native storage kind of this factory. One of
71
75
        'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
155
159
 
156
160
class AbsentContentFactory(ContentFactory):
157
161
    """A placeholder content factory for unavailable texts.
158
 
    
 
162
 
159
163
    :ivar sha1: None.
160
164
    :ivar storage_kind: 'absent'.
161
165
    :ivar key: The key of this content. Each key is a tuple with a single
197
201
 
198
202
class VersionedFile(object):
199
203
    """Versioned text file storage.
200
 
    
 
204
 
201
205
    A versioned file manages versions of line-based text files,
202
206
    keeping track of the originating version for each line.
203
207
 
241
245
    def insert_record_stream(self, stream):
242
246
        """Insert a record stream into this versioned file.
243
247
 
244
 
        :param stream: A stream of records to insert. 
 
248
        :param stream: A stream of records to insert.
245
249
        :return: None
246
250
        :seealso VersionedFile.get_record_stream:
247
251
        """
266
270
            the data back accurately. (Checking the lines have been split
267
271
            correctly is expensive and extremely unlikely to catch bugs so it
268
272
            is not done at runtime unless check_content is True.)
269
 
        :param parent_texts: An optional dictionary containing the opaque 
 
273
        :param parent_texts: An optional dictionary containing the opaque
270
274
            representations of some or all of the parents of version_id to
271
275
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
272
276
            returned by add_lines or data corruption can be caused.
300
304
        parent_texts=None, nostore_sha=None, random_id=False,
301
305
        check_content=True, left_matching_blocks=None):
302
306
        """Add lines to the versioned file, allowing ghosts to be present.
303
 
        
 
307
 
304
308
        This takes the same parameters as add_lines and returns the same.
305
309
        """
306
310
        self._check_write_ok()
330
334
 
331
335
    def get_format_signature(self):
332
336
        """Get a text description of the data encoding in this file.
333
 
        
 
337
 
334
338
        :since: 0.90
335
339
        """
336
340
        raise NotImplementedError(self.get_format_signature)
457
461
        if isinstance(version_ids, basestring):
458
462
            version_ids = [version_ids]
459
463
        raise NotImplementedError(self.get_ancestry)
460
 
        
 
464
 
461
465
    def get_ancestry_with_ghosts(self, version_ids):
462
466
        """Return a list of all ancestors of given version(s). This
463
467
        will not include the null revision.
464
468
 
465
469
        Must raise RevisionNotPresent if any of the given versions are
466
470
        not present in file history.
467
 
        
 
471
 
468
472
        Ghosts that are known about will be included in ancestry list,
469
473
        but are not explicitly marked.
470
474
        """
471
475
        raise NotImplementedError(self.get_ancestry_with_ghosts)
472
 
    
 
476
 
473
477
    def get_parent_map(self, version_ids):
474
478
        """Get a map of the parents of version_ids.
475
479
 
538
542
        unchanged   Alive in both a and b (possibly created in both)
539
543
        new-a       Created in a
540
544
        new-b       Created in b
541
 
        ghost-a     Killed in a, unborn in b    
 
545
        ghost-a     Killed in a, unborn in b
542
546
        ghost-b     Killed in b, unborn in a
543
547
        irrelevant  Not in either revision
544
548
        """
545
549
        raise NotImplementedError(VersionedFile.plan_merge)
546
 
        
 
550
 
547
551
    def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
548
552
                    b_marker=TextMerge.B_MARKER):
549
553
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
551
555
 
552
556
class RecordingVersionedFilesDecorator(object):
553
557
    """A minimal versioned files that records calls made on it.
554
 
    
 
558
 
555
559
    Only enough methods have been added to support tests using it to date.
556
560
 
557
561
    :ivar calls: A list of the calls made; can be reset at any time by
560
564
 
561
565
    def __init__(self, backing_vf):
562
566
        """Create a RecordingVersionedFilesDecorator decorating backing_vf.
563
 
        
 
567
 
564
568
        :param backing_vf: The versioned file to answer all methods.
565
569
        """
566
570
        self._backing_vf = backing_vf
652
656
 
653
657
    def unmap(self, partition_id):
654
658
        """Map a partitioned storage id back to a key prefix.
655
 
        
 
659
 
656
660
        :param partition_id: The underlying partition id.
657
661
        :return: As much of a key (or prefix) as is derivable from the partition
658
662
            id.
690
694
 
691
695
class PrefixMapper(URLEscapeMapper):
692
696
    """A key mapper that extracts the first component of a key.
693
 
    
 
697
 
694
698
    This mapper is for use with a transport based backend.
695
699
    """
696
700
 
729
733
 
730
734
class HashEscapedPrefixMapper(HashPrefixMapper):
731
735
    """Combines the escaped first component of a key with a hash.
732
 
    
 
736
 
733
737
    This mapper is for use with a transport based backend.
734
738
    """
735
739
 
791
795
        check_content=True):
792
796
        """Add a text to the store.
793
797
 
794
 
        :param key: The key tuple of the text to add.
 
798
        :param key: The key tuple of the text to add. If the last element is
 
799
            None, a CHK string will be generated during the addition.
795
800
        :param parents: The parents key tuples of the text to add.
796
801
        :param lines: A list of lines. Each line must be a bytestring. And all
797
802
            of them except the last must be terminated with \n and contain no
801
806
            the data back accurately. (Checking the lines have been split
802
807
            correctly is expensive and extremely unlikely to catch bugs so it
803
808
            is not done at runtime unless check_content is True.)
804
 
        :param parent_texts: An optional dictionary containing the opaque 
 
809
        :param parent_texts: An optional dictionary containing the opaque
805
810
            representations of some or all of the parents of version_id to
806
811
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
807
812
            returned by add_lines or data corruption can be caused.
824
829
        """
825
830
        raise NotImplementedError(self.add_lines)
826
831
 
 
832
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
 
833
        """Add a text to the store.
 
834
 
 
835
        This is a private function for use by CommitBuilder.
 
836
 
 
837
        :param key: The key tuple of the text to add. If the last element is
 
838
            None, a CHK string will be generated during the addition.
 
839
        :param parents: The parents key tuples of the text to add.
 
840
        :param text: A string containing the text to be committed.
 
841
        :param nostore_sha: Raise ExistingContent and do not add the lines to
 
842
            the versioned file if the digest of the lines matches this.
 
843
        :param random_id: If True a random id has been selected rather than
 
844
            an id determined by some deterministic process such as a converter
 
845
            from a foreign VCS. When True the backend may choose not to check
 
846
            for uniqueness of the resulting key within the versioned file, so
 
847
            this should only be done when the result is expected to be unique
 
848
            anyway.
 
849
        :param check_content: If True, the lines supplied are verified to be
 
850
            bytestrings that are correctly formed lines.
 
851
        :return: The text sha1, the number of bytes in the text, and an opaque
 
852
                 representation of the inserted version which can be provided
 
853
                 back to future _add_text calls in the parent_texts dictionary.
 
854
        """
 
855
        # The default implementation just thunks over to .add_lines(),
 
856
        # inefficient, but it works.
 
857
        return self.add_lines(key, parents, osutils.split_lines(text),
 
858
                              nostore_sha=nostore_sha,
 
859
                              random_id=random_id,
 
860
                              check_content=True)
 
861
 
827
862
    def add_mpdiffs(self, records):
828
863
        """Add mpdiffs to this VersionedFile.
829
864
 
925
960
 
926
961
    has_key = index._has_key_from_parent_map
927
962
 
 
963
    def get_missing_compression_parent_keys(self):
 
964
        """Return an iterable of keys of missing compression parents.
 
965
 
 
966
        Check this after calling insert_record_stream to find out if there are
 
967
        any missing compression parents.  If there are, the records that
 
968
        depend on them are not able to be inserted safely. The precise
 
969
        behaviour depends on the concrete VersionedFiles class in use.
 
970
 
 
971
        Classes that do not support this will raise NotImplementedError.
 
972
        """
 
973
        raise NotImplementedError(self.get_missing_compression_parent_keys)
 
974
 
928
975
    def insert_record_stream(self, stream):
929
976
        """Insert a record stream into this container.
930
977
 
931
 
        :param stream: A stream of records to insert. 
 
978
        :param stream: A stream of records to insert.
932
979
        :return: None
933
980
        :seealso VersionedFile.get_record_stream:
934
981
        """
1162
1209
    def insert_record_stream(self, stream):
1163
1210
        """Insert a record stream into this container.
1164
1211
 
1165
 
        :param stream: A stream of records to insert. 
 
1212
        :param stream: A stream of records to insert.
1166
1213
        :return: None
1167
1214
        :seealso VersionedFile.get_record_stream:
1168
1215
        """
1316
1363
            result[revision.NULL_REVISION] = ()
1317
1364
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1318
1365
        result.update(
1319
 
            _StackedParentsProvider(self._providers).get_parent_map(keys))
 
1366
            StackedParentsProvider(self._providers).get_parent_map(keys))
1320
1367
        for key, parents in result.iteritems():
1321
1368
            if parents == ():
1322
1369
                result[key] = (revision.NULL_REVISION,)
1325
1372
 
1326
1373
class PlanWeaveMerge(TextMerge):
1327
1374
    """Weave merge that takes a plan as its input.
1328
 
    
 
1375
 
1329
1376
    This exists so that VersionedFile.plan_merge is implementable.
1330
1377
    Most callers will want to use WeaveMerge instead.
1331
1378
    """
1352
1399
                yield(lines_a,)
1353
1400
            else:
1354
1401
                yield (lines_a, lines_b)
1355
 
       
 
1402
 
1356
1403
        # We previously considered either 'unchanged' or 'killed-both' lines
1357
1404
        # to be possible places to resynchronize.  However, assuming agreement
1358
1405
        # on killed-both lines may be too aggressive. -- mbp 20060324
1364
1411
                lines_a = []
1365
1412
                lines_b = []
1366
1413
                ch_a = ch_b = False
1367
 
                
 
1414
 
1368
1415
            if state == 'unchanged':
1369
1416
                if line:
1370
1417
                    yield ([line],)
1386
1433
            elif state == 'conflicted-b':
1387
1434
                ch_b = ch_a = True
1388
1435
                lines_b.append(line)
 
1436
            elif state == 'killed-both':
 
1437
                # This counts as a change, even though there is no associated
 
1438
                # line
 
1439
                ch_b = ch_a = True
1389
1440
            else:
1390
1441
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
1391
 
                        'killed-base', 'killed-both'):
 
1442
                        'killed-base'):
1392
1443
                    raise AssertionError(state)
1393
1444
        for struct in outstanding_struct():
1394
1445
            yield struct
1397
1448
class WeaveMerge(PlanWeaveMerge):
1398
1449
    """Weave merge that takes a VersionedFile and two versions as its input."""
1399
1450
 
1400
 
    def __init__(self, versionedfile, ver_a, ver_b, 
 
1451
    def __init__(self, versionedfile, ver_a, ver_b,
1401
1452
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1402
1453
        plan = versionedfile.plan_merge(ver_a, ver_b)
1403
1454
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1404
1455
 
1405
1456
 
1406
1457
class VirtualVersionedFiles(VersionedFiles):
1407
 
    """Dummy implementation for VersionedFiles that uses other functions for 
 
1458
    """Dummy implementation for VersionedFiles that uses other functions for
1408
1459
    obtaining fulltexts and parent maps.
1409
1460
 
1410
 
    This is always on the bottom of the stack and uses string keys 
 
1461
    This is always on the bottom of the stack and uses string keys
1411
1462
    (rather than tuples) internally.
1412
1463
    """
1413
1464
 
1415
1466
        """Create a VirtualVersionedFiles.
1416
1467
 
1417
1468
        :param get_parent_map: Same signature as Repository.get_parent_map.
1418
 
        :param get_lines: Should return lines for specified key or None if 
 
1469
        :param get_lines: Should return lines for specified key or None if
1419
1470
                          not available.
1420
1471
        """
1421
1472
        super(VirtualVersionedFiles, self).__init__()
1422
1473
        self._get_parent_map = get_parent_map
1423
1474
        self._get_lines = get_lines
1424
 
        
 
1475
 
1425
1476
    def check(self, progressbar=None):
1426
1477
        """See VersionedFiles.check.
1427
1478
 
1469
1520
        """See VersionedFile.iter_lines_added_or_present_in_versions()."""
1470
1521
        for i, (key,) in enumerate(keys):
1471
1522
            if pb is not None:
1472
 
                pb.update("iterating texts", i, len(keys))
 
1523
                pb.update("Finding changed lines", i, len(keys))
1473
1524
            for l in self._get_lines(key):
1474
1525
                yield (l, key)
 
1526
 
 
1527
 
 
1528
def network_bytes_to_kind_and_offset(network_bytes):
 
1529
    """Strip of a record kind from the front of network_bytes.
 
1530
 
 
1531
    :param network_bytes: The bytes of a record.
 
1532
    :return: A tuple (storage_kind, offset_of_remaining_bytes)
 
1533
    """
 
1534
    line_end = network_bytes.find('\n')
 
1535
    storage_kind = network_bytes[:line_end]
 
1536
    return storage_kind, line_end + 1
 
1537
 
 
1538
 
 
1539
class NetworkRecordStream(object):
 
1540
    """A record_stream which reconstitures a serialised stream."""
 
1541
 
 
1542
    def __init__(self, bytes_iterator):
 
1543
        """Create a NetworkRecordStream.
 
1544
 
 
1545
        :param bytes_iterator: An iterator of bytes. Each item in this
 
1546
            iterator should have been obtained from a record_streams'
 
1547
            record.get_bytes_as(record.storage_kind) call.
 
1548
        """
 
1549
        self._bytes_iterator = bytes_iterator
 
1550
        self._kind_factory = {'knit-ft-gz':knit.knit_network_to_record,
 
1551
            'knit-delta-gz':knit.knit_network_to_record,
 
1552
            'knit-annotated-ft-gz':knit.knit_network_to_record,
 
1553
            'knit-annotated-delta-gz':knit.knit_network_to_record,
 
1554
            'knit-delta-closure':knit.knit_delta_closure_to_records,
 
1555
            'fulltext':fulltext_network_to_record,
 
1556
            'groupcompress-block':groupcompress.network_block_to_records,
 
1557
            }
 
1558
 
 
1559
    def read(self):
 
1560
        """Read the stream.
 
1561
 
 
1562
        :return: An iterator as per VersionedFiles.get_record_stream().
 
1563
        """
 
1564
        for bytes in self._bytes_iterator:
 
1565
            storage_kind, line_end = network_bytes_to_kind_and_offset(bytes)
 
1566
            for record in self._kind_factory[storage_kind](
 
1567
                storage_kind, bytes, line_end):
 
1568
                yield record
 
1569
 
 
1570
 
 
1571
def fulltext_network_to_record(kind, bytes, line_end):
 
1572
    """Convert a network fulltext record to record."""
 
1573
    meta_len, = struct.unpack('!L', bytes[line_end:line_end+4])
 
1574
    record_meta = bytes[line_end+4:line_end+4+meta_len]
 
1575
    key, parents = bencode.bdecode_as_tuple(record_meta)
 
1576
    if parents == 'nil':
 
1577
        parents = None
 
1578
    fulltext = bytes[line_end+4+meta_len:]
 
1579
    return [FulltextContentFactory(key, parents, None, fulltext)]
 
1580
 
 
1581
 
 
1582
def _length_prefix(bytes):
 
1583
    return struct.pack('!L', len(bytes))
 
1584
 
 
1585
 
 
1586
def record_to_fulltext_bytes(record):
 
1587
    if record.parents is None:
 
1588
        parents = 'nil'
 
1589
    else:
 
1590
        parents = record.parents
 
1591
    record_meta = bencode.bencode((record.key, parents))
 
1592
    record_content = record.get_bytes_as('fulltext')
 
1593
    return "fulltext\n%s%s%s" % (
 
1594
        _length_prefix(record_meta), record_meta, record_content)
 
1595
 
 
1596
 
 
1597
def sort_groupcompress(parent_map):
 
1598
    """Sort and group the keys in parent_map into groupcompress order.
 
1599
 
 
1600
    groupcompress is defined (currently) as reverse-topological order, grouped
 
1601
    by the key prefix.
 
1602
 
 
1603
    :return: A sorted-list of keys
 
1604
    """
 
1605
    # gc-optimal ordering is approximately reverse topological,
 
1606
    # properly grouped by file-id.
 
1607
    per_prefix_map = {}
 
1608
    for item in parent_map.iteritems():
 
1609
        key = item[0]
 
1610
        if isinstance(key, str) or len(key) == 1:
 
1611
            prefix = ''
 
1612
        else:
 
1613
            prefix = key[0]
 
1614
        try:
 
1615
            per_prefix_map[prefix].append(item)
 
1616
        except KeyError:
 
1617
            per_prefix_map[prefix] = [item]
 
1618
 
 
1619
    present_keys = []
 
1620
    for prefix in sorted(per_prefix_map):
 
1621
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
 
1622
    return present_keys