~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/vf_repository.py

(gz) Remove bzrlib/util/elementtree/ package (Martin Packman)

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Repository formats built around versioned files."""
18
18
 
 
19
from __future__ import absolute_import
 
20
 
19
21
 
20
22
from bzrlib.lazy_import import lazy_import
21
23
lazy_import(globals(), """
23
25
 
24
26
from bzrlib import (
25
27
    check,
 
28
    config as _mod_config,
26
29
    debug,
27
30
    fetch as _mod_fetch,
28
31
    fifo_cache,
38
41
    tsort,
39
42
    ui,
40
43
    versionedfile,
 
44
    vf_search,
41
45
    )
42
46
 
43
47
from bzrlib.recordcounter import RecordCounter
44
48
from bzrlib.revisiontree import InventoryRevisionTree
45
49
from bzrlib.testament import Testament
 
50
from bzrlib.i18n import gettext
46
51
""")
47
52
 
48
53
from bzrlib import (
64
69
    CommitBuilder,
65
70
    InterRepository,
66
71
    MetaDirRepository,
67
 
    MetaDirRepositoryFormat,
 
72
    RepositoryFormatMetaDir,
68
73
    Repository,
69
74
    RepositoryFormat,
70
75
    )
71
76
 
72
77
from bzrlib.trace import (
73
 
    mutter,
 
78
    mutter
74
79
    )
75
80
 
76
81
 
78
83
    """Base class for all repository formats that are VersionedFiles-based."""
79
84
 
80
85
    supports_full_versioned_files = True
 
86
    supports_versioned_directories = True
 
87
    supports_unreferenced_revisions = True
81
88
 
82
89
    # Should commit add an inventory, or an inventory delta to the repository.
83
90
    _commit_inv_deltas = True
102
109
    # the default CommitBuilder does not manage trees whose root is versioned.
103
110
    _versioned_root = False
104
111
 
105
 
    def __init__(self, repository, parents, config, timestamp=None,
 
112
    def __init__(self, repository, parents, config_stack, timestamp=None,
106
113
                 timezone=None, committer=None, revprops=None,
107
114
                 revision_id=None, lossy=False):
108
115
        super(VersionedFileCommitBuilder, self).__init__(repository,
109
 
            parents, config, timestamp, timezone, committer, revprops,
 
116
            parents, config_stack, timestamp, timezone, committer, revprops,
110
117
            revision_id, lossy)
111
118
        try:
112
119
            basis_id = self.parents[0]
193
200
                       revision_id=self._new_revision_id,
194
201
                       properties=self._revprops)
195
202
        rev.parent_ids = self.parents
196
 
        self.repository.add_revision(self._new_revision_id, rev,
197
 
            self.new_inventory, self._config)
 
203
        if self._config_stack.get('create_signatures') == _mod_config.SIGN_ALWAYS:
 
204
            testament = Testament(rev, self.revision_tree())
 
205
            plaintext = testament.as_short_text()
 
206
            self.repository.store_revision_signature(
 
207
                gpg.GPGStrategy(self._config_stack), plaintext,
 
208
                self._new_revision_id)
 
209
        self.repository._add_revision(rev)
198
210
        self._ensure_fallback_inventories()
199
211
        self.repository.commit_write_group()
200
212
        return self._new_revision_id
278
290
 
279
291
    def _get_delta(self, ie, basis_inv, path):
280
292
        """Get a delta against the basis inventory for ie."""
281
 
        if ie.file_id not in basis_inv:
 
293
        if not basis_inv.has_id(ie.file_id):
282
294
            # add
283
295
            result = (None, path, ie.file_id, ie)
284
296
            self._basis_delta.append(result)
397
409
                # this masks when a change may have occurred against the basis.
398
410
                # To match this we always issue a delta, because the revision
399
411
                # of the root will always be changing.
400
 
                if ie.file_id in basis_inv:
 
412
                if basis_inv.has_id(ie.file_id):
401
413
                    delta = (basis_inv.id2path(ie.file_id), path,
402
414
                        ie.file_id, ie)
403
415
                else:
418
430
                return None, False, None
419
431
        # XXX: Friction: parent_candidates should return a list not a dict
420
432
        #      so that we don't have to walk the inventories again.
421
 
        parent_candiate_entries = ie.parent_candidates(parent_invs)
422
 
        head_set = self._heads(ie.file_id, parent_candiate_entries.keys())
 
433
        parent_candidate_entries = ie.parent_candidates(parent_invs)
 
434
        head_set = self._heads(ie.file_id, parent_candidate_entries.keys())
423
435
        heads = []
424
436
        for inv in parent_invs:
425
 
            if ie.file_id in inv:
 
437
            if inv.has_id(ie.file_id):
426
438
                old_rev = inv[ie.file_id].revision
427
439
                if old_rev in head_set:
428
440
                    heads.append(inv[ie.file_id].revision)
440
452
            store = True
441
453
        if not store:
442
454
            # There is a single head, look it up for comparison
443
 
            parent_entry = parent_candiate_entries[heads[0]]
 
455
            parent_entry = parent_candidate_entries[heads[0]]
444
456
            # if the non-content specific data has changed, we'll be writing a
445
457
            # node:
446
458
            if (parent_entry.parent_id != ie.parent_id or
558
570
        :param iter_changes: An iter_changes iterator with the changes to apply
559
571
            to basis_revision_id. The iterator must not include any items with
560
572
            a current kind of None - missing items must be either filtered out
561
 
            or errored-on beefore record_iter_changes sees the item.
 
573
            or errored-on before record_iter_changes sees the item.
562
574
        :param _entry_factory: Private method to bind entry_factory locally for
563
575
            performance.
564
576
        :return: A generator of (file_id, relpath, fs_hash) tuples for use with
917
929
        """
