~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Robert Collins
  • Date: 2006-08-08 23:19:29 UTC
  • mfrom: (1884 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1912.
  • Revision ID: robertc@robertcollins.net-20060808231929-4e3e298190214b3a
current status

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
import time
22
22
from unittest import TestSuite
23
23
 
24
 
import bzrlib.bzrdir as bzrdir
 
24
from bzrlib import bzrdir, check, delta, gpg, errors, xml5, ui, transactions, osutils
25
25
from bzrlib.decorators import needs_read_lock, needs_write_lock
26
 
import bzrlib.errors as errors
27
26
from bzrlib.errors import InvalidRevisionId
28
 
import bzrlib.gpg as gpg
29
27
from bzrlib.graph import Graph
30
28
from bzrlib.inter import InterObject
31
29
from bzrlib.inventory import Inventory
37
35
from bzrlib.revision import NULL_REVISION, Revision
38
36
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
39
37
from bzrlib.store.text import TextStore
40
 
from bzrlib.symbol_versioning import *
 
38
from bzrlib.symbol_versioning import (deprecated_method,
 
39
        zero_nine, 
 
40
        )
 
41
from bzrlib.testament import Testament
41
42
from bzrlib.trace import mutter, note
42
43
from bzrlib.tree import RevisionTree
43
44
from bzrlib.tsort import topo_sort
44
 
from bzrlib.testament import Testament
45
 
from bzrlib.tree import EmptyTree
46
 
import bzrlib.ui
47
45
from bzrlib.weave import WeaveFile
48
 
import bzrlib.xml5
49
46
 
50
47
 
51
48
class Repository(object):
72
69
        assert inv.revision_id is None or inv.revision_id == revid, \
73
70
            "Mismatch between inventory revision" \
74
71
            " id and insertion revid (%r, %r)" % (inv.revision_id, revid)
75
 
        inv_text = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
76
 
        inv_sha1 = bzrlib.osutils.sha_string(inv_text)
 
72
        inv_text = xml5.serializer_v5.write_inventory_to_string(inv)
 
73
        inv_sha1 = osutils.sha_string(inv_text)
77
74
        inv_vf = self.control_weaves.get_weave('inventory',
78
75
                                               self.get_transaction())
79
 
        self._inventory_add_lines(inv_vf, revid, parents, bzrlib.osutils.split_lines(inv_text))
 
76
        self._inventory_add_lines(inv_vf, revid, parents, osutils.split_lines(inv_text))
80
77
        return inv_sha1
81
78
 
82
79
    def _inventory_add_lines(self, inv_vf, revid, parents, lines):
119
116
        """Return all the possible revisions that we could find."""
120
117
        return self.get_inventory_weave().versions()
121
118
 
 
119
    def all_revision_ids(self):
 
120
        """Returns a list of all the revision ids in the repository. 
 
121
 
 
122
        This is deprecated because code should generally work on the graph
 
123
        reachable from a particular revision, and ignore any other revisions
 
124
        that might be present.  There is no direct replacement method.
 
125
        """
 
126
        return self._all_revision_ids()
 
127
 
122
128
    @needs_read_lock
123
 
    def all_revision_ids(self):
 
129
    def _all_revision_ids(self):
124
130
        """Returns a list of all the revision ids in the repository. 
125
131
 
126
132
        These are in as much topological order as the underlying store can 
174
180
        self.control_files = control_files
175
181
        self._revision_store = _revision_store
176
182
        self.text_store = text_store
177
 
        # backwards compatability
 
183
        # backwards compatibility
178
184
        self.weave_store = text_store
179
185
        # not right yet - should be more semantically clear ? 
180
186
        # 
216
222
        For instance, if the repository is at URL/.bzr/repository,
217
223
        Repository.open(URL) -> a Repository instance.
218
224
        """
219
 
        control = bzrlib.bzrdir.BzrDir.open(base)
 
225
        control = bzrdir.BzrDir.open(base)
220
226
        return control.open_repository()
221
227
 
222
228
    def copy_content_into(self, destination, revision_id=None, basis=None):
267
273
            result = a_bzrdir.create_repository()
268
274
        # FIXME RBC 20060209 split out the repository type to avoid this check ?
269
275
        elif isinstance(a_bzrdir._format,
270
 
                      (bzrlib.bzrdir.BzrDirFormat4,
271
 
                       bzrlib.bzrdir.BzrDirFormat5,
272
 
                       bzrlib.bzrdir.BzrDirFormat6)):
 
276
                      (bzrdir.BzrDirFormat4,
 
277
                       bzrdir.BzrDirFormat5,
 
278
                       bzrdir.BzrDirFormat6)):