918
930
        if not self._format.supports_external_lookups:
919
931
            raise errors.UnstackableRepositoryFormat(self._format, self.base)
 
932
        # This can raise an exception, so should be done before we lock the
 
933
        # fallback repository.
 
934
        self._check_fallback_repository(repository)
920
935
        if self.is_locked():
921
936
            # This repository will call fallback.unlock() when we transition to
922
937
            # the unlocked state, so we make sure to increment the lock count
923
938
            repository.lock_read()
924
 
        self._check_fallback_repository(repository)
925
939
        self._fallback_repositories.append(repository)
926
940
        self.texts.add_fallback_versioned_files(repository.texts)
927
941
        self.inventories.add_fallback_versioned_files(repository.inventories)
1024
1038
        self.inventories._access.flush()
1025
1039
        return result
1026
1040
 
1027
 
    def add_revision(self, revision_id, rev, inv=None, config=None):
 
1041
    def add_revision(self, revision_id, rev, inv=None):
1028
1042
        """Add rev to the revision store as revision_id.
1029
1043
 
1030
1044
        :param revision_id: the revision id to use.
1031
1045
        :param rev: The revision object.
1032
1046
        :param inv: The inventory for the revision. if None, it will be looked
1033
1047
                    up in the inventory storer
1034
 
        :param config: If None no digital signature will be created.
1035
 
                       If supplied its signature_needed method will be used
1036
 
                       to determine if a signature should be made.
1037
1048
        """
1038
1049
        # TODO: jam 20070210 Shouldn't we check rev.revision_id and
1039
1050
        #       rev.parent_ids?
1040
1051
        _mod_revision.check_not_reserved_id(revision_id)
1041
 
        if config is not None and config.signature_needed():
1042
 
            if inv is None:
1043
 
                inv = self.get_inventory(revision_id)
1044
 
            tree = InventoryRevisionTree(self, inv, revision_id)
1045
 
            testament = Testament(rev, tree)
1046
 
            plaintext = testament.as_short_text()
1047
 
            self.store_revision_signature(
1048
 
                gpg.GPGStrategy(config), plaintext, revision_id)
1049
1052
        # check inventory present
1050
1053
        if not self.inventories.get_parent_map([(revision_id,)]):
1051
1054
            if inv is None:
1084
1087
        keys = {'chk_bytes':set(), 'inventories':set(), 'texts':set()}
1085
1088
        kinds = ['chk_bytes', 'texts']
1086
1089
        count = len(checker.pending_keys)