273
279
            result = a_bzrdir.open_repository()
274
280
        else:
275
281
            result = self._format.initialize(a_bzrdir, shared=self.is_shared())
293
299
        """
294
300
        if not revision_id or not isinstance(revision_id, basestring):
295
301
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
296
 
        return self._revision_store.get_revision(revision_id,
297
 
                                                 self.get_transaction())
 
302
        return self._revision_store.get_revisions([revision_id],
 
303
                                                  self.get_transaction())[0]
 
304
    @needs_read_lock
 
305
    def get_revisions(self, revision_ids):
 
306
        return self._revision_store.get_revisions(revision_ids,
 
307
                                                  self.get_transaction())
298
308
 
299
309
    @needs_read_lock
300
310
    def get_revision_xml(self, revision_id):
320
330
        self._check_revision_parents(r, inv)
321
331
        return r
322
332
 
 
333
    @needs_read_lock
 
334
    def get_deltas_for_revisions(self, revisions):
 
335
        """Produce a generator of revision deltas.
 
336
        
 
337
        Note that the input is a sequence of REVISIONS, not revision_ids.
 
338
        Trees will be held in memory until the generator exits.
 
339
        Each delta is relative to the revision's lefthand predecessor.
 
340
        """
 
341
        required_trees = set()
 
342
        for revision in revisions:
 
343
            required_trees.add(revision.revision_id)
 
344
            required_trees.update(revision.parent_ids[:1])
 
345
        trees = dict((t.get_revision_id(), t) for 
 
346
                     t in self.revision_trees(required_trees))
 
347
        for revision in revisions:
 
348
            if not revision.parent_ids:
 
349
                old_tree = self.revision_tree(None)
 
350
            else:
 
351
                old_tree = trees[revision.parent_ids[0]]
 
352
            yield delta.compare_trees(old_tree, trees[revision.revision_id])
 
353
 
 
354
    @needs_read_lock
 
355
    def get_revision_delta(self, revision_id):
 
356
        """Return the delta for one revision.
 
357
 
 
358
        The delta is relative to the left-hand predecessor of the
 
359
        revision.
 
360
        """
 
361
        r = self.get_revision(revision_id)
 
362
        return list(self.get_deltas_for_revisions([r]))[0]
 
363
 
323
364
    def _check_revision_parents(self, revision, inventory):
324
365
        """Private to Repository and Fetch.
325
366
        
355
396
                                         RepositoryFormat6,
356
397
                                         RepositoryFormat7,
357
398
                                         RepositoryFormatKnit1)), \
358
 
            "fileid_involved only supported for branches which store inventory as unnested xml"
 
399
            ("fileids_altered_by_revision_ids only supported for branches " 
 
400
             "which store inventory as unnested xml, not on %r" % self)
359
401
        selected_revision_ids = set(revision_ids)
360
402
        w = self.get_inventory_weave()
361
403
        result = {}
362
404
 
363
405
        # this code needs to read every new line in every inventory for the
364
406
        # inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
365
 
        # not pesent in one of those inventories is unnecessary but not 
 
407
        # not present in one of those inventories is unnecessary but not 
366
408
        # harmful because we are filtering by the revision id marker in the
367
409
        # inventory lines : we only select file ids altered in one of those  
368
 
        # revisions. We dont need to see all lines in the inventory because
 
410
        # revisions. We don't need to see all lines in the inventory because
369
411
        # only those added in an inventory in rev X can contain a revision=X
370
412
        # line.
371
413
        for line in w.iter_lines_added_or_present_in_versions(selected_revision_ids):
401
443
        :param revision_id: The expected revision id of the inventory.
402
444
        :param xml: A serialised inventory.
403
445
        """
404
 
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
446
        return xml5.serializer_v5.read_inventory_from_string(xml)
405
447
 
406
448
    @needs_read_lock
407
449
    def get_inventory_xml(self, revision_id):
411
453
            iw = self.get_inventory_weave()
412
454
            return iw.get_text(revision_id)
413
455
        except IndexError:
414
 
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
 
456
            raise errors.HistoryMissing(self, 'inventory', revision_id)
415
457
 
416
458
    @needs_read_lock
417
459
    def get_inventory_sha1(self, revision_id):
423
465
    def get_revision_graph(self, revision_id=None):
424
466
        """Return a dictionary containing the revision graph.