1087
 
        bar.update("inventories", 0, 2)
 
1090
        bar.update(gettext("inventories"), 0, 2)
1088
1091
        current_keys = checker.pending_keys
1089
1092
        checker.pending_keys = {}
1090
1093
        # Accumulate current checks.
1110
1113
            del keys['inventories']
1111
1114
        else:
1112
1115
            return
1113
 
        bar.update("texts", 1)
 
1116
        bar.update(gettext("texts"), 1)
1114
1117
        while (checker.pending_keys or keys['chk_bytes']
1115
1118
            or keys['texts']):
1116
1119
            # Something to check.
1179
1182
                'sha1 mismatch: %s has sha1 %s expected %s referenced by %s' %
1180
1183
                (record.key, sha1, item_data[1], item_data[2]))
1181
1184
 
 
1185
    @needs_read_lock
 
1186
    def _eliminate_revisions_not_present(self, revision_ids):
 
1187
        """Check every revision id in revision_ids to see if we have it.
 
1188
 
 
1189
        Returns a set of the present revisions.
 
1190
        """
 
1191
        result = []
 
1192
        graph = self.get_graph()
 
1193
        parent_map = graph.get_parent_map(revision_ids)
 
1194
        # The old API returned a list, should this actually be a set?
 
1195
        return parent_map.keys()
 
1196
 
1182
1197
    def __init__(self, _format, a_bzrdir, control_files):
1183
1198
        """Instantiate a VersionedFileRepository.
1184
1199
 
1185
1200
        :param _format: The format of the repository on disk.
1186
 
        :param a_bzrdir: The BzrDir of the repository.
 
1201
        :param controldir: The ControlDir of the repository.
1187
1202
        :param control_files: Control files to use for locking, etc.
1188
1203
        """
1189
1204
        # In the future we will have a single api for all stores for
1191
1206
        # this construct will accept instances of those things.
1192
1207
        super(VersionedFileRepository, self).__init__(_format, a_bzrdir,
1193
1208
            control_files)
 
1209
        self._transport = control_files._transport
 
1210
        self.base = self._transport.base
1194
1211
        # for tests
1195
1212
        self._reconcile_does_inventory_gc = True
1196
1213
        self._reconcile_fixes_text_parents = False
1201
1218
        # rather copying them?
1202
1219
        self._safe_to_return_from_cache = False
1203
1220
 
 
1221
    def fetch(self, source, revision_id=None, find_ghosts=False,
 
1222
            fetch_spec=None):
 
1223
        """Fetch the content required to construct revision_id from source.
 
1224
 
 
1225
        If revision_id is None and fetch_spec is None, then all content is
 
1226
        copied.
 
1227
 
 
1228
        fetch() may not be used when the repository is in a write group -
 
1229
        either finish the current write group before using fetch, or use
 
1230
        fetch before starting the write group.
 
1231
 
 
1232
        :param find_ghosts: Find and copy revisions in the source that are
 
1233
            ghosts in the target (and not reachable directly by walking out to
 
1234
            the first-present revision in target from revision_id).
 
1235
        :param revision_id: If specified, all the content needed for this
 
1236
            revision ID will be copied to the target.  Fetch will determine for
 
1237
            itself which content needs to be copied.
 
1238
        :param fetch_spec: If specified, a SearchResult or
 
1239
            PendingAncestryResult that describes which revisions to copy.  This
 
1240
            allows copying multiple heads at once.  Mutually exclusive with
 
1241
            revision_id.
 
1242
        """
 
1243
        if fetch_spec is not None and revision_id is not None:
 
1244
            raise AssertionError(
 
1245
                "fetch_spec and revision_id are mutually exclusive.")
 
1246
        if self.is_in_write_group():
 
1247
            raise errors.InternalBzrError(
 
1248
                "May not fetch while in a write group.")
 
1249
        # fast path same-url fetch operations
 
1250
        # TODO: lift out to somewhere common with RemoteRepository
 
1251
        # <https://bugs.launchpad.net/bzr/+bug/401646>
 
1252
        if (self.has_same_location(source)
 
1253
            and fetch_spec is None
 
1254
            and self._has_same_fallbacks(source)):
 
1255
            # check that last_revision is in 'from' and then return a
 
1256
            # no-operation.
 
1257
            if (revision_id is not None and
 
1258
                not _mod_revision.is_null(revision_id)):
 
1259
                self.get_revision(revision_id)
 
1260
            return 0, []
 
1261
        inter = InterRepository.get(source, self)
 
1262
        if (fetch_spec is not None and
 
1263
            not getattr(inter, "supports_fetch_spec", False)):
 
1264
            raise errors.UnsupportedOperation(
 
1265
                "fetch_spec not supported for %r" % inter)
 
1266
        return inter.fetch(revision_id=revision_id,
 
1267
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
 
1268
 
1204
1269
    @needs_read_lock
1205
1270
    def gather_stats(self, revid=None, committers=None):
1206
1271
        """See Repository.gather_stats()."""
1215
1280
            # result['size'] = t
1216
1281
        return result
1217
1282
 
1218
 
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
1283
    def get_commit_builder(self, branch, parents, config_stack, timestamp=None,
1219
1284
                           timezone=None, committer=None, revprops=None,
1220
1285
                           revision_id=None, lossy=False):
1221
1286
        """Obtain a CommitBuilder for this repository.
1222
1287
 
1223
1288
        :param branch: Branch to commit to.
1224
1289
        :param parents: Revision ids of the parents of the new revision.
1225
 
        :param config: Configuration to use.
 
1290
        :param config_stack: Configuration stack to use.
1226
1291
        :param timestamp: Optional timestamp recorded for commit.
1227
1292
        :param timezone: Optional timezone for timestamp.
1228
1293
        :param committer: Optional committer to set for commit.
1235
1300
            raise errors.BzrError("Cannot commit directly to a stacked branch"
1236
1301
                " in pre-2a formats. See "
1237
1302
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1238
 
        result = self._commit_builder_class(self, parents, config,
 
1303
        result = self._commit_builder_class(self, parents, config_stack,
1239
1304
            timestamp, timezone, committer, revprops, revision_id,
1240
1305
            lossy)
1241
1306
        self.start_write_group()
1497
1562
            text_keys[(file_id, revision_id)] = callable_data
1498
1563
        for record in self.texts.get_record_stream(text_keys, 'unordered', True):
1499
1564
            if record.storage_kind == 'absent':
1500
 
                raise errors.RevisionNotPresent(record.key, self)
 
1565
                raise errors.RevisionNotPresent(record.key[1], record.key[0])
1501
1566
            yield text_keys[record.key], record.get_bytes_as('chunked')
1502
1567
 
1503
1568
    def _generate_text_key_index(self, text_key_references=None,
1553
1618
        batch_size = 10 # should be ~150MB on a 55K path tree
1554
1619
        batch_count = len(revision_order) / batch_size + 1
1555
1620
        processed_texts = 0
1556
 
        pb.update("Calculating text parents", processed_texts, text_count)
 
1621
        pb.update(gettext("Calculating text parents"), processed_texts, text_count)
1557
1622
        for offset in xrange(batch_count):
1558
1623
            to_query = revision_order[offset * batch_size:(offset + 1) *
1559
1624
                batch_size]
1562
1627
            for revision_id in to_query:
1563
1628
                parent_ids = ancestors[revision_id]
1564
1629
                for text_key in revision_keys[revision_id]:
1565
 
                    pb.update("Calculating text parents", processed_texts)
 
1630
                    pb.update(gettext("Calculating text parents"), processed_texts)
1566
1631
                    processed_texts += 1
1567
1632
                    candidate_parents = []
1568
1633
                    for parent_id in parent_ids:
1638
1703
        num_file_ids = len(file_ids)
1639
1704
        for file_id, altered_versions in file_ids.iteritems():
1640
1705
            if pb is not None:
1641
 
                pb.update("Fetch texts", count, num_file_ids)
 
1706
                pb.update(gettext("Fetch texts"), count, num_file_ids)
1642
1707
            count += 1
1643
1708
            yield ("file", file_id, altered_versions)
1644
1709
 
1681
1746
        if ((None in revision_ids)
1682
1747
            or (_mod_revision.NULL_REVISION in revision_ids)):
1683
1748
            raise ValueError('cannot get null revision inventory')
1684
 
        return self._iter_inventories(revision_ids, ordering)
 
1749
        for inv, revid in self._iter_inventories(revision_ids, ordering):
 
1750
            if inv is None:
 
1751
                raise errors.NoSuchRevision(self, revid)
 
1752
            yield inv
1685
1753
 
1686
1754
    def _iter_inventories(self, revision_ids, ordering):
1687
1755
        """single-document based inventory iteration."""
1688
1756
        inv_xmls = self._iter_inventory_xmls(revision_ids, ordering)
1689
1757
        for text, revision_id in inv_xmls:
1690
 
            yield self._deserialise_inventory(revision_id, text)
 
1758
            if text is None:
 
1759
                yield None, revision_id
 
1760
            else:
 
1761
                yield self._deserialise_inventory(revision_id, text), revision_id
1691
1762
 
1692
1763
    def _iter_inventory_xmls(self, revision_ids, ordering):
1693
1764
        if ordering is None:
1711
1782
                else:
1712
1783
                    yield ''.join(chunks), record.key[-1]
1713
1784
            else:
1714
 
                raise errors.NoSuchRevision(self, record.key)
 
1785
                yield None, record.key[-1]
1715
1786
            if order_as_requested:
1716
1787
                # Yield as many results as we can while preserving order.
1717
1788
                while next_key in text_chunks:
1746
1817
    def _get_inventory_xml(self, revision_id):
1747
1818
        """Get serialized inventory as a string."""
1748
1819
        texts = self._iter_inventory_xmls([revision_id], 'unordered')
1749
 
        try:
1750
 
            text, revision_id = texts.next()
1751
 
        except StopIteration:
1752
 
            raise errors.HistoryMissing(self, 'inventory', revision_id)
 
1820
        text, revision_id = texts.next()
 
1821
        if text is None:
 
1822
            raise errors.NoSuchRevision(self, revision_id)
1753
1823
        return text
1754
1824
 
1755
1825
    @needs_read_lock
1830
1900
        """Return the graph walker for text revisions."""
1831
1901
        return graph.Graph(self.texts)
1832
1902
 
 
1903
    def revision_ids_to_search_result(self, result_set):
 
1904
        """Convert a set of revision ids to a graph SearchResult."""
 
1905
        result_parents = set()
 
1906
        for parents in self.get_graph().get_parent_map(
 
1907
            result_set).itervalues():
 
1908
            result_parents.update(parents)
 
1909
        included_keys = result_set.intersection(result_parents)
 
1910
        start_keys = result_set.difference(included_keys)
 
1911
        exclude_keys = result_parents.difference(result_set)
 
1912
        result = vf_search.SearchResult(start_keys, exclude_keys,
 
1913
            len(result_set), result_set)
 
1914
        return result
 
1915
 
1833
1916
    def _get_versioned_file_checker(self, text_key_references=None,
1834
1917
        ancestors=None):
1835
1918
        """Return an object suitable for checking versioned files.
1920
2003
            control_files)