425
467
        
 
468
        :param revision_id: The revision_id to get a graph from. If None, then
 
469
        the entire revision graph is returned. This is a deprecated mode of
 
470
        operation and will be removed in the future.
426
471
        :return: a dictionary of revision_id->revision_parents_list.
427
472
        """
 
473
        # special case NULL_REVISION
 
474
        if revision_id == NULL_REVISION:
 
475
            return {}
428
476
        weave = self.get_inventory_weave()
429
477
        all_revisions = self._eliminate_revisions_not_present(weave.versions())
430
478
        entire_graph = dict([(node, weave.get_parents(node)) for 
458
506
            required = set([])
459
507
        else:
460
508
            pending = set(revision_ids)
461
 
            required = set(revision_ids)
 
509
            # special case NULL_REVISION
 
510
            if NULL_REVISION in pending:
 
511
                pending.remove(NULL_REVISION)
 
512
            required = set(pending)
462
513
        done = set([])
463
514
        while len(pending):
464
515
            revision_id = pending.pop()
513
564
    def revision_tree(self, revision_id):
514
565
        """Return Tree for a revision on this branch.
515
566
 
516
 
        `revision_id` may be None for the null revision, in which case
517
 
        an `EmptyTree` is returned."""
 
567
        `revision_id` may be None for the empty tree revision.
 
568
        """
518
569
        # TODO: refactor this to use an existing revision object
519
570
        # so we don't need to read it in twice.
520
571
        if revision_id is None or revision_id == NULL_REVISION:
521
 
            return EmptyTree()
 
572
            return RevisionTree(self, Inventory(), NULL_REVISION)
522
573
        else:
523
574
            inv = self.get_revision_inventory(revision_id)
524
575
            return RevisionTree(self, inv, revision_id)
525
576
 
526
577
    @needs_read_lock
 
578
    def revision_trees(self, revision_ids):
 
579
        """Return Tree for a revision on this branch.
 
580
 
 
581
        `revision_id` may not be None or 'null:'"""
 
582
        assert None not in revision_ids
 
583
        assert NULL_REVISION not in revision_ids
 
584
        texts = self.get_inventory_weave().get_texts(revision_ids)
 
585
        for text, revision_id in zip(texts, revision_ids):
 
586
            inv = self.deserialise_inventory(revision_id, text)
 
587
            yield RevisionTree(self, inv, revision_id)
 
588
 
 
589
    @needs_read_lock
527
590
    def get_ancestry(self, revision_id):
528
591
        """Return a list of revision-ids integrated by a revision.
 
592
 
 
593
        The first element of the list is always None, indicating the origin 
 
594
        revision.  This might change when we have history horizons, or 
 
595
        perhaps we should have a new API.
529
596
        
530
597
        This is topologically sorted.
531
598
        """
594
661
        return self._revision_store.get_signature_text(revision_id,
595
662
                                                       self.get_transaction())
596
663
 
 
664
    @needs_read_lock
 
665
    def check(self, revision_ids):
 
666
        """Check consistency of all history of given revision_ids.
 
667
 
 
668
        Different repository implementations should override _check().
 
669
 
 
670
        :param revision_ids: A non-empty list of revision_ids whose ancestry
 
671
             will be checked.  Typically the last revision_id of a branch.
 
672
        """
 
673
        if not revision_ids:
 
674
            raise ValueError("revision_ids must be non-empty in %s.check" 
 
675
                    % (self,))
 
676
        return self._check(revision_ids)
 
677
 
 
678
    def _check(self, revision_ids):
 
679
        result = check.Check(self)
 
680
        result.check()
 
681
        return result
 
682
 
597
683
 
598
684
class AllInOneRepository(Repository):
599
685
    """Legacy support - the repository behaviour for all-in-one branches."""
659
745
            present_parents.append(p_id)
660
746
            parent_trees[p_id] = repository.revision_tree(p_id)
661
747
        else:
662
 
            parent_trees[p_id] = EmptyTree()
 
748
            parent_trees[p_id] = repository.revision_tree(None)
663
749
 
664
750
    inv = revision_tree.inventory
665
751
    
670
756
        if ie.revision not in w:
671
757
            text_parents = []
672
758
            # FIXME: TODO: The following loop *may* be overlapping/duplicate
673
 
            # with inventoryEntry.find_previous_heads(). if it is, then there
 
759
            # with InventoryEntry.find_previous_heads(). if it is, then there
674
760
            # is a latent bug here where the parents may have ancestors of each
675
761
            # other. RBC, AB
676
762
            for revision, tree in parent_trees.iteritems():
742
828
        inv_vf.add_lines_with_ghosts(revid, parents, lines)
743
829
 
744
830
    @needs_read_lock
745
 
    def all_revision_ids(self):
 
831
    def _all_revision_ids(self):
746
832
        """See Repository.all_revision_ids()."""
 
833
        # Knits get the revision graph from the index of the revision knit, so
 
834
        # it's always possible even if they're on an unlistable transport.
747
835
        return self._revision_store.all_revision_ids(self.get_transaction())
748
836
 
749
837
    def fileid_involved_between_revs(self, from_revid, to_revid):
793
881
    @needs_read_lock
794
882
    def get_revision_graph(self, revision_id=None):
795
883
        """Return a dictionary containing the revision graph.
796
 
        
 
884
 
 
885
        :param revision_id: The revision_id to get a graph from. If None, then
 
886
        the entire revision graph is returned. This is a deprecated mode of
 
887
        operation and will be removed in the future.
797
888
        :return: a dictionary of revision_id->revision_parents_list.
798
889
        """
 
890
        # special case NULL_REVISION
 
891
        if revision_id == NULL_REVISION:
 
892
            return {}
799
893
        weave = self._get_revision_vf()
800
894
        entire_graph = weave.get_graph()
801
895
        if revision_id is None:
829
923
            required = set([])
830
924
        else:
831
925
            pending = set(revision_ids)
832
 
            required = set(revision_ids)
 
926
            # special case NULL_REVISION
 
927
            if NULL_REVISION in pending:
 
928
                pending.remove(NULL_REVISION)
 
929
            required = set(pending)
833
930
        done = set([])
834
931
        while len(pending):
835
932
            revision_id = pending.pop()
838
935
                    raise errors.NoSuchRevision(self, revision_id)
839
936
                # a ghost
840
937
                result.add_ghost(revision_id)
841
 
                # mark it as done so we dont try for it again.
 
938
                # mark it as done so we don't try for it again.
842
939
                done.add(revision_id)
843
940
                continue
844
941
            parent_ids = vf.get_parents_with_ghosts(revision_id)
865
962
        reconciler.reconcile()
866
963
        return reconciler
867
964
    
868
 
    def revision_parents(self, revid):
869
 
        return self._get_revision_vf().get_parents(rev_id)
 
965
    def revision_parents(self, revision_id):
 
966
        return self._get_revision_vf().get_parents(revision_id)
 
967
 
870
968
 
871
969
class RepositoryFormat(object):
872
970
    """A repository format.
908
1006
        except errors.NoSuchFile:
909
1007
            raise errors.NoRepositoryPresent(a_bzrdir)
910
1008
        except KeyError:
911
 
            raise errors.UnknownFormatError(format_string)
 
1009
            raise errors.UnknownFormatError(format=format_string)
912
1010
 
913
1011
    def _get_control_store(self, repo_transport, control_files):
914
1012
        """Return the control store for this repository."""
928
1026
        raise NotImplementedError(self.get_format_string)
929
1027
 
930
1028
    def get_format_description(self):
931
 
        """Return the short desciption for this format."""
 
1029
        """Return the short description for this format."""
932
1030
        raise NotImplementedError(self.get_format_description)
933
1031
 
934
1032
    def _get_revision_store(self, repo_transport, control_files):
1035
1133
        
1036
1134
        # Create an empty weave
1037
1135
        sio = StringIO()
1038
 
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
1136
        write_weave_v5(Weave(), sio)
1039
1137
        empty_weave = sio.getvalue()
1040
1138
 
1041
1139
        mutter('creating repository in %s.', a_bzrdir.transport.base)
1043
1141
        files = [('inventory.weave', StringIO(empty_weave)),
1044
1142
                 ]
1045
1143
        
1046
 
        # FIXME: RBC 20060125 dont peek under the covers
 
1144
        # FIXME: RBC 20060125 don't peek under the covers
1047
1145
        # NB: no need to escape relative paths that are url safe.
1048
1146
        control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
1049
1147
                                      TransportLock)
1095
1193
     - TextStores for texts, inventories,revisions.
1096
1194
 
1097
1195
    This format is deprecated: it indexes texts using a text id which is
1098
 
    removed in format 5; initializationa and write support for this format
 
1196
    removed in format 5; initialization and write support for this format
1099
1197
    has been removed.
1100
1198
    """
1101
1199
 
1102
1200
    def __init__(self):
1103
1201
        super(RepositoryFormat4, self).__init__()
1104
 
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat4()
 
1202
        self._matchingbzrdir = bzrdir.BzrDirFormat4()
1105
1203
 
1106
1204
    def get_format_description(self):
1107
1205
        """See RepositoryFormat.get_format_description()."""
1150
1248
 
1151
1249
    def __init__(self):
1152
1250
        super(RepositoryFormat5, self).__init__()
1153
 
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat5()
 
1251
        self._matchingbzrdir = bzrdir.BzrDirFormat5()
1154
1252
 
1155
1253
    def get_format_description(self):
1156
1254
        """See RepositoryFormat.get_format_description()."""
1180
1278
 
1181
1279
    def __init__(self):
1182
1280
        super(RepositoryFormat6, self).__init__()
1183
 
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat6()
 
1281
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
1184
1282
 
1185
1283
    def get_format_description(self):
1186
1284
        """See RepositoryFormat.get_format_description()."""
1200
1298
 
1201
1299
 
1202
1300
class MetaDirRepositoryFormat(RepositoryFormat):
1203
 
    """Common base class for the new repositories using the metadir layour."""
 
1301
    """Common base class for the new repositories using the metadir layout."""
1204
1302
 
1205
1303
    def __init__(self):
1206
1304
        super(MetaDirRepositoryFormat, self).__init__()
1207
 
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirMetaFormat1()
 
1305
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1208
1306
 
1209
1307
    def _create_control_files(self, a_bzrdir):
1210
1308
        """Create the required files and the initial control_files object."""
1211
 
        # FIXME: RBC 20060125 dont peek under the covers
 
1309
        # FIXME: RBC 20060125 don't peek under the covers
1212
1310
        # NB: no need to escape relative paths that are url safe.
1213
1311
        repository_transport = a_bzrdir.get_repository_transport(self)
1214
1312
        control_files = LockableFiles(repository_transport, 'lock', LockDir)
1285
1383
 
1286
1384
        # Create an empty weave
1287
1385
        sio = StringIO()
1288
 
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
1386
        write_weave_v5(Weave(), sio)
1289
1387
        empty_weave = sio.getvalue()
1290
1388
 
1291
1389
        mutter('creating repository in %s.', a_bzrdir.transport.base)
1396
1494
        repo_transport = a_bzrdir.get_repository_transport(None)
1397
1495
        control_files = LockableFiles(repo_transport, 'lock', LockDir)
1398
1496
        control_store = self._get_control_store(repo_transport, control_files)
1399
 
        transaction = bzrlib.transactions.WriteTransaction()
 
1497
        transaction = transactions.WriteTransaction()
1400
1498
        # trigger a write of the inventory store.
1401
1499
        control_store.get_weave_or_empty('inventory', transaction)
1402
1500
        _revision_store = self._get_revision_store(repo_transport, control_files)
1474
1572
        # grab the basis available data
1475
1573
        if basis is not None:
1476
1574
            self.target.fetch(basis, revision_id=revision_id)
1477
 
        # but dont bother fetching if we have the needed data now.
 
1575
        # but don't bother fetching if we have the needed data now.
1478
1576
        if (revision_id not in (None, NULL_REVISION) and 
1479
1577
            self.target.has_revision(revision_id)):
1480
1578
            return
1571
1669
    def is_compatible(source, target):
1572
1670
        """Be compatible with known Weave formats.
1573
1671
        
1574
 
        We dont test for the stores being of specific types becase that
 
1672
        We don't test for the stores being of specific types because that
1575
1673
        could lead to confusing results, and there is no need to be 
1576
1674
        overly general.
1577
1675
        """
1592
1690
        if basis is not None:
1593
1691
            # copy the basis in, then fetch remaining data.
1594
1692
            basis.copy_content_into(self.target, revision_id)
1595
 
            # the basis copy_content_into could misset this.
 
1693
            # the basis copy_content_into could miss-set this.
1596
1694
            try:
1597
1695
                self.target.set_make_working_trees(self.source.make_working_trees())
1598
1696
            except NotImplementedError:
1605
1703
                pass
1606
1704
            # FIXME do not peek!
1607
1705
            if self.source.control_files._transport.listable():
1608
 
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1706
                pb = ui.ui_factory.nested_progress_bar()
1609
1707
                try:
1610
1708
                    self.target.weave_store.copy_all_ids(
1611
1709
                        self.source.weave_store,
1641
1739
    def missing_revision_ids(self, revision_id=None):
1642
1740
        """See InterRepository.missing_revision_ids()."""
1643
1741
        # we want all revisions to satisfy revision_id in source.
1644
 
        # but we dont want to stat every file here and there.
 
1742
        # but we don't want to stat every file here and there.
1645
1743
        # we want then, all revisions other needs to satisfy revision_id 
1646
1744
        # checked, but not those that we have locally.
1647
1745
        # so the first thing is to get a subset of the revisions to 
1660
1758
        source_ids_set = set(source_ids)
1661
1759
        # source_ids is the worst possible case we may need to pull.
1662
1760
        # now we want to filter source_ids against what we actually
1663
 
        # have in target, but dont try to check for existence where we know
 
1761
        # have in target, but don't try to check for existence where we know
1664
1762
        # we do not have a revision as that would be pointless.
1665
1763
        target_ids = set(self.target._all_possible_ids())
1666
1764
        possibly_present_revisions = target_ids.intersection(source_ids_set)
1689
1787
    def is_compatible(source, target):
1690
1788
        """Be compatible with known Knit formats.
1691
1789
        
1692
 
        We dont test for the stores being of specific types becase that
 
1790
        We don't test for the stores being of specific types because that
1693
1791
        could lead to confusing results, and there is no need to be 
1694
1792
        overly general.
1695
1793
        """
1723
1821
        source_ids_set = set(source_ids)
1724
1822
        # source_ids is the worst possible case we may need to pull.
1725
1823
        # now we want to filter source_ids against what we actually
1726
 
        # have in target, but dont try to check for existence where we know
 
1824
        # have in target, but don't try to check for existence where we know
1727
1825
        # we do not have a revision as that would be pointless.
1728
1826
        target_ids = set(self.target._all_possible_ids())
1729
1827
        possibly_present_revisions = target_ids.intersection(source_ids_set)
1915
2013
            self._revprops.update(revprops)
1916
2014
 
1917
2015
        if timestamp is None:
1918
 
            self._timestamp = time.time()
1919
 
        else:
1920
 
            self._timestamp = long(timestamp)
 
2016
            timestamp = time.time()
 
2017
        # Restrict resolution to 1ms
 
2018
        self._timestamp = round(timestamp, 3)
1921
2019
 
1922
2020
        if timezone is None:
1923
2021
            self._timezone = local_time_offset()
2034
2132
            # TODO: Rather than invoking sha_strings here, _add_text_to_weave
2035
2133
            # should return the SHA1 and size
2036
2134
            self._add_text_to_weave(file_id, new_lines, file_parents.keys())
2037
 
            return bzrlib.osutils.sha_strings(new_lines), \
 
2135
            return osutils.sha_strings(new_lines), \
2038
2136
                sum(map(len, new_lines))
2039
2137
 
2040
2138
    def modified_link(self, file_id, file_parents, link_target):
2053
2151
        versionedfile.clear_cache()
2054
2152
 
2055
2153
 
2056
 
# Copied from xml.sax.saxutils
 
2154
_unescape_map = {
 
2155
    'apos':"'",
 
2156
    'quot':'"',
 
2157
    'amp':'&',
 
2158
    'lt':'<',
 
2159
    'gt':'>'
 
2160
}
 
2161
 
 
2162
 
 
2163
def _unescaper(match, _map=_unescape_map):
 
2164
    return _map[match.group(1)]
 
2165
 
 
2166
 
 
2167
_unescape_re = None
 
2168
 
 
2169
 
2057
2170
def _unescape_xml(data):
2058
 
    """Unescape &amp;, &lt;, and &gt; in a string of data.
2059
 
    """
2060
 
    data = data.replace("&lt;", "<")
2061
 
    data = data.replace("&gt;", ">")
2062
 
    # must do ampersand last
2063
 
    return data.replace("&amp;", "&")
 
2171
    """Unescape predefined XML entities in a string of data."""
 
2172
    global _unescape_re
 
2173
    if _unescape_re is None:
 
2174
        _unescape_re = re.compile('\&([^;]*);')
 
2175
    return _unescape_re.sub(_unescaper, data)