1921
2004
 
1922
2005
 
1923
 
class MetaDirVersionedFileRepositoryFormat(MetaDirRepositoryFormat,
 
2006
class MetaDirVersionedFileRepositoryFormat(RepositoryFormatMetaDir,
1924
2007
        VersionedFileRepositoryFormat):
1925
2008
    """Base class for repository formats using versioned files in metadirs."""
1926
2009
 
2449
2532
            self.text_index.iterkeys()])
2450
2533
        # text keys is now grouped by file_id
2451
2534
        n_versions = len(self.text_index)
2452
 
        progress_bar.update('loading text store', 0, n_versions)
 
2535
        progress_bar.update(gettext('loading text store'), 0, n_versions)
2453
2536
        parent_map = self.repository.texts.get_parent_map(self.text_index)
2454
2537
        # On unlistable transports this could well be empty/error...
2455
2538
        text_keys = self.repository.texts.keys()
2456
2539
        unused_keys = frozenset(text_keys) - set(self.text_index)
2457
2540
        for num, key in enumerate(self.text_index.iterkeys()):
2458
 
            progress_bar.update('checking text graph', num, n_versions)
 
2541
            progress_bar.update(gettext('checking text graph'), num, n_versions)
2459
2542
            correct_parents = self.calculate_file_version_parents(key)
2460
2543
            try:
2461
2544
                knit_parents = parent_map[key]
2471
2554
 
2472
2555
    _walk_to_common_revisions_batch_size = 50
2473
2556
 
 
2557
    supports_fetch_spec = True
 
2558
 
2474
2559
    @needs_write_lock
2475
2560
    def fetch(self, revision_id=None, find_ghosts=False,
2476
2561
            fetch_spec=None):
2482
2567
                            content is copied.
2483
2568
        :return: None.
2484
2569
        """
2485
 
        ui.ui_factory.warn_experimental_format_fetch(self)
 
2570
        if self.target._format.experimental:
 
2571
            ui.ui_factory.show_user_warning('experimental_format_fetch',
 
2572
                from_format=self.source._format,
 
2573
                to_format=self.target._format)
2486
2574
        from bzrlib.fetch import RepoFetcher
2487
2575
        # See <https://launchpad.net/bugs/456077> asking for a warning here
2488
2576
        if self.source._format.network_name() != self.target._format.network_name():
2549
2637
                searcher.stop_searching_any(stop_revs)
2550
2638
            if searcher_exhausted:
2551
2639
                break
2552
 
        return searcher.get_result()
 
2640
        (started_keys, excludes, included_keys) = searcher.get_state()
 
2641
        return vf_search.SearchResult(started_keys, excludes,
 
2642
            len(included_keys), included_keys)
2553
2643
 
2554
2644
    @needs_read_lock
2555
2645
    def search_missing_revision_ids(self,
2902
2992
        for offset in range(0, len(revision_ids), batch_size):
2903
2993
            self.target.start_write_group()
2904
2994
            try:
2905
 
                pb.update('Transferring revisions', offset,
 
2995
                pb.update(gettext('Transferring revisions'), offset,
2906
2996
                          len(revision_ids))
2907
2997
                batch = revision_ids[offset:offset+batch_size]
2908
2998
                basis_id = self._fetch_batch(batch, basis_id, cache)
2916
3006
                    hints.extend(hint)
2917
3007
        if hints and self.target._format.pack_compresses:
2918
3008
            self.target.pack(hint=hints)
2919
 
        pb.update('Transferring revisions', len(revision_ids),
 
3009
        pb.update(gettext('Transferring revisions'), len(revision_ids),
2920
3010
                  len(revision_ids))
2921
3011
 
2922
3012
    @needs_write_lock
2927
3017
            revision_ids = fetch_spec.get_keys()
2928
3018
        else:
2929
3019
            revision_ids = None
2930
 
        ui.ui_factory.warn_experimental_format_fetch(self)
 
3020
        if self.source._format.experimental:
 
3021
            ui.ui_factory.show_user_warning('experimental_format_fetch',
 
3022
                from_format=self.source._format,
 
3023
                to_format=self.target._format)
2931
3024
        if (not self.source.supports_rich_root()
2932
3025
            and self.target.supports_rich_root()):
2933
3026
            self._converting_to_rich_root = True
3028
3121
            _install_revision(repository, revision, revision_tree, signature,
3029
3122
                inventory_cache)
3030
3123
            if pb is not None:
3031
 
                pb.update('Transferring revisions', n + 1, num_revisions)
 
3124
                pb.update(gettext('Transferring revisions'), n + 1, num_revisions)
3032
3125
    except:
3033
3126
        repository.abort_write_group()
3034
3127
        raise
3070
3163
        # the parents inserted are not those commit would do - in particular
3071
3164
        # they are not filtered by heads(). RBC, AB
3072
3165
        for revision, tree in parent_trees.iteritems():
3073
 
            if ie.file_id not in tree:
 
3166
            if not tree.has_id(ie.file_id):
3074
3167
                continue
3075
3168
            parent_id = tree.get_file_revision(ie.file_id)
3076
3169
            if parent_id in text_parents: